HEX
Server: Apache
System: Linux www3.pit.tblive.com 5.14.0-687.38.1.el9_8.x86_64 #1 SMP PREEMPT_DYNAMIC Wed Aug 12 17:19:12 EDT 2026 x86_64
User: awaldron (1020)
PHP: 8.1.34
Disabled: exec,passthru,shell_exec,system
Upload Files
File: //proc/self/root/opt/imunify360/venv/share/imunify360/core-releases/imunify-core-release.tar
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  �

XҘ���<��(�ddlZd�eD��Z[dS)�Nc�L�g|]!}tj�|����"S�)�_os�path�realpath)�.0rs  �M/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/__init__.py�
<listcomp>r
s(��9�9�9��C�H���d�#�#�9�9�9�)�osr�__path__rrr	�<module>rs-������:�9��9�9�9���C�Crdefence360agent/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000075700000000000017230 0ustar  �

XҘ���<��(�ddlZd�eD��Z[dS)�Nc�L�g|]!}tj�|����"S�)�_os�path�realpath)�.0rs  �M/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/__init__.py�
<listcomp>r
s(��9�9�9��C�H���d�#�#�9�9�9�)�osr�__path__rrr	�<module>rs-������:�9��9�9�9���C�Crdefence360agent/__pycache__/__main__.cpython-311.opt-1.pyc0000644000000000000000000000044100000000000020136 0ustar  �

Q�����0�ddlmZej��dS)�)�cliN)�defence360agentr�run���M/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/__main__.py�<module>r	s(�����������	�	�	�	�	rdefence360agent/__pycache__/__main__.cpython-311.pyc0000644000000000000000000000044100000000000017177 0ustar  �

Q�����0�ddlmZej��dS)�)�cliN)�defence360agentr�run���M/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/__main__.py�<module>r	s(�����������	�	�	�	�	rdefence360agent/__pycache__/_version.cpython-311.opt-1.pyc0000644000000000000000000000031700000000000020244 0ustar  �

���e����
�dZdS)z8.12.0N)�__version__���M/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/_version.py�<module>rs�����rdefence360agent/__pycache__/_version.cpython-311.pyc0000644000000000000000000000031700000000000017305 0ustar  �

���e����
�dZdS)z8.12.0N)�__version__���M/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/_version.py�<module>rs�����rdefence360agent/__pycache__/defence360.cpython-311.opt-1.pyc0000644000000000000000000001516100000000000020245 0ustar  �

|�8�j�9����ddlZddlZddlZddlZddlmZddlZddlm	Z
ddlmZddl
mZmZddlmZddlmZmZmZmZmZddlmZmZdd	lmZeje��Zed
��Z d�Z!d�Z"dS)
�N)�Path)�Core)�
ResponseError)�SUCCESS�SocketError)�is_root_user)�
EXIT_CODES�EXITCODE_GENERAL_ERROR�print_error�print_response�print_warnings)�	EnvParser�create_cli_parser)�flush_sentryz5/var/lib/rpm-state/imunify360-transaction-in-progressc��tjtj��tjj���|��t��}|�	|���}|j
stj�d��rHtjj�
|j
ptj�d����|jr)tjj�|j��t!|d��r&ddlm}t'|||j����dSt!|d���rPt!|d���r?	|�|��}t-jtj|j|j|���}|jdi|�|��\}}t7|��t9��|t:kr"t=|j||j|j ��n=tC|||j|j ��tEj#tH|��dSdS#tJ$rU}	t=dd	d
�&|	��i|j|j ��tEj#tN��Yd}	~	dSd}	~	wwxYwt'|�(����dS)N)�args�IMUNIFY360_LOGGING_CONFIG_FILE�completions_commandr)�generate_completions�endpoint�generate_endpoint_params)�exclude�itemsz	ERROR: {}�))�os�umask�Config�
FILE_UMASK�defence360agent�	internals�logger�reconfigurer�
parse_args�
log_config�environ�get�update_logging_config_from_file�console_log_level�setConsoleLogLevel�hasattr�!defence360agent.utils.completionsr�print�shellrr�parse�command�envvar_parameter_optionsrr
rrr�json�verboser�sys�exitr	r�formatr
�format_help)
�rpc_handlers_init�cli_args�parserrr�
cli_kwargs�
envvar_kwargs�result�data�es
          �O/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/defence360.py�mainr@s����H�V�
������$�0�0�2�2�2������
�
 �
 �F����(��+�+�D���
�"�*�.�.�)I�J�J�
��!�(�H�H��O�O�r�z�~�~�.N�O�O�	
�	
�	
���
��!�(�;�;��"�	
�	
�	
��t�*�+�+��J�J�J�J�J�J�
�"�"�6�4�:�6�6�7�7�7����t�Z� � �$�W�T�3M�%N�%N�$�	-��6�6�t�<�<�J�%�O��
����-�"�	���M�)�4�=�G�G�=�G�J�G�G�L�F�D��4� � � ��N�N�N��� � ��t�|�T�4�9�d�l�K�K�K�K��F�D�$�)�T�\�B�B�B����F�+�,�,�,�,�,�L�K���	-�	-�	-���w�� 2� 2�1� 5� 5�6��	�4�<�
�
�
�
�H�+�,�,�,�,�,�,�,�,�,�����		-����	�f� � �"�"�#�#�#�#�#s�'CI�
J"�
A
J�J"c��t��sYt�dtj��tdtj���t
jt��	t|tjdd����nv#t$r7t�
d��t
jt��Y�n6t$r>}t�d|��t
jt��Yd}~n�d}~wt $r�}t"���rPt�d|��tdtj���t
jt��n3t�d	��t
jt��Yd}~nFd}~wt($r6t�d	��t
jt��YnwxYwt+j�����dS#t+j�����wxYw)
Nz'%s could be used by the root user only!z/Imunify360 CLI is unavailable for non-root user)�file�zUser 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!�infor�NAMEr,r3�stderrr4r
r@�argv�KeyboardInterrupt�warningr�error�ImportError�RPM_TRANSACTION_LOCK�exists�	exception�	Exception�asyncio�get_event_loop�close)r7r>s  r?�
entrypointrSQs<���>�>�)����=�v�{�K�K�K�
�=�C�J�	
�	
�	
�	
�	��'�(�(�(�)��
�������-�-�-�-���)�)�)����8�9�9�9���'�(�(�(�(�(��)�)�)����)�1�-�-�-���'�(�(�(�(�(�(�(�(������
-�
-�
-��&�&�(�(�	-��L�L�=�q�A�A�A��*��Z�
�
�
�
�

�H�+�,�,�,�,����G�
�
�
�
�H�+�,�,�,�����������)�)�)����C�	
�	
�	
�	��'�(�(�(�(�(�	)����	�� � �&�&�(�(�(�(�(���� � �&�&�(�(�(�(���s\�)"B
�H*�
=H�
H*�
	H�4D�
H*�
H�BF=�8H*�=AH�=H*�?H�H*�*'I)#rP�loggingrr3�pathlibr� defence360agent.internals.loggerr� defence360agent.contracts.configrr�$defence360agent.rpc_tools.exceptionsr�defence360agent.simple_rpcrr�defence360agent.utilsr�defence360agent.utils.clir	r
rrr
�defence360agent.utils.parsersrr�defence360agent.sentryr�	getLogger�__name__r!rLr@rSr�r?�<module>rasZ����������	�	�	�	�
�
�
�
�������'�'�'�'�;�;�;�;�;�;�>�>�>�>�>�>�;�;�;�;�;�;�;�;�.�.�.�.�.�.���������������G�F�F�F�F�F�F�F�/�/�/�/�/�/�
��	�8�	$�	$���t�;����
1$�1$�1$�h%)�%)�%)�%)�%)r`defence360agent/__pycache__/defence360.cpython-311.pyc0000644000000000000000000001516100000000000017306 0ustar  �

|�8�j�9����ddlZddlZddlZddlZddlmZddlZddlm	Z
ddlmZddl
mZmZddlmZddlmZmZmZmZmZddlmZmZdd	lmZeje��Zed
��Z d�Z!d�Z"dS)
�N)�Path)�Core)�
ResponseError)�SUCCESS�SocketError)�is_root_user)�
EXIT_CODES�EXITCODE_GENERAL_ERROR�print_error�print_response�print_warnings)�	EnvParser�create_cli_parser)�flush_sentryz5/var/lib/rpm-state/imunify360-transaction-in-progressc��tjtj��tjj���|��t��}|�	|���}|j
stj�d��rHtjj�
|j
ptj�d����|jr)tjj�|j��t!|d��r&ddlm}t'|||j����dSt!|d���rPt!|d���r?	|�|��}t-jtj|j|j|���}|jdi|�|��\}}t7|��t9��|t:kr"t=|j||j|j ��n=tC|||j|j ��tEj#tH|��dSdS#tJ$rU}	t=dd	d
�&|	��i|j|j ��tEj#tN��Yd}	~	dSd}	~	wwxYwt'|�(����dS)N)�args�IMUNIFY360_LOGGING_CONFIG_FILE�completions_commandr)�generate_completions�endpoint�generate_endpoint_params)�exclude�itemsz	ERROR: {}�))�os�umask�Config�
FILE_UMASK�defence360agent�	internals�logger�reconfigurer�
parse_args�
log_config�environ�get�update_logging_config_from_file�console_log_level�setConsoleLogLevel�hasattr�!defence360agent.utils.completionsr�print�shellrr�parse�command�envvar_parameter_optionsrr
rrr�json�verboser�sys�exitr	r�formatr
�format_help)
�rpc_handlers_init�cli_args�parserrr�
cli_kwargs�
envvar_kwargs�result�data�es
          �O/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/defence360.py�mainr@s����H�V�
������$�0�0�2�2�2������
�
 �
 �F����(��+�+�D���
�"�*�.�.�)I�J�J�
��!�(�H�H��O�O�r�z�~�~�.N�O�O�	
�	
�	
���
��!�(�;�;��"�	
�	
�	
��t�*�+�+��J�J�J�J�J�J�
�"�"�6�4�:�6�6�7�7�7����t�Z� � �$�W�T�3M�%N�%N�$�	-��6�6�t�<�<�J�%�O��
����-�"�	���M�)�4�=�G�G�=�G�J�G�G�L�F�D��4� � � ��N�N�N��� � ��t�|�T�4�9�d�l�K�K�K�K��F�D�$�)�T�\�B�B�B����F�+�,�,�,�,�,�L�K���	-�	-�	-���w�� 2� 2�1� 5� 5�6��	�4�<�
�
�
�
�H�+�,�,�,�,�,�,�,�,�,�����		-����	�f� � �"�"�#�#�#�#�#s�'CI�
J"�
A
J�J"c��t��sYt�dtj��tdtj���t
jt��	t|tjdd����nv#t$r7t�
d��t
jt��Y�n6t$r>}t�d|��t
jt��Yd}~n�d}~wt $r�}t"���rPt�d|��tdtj���t
jt��n3t�d	��t
jt��Yd}~nFd}~wt($r6t�d	��t
jt��YnwxYwt+j�����dS#t+j�����wxYw)
Nz'%s could be used by the root user only!z/Imunify360 CLI is unavailable for non-root user)�file�zUser 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!�infor�NAMEr,r3�stderrr4r
r@�argv�KeyboardInterrupt�warningr�error�ImportError�RPM_TRANSACTION_LOCK�exists�	exception�	Exception�asyncio�get_event_loop�close)r7r>s  r?�
entrypointrSQs<���>�>�)����=�v�{�K�K�K�
�=�C�J�	
�	
�	
�	
�	��'�(�(�(�)��
�������-�-�-�-���)�)�)����8�9�9�9���'�(�(�(�(�(��)�)�)����)�1�-�-�-���'�(�(�(�(�(�(�(�(������
-�
-�
-��&�&�(�(�	-��L�L�=�q�A�A�A��*��Z�
�
�
�
�

�H�+�,�,�,�,����G�
�
�
�
�H�+�,�,�,�����������)�)�)����C�	
�	
�	
�	��'�(�(�(�(�(�	)����	�� � �&�&�(�(�(�(�(���� � �&�&�(�(�(�(���s\�)"B
�H*�
=H�
H*�
	H�4D�
H*�
H�BF=�8H*�=AH�=H*�?H�H*�*'I)#rP�loggingrr3�pathlibr� defence360agent.internals.loggerr� defence360agent.contracts.configrr�$defence360agent.rpc_tools.exceptionsr�defence360agent.simple_rpcrr�defence360agent.utilsr�defence360agent.utils.clir	r
rrr
�defence360agent.utils.parsersrr�defence360agent.sentryr�	getLogger�__name__r!rLr@rSr�r?�<module>rasZ����������	�	�	�	�
�
�
�
�������'�'�'�'�;�;�;�;�;�;�>�>�>�>�>�>�;�;�;�;�;�;�;�;�.�.�.�.�.�.���������������G�F�F�F�F�F�F�F�/�/�/�/�/�/�
��	�8�	$�	$���t�;����
1$�1$�1$�h%)�%)�%)�%)�%)r`defence360agent/__pycache__/migrate.cpython-311.opt-1.pyc0000644000000000000000000002246000000000000020053 0ustar  �

$p�To������dZddlZddlZddlZddlZddlZddlZddlmZddl	m
Z
ddlmZddl
mZddlZddlmZddlmZdd	lmZdd
lmZddlmZddlmZdd
lmZddlm Z ddl!m"Z"m#Z#m$Z$ddl%m&Z&e
e'��Z(dZ)ej*de+de,fd���Z-dedee+fd�Z.e/��fdee+de/e/e+e+fdffd�Z0d�Z1ded�d�Z2e'dkre2��dSdS) zbThis module import peewee_migrate and apply migrations, for Imunify-AV
it's entrypoint for service�N)�Iterable)�	getLogger)�migrator)�SqliteExtDatabase)�app)�	configure)�Core)�Model)�Router)�systemd_notifier)�db)�	tls_check)�write_pid_file�IM360_RESIDENT_PID_PATH�cleanup_pid_file)�recreate_schema_modelsz/usr/bin/imunify-resident�log_msg�reraisec#�vK�	dV�dS#t$r$t�||���|r�YdSwxYw)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)�	Exception�logger�error)rrs  �L/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrate.py�exc_handlerr)se�����
��������������W�7�{��3�3�3��	��	�	�	����s�
�*8�8r
�migrations_dirsc�~�t||t���}tt_|���dS)z4Apply migrations: restructure db, config files, etc.)rrN)rrr�LOGGER�run)r
r�routers   r�apply_migrationsr!:s;���
�'�����F��H�O�
�J�J�L�L�L�L�L��attached_dbs.c��tj��tjtj��g}|D]1\}}tjd||f��|�|���2	t�	d��tjtjj
��tjd��5tdd���5t!t|��ddd��n#1swxYwYddd��n#1swxYwYt�	d��tjd��5td	d
���5t#t|��tdd
���5t!t|��ddd��n#1swxYwYddd��n#1swxYwYddd��n#1swxYwYtj��dS#tj��wxYw)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)r�reset�db_instance�initr
�PATH�execute_sql�appendr�infor�notify�
AgentState�	MIGRATING�atomicrr!r�close)rr#�attached_schemas�db_path�schema_names     r�prepare_databasesr5Hs2��"�O������U�Z� � � ��� ,�-�-�������'�;�1G�H�H�H�����,�,�,�,�����5�6�6�6��� 0� ;� E�F�F�F�
�
��
,�
,�	;�	;�k�'��/
�/
�/
�	;�	;�
�[�/�:�:�:�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;����	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;����	;�	;�	;�	;�
	���6�7�7�7�
�
��
,�
,�
	?�
	?�k�1�4�/
�/
�/
�
	?�
	?�

#�;�0@�A�A�A�������
?�
?�
!��o�>�>�>�
?�
?�
?�
?�
?�
?�
?�
?�
?�
?�
?����
?�
?�
?�
?�
	?�
	?�
	?�
	?�
	?�
	?�
	?�
	?�
	?�
	?�
	?����
	?�
	?�
	?�
	?�
	?�
	?�
	?�
	?�
	?�
	?�
	?�
	?�
	?�
	?�
	?����
	?�
	?�
	?�
	?�"	����������������s��)AG'�:D�C.�"D�.C2	�2D�5C2	�6D�9G'�D	�	G'�D	�
1G'�>G�'F0�7F�
F0�F�F0� F�!F0�$G�0F4	�4G�7F4	�8G�;G'�G�G'�G�G'�'G<c��t�d|��t�dtj��t	jtj��t�d��tjd��dS)Nz$Received signal %s in signal_handlerz0waiting %d seconds so that migrations can finish�Exitingr)	r�warningr	�%SIGNAL_HANDLER_MIGRATION_TIMEOUT_SECS�time�sleepr,�sys�exit)�sig�_s  r�signal_handlerr@~sn��
�N�N�9�3�?�?�?�
�N�N�:��2����	�J�t�9�:�:�:�
�K�K�	�����H�Q�K�K�K�K�Kr"�defence360agent)�	start_pkgrc��tjtjtjfD]}tj|t���	|dkrtt��tjtj
��|��tjj
���tjt"t$jt$jf���}|���|���t/jt.jj��t�d��t/jt.jj��|dkrqtj�d���t�d��tjt@t@gtBj"dd�z��dStjtBj#tBj#d	d
�$|��gtBj"dd�z��dS#tJ$r!|dkrtMt��YdSYdSwxYw)zoEntry point for Imunify-AV service. Apply migrations,
    and then replace process with {start_pkg}.run module.zim360.run_resident)�target�argszStarting main process...T)�exist_okzRun imunify-resident service�Nz-mz{})'�signal�SIGINT�SIGTERM�SIGHUPr@rr�os�umaskr	�
FILE_UMASKrA�	internalsr�reconfigure�	threading�Threadr5r�MIGRATIONS_DIRS�MIGRATIONS_ATTACHED_DBS�start�joinrr-r.�READYr,�STARTING�GO_FLAG_FILE�touch�execv�GO_SERVICE_NAMEr<�argv�
executable�formatrr)rBrr>�migration_threads    rrr�s*���
�v�~�v�}�=�+�+���
�c�>�*�*�*�*�"6��,�,�,��2�3�3�3�
����!�!�!��	�����!�(�4�4�6�6�6�$�+�$��%�s�'B�C�
�
�
��	��� � � ��������� 0� ;� A�B�B�B����.�/�/�/��� 0� ;� D�E�E�E��,�,�,���#�#�T�#�2�2�2��K�K�6�7�7�7��H��#���(�1�2�2�,��
�
�
�
�
�
�H������t�{�{�9�'=�'=�>���!�"�"��M�
�
�
�
�
���6�6�6��,�,�,��4�5�5�5�5�5�5�-�,�,�6���s�FH-�AH-�-$I�I�__main__)3�__doc__�
contextlibrLr<rHrQr:�collections.abcr�loggingr�peewee_migrater�playhouse.sqlite_extr� defence360agent.internals.loggerrA�defence360agent.applicationr�$defence360agent.application.settingsr� defence360agent.contracts.configr	r
�defence360agent.routerr�defence360agent.subsysr�defence360agent.model.instancer
r'�defence360agent.modelr�defence360agent.utilsrrr�defence360agent.utils.check_dbr�__name__rr\�contextmanager�str�boolrr!�tupler5r@r�r"r�<module>rxs���������	�	�	�	�
�
�
�
�
�
�
�
���������$�$�$�$�$�$�������#�#�#�#�#�#�2�2�2�2�2�2�'�'�'�'�+�+�+�+�+�+�:�:�:�:�:�:�1�1�1�1�1�1�2�2�2�2�2�2�)�)�)�)�)�)�3�3�3�3�3�3�<�<�<�<�<�<�+�+�+�+�+�+�����������
������
��8�	�	��-����
��
�t�
�
�
���
� �*��X�c�]����� 16����2�2��c�]�2���c�3�h���,�-�2�2�2�2�l���'�)�(6�(6�(6�(6�(6�V�z����C�E�E�E�E�E��r"defence360agent/__pycache__/migrate.cpython-311.pyc0000644000000000000000000002246000000000000017114 0ustar  �

$p�To������dZddlZddlZddlZddlZddlZddlZddlmZddl	m
Z
ddlmZddl
mZddlZddlmZddlmZdd	lmZdd
lmZddlmZddlmZdd
lmZddlm Z ddl!m"Z"m#Z#m$Z$ddl%m&Z&e
e'��Z(dZ)ej*de+de,fd���Z-dedee+fd�Z.e/��fdee+de/e/e+e+fdffd�Z0d�Z1ded�d�Z2e'dkre2��dSdS) zbThis module import peewee_migrate and apply migrations, for Imunify-AV
it's entrypoint for service�N)�Iterable)�	getLogger)�migrator)�SqliteExtDatabase)�app)�	configure)�Core)�Model)�Router)�systemd_notifier)�db)�	tls_check)�write_pid_file�IM360_RESIDENT_PID_PATH�cleanup_pid_file)�recreate_schema_modelsz/usr/bin/imunify-resident�log_msg�reraisec#�vK�	dV�dS#t$r$t�||���|r�YdSwxYw)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)�	Exception�logger�error)rrs  �L/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrate.py�exc_handlerr)se�����
��������������W�7�{��3�3�3��	��	�	�	����s�
�*8�8r
�migrations_dirsc�~�t||t���}tt_|���dS)z4Apply migrations: restructure db, config files, etc.)rrN)rrr�LOGGER�run)r
r�routers   r�apply_migrationsr!:s;���
�'�����F��H�O�
�J�J�L�L�L�L�L��attached_dbs.c��tj��tjtj��g}|D]1\}}tjd||f��|�|���2	t�	d��tjtjj
��tjd��5tdd���5t!t|��ddd��n#1swxYwYddd��n#1swxYwYt�	d��tjd��5td	d
���5t#t|��tdd
���5t!t|��ddd��n#1swxYwYddd��n#1swxYwYddd��n#1swxYwYtj��dS#tj��wxYw)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)r�reset�db_instance�initr
�PATH�execute_sql�appendr�infor�notify�
AgentState�	MIGRATING�atomicrr!r�close)rr#�attached_schemas�db_path�schema_names     r�prepare_databasesr5Hs2��"�O������U�Z� � � ��� ,�-�-�������'�;�1G�H�H�H�����,�,�,�,�����5�6�6�6��� 0� ;� E�F�F�F�
�
��
,�
,�	;�	;�k�'��/
�/
�/
�	;�	;�
�[�/�:�:�:�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;����	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;����	;�	;�	;�	;�
	���6�7�7�7�
�
��
,�
,�
	?�
	?�k�1�4�/
�/
�/
�
	?�
	?�

#�;�0@�A�A�A�������
?�
?�
!��o�>�>�>�
?�
?�
?�
?�
?�
?�
?�
?�
?�
?�
?����
?�
?�
?�
?�
	?�
	?�
	?�
	?�
	?�
	?�
	?�
	?�
	?�
	?�
	?����
	?�
	?�
	?�
	?�
	?�
	?�
	?�
	?�
	?�
	?�
	?�
	?�
	?�
	?�
	?����
	?�
	?�
	?�
	?�"	����������������s��)AG'�:D�C.�"D�.C2	�2D�5C2	�6D�9G'�D	�	G'�D	�
1G'�>G�'F0�7F�
F0�F�F0� F�!F0�$G�0F4	�4G�7F4	�8G�;G'�G�G'�G�G'�'G<c��t�d|��t�dtj��t	jtj��t�d��tjd��dS)Nz$Received signal %s in signal_handlerz0waiting %d seconds so that migrations can finish�Exitingr)	r�warningr	�%SIGNAL_HANDLER_MIGRATION_TIMEOUT_SECS�time�sleepr,�sys�exit)�sig�_s  r�signal_handlerr@~sn��
�N�N�9�3�?�?�?�
�N�N�:��2����	�J�t�9�:�:�:�
�K�K�	�����H�Q�K�K�K�K�Kr"�defence360agent)�	start_pkgrc��tjtjtjfD]}tj|t���	|dkrtt��tjtj
��|��tjj
���tjt"t$jt$jf���}|���|���t/jt.jj��t�d��t/jt.jj��|dkrqtj�d���t�d��tjt@t@gtBj"dd�z��dStjtBj#tBj#d	d
�$|��gtBj"dd�z��dS#tJ$r!|dkrtMt��YdSYdSwxYw)zoEntry point for Imunify-AV service. Apply migrations,
    and then replace process with {start_pkg}.run module.zim360.run_resident)�target�argszStarting main process...T)�exist_okzRun imunify-resident service�Nz-mz{})'�signal�SIGINT�SIGTERM�SIGHUPr@rr�os�umaskr	�
FILE_UMASKrA�	internalsr�reconfigure�	threading�Threadr5r�MIGRATIONS_DIRS�MIGRATIONS_ATTACHED_DBS�start�joinrr-r.�READYr,�STARTING�GO_FLAG_FILE�touch�execv�GO_SERVICE_NAMEr<�argv�
executable�formatrr)rBrr>�migration_threads    rrr�s*���
�v�~�v�}�=�+�+���
�c�>�*�*�*�*�"6��,�,�,��2�3�3�3�
����!�!�!��	�����!�(�4�4�6�6�6�$�+�$��%�s�'B�C�
�
�
��	��� � � ��������� 0� ;� A�B�B�B����.�/�/�/��� 0� ;� D�E�E�E��,�,�,���#�#�T�#�2�2�2��K�K�6�7�7�7��H��#���(�1�2�2�,��
�
�
�
�
�
�H������t�{�{�9�'=�'=�>���!�"�"��M�
�
�
�
�
���6�6�6��,�,�,��4�5�5�5�5�5�5�-�,�,�6���s�FH-�AH-�-$I�I�__main__)3�__doc__�
contextlibrLr<rHrQr:�collections.abcr�loggingr�peewee_migrater�playhouse.sqlite_extr� defence360agent.internals.loggerrA�defence360agent.applicationr�$defence360agent.application.settingsr� defence360agent.contracts.configr	r
�defence360agent.routerr�defence360agent.subsysr�defence360agent.model.instancer
r'�defence360agent.modelr�defence360agent.utilsrrr�defence360agent.utils.check_dbr�__name__rr\�contextmanager�str�boolrr!�tupler5r@r�r"r�<module>rxs���������	�	�	�	�
�
�
�
�
�
�
�
���������$�$�$�$�$�$�������#�#�#�#�#�#�2�2�2�2�2�2�'�'�'�'�+�+�+�+�+�+�:�:�:�:�:�:�1�1�1�1�1�1�2�2�2�2�2�2�)�)�)�)�)�)�3�3�3�3�3�3�<�<�<�<�<�<�+�+�+�+�+�+�����������
������
��8�	�	��-����
��
�t�
�
�
���
� �*��X�c�]����� 16����2�2��c�]�2���c�3�h���,�-�2�2�2�2�l���'�)�(6�(6�(6�(6�(6�V�z����C�E�E�E�E�E��r"defence360agent/__pycache__/router.cpython-311.opt-1.pyc0000644000000000000000000000672500000000000017751 0ustar  �

�x�ԇ'o��X�dZddlZddlmZddlmZddlmZdgZ	Gd�de��ZdS)z!Provide Router for db migrations.�N)�suppress)�Router)�voidrc�>��eZdZdZ�fd�Zed���Zd�Z�xZS)rzALike peewee_migrate.Router but supports multiple migrations dirs.c�Z��t��j|fd|di|��||_dS)N�migrate_dirr)�super�__init__�migrations_dirs)�self�databaser�kwargs�	__class__s    ��K/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/router.pyr
zRouter.__init__s:���������L�L��q�/A�L�V�L�L�L�.�����c�0���jD]P}tj�|��s/�j�d|��tj|���Qg}�jD]2}|t�fd�tj|��D����z
}�3|S)zScan migrations in file system.z'Migration directory: %s does not exist.c3�|�K�|]6}�j�|���|dtd���V��7dS)N�.py)�filemask�match�len)�.0�frs  �r�	<genexpr>zRouter.todo.<locals>.<genexpr>s^�����&�&���=�&�&�q�)�)�&��-�S��Z�Z�K�-� �&�&�&�&�&�&r)	r�os�path�exists�logger�warn�makedirs�sorted�listdir)rr�migration_namess`  r�todozRouter.todos���� �/�	)�	)�K��7�>�>�+�.�.�
)��� � �=�{������K�(�(�(�����/�	�	�K��v�&�&�&�&���K�0�0�&�&�&� � �
�O�O�
�rc	���i}|jD]�}tt��5ttj�||dz����5}t|���ddd���}t||��ddd��n#1swxYwYddd��n#1swxYwY��|�
dt��|�
dt��fS)	zRead migration from file.rz<string>�execT)�dont_inheritN�migrate�rollback)rr�FileNotFoundError�openrr�join�compile�readr&�getr)r�name�scoperr�codes      rr.zRouter.read&sR�����/�	&�	&�K��+�,�,�
&�
&��"�'�,�,�{�D�5�L�A�A�B�B�&�a�"������*�f�4����D���u�%�%�%�	&�&�&�&�&�&�&�&�&�&�&����&�&�&�&�
&�
&�
&�
&�
&�
&�
&�
&�
&�
&�
&����
&�
&�
&�
&���y�y��D�)�)�5�9�9�Z��+F�+F�F�Fs5�1B*�6B�B*�B�B*�B�B*�*B.	�1B.	)	�__name__�
__module__�__qualname__�__doc__r
�propertyr$r.�
__classcell__)rs@rrrso�������K�K�/�/�/�/�/�����X��"
G�
G�
G�
G�
G�
G�
Gr)
r6r�
contextlibr�peewee_migrater�PeeweeRouter�peewee_migrate.routerr�__all__�rr�<module>r?s���'�'�	�	�	�	�������1�1�1�1�1�1�&�&�&�&�&�&��*��$G�$G�$G�$G�$G�\�$G�$G�$G�$G�$Grdefence360agent/__pycache__/router.cpython-311.pyc0000644000000000000000000000672500000000000017012 0ustar  �

�x�ԇ'o��X�dZddlZddlmZddlmZddlmZdgZ	Gd�de��ZdS)z!Provide Router for db migrations.�N)�suppress)�Router)�voidrc�>��eZdZdZ�fd�Zed���Zd�Z�xZS)rzALike peewee_migrate.Router but supports multiple migrations dirs.c�Z��t��j|fd|di|��||_dS)N�migrate_dirr)�super�__init__�migrations_dirs)�self�databaser�kwargs�	__class__s    ��K/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/router.pyr
zRouter.__init__s:���������L�L��q�/A�L�V�L�L�L�.�����c�0���jD]P}tj�|��s/�j�d|��tj|���Qg}�jD]2}|t�fd�tj|��D����z
}�3|S)zScan migrations in file system.z'Migration directory: %s does not exist.c3�|�K�|]6}�j�|���|dtd���V��7dS)N�.py)�filemask�match�len)�.0�frs  �r�	<genexpr>zRouter.todo.<locals>.<genexpr>s^�����&�&���=�&�&�q�)�)�&��-�S��Z�Z�K�-� �&�&�&�&�&�&r)	r�os�path�exists�logger�warn�makedirs�sorted�listdir)rr�migration_namess`  r�todozRouter.todos���� �/�	)�	)�K��7�>�>�+�.�.�
)��� � �=�{������K�(�(�(�����/�	�	�K��v�&�&�&�&���K�0�0�&�&�&� � �
�O�O�
�rc	���i}|jD]�}tt��5ttj�||dz����5}t|���ddd���}t||��ddd��n#1swxYwYddd��n#1swxYwY��|�
dt��|�
dt��fS)	zRead migration from file.rz<string>�execT)�dont_inheritN�migrate�rollback)rr�FileNotFoundError�openrr�join�compile�readr&�getr)r�name�scoperr�codes      rr.zRouter.read&sR�����/�	&�	&�K��+�,�,�
&�
&��"�'�,�,�{�D�5�L�A�A�B�B�&�a�"������*�f�4����D���u�%�%�%�	&�&�&�&�&�&�&�&�&�&�&����&�&�&�&�
&�
&�
&�
&�
&�
&�
&�
&�
&�
&�
&����
&�
&�
&�
&���y�y��D�)�)�5�9�9�Z��+F�+F�F�Fs5�1B*�6B�B*�B�B*�B�B*�*B.	�1B.	)	�__name__�
__module__�__qualname__�__doc__r
�propertyr$r.�
__classcell__)rs@rrrso�������K�K�/�/�/�/�/�����X��"
G�
G�
G�
G�
G�
G�
Gr)
r6r�
contextlibr�peewee_migrater�PeeweeRouter�peewee_migrate.routerr�__all__�rr�<module>r?s���'�'�	�	�	�	�������1�1�1�1�1�1�&�&�&�&�&�&��*��$G�$G�$G�$G�$G�\�$G�$G�$G�$G�$Grdefence360agent/__pycache__/run.cpython-311.opt-1.pyc0000644000000000000000000000043000000000000017220 0ustar  �

^�'��A��
�dZdS))zdefence360agent.pluginsz*defence360agent.feature_management.pluginsN)�CORE_PLUGINS_PACKAGES���H/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/run.py�<module>rs�����rdefence360agent/__pycache__/run.cpython-311.pyc0000644000000000000000000000043000000000000016261 0ustar  �

^�'��A��
�dZdS))zdefence360agent.pluginsz*defence360agent.feature_management.pluginsN)�CORE_PLUGINS_PACKAGES���H/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/run.py�<module>rs�����rdefence360agent/__pycache__/sentry.cpython-311.opt-1.pyc0000644000000000000000000002055300000000000017750 0ustar  �

^~C(�)���z�dZddlZddlZddlZddlmZddlmZddlm	Z	m
Z
mZddlZddl
Z
ddlmZddlmZdZd	Zd
ZdZdZd
ZdZed��ZdZdefd�Zdefd�Zde	edefd�Zdedefd�Zdedefd�Z d�Z!d�Z"defd�Z#d�Z$d�Z%				d&ded e
e&d!ed"d#e
ed$e
ef
d%�Z'dS)'z2Helper for integrate sentry in stand-alone scripts�N)�suppress)�Path)�List�Optional�Literal)�tags)�sentry�
imunify360zimunify-antiviruszimunify360-firewallz/var/imunify360/license.jsonz!/var/imunify360/license-free.json�	IMUNIFYAV�UNKNOWNz,/opt/imunify360/venv/share/imunify360/sentryzQhttps://6de77a2763bd40c58fc9e3a89285aaa8@im360.sentry.cloudlinux.com/3?timeout=20�returnc��	t�d������S#ttf$r
t
cYSwxYw)z,Return dsn from the file or the default one.�ascii)�encoding)�SENTRY_DSN_PATH�	read_text�strip�OSError�UnicodeDecodeError�SENTRY_DSN_DEFAULT���K/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/sentry.py�get_sentry_dsnrsW��"��(�(�'�(�:�:�@�@�B�B�B���'�(�"�"�"�!�!�!�!�"���s�,/�A
�	A
c	��tt��5ttfD]�}tt��5t|��5}t
j|��dcddd��cddd��ccddd��S#1swxYwYddd��n#1swxYwY��	ddd��n#1swxYwYtS)N�id)	r�	Exception�LICENSE�LICENSE_FREE�FileNotFoundError�open�json�load�
UNKNOWN_ID)�filename�files  r�
get_server_idr'$s���	�)�	�	�-�-� �,�/�	-�	-�H��+�,�,�
-�
-�d�8�n�n�
-���y����t�,�
-�
-�
-�
-�
-�
-�
-�
-�
-�
-�
-�
-�
-�
-�
-�
-�-�-�-�-�-�-�-�-�
-�
-�
-�
-����
-�
-�
-�
-�
-�
-�
-�
-�
-�
-�
-�
-�
-�
-�
-����
-�
-�
-�
-��	-�-�-�-�-�-�-�-�-�-�-�-����-�-�-�-��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|j��S)N)�stdin�stdout�stderr�r)	�
subprocess�run�DEVNULL�PIPEr�
returncode�os�fsdecoder+)r(�cps  r�collect_outputr6,s{���
�^���$��?��%�	
�
�
���������r�r�����	�}�����r�
�;�r�y�!�!�!s�69�
A�A�pkgc�,�ddd|g}t|��S)N�rpmz-qz#--queryformat=%{VERSION}-%{RELEASE}�r6�r7r(s  r�get_rpm_versionr<;s���$�=�s�
C�C��#���rc�,�ddd|g}t|��S)Nz
dpkg-queryz--showformat=${Version}z--showr:r;s  r�get_dpkg_versionr>@s���2�H�c�
B�C��#���rc�\�tj��d}|���S)Nr)�distro�linux_distribution�lower)�platform_oss r�get_current_osrDEs'���+�-�-�a�0�K������rc��t��}t}|dkrtt��rt}nt}|S�N�ubuntu)rD�IMUNIFY360_PKGr<r�
IMUNIFY360)rC�service_names  r�get_package_namerKJs<�� �"�"�K�!�L��h���?�9�#=�#=�� ���!���rc�l�t��}|dkrt|��}nt|��}|SrF)rDr<r>)rJrC�versions   r�get_service_versionrNTs9�� �"�"�K��h���!�,�/�/���"�<�0�0���Nrc��tjt�����tj��5}t	��}dt��i|_|�d|��|�dt|����tj
��tj	�����D]\}}|�||���	ddd��dS#1swxYwYdS)N)�dsnr�namerM)
�
sentry_sdk�initr�configure_scoperKr'�user�set_tagrNr�cached_fillr	�items)�scope�package�tag�values    r�configure_sentryr]]s)���O��(�(�)�)�)�)�	�	#�	%�	%�&��"�$�$���M�O�O�,��
�
�
�
�f�g�&�&�&�
�
�
�i�!4�W�!=�!=�>�>�>������ �+�-�-�-�-�/�/�	&�	&�J�C���M�M�#�u�%�%�%�%�	&�
&�&�&�&�&�&�&�&�&�&�&�&����&�&�&�&�&�&s�B1C4�4C8�;C8c�f�tjjj}|�|�d���dSdS)Ng@)�timeout)rR�Hub�current�client�flush)rbs r�flush_sentryrdks7��
�^�
#�
*�F�
�����S��!�!�!�!�!��r�warning�message�format_args�level)�fatal�critical�errorre�info�debug�fingerprint�	componentc�|�|�i}|r!	|jdi|��}n#t$r|}YnwxYw|}|�dd��|s|rdtj��5}|r|g|_|r|�d|��tj|fd|i|��ddd��dS#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)�format�KeyError�poprR�
push_scopernrV�capture_message)rfrgrhrnro�kwargs�formatted_messagerYs        r�log_messagerxqs���6�����$�	(� .��� =� =�� =� =�����	(�	(�	(� '����	(����$���J�J�w������
M�i�
M�
�
"�
$�
$�	���
2�%0�M��!��
6��
�
�k�9�5�5�5��&�!�
�
�).�
�28�
�
�
�	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	�	�	�"�#4�L�L�E�L�V�L�L�L�L�Ls�
�%�%�7B�B�"B)NreNN)(�__doc__r"r3r.�
contextlibr�pathlibr�typingrrrr@rR�defence360agent.applicationr�defence360agent.contractsr	rIrrHrr�FREE_IDr$rr�strrr'r6r<r>rDrKrNr]rd�dictrxrrr�<module>r�sW��8�8�����	�	�	�	�����������������*�*�*�*�*�*�*�*�*�*�
�
�
�
�����,�,�,�,�,�,�,�,�,�,�,�,��
��	�&��
(��2��
��
�
��$�E�F�F��h��"��"�"�"�"��s�����"��S�	�"�c�"�"�"�"���������
�#��#�����
���
���������&�&�&�"�"�"�#'�	�!%�#�5M�5M�
�5M��$��5M��@��5M��#��
5M���}�5M�5M�5M�5M�5M�5Mrdefence360agent/__pycache__/sentry.cpython-311.pyc0000644000000000000000000002055300000000000017011 0ustar  �

^~C(�)���z�dZddlZddlZddlZddlmZddlmZddlm	Z	m
Z
mZddlZddl
Z
ddlmZddlmZdZd	Zd
ZdZdZd
ZdZed��ZdZdefd�Zdefd�Zde	edefd�Zdedefd�Zdedefd�Z d�Z!d�Z"defd�Z#d�Z$d�Z%				d&ded e
e&d!ed"d#e
ed$e
ef
d%�Z'dS)'z2Helper for integrate sentry in stand-alone scripts�N)�suppress)�Path)�List�Optional�Literal)�tags)�sentry�
imunify360zimunify-antiviruszimunify360-firewallz/var/imunify360/license.jsonz!/var/imunify360/license-free.json�	IMUNIFYAV�UNKNOWNz,/opt/imunify360/venv/share/imunify360/sentryzQhttps://6de77a2763bd40c58fc9e3a89285aaa8@im360.sentry.cloudlinux.com/3?timeout=20�returnc��	t�d������S#ttf$r
t
cYSwxYw)z,Return dsn from the file or the default one.�ascii)�encoding)�SENTRY_DSN_PATH�	read_text�strip�OSError�UnicodeDecodeError�SENTRY_DSN_DEFAULT���K/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/sentry.py�get_sentry_dsnrsW��"��(�(�'�(�:�:�@�@�B�B�B���'�(�"�"�"�!�!�!�!�"���s�,/�A
�	A
c	��tt��5ttfD]�}tt��5t|��5}t
j|��dcddd��cddd��ccddd��S#1swxYwYddd��n#1swxYwY��	ddd��n#1swxYwYtS)N�id)	r�	Exception�LICENSE�LICENSE_FREE�FileNotFoundError�open�json�load�
UNKNOWN_ID)�filename�files  r�
get_server_idr'$s���	�)�	�	�-�-� �,�/�	-�	-�H��+�,�,�
-�
-�d�8�n�n�
-���y����t�,�
-�
-�
-�
-�
-�
-�
-�
-�
-�
-�
-�
-�
-�
-�
-�
-�-�-�-�-�-�-�-�-�
-�
-�
-�
-����
-�
-�
-�
-�
-�
-�
-�
-�
-�
-�
-�
-�
-�
-�
-����
-�
-�
-�
-��	-�-�-�-�-�-�-�-�-�-�-�-����-�-�-�-��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|j��S)N)�stdin�stdout�stderr�r)	�
subprocess�run�DEVNULL�PIPEr�
returncode�os�fsdecoder+)r(�cps  r�collect_outputr6,s{���
�^���$��?��%�	
�
�
���������r�r�����	�}�����r�
�;�r�y�!�!�!s�69�
A�A�pkgc�,�ddd|g}t|��S)N�rpmz-qz#--queryformat=%{VERSION}-%{RELEASE}�r6�r7r(s  r�get_rpm_versionr<;s���$�=�s�
C�C��#���rc�,�ddd|g}t|��S)Nz
dpkg-queryz--showformat=${Version}z--showr:r;s  r�get_dpkg_versionr>@s���2�H�c�
B�C��#���rc�\�tj��d}|���S)Nr)�distro�linux_distribution�lower)�platform_oss r�get_current_osrDEs'���+�-�-�a�0�K������rc��t��}t}|dkrtt��rt}nt}|S�N�ubuntu)rD�IMUNIFY360_PKGr<r�
IMUNIFY360)rC�service_names  r�get_package_namerKJs<�� �"�"�K�!�L��h���?�9�#=�#=�� ���!���rc�l�t��}|dkrt|��}nt|��}|SrF)rDr<r>)rJrC�versions   r�get_service_versionrNTs9�� �"�"�K��h���!�,�/�/���"�<�0�0���Nrc��tjt�����tj��5}t	��}dt��i|_|�d|��|�dt|����tj
��tj	�����D]\}}|�||���	ddd��dS#1swxYwYdS)N)�dsnr�namerM)
�
sentry_sdk�initr�configure_scoperKr'�user�set_tagrNr�cached_fillr	�items)�scope�package�tag�values    r�configure_sentryr]]s)���O��(�(�)�)�)�)�	�	#�	%�	%�&��"�$�$���M�O�O�,��
�
�
�
�f�g�&�&�&�
�
�
�i�!4�W�!=�!=�>�>�>������ �+�-�-�-�-�/�/�	&�	&�J�C���M�M�#�u�%�%�%�%�	&�
&�&�&�&�&�&�&�&�&�&�&�&����&�&�&�&�&�&s�B1C4�4C8�;C8c�f�tjjj}|�|�d���dSdS)Ng@)�timeout)rR�Hub�current�client�flush)rbs r�flush_sentryrdks7��
�^�
#�
*�F�
�����S��!�!�!�!�!��r�warning�message�format_args�level)�fatal�critical�errorre�info�debug�fingerprint�	componentc�|�|�i}|r!	|jdi|��}n#t$r|}YnwxYw|}|�dd��|s|rdtj��5}|r|g|_|r|�d|��tj|fd|i|��ddd��dS#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)�format�KeyError�poprR�
push_scopernrV�capture_message)rfrgrhrnro�kwargs�formatted_messagerYs        r�log_messagerxqs���6�����$�	(� .��� =� =�� =� =�����	(�	(�	(� '����	(����$���J�J�w������
M�i�
M�
�
"�
$�
$�	���
2�%0�M��!��
6��
�
�k�9�5�5�5��&�!�
�
�).�
�28�
�
�
�	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	�	�	�"�#4�L�L�E�L�V�L�L�L�L�Ls�
�%�%�7B�B�"B)NreNN)(�__doc__r"r3r.�
contextlibr�pathlibr�typingrrrr@rR�defence360agent.applicationr�defence360agent.contractsr	rIrrHrr�FREE_IDr$rr�strrr'r6r<r>rDrKrNr]rd�dictrxrrr�<module>r�sW��8�8�����	�	�	�	�����������������*�*�*�*�*�*�*�*�*�*�
�
�
�
�����,�,�,�,�,�,�,�,�,�,�,�,��
��	�&��
(��2��
��
�
��$�E�F�F��h��"��"�"�"�"��s�����"��S�	�"�c�"�"�"�"���������
�#��#�����
���
���������&�&�&�"�"�"�#'�	�!%�#�5M�5M�
�5M��$��5M��@��5M��#��
5M���}�5M�5M�5M�5M�5M�5Mrdefence360agent/_version.py0000644000000000000000000000012300000000000012740 0ustar  # DO NOT EDIT: bump_version.py keeps it in sync with *.spec
__version__ = "8.12.0"
defence360agent/api/0000755000000000000000000000000000000000000011317 5ustar  defence360agent/api/__init__.py0000644000000000000000000000000000000000000013416 0ustar  defence360agent/api/__pycache__/0000755000000000000000000000000000000000000013527 5ustar  defence360agent/api/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000000027600000000000020734 0ustar  �

s�����s���dS)N�r��Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/__init__.py�<module>rs���rdefence360agent/api/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000027600000000000017775 0ustar  �

s�����s���dS)N�r��Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/__init__.py�<module>rs���rdefence360agent/api/__pycache__/health.cpython-311.opt-1.pyc0000644000000000000000000001142200000000000020435 0ustar  �

|��n����f�dZddlZejdddg��ZGd�d��Ze��ZdS)a�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.�N�HealthStatus�healthy�whyc��eZdZdZdZdZdZd�Zdeddfd	�Z	deddfd
�Z
deddfd�Zdeddfd�Zdd
�Z
dd�Zdedefd�ZdS)�HealthSensora�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).i �i`TiXc�L�d|_d|_d|_d|_d|_dS)NgT)�_started_at�_shutdown_at�_last_received�
_last_sent�_is_registered��selfs �O/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/health.py�__init__zHealthSensor.__init__1s/��������!������"������when�returnNc��||_dS)z!Records a moment of agent startupN)r	�rrs  r�startingzHealthSensor.starting8s������rc��||_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)rrs  r�server_data_receivedz!HealthSensor.server_data_received@���"����rc��||_dS)z-Records a moment when data was sent to serverN)rrs  r�server_data_sentzHealthSensor.server_data_sentDs
������rc��d|_dS)zMarks agent as being registeredTN�r
rs r�
registeredzHealthSensor.registeredHrrc��d|_dS)z#Marks agent as being not registeredFNr rs r�unregisteredzHealthSensor.unregisteredLs��#����r�nowc��|jdkr3||jz
|jkrtdd��Stdd��S|jstdd��S||jz
|jkr#||jz
|jkrtdd��S||jz
|jkr#||jz
|jkrtdd��Stdd	��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  r�statuszHealthSensor.statusPs�����q� � ��T�&�&�$�*?�?�?�#�E�+>�?�?�?���&?�@�@�@��"�	8���&6�7�7�7��$�"�"�d�&9�9�9��d�)�)�T�-@�@�@���'E�F�F�F��$�"�"�d�&6�6�6��d�o�%��)9�9�9���'?�@�@�@��D�+�.�.�.r)rN)�__name__�
__module__�__qualname__�__doc__r'r(r&r�floatrrrrr!r#rr)�rrrrs������4�4��N��K���#�#�#� �U� �t� � � � �!�%�!�D�!�!�!�!�#��#�4�#�#�#�#��U��t�����#�#�#�#�$�$�$�$�/�%�/�L�/�/�/�/�/�/rr)r-�collections�
namedtuplerr�sensorr/rr�<module>r3sz��C�C�2����%�{�%�n�y�%�6H�I�I��B/�B/�B/�B/�B/�B/�B/�B/�J
������rdefence360agent/api/__pycache__/health.cpython-311.pyc0000644000000000000000000001142200000000000017476 0ustar  �

|��n����f�dZddlZejdddg��ZGd�d��Ze��ZdS)a�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.�N�HealthStatus�healthy�whyc��eZdZdZdZdZdZd�Zdeddfd	�Z	deddfd
�Z
deddfd�Zdeddfd�Zdd
�Z
dd�Zdedefd�ZdS)�HealthSensora�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).i �i`TiXc�L�d|_d|_d|_d|_d|_dS)NgT)�_started_at�_shutdown_at�_last_received�
_last_sent�_is_registered��selfs �O/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/health.py�__init__zHealthSensor.__init__1s/��������!������"������when�returnNc��||_dS)z!Records a moment of agent startupN)r	�rrs  r�startingzHealthSensor.starting8s������rc��||_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)rrs  r�server_data_receivedz!HealthSensor.server_data_received@���"����rc��||_dS)z-Records a moment when data was sent to serverN)rrs  r�server_data_sentzHealthSensor.server_data_sentDs
������rc��d|_dS)zMarks agent as being registeredTN�r
rs r�
registeredzHealthSensor.registeredHrrc��d|_dS)z#Marks agent as being not registeredFNr rs r�unregisteredzHealthSensor.unregisteredLs��#����r�nowc��|jdkr3||jz
|jkrtdd��Stdd��S|jstdd��S||jz
|jkr#||jz
|jkrtdd��S||jz
|jkr#||jz
|jkrtdd��Stdd	��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  r�statuszHealthSensor.statusPs�����q� � ��T�&�&�$�*?�?�?�#�E�+>�?�?�?���&?�@�@�@��"�	8���&6�7�7�7��$�"�"�d�&9�9�9��d�)�)�T�-@�@�@���'E�F�F�F��$�"�"�d�&6�6�6��d�o�%��)9�9�9���'?�@�@�@��D�+�.�.�.r)rN)�__name__�
__module__�__qualname__�__doc__r'r(r&r�floatrrrrr!r#rr)�rrrrs������4�4��N��K���#�#�#� �U� �t� � � � �!�%�!�D�!�!�!�!�#��#�4�#�#�#�#��U��t�����#�#�#�#�$�$�$�$�/�%�/�L�/�/�/�/�/�/rr)r-�collections�
namedtuplerr�sensorr/rr�<module>r3sz��C�C�2����%�{�%�n�y�%�6H�I�I��B/�B/�B/�B/�B/�B/�B/�B/�J
������rdefence360agent/api/__pycache__/inactivity.cpython-311.opt-1.pyc0000644000000000000000000000713000000000000021354 0ustar  �

;hY.,�T��r�dZddlZddlmZmZddlmZee��ZGd�d��Z	e	��Z
dS)z�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).
�N)�contextmanager�suppress)�	getLoggerc�X�eZdZd�Zd�Zed���Zd�Zd�Zd�Z	d�Z
ded	d
fd�Zd
S)�InactivityTrackerc�`�tj��|_d|_g|_d|_dS)Nr)�time�	monotonic�_last_action_timestamp�_long_action_counter�_long_actions_list�_timeout��selfs �S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/inactivity.py�__init__zInactivityTracker.__init__
s-��&*�n�&6�&6��#�$%��!�"$�����
�
�
�c�j�d�tj��|jz
|j��S)Nz0Time from last action is {:.0f}, long actions {})�formatr	r
rr
rs r�__str__zInactivityTracker.__str__s3��A�H�H��N���t�:�:��#�
�
�	
rc#�K�|�|��	dV�|�|��dS#|�|��wxYw�N)�start�stop�r�names  r�taskzInactivityTracker.tasksM�����
�
�4����	��E�E�E��I�I�d�O�O�O�O�O��D�I�I�d�O�O�O�O���s	�4�Ac�6�tj��|_dSr)r	r
rrs r�reset_timerzInactivityTracker.reset_timer!s��&*�n�&6�&6��#�#�#rc��|xjdz
c_|j�|��|���dS�N�)rr
�appendrrs  rrzInactivityTracker.start$sE���!�!�Q�&�!�!���&�&�t�,�,�,��������rc���|xjdzc_tt��5|j�|��ddd��n#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��������s�A�A�Ac�Z�|jo#|j|jztj��kSr)rrrr	r
rs r�
is_timeoutzInactivityTracker.is_timeout/s/���-�-�
��'�$�-�7�4�>�;K�;K�K�	
r�timeout�returnNc��||_dSr)r)rr)s  r�set_timeoutzInactivityTracker.set_timeout4s
����
�
�
r)
�__name__�
__module__�__qualname__rrrrrrrr(�intr,�rrrrs����������
�
�
�����^��7�7�7����
���
�
�
�
 �3� �4� � � � � � rr)�__doc__r	�
contextlibrr�loggingrr-�loggerr�trackr1rr�<module>r7s���������/�/�/�/�/�/�/�/�������	��8�	�	��) �) �) �) �) �) �) �) �X	������rdefence360agent/api/__pycache__/inactivity.cpython-311.pyc0000644000000000000000000000713000000000000020415 0ustar  �

;hY.,�T��r�dZddlZddlmZmZddlmZee��ZGd�d��Z	e	��Z
dS)z�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).
�N)�contextmanager�suppress)�	getLoggerc�X�eZdZd�Zd�Zed���Zd�Zd�Zd�Z	d�Z
ded	d
fd�Zd
S)�InactivityTrackerc�`�tj��|_d|_g|_d|_dS)Nr)�time�	monotonic�_last_action_timestamp�_long_action_counter�_long_actions_list�_timeout��selfs �S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/inactivity.py�__init__zInactivityTracker.__init__
s-��&*�n�&6�&6��#�$%��!�"$�����
�
�
�c�j�d�tj��|jz
|j��S)Nz0Time from last action is {:.0f}, long actions {})�formatr	r
rr
rs r�__str__zInactivityTracker.__str__s3��A�H�H��N���t�:�:��#�
�
�	
rc#�K�|�|��	dV�|�|��dS#|�|��wxYw�N)�start�stop�r�names  r�taskzInactivityTracker.tasksM�����
�
�4����	��E�E�E��I�I�d�O�O�O�O�O��D�I�I�d�O�O�O�O���s	�4�Ac�6�tj��|_dSr)r	r
rrs r�reset_timerzInactivityTracker.reset_timer!s��&*�n�&6�&6��#�#�#rc��|xjdz
c_|j�|��|���dS�N�)rr
�appendrrs  rrzInactivityTracker.start$sE���!�!�Q�&�!�!���&�&�t�,�,�,��������rc���|xjdzc_tt��5|j�|��ddd��n#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��������s�A�A�Ac�Z�|jo#|j|jztj��kSr)rrrr	r
rs r�
is_timeoutzInactivityTracker.is_timeout/s/���-�-�
��'�$�-�7�4�>�;K�;K�K�	
r�timeout�returnNc��||_dSr)r)rr)s  r�set_timeoutzInactivityTracker.set_timeout4s
����
�
�
r)
�__name__�
__module__�__qualname__rrrrrrrr(�intr,�rrrrs����������
�
�
�����^��7�7�7����
���
�
�
�
 �3� �4� � � � � � rr)�__doc__r	�
contextlibrr�loggingrr-�loggerr�trackr1rr�<module>r7s���������/�/�/�/�/�/�/�/�������	��8�	�	��) �) �) �) �) �) �) �) �X	������rdefence360agent/api/__pycache__/integration_conf.cpython-311.opt-1.pyc0000644000000000000000000001436700000000000022533 0ustar  �

q(&��c���|�dZddlZddlmZddlmZGd�d��ZGd�de��ZGd	�d
e��ZdS)a�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.
�N)�Optional)�GP_FILEc�j�eZdZed���Zed���Zedededeefd���ZdS)�
BaseConfigc�J�tj�|j��S)N)�os�path�exists�
_conf_path)�clss �Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/integration_conf.pyr
zBaseConfig.existsqs���w�~�~�c�n�-�-�-�c�Z�ddlm}|��}|�|j��|S)Nr)�ConfigParser)�configparserr�readr)rr�integration_confs   r
�to_dictzBaseConfig.to_dictus<��-�-�-�-�-�-�'�<�>�>�����c�n�-�-�-��r�section�option�returnc�f�	|���||S#t$rYdSwxYw)z`
        Return *option* value in *section* in config if exist,
        None otherwise.
        N)r�KeyError)rrrs   r
�getzBaseConfig.get~sA��	��;�;�=�=��)�&�1�1���	�	�	��4�4�	���s�"�
0�0N)	�__name__�
__module__�__qualname__�classmethodr
r�strrr�rr
rrps��������.�.��[�.�� � ��[� ���#��s��x��}�����[���rrc��eZdZeZdS)�IntegrationConfigN)rrrrrr rr
r"r"�s�������J�J�Jrr"c��eZdZdZdS)�ClIntegrationConfigz!/opt/cpvendor/etc/integration.iniN)rrrrr rr
r$r$�s������4�J�J�Jrr$)	�__doc__r�typingr�3defence360agent.application.determine_hosting_panelrrr"r$r rr
�<module>r(s���g�g�R
�	�	�	�������G�G�G�G�G�G���������4�����
����5�5�5�5�5�*�5�5�5�5�5rdefence360agent/api/__pycache__/integration_conf.cpython-311.pyc0000644000000000000000000001436700000000000021574 0ustar  �

q(&��c���|�dZddlZddlmZddlmZGd�d��ZGd�de��ZGd	�d
e��ZdS)a�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.
�N)�Optional)�GP_FILEc�j�eZdZed���Zed���Zedededeefd���ZdS)�
BaseConfigc�J�tj�|j��S)N)�os�path�exists�
_conf_path)�clss �Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/integration_conf.pyr
zBaseConfig.existsqs���w�~�~�c�n�-�-�-�c�Z�ddlm}|��}|�|j��|S)Nr)�ConfigParser)�configparserr�readr)rr�integration_confs   r
�to_dictzBaseConfig.to_dictus<��-�-�-�-�-�-�'�<�>�>�����c�n�-�-�-��r�section�option�returnc�f�	|���||S#t$rYdSwxYw)z`
        Return *option* value in *section* in config if exist,
        None otherwise.
        N)r�KeyError)rrrs   r
�getzBaseConfig.get~sA��	��;�;�=�=��)�&�1�1���	�	�	��4�4�	���s�"�
0�0N)	�__name__�
__module__�__qualname__�classmethodr
r�strrr�rr
rrps��������.�.��[�.�� � ��[� ���#��s��x��}�����[���rrc��eZdZeZdS)�IntegrationConfigN)rrrrrr rr
r"r"�s�������J�J�Jrr"c��eZdZdZdS)�ClIntegrationConfigz!/opt/cpvendor/etc/integration.iniN)rrrrr rr
r$r$�s������4�J�J�Jrr$)	�__doc__r�typingr�3defence360agent.application.determine_hosting_panelrrr"r$r rr
�<module>r(s���g�g�R
�	�	�	�������G�G�G�G�G�G���������4�����
����5�5�5�5�5�*�5�5�5�5�5rdefence360agent/api/__pycache__/jwt_issuer.cpython-311.opt-1.pyc0000644000000000000000000001257400000000000021377 0ustar  �

X�.�������ddlZddlZddlZddlmZmZddlmZddlmZddl	m
Z
mZddlm
Z
e
jeje
jejiZGd�d��ZdS)	�N)�datetime�	timedelta)�Path)�InvalidTokenException)�UIRole�UserType)�atomic_rewritec�n�eZdZed��Zed��Zejdd��Zejdd��Z	e
ee�����Ze
ee	�����Z
ed���Zed	efd
���Zededed	efd
���Zededed	edzfd���Zedefd���ZdS)�	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}YnwxYwtj�����|z
|jj	kS)Ng)
�os�stat�JWT_SECRET_FILE�st_mtime�FileNotFoundErrorr�now�	timestamp�SECRET_EXPIRATION_TTL�seconds)�clsrrs   �S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/jwt_issuer.py�is_secret_expiredzJWTIssuer.is_secret_expiredsy��	%��7�3�.�/�/�D��}�H�H��!�	�	�	��H�H�H�	����

�L�N�N�$�$�&�&��1��'�/�
0�	
s�#�2�2�returnc����|���r�tjtjz�d��fd�td��D����}|j���s|j���tt|j��|t|j��dd���|S|j���S)N�c3�@�K�|]}tj���V��dS)N)�secrets�choice)�.0�_�alphabets  �r�	<genexpr>z(JWTIssuer._get_secret.<locals>.<genexpr>0s-����� M� M�a����!9�!9� M� M� M� M� M� M��@���i�)�backup�uid�permissions)
r�string�ascii_uppercase�digits�join�ranger�exists�touchr	�str�JWT_SECRET_FILE_PREV�	read_text)r�
new_secretr&s  @r�_get_secretzJWTIssuer._get_secret,s����� � �"�"�	3��-��
�=�H���� M� M� M� M�5��9�9� M� M� M�M�M�J��&�-�-�/�/�
,��#�)�)�+�+�+���C�'�(�(���3�3�4�4��!�
�
�
�
����&�0�0�2�2�2r(�	user_name�	user_typec��ddl}|�||tj��|jz���d�|�����S)z�
        Generates a token with several encoded fields:
            user name,
            user type,
            expiration timestamp
        rN)r;�username�exp)�jwt�encoderr�TOKEN_EXPIRATION_TTLrr9)rr:r;r?s    r�	get_tokenzJWTIssuer.get_token>s_��	�
�
�
��z�z�&�%� �����)A�A�L�L�N�N�
�
�

�O�O���

�
�	
r(�token�secretNc�`�ddl}	|�||dg���S#|j$rYdSwxYw)Nr�HS256)�
algorithms)r?�decode�
PyJWTError)rrCrDr?s    r�_parse_tokenzJWTIssuer._parse_tokenRsN���
�
�
�
	��:�:�e�V��	�:�B�B�B���~�	�	�	��D�D�	���s��
-�-c��|j|jfD]_}|���s�|�||�����}|r|dt
|dd�cS�`t
d���)Nr=r;)r:r;�
INVALID_TOKEN)rr6r3rJr7�UIRoleToUserTyper)rrC�
secret_pth�decodeds    r�parse_tokenzJWTIssuer.parse_token^s����.��0H�I�
	9�
	9�J��$�$�&�&�
���&�&�u�j�.B�.B�.D�.D�E�E�G��
�!(��!4�!1�'�+�2F�!G������
�(��8�8�8r()�__name__�
__module__�__qualname__rrr6r�getenv�JWT_TOKEN_EXPIRATION_TTL_HOURS�JWT_SECRET_EXPIRATION_TTL_HOURSr�intrAr�classmethodrr5r9rrB�dictrJrP�r(rrrs��������d�<�=�=�O��4� F�G�G��%.�R�Y�-�q�&�&�"�'0�b�i�.��'�'�#�%�9�3�3�/M�+N�+N�O�O�O��%�I��c�1�2�2������

�

��[�

��3�C�3�3�3��[�3�"�
�#�
�&�
�S�
�
�
��[�
�&�	��	�c�	�d�T�k�	�	�	��[�	��9��9�9�9��[�9�9�9r(r)rr"r.rr�pathlibr�"defence360agent.subsys.panels.baser� defence360agent.contracts.configrr�defence360agent.utilsr	�ADMIN�ROOT�CLIENT�NON_ROOTrMrrZr(r�<module>rcs���	�	�	�	�����
�
�
�
�(�(�(�(�(�(�(�(�������D�D�D�D�D�D�=�=�=�=�=�=�=�=�0�0�0�0�0�0��L�(�-�
�M�8�$���Y9�Y9�Y9�Y9�Y9�Y9�Y9�Y9�Y9�Y9r(defence360agent/api/__pycache__/jwt_issuer.cpython-311.pyc0000644000000000000000000001257400000000000020440 0ustar  �

X�.�������ddlZddlZddlZddlmZmZddlmZddlmZddl	m
Z
mZddlm
Z
e
jeje
jejiZGd�d��ZdS)	�N)�datetime�	timedelta)�Path)�InvalidTokenException)�UIRole�UserType)�atomic_rewritec�n�eZdZed��Zed��Zejdd��Zejdd��Z	e
ee�����Ze
ee	�����Z
ed���Zed	efd
���Zededed	efd
���Zededed	edzfd���Zedefd���ZdS)�	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}YnwxYwtj�����|z
|jj	kS)Ng)
�os�stat�JWT_SECRET_FILE�st_mtime�FileNotFoundErrorr�now�	timestamp�SECRET_EXPIRATION_TTL�seconds)�clsrrs   �S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/jwt_issuer.py�is_secret_expiredzJWTIssuer.is_secret_expiredsy��	%��7�3�.�/�/�D��}�H�H��!�	�	�	��H�H�H�	����

�L�N�N�$�$�&�&��1��'�/�
0�	
s�#�2�2�returnc����|���r�tjtjz�d��fd�td��D����}|j���s|j���tt|j��|t|j��dd���|S|j���S)N�c3�@�K�|]}tj���V��dS)N)�secrets�choice)�.0�_�alphabets  �r�	<genexpr>z(JWTIssuer._get_secret.<locals>.<genexpr>0s-����� M� M�a����!9�!9� M� M� M� M� M� M��@���i�)�backup�uid�permissions)
r�string�ascii_uppercase�digits�join�ranger�exists�touchr	�str�JWT_SECRET_FILE_PREV�	read_text)r�
new_secretr&s  @r�_get_secretzJWTIssuer._get_secret,s����� � �"�"�	3��-��
�=�H���� M� M� M� M�5��9�9� M� M� M�M�M�J��&�-�-�/�/�
,��#�)�)�+�+�+���C�'�(�(���3�3�4�4��!�
�
�
�
����&�0�0�2�2�2r(�	user_name�	user_typec��ddl}|�||tj��|jz���d�|�����S)z�
        Generates a token with several encoded fields:
            user name,
            user type,
            expiration timestamp
        rN)r;�username�exp)�jwt�encoderr�TOKEN_EXPIRATION_TTLrr9)rr:r;r?s    r�	get_tokenzJWTIssuer.get_token>s_��	�
�
�
��z�z�&�%� �����)A�A�L�L�N�N�
�
�

�O�O���

�
�	
r(�token�secretNc�`�ddl}	|�||dg���S#|j$rYdSwxYw)Nr�HS256)�
algorithms)r?�decode�
PyJWTError)rrCrDr?s    r�_parse_tokenzJWTIssuer._parse_tokenRsN���
�
�
�
	��:�:�e�V��	�:�B�B�B���~�	�	�	��D�D�	���s��
-�-c��|j|jfD]_}|���s�|�||�����}|r|dt
|dd�cS�`t
d���)Nr=r;)r:r;�
INVALID_TOKEN)rr6r3rJr7�UIRoleToUserTyper)rrC�
secret_pth�decodeds    r�parse_tokenzJWTIssuer.parse_token^s����.��0H�I�
	9�
	9�J��$�$�&�&�
���&�&�u�j�.B�.B�.D�.D�E�E�G��
�!(��!4�!1�'�+�2F�!G������
�(��8�8�8r()�__name__�
__module__�__qualname__rrr6r�getenv�JWT_TOKEN_EXPIRATION_TTL_HOURS�JWT_SECRET_EXPIRATION_TTL_HOURSr�intrAr�classmethodrr5r9rrB�dictrJrP�r(rrrs��������d�<�=�=�O��4� F�G�G��%.�R�Y�-�q�&�&�"�'0�b�i�.��'�'�#�%�9�3�3�/M�+N�+N�O�O�O��%�I��c�1�2�2������

�

��[�

��3�C�3�3�3��[�3�"�
�#�
�&�
�S�
�
�
��[�
�&�	��	�c�	�d�T�k�	�	�	��[�	��9��9�9�9��[�9�9�9r(r)rr"r.rr�pathlibr�"defence360agent.subsys.panels.baser� defence360agent.contracts.configrr�defence360agent.utilsr	�ADMIN�ROOT�CLIENT�NON_ROOTrMrrZr(r�<module>rcs���	�	�	�	�����
�
�
�
�(�(�(�(�(�(�(�(�������D�D�D�D�D�D�=�=�=�=�=�=�=�=�0�0�0�0�0�0��L�(�-�
�M�8�$���Y9�Y9�Y9�Y9�Y9�Y9�Y9�Y9�Y9�Y9r(defence360agent/api/__pycache__/newsfeed.cpython-311.opt-1.pyc0000644000000000000000000002041600000000000020773 0ustar  �

���#=8�����dZddlZddlZddlZddlZddlZddlmZddl	m
Z
ddlmZddl
mZddlmZddlmZee��Zd	Zd
Zgd�Zdd
gZGd�d
��Zd�ZGd�d��ZdS)z4
This module gets and caches news from imunify blog
�N)�	HTTPError)�ElementTree)�suppress)�	getLogger)�HostingPanel)�retry_onz!https://blog.imunify360.com/feed/i,)�title�pubDate�guid�linkr�NewsFeedc���eZdZdZdZeeeje	j
jfdd����d�����Zed���Z
ed���Zeefd	���Zed
���ZdS)r
�<z"/var/imunify360/tmp/feed_cache.rss�
c��tj|�S�N)r
�clear_cache)�argss �Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/newsfeed.py�<lambda>zNewsFeed.<lambda>"s��x�3�T�:��)�	max_tries�on_errorc���K�|���r|����d{V��tt��j���t|j��5}tj|�	����}|�
d��}�fd�|D��cddd��S#1swxYwYdS)N�itemc�R��g|]#}��|���d�|D����$S)c�D�i|]}|jtv�|j|j��S�)�tag�TAGS_TO_READ�text��.0�childs  r�
<dictcomp>z+NewsFeed.get.<locals>.<listcomp>.<dictcomp>-s5�������y�L�0�0��I�u�z�0�0�0r)�
is_allowed)r#r�
category_infos  �r�
<listcomp>z NewsFeed.get.<locals>.<listcomp>,sX������� �+�+�D�1�1����!%������r)�_expired�_refresh�
PanelCategoryr�NAME�open�cache_file_pathr�
fromstring�read�iter)�cls�
cache_file�root�imunify_newsr's    @r�getzNewsFeed.gets������<�<�>�>�	!��,�,�.�.� � � � � � � �%�l�n�n�&9�:�:�
�
�#�%�
&�
&�	�*��)�*�/�/�*;�*;�<�<�D��9�9�V�,�,�L�����)�
���	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	�	s�&A	B<�<C�Cc��K�tj�|j��}tj�|��stj|��t�d��t|jd��5}|�	|�
���d{V����ddd��dS#1swxYwYdS)NzRefresh news cache�wb)�os�path�dirnamer.�exists�makedirs�logger�infor-�write�_fetch)r2�cache_file_dir_pathr3s   rr*zNewsFeed._refresh6s����� �g�o�o�c�.A�B�B���w�~�~�1�2�2�	-��K�+�,�,�,����(�)�)�)�
�#�%�t�
,�
,�	1�
����3�:�:�<�<�/�/�/�/�/�/�0�0�0�	1�	1�	1�	1�	1�	1�	1�	1�	1�	1�	1�	1����	1�	1�	1�	1�	1�	1s�	.C�C�Cc���tj�|j��r%tj�|j��}nd}tj��|z
dz}||jkS)Nrr)r9r:r<r.�getmtime�time�	cache_ttl)r2�last_modified_time�	cache_ages   rr)zNewsFeed._expired?s`��
�7�>�>�#�-�.�.�	#�!#��!1�!1�#�2E�!F�!F���!"���Y�[�[�#5�5��;�	��3�=�(�(rc��xK�tj���dtt|���d{V��Sr)�asyncio�get_event_loop�run_in_executor�
_fetch_url�RSS_FEED_REMOTE_URL)r2�timeouts  rrAzNewsFeed._fetchIsO�����+�-�-�=�=��*�1�7�
�
�
�
�
�
�
�
�	
rc���K�t�d|��tt��5t	j|j��ddd��dS#1swxYwYdS)NzClearing cache due to error: %s)r>�warningr�FileNotFoundErrorr9�unlinkr.)r2rs  rrzNewsFeed.clear_cacheOs��������8�$�?�?�?�
�'�
(�
(�	+�	+��I�c�)�*�*�*�	+�	+�	+�	+�	+�	+�	+�	+�	+�	+�	+�	+����	+�	+�	+�	+�	+�	+s�A�A� AN)�__name__�
__module__�__qualname__rFr.�classmethodrr�
ParseError�urllib�request�URLErrorr6r*r)�_TIMEOUTrArrrrr
r
s��������I�:�O��
�X�	�	���!8�9��:�:����
�����[��$�1�1��[�1��)�)��[�)��"*�
�
�
��[�
�
�+�+��[�+�+�+rc� �	ddi}tj�||���}tj�||���5}|���cddd��S#1swxYwYdS#t
j$rt�wxYw)Nz
User-Agentzimunify360-urllib/0.1)�headers)rO)rYrZ�Request�urlopenr0�socketrO�TimeoutError)�urlrOr^�req�responses     rrMrMVs���	� �!8�9���n�$�$�S�'�$�:�:��
�^�
#�
#�C��
#�
9�
9�	#�X��=�=�?�?�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#����	#�	#�	#�	#�	#�	#���>���������s0�AA6�A)�A6�)A-�-A6�0A-�1A6�6B
c�&�eZdZhd�ZdZd�Zd�ZdS)r+>�plesk�cpanel�directadminzstandalone-imunifyc��|���}|tjvr|ntj|_tjtjh|jhz
z|_dSr)�lowerr+�panel_categories�no_panel_category�current�competitors)�self�p_names  r�__init__zPanelCategory.__init__isb����������7�7�7�
�F��0�	
��
)�9��+�=
�
�\�N�=�����rc����d�|D��}d�|������|j�v}t�fd�|jD����}|p|S)Nc�2�h|]}|jdk�
|j��S)�category)rr!r"s  r�	<setcomp>z+PanelCategory.is_allowed.<locals>.<setcomp>us-��
�
�
� �E�I��,C�,C�E�J�,C�,C�,Crz|||c3� �K�|]}|�vV��	dSrr)r#�com�joined_categorys  �r�	<genexpr>z+PanelCategory.is_allowed.<locals>.<genexpr>}s9�����&
�&
�'*�C�?�"�&
�&
�&
�&
�&
�&
r)�joinrkrn�anyro)rpr�item_categories�current_in_category�competitors_in_categoryrys     @rr&zPanelCategory.is_allowedts����
�
�$(�
�
�
��
 �*�*�_�5�5�;�;�=�=��"�l�o�=��"%�&
�&
�&
�&
�.2�.>�&
�&
�&
�#
�#
��
+�*�A�.A�ArN)rTrUrVrlrmrrr&rrrr+r+csK������:�9�9��,��	�	�	�B�B�B�B�Brr+)�__doc__rJr9rarE�urllib.requestrY�urllib.errorr�	xml.etreer�
contextlibr�loggingr�(defence360agent.simple_rpc.hosting_panelr�defence360agent.utilsr�__file__r>rNr\r �__all__r
rMr+rrr�<module>r�sP��������	�	�	�	�
�
�
�
���������"�"�"�"�"�"�!�!�!�!�!�!�������������A�A�A�A�A�A�*�*�*�*�*�*�	��8�	�	��9����3�3�3���
�
#��9+�9+�9+�9+�9+�9+�9+�9+�x
�
�
�B�B�B�B�B�B�B�B�B�Brdefence360agent/api/__pycache__/newsfeed.cpython-311.pyc0000644000000000000000000002041600000000000020034 0ustar  �

���#=8�����dZddlZddlZddlZddlZddlZddlmZddl	m
Z
ddlmZddl
mZddlmZddlmZee��Zd	Zd
Zgd�Zdd
gZGd�d
��Zd�ZGd�d��ZdS)z4
This module gets and caches news from imunify blog
�N)�	HTTPError)�ElementTree)�suppress)�	getLogger)�HostingPanel)�retry_onz!https://blog.imunify360.com/feed/i,)�title�pubDate�guid�linkr�NewsFeedc���eZdZdZdZeeeje	j
jfdd����d�����Zed���Z
ed���Zeefd	���Zed
���ZdS)r
�<z"/var/imunify360/tmp/feed_cache.rss�
c��tj|�S�N)r
�clear_cache)�argss �Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/newsfeed.py�<lambda>zNewsFeed.<lambda>"s��x�3�T�:��)�	max_tries�on_errorc���K�|���r|����d{V��tt��j���t|j��5}tj|�	����}|�
d��}�fd�|D��cddd��S#1swxYwYdS)N�itemc�R��g|]#}��|���d�|D����$S)c�D�i|]}|jtv�|j|j��S�)�tag�TAGS_TO_READ�text��.0�childs  r�
<dictcomp>z+NewsFeed.get.<locals>.<listcomp>.<dictcomp>-s5�������y�L�0�0��I�u�z�0�0�0r)�
is_allowed)r#r�
category_infos  �r�
<listcomp>z NewsFeed.get.<locals>.<listcomp>,sX������� �+�+�D�1�1����!%������r)�_expired�_refresh�
PanelCategoryr�NAME�open�cache_file_pathr�
fromstring�read�iter)�cls�
cache_file�root�imunify_newsr's    @r�getzNewsFeed.gets������<�<�>�>�	!��,�,�.�.� � � � � � � �%�l�n�n�&9�:�:�
�
�#�%�
&�
&�	�*��)�*�/�/�*;�*;�<�<�D��9�9�V�,�,�L�����)�
���	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	�	s�&A	B<�<C�Cc��K�tj�|j��}tj�|��stj|��t�d��t|jd��5}|�	|�
���d{V����ddd��dS#1swxYwYdS)NzRefresh news cache�wb)�os�path�dirnamer.�exists�makedirs�logger�infor-�write�_fetch)r2�cache_file_dir_pathr3s   rr*zNewsFeed._refresh6s����� �g�o�o�c�.A�B�B���w�~�~�1�2�2�	-��K�+�,�,�,����(�)�)�)�
�#�%�t�
,�
,�	1�
����3�:�:�<�<�/�/�/�/�/�/�0�0�0�	1�	1�	1�	1�	1�	1�	1�	1�	1�	1�	1�	1����	1�	1�	1�	1�	1�	1s�	.C�C�Cc���tj�|j��r%tj�|j��}nd}tj��|z
dz}||jkS)Nrr)r9r:r<r.�getmtime�time�	cache_ttl)r2�last_modified_time�	cache_ages   rr)zNewsFeed._expired?s`��
�7�>�>�#�-�.�.�	#�!#��!1�!1�#�2E�!F�!F���!"���Y�[�[�#5�5��;�	��3�=�(�(rc��xK�tj���dtt|���d{V��Sr)�asyncio�get_event_loop�run_in_executor�
_fetch_url�RSS_FEED_REMOTE_URL)r2�timeouts  rrAzNewsFeed._fetchIsO�����+�-�-�=�=��*�1�7�
�
�
�
�
�
�
�
�	
rc���K�t�d|��tt��5t	j|j��ddd��dS#1swxYwYdS)NzClearing cache due to error: %s)r>�warningr�FileNotFoundErrorr9�unlinkr.)r2rs  rrzNewsFeed.clear_cacheOs��������8�$�?�?�?�
�'�
(�
(�	+�	+��I�c�)�*�*�*�	+�	+�	+�	+�	+�	+�	+�	+�	+�	+�	+�	+����	+�	+�	+�	+�	+�	+s�A�A� AN)�__name__�
__module__�__qualname__rFr.�classmethodrr�
ParseError�urllib�request�URLErrorr6r*r)�_TIMEOUTrArrrrr
r
s��������I�:�O��
�X�	�	���!8�9��:�:����
�����[��$�1�1��[�1��)�)��[�)��"*�
�
�
��[�
�
�+�+��[�+�+�+rc� �	ddi}tj�||���}tj�||���5}|���cddd��S#1swxYwYdS#t
j$rt�wxYw)Nz
User-Agentzimunify360-urllib/0.1)�headers)rO)rYrZ�Request�urlopenr0�socketrO�TimeoutError)�urlrOr^�req�responses     rrMrMVs���	� �!8�9���n�$�$�S�'�$�:�:��
�^�
#�
#�C��
#�
9�
9�	#�X��=�=�?�?�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#����	#�	#�	#�	#�	#�	#���>���������s0�AA6�A)�A6�)A-�-A6�0A-�1A6�6B
c�&�eZdZhd�ZdZd�Zd�ZdS)r+>�plesk�cpanel�directadminzstandalone-imunifyc��|���}|tjvr|ntj|_tjtjh|jhz
z|_dSr)�lowerr+�panel_categories�no_panel_category�current�competitors)�self�p_names  r�__init__zPanelCategory.__init__isb����������7�7�7�
�F��0�	
��
)�9��+�=
�
�\�N�=�����rc����d�|D��}d�|������|j�v}t�fd�|jD����}|p|S)Nc�2�h|]}|jdk�
|j��S)�category)rr!r"s  r�	<setcomp>z+PanelCategory.is_allowed.<locals>.<setcomp>us-��
�
�
� �E�I��,C�,C�E�J�,C�,C�,Crz|||c3� �K�|]}|�vV��	dSrr)r#�com�joined_categorys  �r�	<genexpr>z+PanelCategory.is_allowed.<locals>.<genexpr>}s9�����&
�&
�'*�C�?�"�&
�&
�&
�&
�&
�&
r)�joinrkrn�anyro)rpr�item_categories�current_in_category�competitors_in_categoryrys     @rr&zPanelCategory.is_allowedts����
�
�$(�
�
�
��
 �*�*�_�5�5�;�;�=�=��"�l�o�=��"%�&
�&
�&
�&
�.2�.>�&
�&
�&
�#
�#
��
+�*�A�.A�ArN)rTrUrVrlrmrrr&rrrr+r+csK������:�9�9��,��	�	�	�B�B�B�B�Brr+)�__doc__rJr9rarE�urllib.requestrY�urllib.errorr�	xml.etreer�
contextlibr�loggingr�(defence360agent.simple_rpc.hosting_panelr�defence360agent.utilsr�__file__r>rNr\r �__all__r
rMr+rrr�<module>r�sP��������	�	�	�	�
�
�
�
���������"�"�"�"�"�"�!�!�!�!�!�!�������������A�A�A�A�A�A�*�*�*�*�*�*�	��8�	�	��9����3�3�3���
�
#��9+�9+�9+�9+�9+�9+�9+�9+�x
�
�
�B�B�B�B�B�B�B�B�B�Brdefence360agent/api/__pycache__/pam_auth.cpython-311.opt-1.pyc0000644000000000000000000000340100000000000020764 0ustar  �

�~.5��8���D�ddlmZddlmZddlmZGd�d��ZdS)�)�IntegrationConfig)�UIRole)�get_admin_listc�.�eZdZdZdefd�Zdedefd�ZdS)�PamAuthzsystem-auth�returnc���ddlm}	t�����}|dd}n#t$r
|j}YnwxYw|��}|�|||���S)Nr)�pam�PAM�SERVICE_NAME)�service)r
r�to_dict�KeyError�DEFAULT_AUTH_SERVICE�authenticate)�self�username�passwordr
�configr
�ps       �Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/pam_auth.pyrzPamAuth.authenticate	s���������	0�&�(�(�0�0�2�2�F��U�m�N�3�G�G���	0�	0�	0��/�G�G�G�	0����
�C�E�E���~�~�h��'�~�B�B�Bs�.7�A�
Arc��fK�t���d{V��}||vrtjntjS)N)rr�ADMIN�CLIENT)rr�adminss   r�
get_user_typezPamAuth.get_user_types;����%�'�'�'�'�'�'�'�'��'�6�1�1�v�|�|�v�}�D�N)	�__name__�
__module__�__qualname__r�boolr�strrr�rrrrsd������(��
C�$�
C�
C�
C�
C�E�C�E�F�E�E�E�E�E�ErrN)�$defence360agent.api.integration_confr� defence360agent.contracts.configr�+defence360agent.subsys.panels.generic.panelrrr#rr�<module>r'sw��B�B�B�B�B�B�3�3�3�3�3�3�F�F�F�F�F�F�E�E�E�E�E�E�E�E�E�Erdefence360agent/api/__pycache__/pam_auth.cpython-311.pyc0000644000000000000000000000340100000000000020025 0ustar  �

�~.5��8���D�ddlmZddlmZddlmZGd�d��ZdS)�)�IntegrationConfig)�UIRole)�get_admin_listc�.�eZdZdZdefd�Zdedefd�ZdS)�PamAuthzsystem-auth�returnc���ddlm}	t�����}|dd}n#t$r
|j}YnwxYw|��}|�|||���S)Nr)�pam�PAM�SERVICE_NAME)�service)r
r�to_dict�KeyError�DEFAULT_AUTH_SERVICE�authenticate)�self�username�passwordr
�configr
�ps       �Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/pam_auth.pyrzPamAuth.authenticate	s���������	0�&�(�(�0�0�2�2�F��U�m�N�3�G�G���	0�	0�	0��/�G�G�G�	0����
�C�E�E���~�~�h��'�~�B�B�Bs�.7�A�
Arc��fK�t���d{V��}||vrtjntjS)N)rr�ADMIN�CLIENT)rr�adminss   r�
get_user_typezPamAuth.get_user_types;����%�'�'�'�'�'�'�'�'��'�6�1�1�v�|�|�v�}�D�N)	�__name__�
__module__�__qualname__r�boolr�strrr�rrrrsd������(��
C�$�
C�
C�
C�
C�E�C�E�F�E�E�E�E�E�ErrN)�$defence360agent.api.integration_confr� defence360agent.contracts.configr�+defence360agent.subsys.panels.generic.panelrrr#rr�<module>r'sw��B�B�B�B�B�B�3�3�3�3�3�3�F�F�F�F�F�F�E�E�E�E�E�E�E�E�E�Erdefence360agent/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  �

]�栓Z���ddlZddlZddlZddlZddlZddlZddlZddl	m
Z
eje��Z
Gd�de��ZGd�de��ZGd�de��ZGd	�d
e��ZGd�de��ZGd
�d��ZdS)�N)�Corec� ��eZdZd�fd�Z�xZS)�APIError�returnNc���t��j|i|��t|��dkr|^}}}||_dSd|_dS)N�)�super�__init__�len�status_code)�self�args�kwargs�_r�	__class__s     ��X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/server/__init__.pyr
zAPIError.__init__sV��������$�)�&�)�)�)��t�9�9��>�>�$(�!�A�{�T�*�D����#�D����)rN��__name__�
__module__�__qualname__r
�
__classcell__�rs@rrrs=�������$�$�$�$�$�$�$�$�$�$rrc��eZdZdS)�APIErrorTooManyRequestsN�rrr�rrrr��������Crrc��eZdZdS)�
APITokenErrorNrrrrr r rrr c��eZdZdS)�FGWSendMessgeExceptionNrrrrr"r" rrr"c�$��eZdZdd��fd�
Z�xZS)�NATSSendMessageExceptionr)�	publishedc�B��t��j|�||_dS�N)r	r
r%)r
r%rrs   �rr
z!NATSSendMessageException.__init__%s"��������$���"����rrrs@rr$r$$sE�������()�#�#�#�#�#�#�#�#�#�#�#rr$c�j�eZdZejZdZeddej	j
fd���Z	edd���ZdS)	�API�-T�requestc
�p�	tj�||j���5}t�d|jt|dd��|jrt|j��nd|j
��|j
dkr-td�|j
��|j
���|�
��}t�d|dd���|r'tj|�����}n|}|cddd��S#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=%s�methodr��zstatus code is {}zResponse=%s ...�2�codei�zrequest failed, reason: )�urllibr+�urlopen�_SOCKET_TIMEOUT�logger�info�full_url�getattr�datar�statusr�format�read�json�loads�decode�UnicodeDecodeError�http�client�
HTTPException�JSONDecodeError�socketr-�error�URLErrorr)�clsr+�
json_loads�response�plain_response�result�ers        rr+zAPI.request1s��)	���'�'���+�	(���
�
����!��$��G�X�t�4�4�)0��<�C���%�%�%�1��O�
����?�c�)�)�"�+�2�2�8�?�C�C� �����"*���������-�~�c�r�c�/B�C�C�C��,�!�Z��(=�(=�(?�(?�@�@�F�F�+�F��3
�
�
�
�
�
�
�
�
�
�
�
����
�
�
�
�
�
��6
��K�%�� ��N��L�!�
�	�	�	�"�!�V�T�2�2�K��c�!�!�-�-�45�A�7�������(�01��3�[����
�����	���s<�&D0�C.D#�D0�#D'�'D0�*D'�+D0�0AF5�1?F0�0F5Nc��pK�tj��}|�||j|���d{V��Sr')�asyncio�get_event_loop�run_in_executorr+)rHr+�executor�loops    r�
async_requestzAPI.async_request^s@�����%�'�'���)�)�(�C�K��I�I�I�I�I�I�I�I�Ir)Tr')rrrr�API_BASE_URL�	_BASE_URLr4�classmethodr2r+�RequestrTrrrr)r)*sy�������!�I��O��*�*�f�n�4�*�*�*��[�*�X�J�J�J��[�J�J�Jrr))rO�http.clientrAr=�loggingrE�urllib.errorr2�urllib.request� defence360agent.contracts.configr�	getLoggerrr5�	Exceptionrrr r"r$r)rrr�<module>r`s������������������
�
�
�
���������1�1�1�1�1�1�	��	�8�	$�	$��$�$�$�$�$�y�$�$�$������h���������H���������Y����#�#�#�#�#�y�#�#�#�7J�7J�7J�7J�7J�7J�7J�7J�7J�7Jrdefence360agent/api/server/__pycache__/__init__.cpython-311.pyc0000644000000000000000000001224000000000000021275 0ustar  �

]�栓Z���ddlZddlZddlZddlZddlZddlZddlZddl	m
Z
eje��Z
Gd�de��ZGd�de��ZGd�de��ZGd	�d
e��ZGd�de��ZGd
�d��ZdS)�N)�Corec� ��eZdZd�fd�Z�xZS)�APIError�returnNc���t��j|i|��t|��dkr|^}}}||_dSd|_dS)N�)�super�__init__�len�status_code)�self�args�kwargs�_r�	__class__s     ��X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/server/__init__.pyr
zAPIError.__init__sV��������$�)�&�)�)�)��t�9�9��>�>�$(�!�A�{�T�*�D����#�D����)rN��__name__�
__module__�__qualname__r
�
__classcell__�rs@rrrs=�������$�$�$�$�$�$�$�$�$�$rrc��eZdZdS)�APIErrorTooManyRequestsN�rrr�rrrr��������Crrc��eZdZdS)�
APITokenErrorNrrrrr r rrr c��eZdZdS)�FGWSendMessgeExceptionNrrrrr"r" rrr"c�$��eZdZdd��fd�
Z�xZS)�NATSSendMessageExceptionr)�	publishedc�B��t��j|�||_dS�N)r	r
r%)r
r%rrs   �rr
z!NATSSendMessageException.__init__%s"��������$���"����rrrs@rr$r$$sE�������()�#�#�#�#�#�#�#�#�#�#�#rr$c�j�eZdZejZdZeddej	j
fd���Z	edd���ZdS)	�API�-T�requestc
�p�	tj�||j���5}t�d|jt|dd��|jrt|j��nd|j
��|j
dkr-td�|j
��|j
���|�
��}t�d|dd���|r'tj|�����}n|}|cddd��S#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=%s�methodr��zstatus code is {}zResponse=%s ...�2�codei�zrequest failed, reason: )�urllibr+�urlopen�_SOCKET_TIMEOUT�logger�info�full_url�getattr�datar�statusr�format�read�json�loads�decode�UnicodeDecodeError�http�client�
HTTPException�JSONDecodeError�socketr-�error�URLErrorr)�clsr+�
json_loads�response�plain_response�result�ers        rr+zAPI.request1s��)	���'�'���+�	(���
�
����!��$��G�X�t�4�4�)0��<�C���%�%�%�1��O�
����?�c�)�)�"�+�2�2�8�?�C�C� �����"*���������-�~�c�r�c�/B�C�C�C��,�!�Z��(=�(=�(?�(?�@�@�F�F�+�F��3
�
�
�
�
�
�
�
�
�
�
�
����
�
�
�
�
�
��6
��K�%�� ��N��L�!�
�	�	�	�"�!�V�T�2�2�K��c�!�!�-�-�45�A�7�������(�01��3�[����
�����	���s<�&D0�C.D#�D0�#D'�'D0�*D'�+D0�0AF5�1?F0�0F5Nc��pK�tj��}|�||j|���d{V��Sr')�asyncio�get_event_loop�run_in_executorr+)rHr+�executor�loops    r�
async_requestzAPI.async_request^s@�����%�'�'���)�)�(�C�K��I�I�I�I�I�I�I�I�Ir)Tr')rrrr�API_BASE_URL�	_BASE_URLr4�classmethodr2r+�RequestrTrrrr)r)*sy�������!�I��O��*�*�f�n�4�*�*�*��[�*�X�J�J�J��[�J�J�Jrr))rO�http.clientrAr=�loggingrE�urllib.errorr2�urllib.request� defence360agent.contracts.configr�	getLoggerrr5�	Exceptionrrr r"r$r)rrr�<module>r`s������������������
�
�
�
���������1�1�1�1�1�1�	��	�8�	$�	$��$�$�$�$�$�y�$�$�$������h���������H���������Y����#�#�#�#�#�y�#�#�#�7J�7J�7J�7J�7J�7J�7J�7J�7J�7Jrdefence360agent/api/server/__pycache__/analyst_cleanup.cpython-311.opt-1.pyc0000644000000000000000000002624600000000000023672 0ustar  �

,��q�;�<����ddlZddlZddlZddlZddlZddlmZmZddl	m
Z
ddlmZddl
mZmZddlmZddlmZeje��Zed�	��Zd
Zd�ZGd�d
e
��ZdS)�N)�datetime�	timedelta)�API)�ANTIVIRUS_MODE)�IndependentAgentIDAPI�IAIDTokenError)�run_in_executor_decorator)�parse_params�
)�minutes�no_agent_tokenc�P�	tj|��}nK#tttjjf$r'}t�d|��icYd}~Sd}~wwxYwt|t��r|St�dt|��j��iS)Nz#Cannot decode API response body: %sz&API response body is %s, not an object)
�json�load�
ValueError�OSError�http�client�
HTTPException�logger�warning�
isinstance�dict�type�__name__)�response�body�es   �_/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/server/analyst_cleanup.py�_json_objectr s�����y��"�"��������!:�;�������<�a�@�@�@��	�	�	�	�	�	����������$�������
�N�N�0�$�t�*�*�2E�����Is��!A�A�A�Ac�0�eZdZdZdZdZdZdejd�Z	e
d���Ze
ed�����Z
e
d	egd
egfd���Ze
ed�����Ze
d
���Ze
ed�����Ze
d���Ze
ed�����ZdS)�AnalystCleanupAPIz.{base}/api/analyst-assisted-cleanup/is-allowedz+{base}/api/analyst-assisted-cleanup/ticketsz1{base}/api/analyst-assisted-cleanup/is-registeredz1{base}/api/analyst-assisted-cleanup/create-ticketN)�result�	timestampc���K�tj��}|jd�&||jdz
tkr
|jdS	tj�|j�|j	���dtj���d{V��id���}|�|���d{V��}||jd<tj��|jd<|S#t$rYdSwxYw)	z9Check if analyst cleanup is allowed for this installationr#Nr$��base�X-Auth�GET��headers�methodF)r�now�_cache�	CACHE_TTL�urllib�request�Request�CLEANUP_ALLOWED_URL_TEMPLATE�format�	_BASE_URLr�	get_token�_check_allowedr)�cls�current_timer1r#s    r�check_cleanup_allowedz'AnalystCleanupAPI.check_cleanup_allowed9s���� �|�~�~���J�x� �,��s�z�+�6�6��B�B��:�h�'�'�	��n�,�,��0�7�7�S�]�7�K�K�!�)>�)H�)J�)J�#J�#J�#J�#J�#J�#J�K��-���G��-�-�g�6�6�6�6�6�6�6�6�F�#)�C�J�x� �&.�l�n�n�C�J�{�#��M��
�	�	�	��5�5�	���s�
AC%�%
C3�2C3c��	|�|��}|�dd��S#t$r&}t�d|��Yd}~dSd}~wwxYw)�&Execute the actual request in executorr#Fz&Failed to check cleanup permission: %sNT)r1�get�	Exceptionr�error�r8r1r#rs    rr7z AnalystCleanupAPI._check_allowedQso��		��[�[��)�)�F��:�:�h��.�.�.���	�	�	��L�L�A�1�E�E�E�
�4�4�4�4�4�����
	���s�*-�
A�A�A�ids�returnc��K�t|t��r d�d�|D����}nt|��}|j�|j���}d|i}	tj�	t||��dtj���d{V��id���}n0#t$r#}t�d	|�����d}~wwxYw|�|���d{V��S)
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
        �,c3�4K�|]}t|��V��dS)N)�str)�.0�_ids  r�	<genexpr>z0AnalystCleanupAPI.get_tickets.<locals>.<genexpr>ms(����7�7�C�s�3�x�x�7�7�7�7�7�7�r&rAr(Nr)r*z&Failed to get IAID token for tickets: )r�list�joinrF�SHOW_MANY_URL_TEMPLATEr4r5r0r1r2r
rr6rrr?�_execute_get_tickets)r8rA�ids_str�url�paramsr1rs       r�get_ticketszAnalystCleanupAPI.get_tickets`s,�����c�4� � �	��h�h�7�7�3�7�7�7�7�7�G�G��#�h�h�G��(�/�/�S�]�/�C�C����!��	��n�,�,��V�S�)�)�!�)>�)H�)J�)J�#J�#J�#J�#J�#J�#J�K��-���G�G��
�	�	�	��L�L�E�!�E�E�F�F�F������	�����-�-�g�6�6�6�6�6�6�6�6�6s�,A	B6�6
C#�C�C#c��g}	|�|��}|�dg��D]T}|�|�d��|�d��|�d��d����U||cS#t$r'}t�d|����Yd}~nd}~wwxYw	|S#|ccYSxYw)z2Execute the actual get_tickets request in executor�tickets�id�status�
updated_at)rUrVrWzFailed to get tickets: N)r1r=�appendr>rr?)r8r1�simplified_ticketsr#�ticketrs      rrNz&AnalystCleanupAPI._execute_get_tickets�s�� ��	&��[�[��)�)�F�!�*�*�Y��3�3�
�
��"�)�)�$�j�j��.�.�"(�*�*�X�"6�"6�&,�j�j��&>�&>�������&�&�%�%�%���	8�	8�	8��L�L�6�1�6�6�7�7�7�7�7�7�7�7�����	8����7�%�%��%�%�%�%�%�%�%�%�%s*�BB	�	
B:�B5�0C�5B:�:C�Cc��K�	tj�|j�|j���t
j���d{V��dd�tj	d|i���
��d���}|�|���d{V��}|S#t$rt�d��icYSwxYw)	z'Check if email is registered in Zendeskr&N�application/json�r(zContent-Type�customer_email�POST�r+�datar,zGot IAIDTokenError)r0r1r2�IS_REGISTERED_URL_TEMPLATEr4r5rr6r�dumps�encode�_register_statusrrr?)r8�emailr1r#s    r�check_registeredz"AnalystCleanupAPI.check_registered�s�����	��n�,�,��.�5�5�3�=�5�I�I�$9�$C�$E�$E�E�E�E�E�E�E�$6����Z�!1�5� 9�:�:�A�A�C�C��-���G��/�/��8�8�8�8�8�8�8�8�F��M���	�	�	��L�L�-�.�.�.��I�I�I�	���s�BB"�"&C�
Cc��	|�|��}|S#t$r'}t�d|��icYd}~Sd}~wwxYw)r<z&Failed to check email registration: %sN)r1r>rr?r@s    rrez"AnalystCleanupAPI._register_status�s`��	��[�[��)�)�F��M���	�	�	��L�L�A�1�E�E�E��I�I�I�I�I�I�����	���s��
A
�A�A
�A
c��K�	tj���d{V��}n#t$rddtifcYSwxYwtj�|j�|j	���|dd�tj|||trdndd����
��d	�
��}|�|���d{V��S)z�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.
        N�messager&r\r]�
pr_imunify_av�pr_im360)rf�subject�description�productr_r`)rr6r�NO_AGENT_TOKENr0r1r2�CREATE_TICKET_URL_TEMPLATEr4r5rrcrrd�_send_create_ticket)r8rfrmrn�tokenr1s      r�
create_ticketzAnalystCleanupAPI.create_ticket�s����	5�/�9�;�;�;�;�;�;�;�;�E�E���	5�	5�	5��)�^�4�4�4�4�4�	5�����.�(�(��*�1�1�s�}�1�E�E�� 2�����"�&�#.�+9�I���z���	�	��f�h�h��!)�
�
��$�,�,�W�5�5�5�5�5�5�5�5�5s��6�6c��	tj�||j���5}|jt|��fcddd��S#1swxYwYdS#tjj$r)}|j|j	�t|��nifcYd}~Sd}~wt$r)}t�d|��difcYd}~Sd}~wwxYw)r<)�timeoutNz*Failed to reach create-ticket endpoint: %s)
r0r1�urlopen�_SOCKET_TIMEOUTrVr r?�	HTTPError�code�fpr>rr)r8r1rrs    rrrz%AnalystCleanupAPI._send_create_ticket�s4��		���'�'���!4�(���
?�����X�(>�(>�>�
?�
?�
?�
?�
?�
?�
?�
?�
?�
?�
?�
?����
?�
?�
?�
?�
?�
?���|�%�	G�	G�	G��6�a�d�.>�<��?�?�?�B�F�F�F�F�F�F�F������	�	�	��N�N�G��K�K�K���8�O�O�O�O�O�O�����	���sQ�&A�A�A�A�A�A�A�C�,B�
C�
C�C�;C�C)r�
__module__�__qualname__r3rMrbrqr�minr.�classmethodr:r	r7rFrrRrNrgrertrr�rJrr"r"'si������8�!�K��;��	<����\���F�
����[��.�������[���7�S�E�7�t�f�7�7�7��[�7�@��&�&����[�&�,����[��&�������[���6�6��[�6�>�������[���rJr")�http.clientrr�urllib.errorr0�urllib.request�loggingrr�defence360agent.api.serverr� defence360agent.contracts.configr�defence360agent.internals.iaidrr�defence360agent.rpc_tools.utilsr	�defence360agent.utils.supportr
�	getLoggerrrr/rpr r"r�rJr�<module>r�s>����������������������(�(�(�(�(�(�(�(�*�*�*�*�*�*�;�;�;�;�;�;���������F�E�E�E�E�E�6�6�6�6�6�6�	��	�8�	$�	$���I�b�!�!�!�	�"�����~�~�~�~�~��~�~�~�~�~rJdefence360agent/api/server/__pycache__/analyst_cleanup.cpython-311.pyc0000644000000000000000000002624600000000000022733 0ustar  �

,��q�;�<����ddlZddlZddlZddlZddlZddlmZmZddl	m
Z
ddlmZddl
mZmZddlmZddlmZeje��Zed�	��Zd
Zd�ZGd�d
e
��ZdS)�N)�datetime�	timedelta)�API)�ANTIVIRUS_MODE)�IndependentAgentIDAPI�IAIDTokenError)�run_in_executor_decorator)�parse_params�
)�minutes�no_agent_tokenc�P�	tj|��}nK#tttjjf$r'}t�d|��icYd}~Sd}~wwxYwt|t��r|St�dt|��j��iS)Nz#Cannot decode API response body: %sz&API response body is %s, not an object)
�json�load�
ValueError�OSError�http�client�
HTTPException�logger�warning�
isinstance�dict�type�__name__)�response�body�es   �_/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/server/analyst_cleanup.py�_json_objectr s�����y��"�"��������!:�;�������<�a�@�@�@��	�	�	�	�	�	����������$�������
�N�N�0�$�t�*�*�2E�����Is��!A�A�A�Ac�0�eZdZdZdZdZdZdejd�Z	e
d���Ze
ed�����Z
e
d	egd
egfd���Ze
ed�����Ze
d
���Ze
ed�����Ze
d���Ze
ed�����ZdS)�AnalystCleanupAPIz.{base}/api/analyst-assisted-cleanup/is-allowedz+{base}/api/analyst-assisted-cleanup/ticketsz1{base}/api/analyst-assisted-cleanup/is-registeredz1{base}/api/analyst-assisted-cleanup/create-ticketN)�result�	timestampc���K�tj��}|jd�&||jdz
tkr
|jdS	tj�|j�|j	���dtj���d{V��id���}|�|���d{V��}||jd<tj��|jd<|S#t$rYdSwxYw)	z9Check if analyst cleanup is allowed for this installationr#Nr$��base�X-Auth�GET��headers�methodF)r�now�_cache�	CACHE_TTL�urllib�request�Request�CLEANUP_ALLOWED_URL_TEMPLATE�format�	_BASE_URLr�	get_token�_check_allowedr)�cls�current_timer1r#s    r�check_cleanup_allowedz'AnalystCleanupAPI.check_cleanup_allowed9s���� �|�~�~���J�x� �,��s�z�+�6�6��B�B��:�h�'�'�	��n�,�,��0�7�7�S�]�7�K�K�!�)>�)H�)J�)J�#J�#J�#J�#J�#J�#J�K��-���G��-�-�g�6�6�6�6�6�6�6�6�F�#)�C�J�x� �&.�l�n�n�C�J�{�#��M��
�	�	�	��5�5�	���s�
AC%�%
C3�2C3c��	|�|��}|�dd��S#t$r&}t�d|��Yd}~dSd}~wwxYw)�&Execute the actual request in executorr#Fz&Failed to check cleanup permission: %sNT)r1�get�	Exceptionr�error�r8r1r#rs    rr7z AnalystCleanupAPI._check_allowedQso��		��[�[��)�)�F��:�:�h��.�.�.���	�	�	��L�L�A�1�E�E�E�
�4�4�4�4�4�����
	���s�*-�
A�A�A�ids�returnc��K�t|t��r d�d�|D����}nt|��}|j�|j���}d|i}	tj�	t||��dtj���d{V��id���}n0#t$r#}t�d	|�����d}~wwxYw|�|���d{V��S)
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
        �,c3�4K�|]}t|��V��dS)N)�str)�.0�_ids  r�	<genexpr>z0AnalystCleanupAPI.get_tickets.<locals>.<genexpr>ms(����7�7�C�s�3�x�x�7�7�7�7�7�7�r&rAr(Nr)r*z&Failed to get IAID token for tickets: )r�list�joinrF�SHOW_MANY_URL_TEMPLATEr4r5r0r1r2r
rr6rrr?�_execute_get_tickets)r8rA�ids_str�url�paramsr1rs       r�get_ticketszAnalystCleanupAPI.get_tickets`s,�����c�4� � �	��h�h�7�7�3�7�7�7�7�7�G�G��#�h�h�G��(�/�/�S�]�/�C�C����!��	��n�,�,��V�S�)�)�!�)>�)H�)J�)J�#J�#J�#J�#J�#J�#J�K��-���G�G��
�	�	�	��L�L�E�!�E�E�F�F�F������	�����-�-�g�6�6�6�6�6�6�6�6�6s�,A	B6�6
C#�C�C#c��g}	|�|��}|�dg��D]T}|�|�d��|�d��|�d��d����U||cS#t$r'}t�d|����Yd}~nd}~wwxYw	|S#|ccYSxYw)z2Execute the actual get_tickets request in executor�tickets�id�status�
updated_at)rUrVrWzFailed to get tickets: N)r1r=�appendr>rr?)r8r1�simplified_ticketsr#�ticketrs      rrNz&AnalystCleanupAPI._execute_get_tickets�s�� ��	&��[�[��)�)�F�!�*�*�Y��3�3�
�
��"�)�)�$�j�j��.�.�"(�*�*�X�"6�"6�&,�j�j��&>�&>�������&�&�%�%�%���	8�	8�	8��L�L�6�1�6�6�7�7�7�7�7�7�7�7�����	8����7�%�%��%�%�%�%�%�%�%�%�%s*�BB	�	
B:�B5�0C�5B:�:C�Cc��K�	tj�|j�|j���t
j���d{V��dd�tj	d|i���
��d���}|�|���d{V��}|S#t$rt�d��icYSwxYw)	z'Check if email is registered in Zendeskr&N�application/json�r(zContent-Type�customer_email�POST�r+�datar,zGot IAIDTokenError)r0r1r2�IS_REGISTERED_URL_TEMPLATEr4r5rr6r�dumps�encode�_register_statusrrr?)r8�emailr1r#s    r�check_registeredz"AnalystCleanupAPI.check_registered�s�����	��n�,�,��.�5�5�3�=�5�I�I�$9�$C�$E�$E�E�E�E�E�E�E�$6����Z�!1�5� 9�:�:�A�A�C�C��-���G��/�/��8�8�8�8�8�8�8�8�F��M���	�	�	��L�L�-�.�.�.��I�I�I�	���s�BB"�"&C�
Cc��	|�|��}|S#t$r'}t�d|��icYd}~Sd}~wwxYw)r<z&Failed to check email registration: %sN)r1r>rr?r@s    rrez"AnalystCleanupAPI._register_status�s`��	��[�[��)�)�F��M���	�	�	��L�L�A�1�E�E�E��I�I�I�I�I�I�����	���s��
A
�A�A
�A
c��K�	tj���d{V��}n#t$rddtifcYSwxYwtj�|j�|j	���|dd�tj|||trdndd����
��d	�
��}|�|���d{V��S)z�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.
        N�messager&r\r]�
pr_imunify_av�pr_im360)rf�subject�description�productr_r`)rr6r�NO_AGENT_TOKENr0r1r2�CREATE_TICKET_URL_TEMPLATEr4r5rrcrrd�_send_create_ticket)r8rfrmrn�tokenr1s      r�
create_ticketzAnalystCleanupAPI.create_ticket�s����	5�/�9�;�;�;�;�;�;�;�;�E�E���	5�	5�	5��)�^�4�4�4�4�4�	5�����.�(�(��*�1�1�s�}�1�E�E�� 2�����"�&�#.�+9�I���z���	�	��f�h�h��!)�
�
��$�,�,�W�5�5�5�5�5�5�5�5�5s��6�6c��	tj�||j���5}|jt|��fcddd��S#1swxYwYdS#tjj$r)}|j|j	�t|��nifcYd}~Sd}~wt$r)}t�d|��difcYd}~Sd}~wwxYw)r<)�timeoutNz*Failed to reach create-ticket endpoint: %s)
r0r1�urlopen�_SOCKET_TIMEOUTrVr r?�	HTTPError�code�fpr>rr)r8r1rrs    rrrz%AnalystCleanupAPI._send_create_ticket�s4��		���'�'���!4�(���
?�����X�(>�(>�>�
?�
?�
?�
?�
?�
?�
?�
?�
?�
?�
?�
?����
?�
?�
?�
?�
?�
?���|�%�	G�	G�	G��6�a�d�.>�<��?�?�?�B�F�F�F�F�F�F�F������	�	�	��N�N�G��K�K�K���8�O�O�O�O�O�O�����	���sQ�&A�A�A�A�A�A�A�C�,B�
C�
C�C�;C�C)r�
__module__�__qualname__r3rMrbrqr�minr.�classmethodr:r	r7rFrrRrNrgrertrr�rJrr"r"'si������8�!�K��;��	<����\���F�
����[��.�������[���7�S�E�7�t�f�7�7�7��[�7�@��&�&����[�&�,����[��&�������[���6�6��[�6�>�������[���rJr")�http.clientrr�urllib.errorr0�urllib.request�loggingrr�defence360agent.api.serverr� defence360agent.contracts.configr�defence360agent.internals.iaidrr�defence360agent.rpc_tools.utilsr	�defence360agent.utils.supportr
�	getLoggerrrr/rpr r"r�rJr�<module>r�s>����������������������(�(�(�(�(�(�(�(�*�*�*�*�*�*�;�;�;�;�;�;���������F�E�E�E�E�E�6�6�6�6�6�6�	��	�8�	$�	$���I�b�!�!�!�	�"�����~�~�~�~�~��~�~�~�~�~rJdefence360agent/api/server/__pycache__/cleanup_revert.cpython-311.opt-1.pyc0000644000000000000000000000357700000000000023530 0ustar  �

�~�_������ddlZddlmZddlmZddlmZmZddlm	Z	m
Z
eje��Z
Gd�de��ZdS)�N)�urljoin)�Request)�API�APIError)�IndependentAgentIDAPI�IAIDTokenErrorc�F�eZdZeejd��Zed���ZdS)�CleanupRevertAPIz/api/cleanup/revertc��BK�	tj���d{V��}n#t$rgcYSwxYwt|jd|i���}	|�|���d{V��}n4#t$r'}t�d|��gcYd}~Sd}~wwxYw|dS)NzX-Auth)�headersz'Failed to fetch cleanup revert data: %s�paths)	r�	get_tokenrr�URL�
async_requestr�logger�warning)�cls�token�request�result�excs     �^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/server/cleanup_revert.pyr
zCleanupRevertAPI.pathss�����	�/�9�;�;�;�;�;�;�;�;�E�E���	�	�	��I�I�I�	�����#�'�H�e�+<�=�=�=��	��,�,�W�5�5�5�5�5�5�5�5�F�F���	�	�	��N�N�D�c�J�J�J��I�I�I�I�I�I�����	�����g��s*��-�-�	A%�%
B�/B�B�BN)	�__name__�
__module__�__qualname__rr�	_BASE_URLr�classmethodr
��rr
r
sA������
�'�#�-�!6�
7�
7�C��
�
��[�
�
�
rr
)�logging�urllib.parser�urllib.requestr�defence360agent.api.serverrr�defence360agent.internals.iaidrr�	getLoggerrrr
rrr�<module>r&s������� � � � � � �"�"�"�"�"�"�4�4�4�4�4�4�4�4���������

��	�8�	$�	$�������s�����rdefence360agent/api/server/__pycache__/cleanup_revert.cpython-311.pyc0000644000000000000000000000357700000000000022571 0ustar  �

�~�_������ddlZddlmZddlmZddlmZmZddlm	Z	m
Z
eje��Z
Gd�de��ZdS)�N)�urljoin)�Request)�API�APIError)�IndependentAgentIDAPI�IAIDTokenErrorc�F�eZdZeejd��Zed���ZdS)�CleanupRevertAPIz/api/cleanup/revertc��BK�	tj���d{V��}n#t$rgcYSwxYwt|jd|i���}	|�|���d{V��}n4#t$r'}t�d|��gcYd}~Sd}~wwxYw|dS)NzX-Auth)�headersz'Failed to fetch cleanup revert data: %s�paths)	r�	get_tokenrr�URL�
async_requestr�logger�warning)�cls�token�request�result�excs     �^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/server/cleanup_revert.pyr
zCleanupRevertAPI.pathss�����	�/�9�;�;�;�;�;�;�;�;�E�E���	�	�	��I�I�I�	�����#�'�H�e�+<�=�=�=��	��,�,�W�5�5�5�5�5�5�5�5�F�F���	�	�	��N�N�D�c�J�J�J��I�I�I�I�I�I�����	�����g��s*��-�-�	A%�%
B�/B�B�BN)	�__name__�
__module__�__qualname__rr�	_BASE_URLr�classmethodr
��rr
r
sA������
�'�#�-�!6�
7�
7�C��
�
��[�
�
�
rr
)�logging�urllib.parser�urllib.requestr�defence360agent.api.serverrr�defence360agent.internals.iaidrr�	getLoggerrrr
rrr�<module>r&s������� � � � � � �"�"�"�"�"�"�4�4�4�4�4�4�4�4���������

��	�8�	$�	$�������s�����rdefence360agent/api/server/__pycache__/events.cpython-311.opt-1.pyc0000644000000000000000000000660000000000000022004 0ustar  �

��Ԕ����~�ddlZddlZddlZddlmZddlmZddlm	Z	ej
e��ZGd�de��Z
dS)�N)�API)�IndependentAgentIDAPI)�run_in_executor_decoratorc��eZdZdZdZdZeed�����Zed���Z	ed���Z
eed�����ZdS)	�	EventsAPIzV{base}/api/dashboard/events?dashboard=false&popup=true&not_snoozed_at={not_snoozed_at}z:{base}/api/dashboard/v2/events?notification=1&enduser=truez/{base}/api/dashboard/v2/events?smartadvice=truec	�2�tj�|j�|jt
tj����	�������d���}|�|��}|dS)N)�base�not_snoozed_at�GET)�method�result)
�urllib�request�Request�ADVICES_API_URL_TEMPLATE�format�	_BASE_URL�int�datetime�now�	timestamp��clsrr
s   �V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/server/events.py�adviceszEventsAPI.advicess����.�(�(��(�/�/��]�"�8�#4�#8�#8�#:�#:�#D�#D�#F�#F�G�G�
0�
�
��)�
�
�����W�%�%���h���c���K�tj�|j�|j���ddt
j���d{V��i���}|�|���d{V��S)N�r	r�X-Auth�r�headers)	rrr�NOTIFICATIONS_API_URL_TEMPLATErrr�	get_token�_send_notifications�rrs  r�notificationzEventsAPI.notification%s������.�(�(��.�5�5�3�=�5�I�I���%:�%D�%F�%F�F�F�F�F�F�F�G�)�
�
��
�,�,�W�5�5�5�5�5�5�5�5�5rc���K�tj�|j�|j���ddt
j���d{V��i���}|�|��dS)Nrrrr r
)rrr�SMART_ADVICE_API_URL_TEMPLATErrrr#r%s  r�
smart_adviceszEventsAPI.smart_advices.s{�����.�(�(��-�4�4�#�-�4�H�H���%:�%D�%F�%F�F�F�F�F�F�F�G�)�
�
��
�{�{�7�#�#�H�-�-rc�<�|�|��}|dS)Nr
)rrs   rr$zEventsAPI._send_notifications7s �����W�%�%���h��rN)�__name__�
__module__�__qualname__rr"r(�classmethodrrr&r)r$�rrrrs�������	5��
	E�#�	:�"���	 �	 ����[�	 ��6�6��[�6��.�.��[�.��� � ����[� � � rr)�urllib.requestr�loggingr�defence360agent.api.serverr�defence360agent.internals.iaidr�defence360agent.rpc_tools.utilsr�	getLoggerr+�loggerrr/rr�<module>r7s���������������*�*�*�*�*�*�@�@�@�@�@�@�E�E�E�E�E�E�	��	�8�	$�	$��/ �/ �/ �/ �/ ��/ �/ �/ �/ �/ rdefence360agent/api/server/__pycache__/events.cpython-311.pyc0000644000000000000000000000660000000000000021045 0ustar  �

��Ԕ����~�ddlZddlZddlZddlmZddlmZddlm	Z	ej
e��ZGd�de��Z
dS)�N)�API)�IndependentAgentIDAPI)�run_in_executor_decoratorc��eZdZdZdZdZeed�����Zed���Z	ed���Z
eed�����ZdS)	�	EventsAPIzV{base}/api/dashboard/events?dashboard=false&popup=true&not_snoozed_at={not_snoozed_at}z:{base}/api/dashboard/v2/events?notification=1&enduser=truez/{base}/api/dashboard/v2/events?smartadvice=truec	�2�tj�|j�|jt
tj����	�������d���}|�|��}|dS)N)�base�not_snoozed_at�GET)�method�result)
�urllib�request�Request�ADVICES_API_URL_TEMPLATE�format�	_BASE_URL�int�datetime�now�	timestamp��clsrr
s   �V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/server/events.py�adviceszEventsAPI.advicess����.�(�(��(�/�/��]�"�8�#4�#8�#8�#:�#:�#D�#D�#F�#F�G�G�
0�
�
��)�
�
�����W�%�%���h���c���K�tj�|j�|j���ddt
j���d{V��i���}|�|���d{V��S)N�r	r�X-Auth�r�headers)	rrr�NOTIFICATIONS_API_URL_TEMPLATErrr�	get_token�_send_notifications�rrs  r�notificationzEventsAPI.notification%s������.�(�(��.�5�5�3�=�5�I�I���%:�%D�%F�%F�F�F�F�F�F�F�G�)�
�
��
�,�,�W�5�5�5�5�5�5�5�5�5rc���K�tj�|j�|j���ddt
j���d{V��i���}|�|��dS)Nrrrr r
)rrr�SMART_ADVICE_API_URL_TEMPLATErrrr#r%s  r�
smart_adviceszEventsAPI.smart_advices.s{�����.�(�(��-�4�4�#�-�4�H�H���%:�%D�%F�%F�F�F�F�F�F�F�G�)�
�
��
�{�{�7�#�#�H�-�-rc�<�|�|��}|dS)Nr
)rrs   rr$zEventsAPI._send_notifications7s �����W�%�%���h��rN)�__name__�
__module__�__qualname__rr"r(�classmethodrrr&r)r$�rrrrs�������	5��
	E�#�	:�"���	 �	 ����[�	 ��6�6��[�6��.�.��[�.��� � ����[� � � rr)�urllib.requestr�loggingr�defence360agent.api.serverr�defence360agent.internals.iaidr�defence360agent.rpc_tools.utilsr�	getLoggerr+�loggerrr/rr�<module>r7s���������������*�*�*�*�*�*�@�@�@�@�@�@�E�E�E�E�E�E�	��	�8�	$�	$��/ �/ �/ �/ �/ ��/ �/ �/ �/ �/ rdefence360agent/api/server/__pycache__/reputation.cpython-311.opt-1.pyc0000644000000000000000000000771600000000000022703 0ustar  �

�s	���D����ddlZddlZddlZddlZddlZddlmZddlZddl	Z	ddl
mZmZddl
mZmZe	je��ZGd�de��ZdS)�N)�List)�retry_on�split_for_chunk)�API�APIErrorc��eZdZdZdZdZdZdZdZe	de
ede
efd	���Z
e	de
ede
efd
���Ze	eee���defd�����Ze	eee���d
efd�����ZdS)�
ReputationAPIz/api/reputation/checkz/api/reputation/resulti��i��<�domains�returnc��K�t�d|��tj��}|�d|j|���d{V��S)NzDomainListRequest domains: %s)�logger�info�asyncio�get_event_loop�run_in_executor�_check)�clsr�loops   �Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/server/reputation.py�checkzReputationAPI.checksU�������3�W�=�=�=��%�'�'���)�)�$��
�G�D�D�D�D�D�D�D�D�D�c��g}t||j��D]7}|�|��}|�|d��}||z
}�8|S)N�	result_id)r�
CHUNK_SIZE�_check_chunk�_get_result)rr�result_list�chunk�result�
next_chunks      rrzReputationAPI._check"s]����$�W�c�n�=�=�	&�	&�E��%�%�e�,�,�F������)<�=�=�J��:�%�K�K��r)�timeoutc
���tj�|j|jzddditjt|�����������}|�|��S)N�POSTzContent-Typezapplication/json)r)�method�headers�data)	�urllib�request�Request�	_BASE_URL�REQUEST_URL�json�dumps�dict�encode)rr �
check_requests   rrzReputationAPI._check_chunk+sp����.�.��M�C�O�+��#�%7�8���D��/�/�/�0�0�7�7�9�9�	/�
�
�
��{�{�=�)�)�)rrc�r�t|���}d�|j|jztj�|����}tj�|��}|�|��}|d}|�(tj
|j��td���|S)N)rz{}?{}r!zResponse not ready yet)
r0�formatr,�
RESULT_URLr)�parse�	urlencoder*r+�time�sleep�WAIT_BEFORE_RETRYr)rrr(�urlr*�responser!s       rrzReputationAPI._get_result6s����i�(�(�(���n�n��M�C�N�*�F�L�,B�,B�4�,H�,H�
�
���.�(�(��-�-���;�;�w�'�'���(�#���>��J�s�,�-�-�-��3�4�4�4��
rN)�__name__�
__module__�__qualname__r-r5rr:�WAIT_FOR_RESULT�_SOCKET_TIMEOUT�classmethodr�strr0rrrrrr�rrr	r	s'������)�K�)�J�
�J����O��O��E�$�s�)�E��T�
�E�E�E��[�E�
��T�#�Y��4��:�����[���
�X�h��0�0�0�*�D�*�*�*�1�0��[�*��
�X�h��0�0�0��C����1�0��[���rr	)r.�urllib.errorr)�urllib.request�urllib.parser�typingrr8�logging�defence360agent.utilsrr�defence360agent.api.serverrr�	getLoggerr=rr	rDrr�<module>rMs�������������������������������������;�;�;�;�;�;�;�;�4�4�4�4�4�4�4�4�	��	�8�	$�	$��4�4�4�4�4�C�4�4�4�4�4rdefence360agent/api/server/__pycache__/reputation.cpython-311.pyc0000644000000000000000000000771600000000000021744 0ustar  �

�s	���D����ddlZddlZddlZddlZddlZddlmZddlZddl	Z	ddl
mZmZddl
mZmZe	je��ZGd�de��ZdS)�N)�List)�retry_on�split_for_chunk)�API�APIErrorc��eZdZdZdZdZdZdZdZe	de
ede
efd	���Z
e	de
ede
efd
���Ze	eee���defd�����Ze	eee���d
efd�����ZdS)�
ReputationAPIz/api/reputation/checkz/api/reputation/resulti��i��<�domains�returnc��K�t�d|��tj��}|�d|j|���d{V��S)NzDomainListRequest domains: %s)�logger�info�asyncio�get_event_loop�run_in_executor�_check)�clsr�loops   �Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/server/reputation.py�checkzReputationAPI.checksU�������3�W�=�=�=��%�'�'���)�)�$��
�G�D�D�D�D�D�D�D�D�D�c��g}t||j��D]7}|�|��}|�|d��}||z
}�8|S)N�	result_id)r�
CHUNK_SIZE�_check_chunk�_get_result)rr�result_list�chunk�result�
next_chunks      rrzReputationAPI._check"s]����$�W�c�n�=�=�	&�	&�E��%�%�e�,�,�F������)<�=�=�J��:�%�K�K��r)�timeoutc
���tj�|j|jzddditjt|�����������}|�|��S)N�POSTzContent-Typezapplication/json)r)�method�headers�data)	�urllib�request�Request�	_BASE_URL�REQUEST_URL�json�dumps�dict�encode)rr �
check_requests   rrzReputationAPI._check_chunk+sp����.�.��M�C�O�+��#�%7�8���D��/�/�/�0�0�7�7�9�9�	/�
�
�
��{�{�=�)�)�)rrc�r�t|���}d�|j|jztj�|����}tj�|��}|�|��}|d}|�(tj
|j��td���|S)N)rz{}?{}r!zResponse not ready yet)
r0�formatr,�
RESULT_URLr)�parse�	urlencoder*r+�time�sleep�WAIT_BEFORE_RETRYr)rrr(�urlr*�responser!s       rrzReputationAPI._get_result6s����i�(�(�(���n�n��M�C�N�*�F�L�,B�,B�4�,H�,H�
�
���.�(�(��-�-���;�;�w�'�'���(�#���>��J�s�,�-�-�-��3�4�4�4��
rN)�__name__�
__module__�__qualname__r-r5rr:�WAIT_FOR_RESULT�_SOCKET_TIMEOUT�classmethodr�strr0rrrrrr�rrr	r	s'������)�K�)�J�
�J����O��O��E�$�s�)�E��T�
�E�E�E��[�E�
��T�#�Y��4��:�����[���
�X�h��0�0�0�*�D�*�*�*�1�0��[�*��
�X�h��0�0�0��C����1�0��[���rr	)r.�urllib.errorr)�urllib.request�urllib.parser�typingrr8�logging�defence360agent.utilsrr�defence360agent.api.serverrr�	getLoggerr=rr	rDrr�<module>rMs�������������������������������������;�;�;�;�;�;�;�;�4�4�4�4�4�4�4�4�	��	�8�	$�	$��4�4�4�4�4�C�4�4�4�4�4rdefence360agent/api/server/__pycache__/send_message.cpython-311.opt-1.pyc0000644000000000000000000006060300000000000023140 0ustar  �

=VX�B؄����ddlZddlZddlZddlZddlZddlZddlZ	ddlZddl	Zddl
ZdZejj
ZejjjZn)#e$r!dZGd�de��ZGd�de��ZYnwxYwddlZddlmZmZdd	lmZdd
lmZddlZddlZddlmZmZm 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.m/Z/ddl0m1Z1ddl2m3Z3ee4��Z5e.��Z6e.��Z7dZ8dZ9Gd�de��Z:de;fd�Z<de;fd�Z=deddfd�Z>Gd�dee��Z?Gd �d!e?��Z@Gd"�d#e@��ZAGd$�d%��ZBdS)&�NTFc��eZdZdS)�_NATSMaxPayloadErrorN)�__name__�
__module__�__qualname__���\/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/server/send_message.pyrrs�������r	rc��eZdZdZdS)�
_NATSAPIErrorN)rrr�err_coderr	r
rrs���������r	r)�ABC�abstractmethod)�	getLogger)�Optional)�API�APIError�
APITokenError�FGWSendMessgeException�NATSSendMessageException)�Core)�
estimate_size�Message)�g)�IndependentAgentIDAPI�IAIDTokenError)�Gen�	publisher)�AsyncIterate)�ServerJSONEncoderi]'iF'c��eZdZdZdS)�_StreamFullzDStream is at capacity: re-queue the rest, the connection is healthy.N)rrr�__doc__rr	r
r"r"Bs������N�N�N�Nr	r"�itemc�0�	�d}d}|���D]M\}�	t�	ttf��r,t	�	��dkrt�	��}||kr||}}�N|�dS||�	t�	t��r2t	�	��dz}i|�|�	d|�i�i|�|�	|d�i�gSt�	��}t	|��dz}�	fd�|d|�D��}�	fd�||d�D��}i|�||i�i|�||i�gS)a%Split a single sub-message on its largest list/dict field, chosen by
    serialized byte size (not element count) so the heaviest field is the one
    that shrinks. Returns two sub-messages, or None when nothing inside can be
    split further (no list/dict field holds at least two elements).Nr��c�"��i|]}|�|��Srr��.0�k�values  �r
�
<dictcomp>z(_split_largest_field.<locals>.<dictcomp>Zs���,�,�,�A�A�u�Q�x�,�,�,r	c�"��i|]}|�|��Srrr)s  �r
r-z(_split_largest_field.<locals>.<dictcomp>[s���-�-�-�Q�Q��a��-�-�-r	)�items�
isinstance�list�dict�lenr)
r$�field�largest�key�size�mid�keys�left�rightr,s
         @r
�_split_largest_fieldr<Fsc���

�E��G��j�j�l�l�+�+�
��U��e�d�D�\�*�*�	+�s�5�z�z�A�~�~� ��'�'�D��g�~�~�!$�d�w����}��t���K�E��%����L��%�j�j�A�o��,�4�,���d�s�d��,�,�.J��.J�u�e�C�D�D�k�.J�.J�K�K���;�;�D�

�d�)�)�q�.�C�,�,�,�,��d�s�d��,�,�,�D�-�-�-�-�$�s�t�t�*�-�-�-�E�!�t�!�U�D�!�!�#9�d�#9�E�5�#9�#9�:�:r	�loadedc�����d��}t|t��rEt|��dkr2t|��dz}i��d|d|�i�i��d||d�i�gSt|t��r8t|��dkr%t	|d��}|��fd�|D��SdS)z�Split an oversized message into smaller parts. Returns a list of parts,
    or None when the message carries a single irreducible record.r/r&r'Nrc�"��g|]}i��d|gi���S)r/r)r*�halfr=s  �r
�
<listcomp>z$_split_oversized.<locals>.<listcomp>ls+���C�C�C�D�/�v�/�w���/�/�C�C�Cr	)�getr0r1r3r<)r=r/r8�halvess`   r
�_split_oversizedrD_s����
�J�J�w���E��%����
�3�u�:�:��>�>��%�j�j�A�o��,�v�,�w��d�s�d��,�,�,�v�,�w��c�d�d��,�,�
�	
��%����D�3�u�:�:��?�?�%�e�A�h�/�/����C�C�C�C�F�C�C�C�C��4r	�ex�returnc��@K�t�d|��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)�logger�debug)rEs r
�_nats_error_cbrJps"�����L�L��R� � � � � r	c�R�eZdZdZedefd���Zdeddfd�Zdede	ddfd	�Z
dS)
�BaseSendMessageAPIz/api/v2/send-message/{method}rFc��
K�dS�Nr)�self�message_method�headers�	post_datas    r
�
_send_requestz BaseSendMessageAPI._send_request}s�����r	�resultNc���d|vr"td�|�����|ddkr5td�|�d�������dS)N�statusz unexpected server response: {!r}�okzserver error: {}�msg)r�formatrB)rOrTs  r
�check_responsez!BaseSendMessageAPI.check_response�sk���6�!�!��=�D�D�V�L�L�M�M�M��(��t�#�#��-�4�4�V�Z�Z��5F�5F�G�G�H�H�H�$�#r	�methodrRc��K�	tj���d{V��}n$#t$r}td|�����d}~wwxYwd|d�}|�|||���d{V��}|�|��dS)NzIAID token error occurred zapplication/json)zContent-TypezX-Auth)r�	get_tokenrrrSrZ)rOr[rR�token�erQrTs       r
�	send_datazBaseSendMessageAPI.send_data�s�����	B�/�9�;�;�;�;�;�;�;�;�E�E���	B�	B�	B�� @�Q� @� @�A�A�A�����	B����/��
�
���)�)�&�'�9�E�E�E�E�E�E�E�E�����F�#�#�#�#�#s��
?�:�?)rrr�URLrr2rSrZ�str�bytesr`rr	r
rLrLzs�������
)�C��
��
�
�
��^�
�I�T�I�d�I�I�I�I�
$�c�
$�e�
$��
$�
$�
$�
$�
$�
$r	rLc��eZdZejZddedefd�Zdeddfd�Zde	eddfd	�Z
d
eddfd�Zd�Z
d
eddfd�ZdS)�SendMessageAPIN�rpm_ver�base_urlc�z�||_||_d|_d|_i|_|r	||_dS|j|_dS)N�)�	_executorrf�product_name�	server_id�licenserg�	_BASE_URL)rOrfrg�executors    r
�__init__zSendMessageAPI.__init__�sF��!����������������	+�$�D�M�M�M� �N�D�M�M�Mr	rkrFc��||_dSrN)rk)rOrks  r
�set_product_namezSendMessageAPI.set_product_name�s��(����r	rlc��||_dSrN)rl)rOrls  r
�
set_server_idzSendMessageAPI.set_server_id�s
��"����r	rmc��||_dSrN)rm)rOrms  r
�set_licensezSendMessageAPI.set_license�s
������r	c���K�tj�|j|j�|���z||d���}|�||j����d{V��S)N�r[�POST)�datarQr[)ro)�urllib�request�RequestrgrarY�
async_requestrj)rOrPrQrRr|s     r
rSzSendMessageAPI._send_request�st�����.�(�(��M�D�H�O�O�>�O�B�B�B����	)�
�
���'�'��$�.�'�I�I�I�I�I�I�I�I�Ir	�messagec��lK�d|vrtj��|d<d|vrtj��j|d<d|vrd|d<|j|j|j|j|jd�}tj
|t������}|�
|j|���d{V��dS)N�	timestamp�
message_idr[�
INCIDENT_LIST)�payloadrfr�rl�name)�cls)�time�uuid�uuid4�hexr�rfr�rlrk�json�dumpsr �encoder`r[)rOr�	data2sendrRs    r
�send_messagezSendMessageAPI.send_message�s������g�%�%�#'�9�;�;�G�K� ��w�&�&�$(�J�L�L�$4�G�L�!��7�"�"� /�G�H�����|�!�,����%�
�
�	��J�y�.?�@�@�@�G�G�I�I�	��n�n�W�^�Y�7�7�7�7�7�7�7�7�7�7�7r	)NN)rrrr�DEFAULT_SOCKET_TIMEOUT�_SOCKET_TIMEOUTrbrprrrrtr2rvrSrr�rr	r
rere�s��������1�O�	+�	+��	+�s�	+�	+�	+�	+�)�S�)�T�)�)�)�)�#�x��}�#��#�#�#�#��4��D�����J�J�J�8�'�8�d�8�8�8�8�8�8r	rec�F�eZdZdefd�Zdeeeefddfd�Z	dS)�FileBasedGatewayAPIrFc��K�|4�d{V��tjtj|���d{V��}|dd�|���D��d�cddd���d{V��S#1�d{V��swxYwYdS)Nr[c�&�i|]\}}|dk�||��Srxr)r*r+�vs   r
r-z8FileBasedGatewayAPI._prepare_message.<locals>.<dictcomp>�s#��J�J�J�$�!�Q�A��M�M��A�M�M�Mr	)r[rz)�asyncio�	to_threadr��loadsr/)rOr�	semaphorer=s    r
�_prepare_messagez$FileBasedGatewayAPI._prepare_message�s�����	�	�	�	�	�	�	�	�"�,�T�Z��A�A�A�A�A�A�A�A�F� ��*�J�J�&�,�,�.�.�J�J�J���	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	�	s�AA*�*
A4�7A4�messagesNc��n��K�d}tj|�����fd�t|��2���d{V��}tj|��d{V��}|D]N}i|�di���d|�dd��i�}tj|td����Otjtj
|���d{V��}tjdd	��}tj
�|d
��}	|	ddg}
tj|
tjjtjjtjjd
���d{V��}t%j|�����}|�|����d{V��\}
}t-jd��r*t.�dt3|��|
|��|jdkr`t.�d|�������t;t=d|����������dS)N�c��T�K�g|3d{V��	\}}��|�����"6SrN)r�)r*�_rXrOr�s   ��r
rAz5FileBasedGatewayAPI.send_messages.<locals>.<listcomp>�sd�����
�
�
�
�
�
�
�
�
��a��
�!�!�#�y�1�1�
�
�
�
s�(rzr[rizagent-fgw-sending��stage�I360_MESSAGE_GATEWAY_BIN_PATHz
/usr/libexec/zimunify-message-gatewayz	send-manyz"--producer=i360-agent-non-resident)�stdin�stdout�stderr)�input�DEBUGzMessage sent to fgw: %s %s %srzError sending message: )r��	Semaphorer�gatherrBr�report�_reporter_gen_fgwr�r�r��os�getenv�path�join�create_subprocess_exec�
subprocess�PIPE�base64�	b64encoder��communicaterrH�infor3�
returncode�error�decoderrb)rOr��max_threads�tasks�prepared_messagesrX�flat�dumped_messages�
bin_file_path�bin_file�command�process�b64datar�r�r�s`              @r
�
send_messagesz!FileBasedGatewayAPI.send_messages�s����������%�k�2�2�	�
�
�
�
�
� ,�X� 6� 6�
�
�
�
�
�
�
�
�
��#*�.�%�"8�8�8�8�8�8�8��$�	�	�C�K�c�g�g�f�b�)�)�K�8�S�W�W�X�r�5J�5J�K�K�D����'�/B�
�
�
�
�
�!(� 1��J�)�!
�!
�
�
�
�
�
�
���	�+�_�
�
�
��7�<�<�
�/H�I�I��
��0�
�� �6�
��$�)��%�*��%�*�	
�
�
�
�
�
�
�
�
���"�?�#9�#9�#;�#;�<�<��&�2�2��2�A�A�A�A�A�A�A�A�����5��>�>�	��K�K�/��X�����
�
�
����"�"��L�L�D�6�=�=�?�?�D�D�E�E�E�(��?�f�m�m�o�o�?�?�@�@���
�#�"r	)
rrrr2r�r1�tuple�floatrcr�rr	r
r�r��s_�������D�����/�D��u�e�|�1D�,E�/�$�/�/�/�/�/�/r	r�c��eZdZdZdZdZdZdZdZdZ	d�Z
ed���Zd	�Z
d
eeeefddfd
�Zd�Zd�ZdS)�NATSGatewayAPIz�Publishes messages to the embedded NATS server via localhost TCP.

    Connects to nats://127.0.0.1:<port> with an auth token read from
    a file written by the resident-agent on startup.
    zimunify.api.i��z/var/run/imunify360/nats.tokenz/var/run/imunify360/nats.addrr�c�0�d|_d|_d|_dS)Nr)�_nc�_last_connect_attempt�_oversized_dropped�rOs r
rpzNATSGatewayAPI.__init__s�����%&��"�"#����r	c��tjdtj��}	t	|��5}|������}ddd��n#1swxYwY|r|Sn#t$rYnwxYwttjdttj
������}d|��S)zBRead NATS listen address from addr file, fall back to env/default.�I360_NATS_ADDR_PATHN�I360_NATS_PORTz
127.0.0.1:)r�r�r��DEFAULT_ADDR_PATH�open�read�strip�OSError�intrb�DEFAULT_PORT)�	addr_path�f�addr�ports    r
�
_read_addrzNATSGatewayAPI._read_addrs���I�!�>�#C�
�
�	�	��i���
(�A��v�v�x�x�~�~�'�'��
(�
(�
(�
(�
(�
(�
(�
(�
(�
(�
(����
(�
(�
(�
(��
���
���	�	�	��D�	������I�&��N�,G�(H�(H�I�I�
�
��#�D�"�"�"s4�A3�'A#�A3�#A'�'A3�*A'�+A3�3
B�?Bc��K�|j�|jjrdStstd���t	j��}||jz
}||jkrtd|j|z
d�d����||_|����d{V��|�	��}tjd|j��}	t|��5}|������}ddd��n#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)r^�connect_timeout�max_reconnect_attempts�error_cbzConnected to NATS at %szFailed to connect to NATS: )r��is_connected�	_has_natsrr��	monotonicr��MIN_RECONNECT_INTERVAL�_closer�r�r��DEFAULT_TOKEN_PATHr�r�r��nats�connect�CONNECT_TIMEOUTrJrHr��	Exception)rO�now�
since_lastr��
token_pathr�r^r_s        r
�_ensure_connectedz NATSGatewayAPI._ensure_connected+s
�����8��D�H�$9���F��	G�*�+E�F�F�F��n�����4�5�5�
���3�3�3�*�P��0�:�=�O�P�P�P���
�&)��"��k�k�m�m����������� � ���Y�5�t�7N�O�O�
�	��j�!�!�
)�Q��������(�(��
)�
)�
)�
)�
)�
)�
)�
)�
)�
)�
)����
)�
)�
)�
)�!�\� �$� � �� $� 4�'(�'����������D�H�
�K�K�1�4�8�8�8�8�8���	�	�	�*�1�a�1�1����
�����	���s=�E�'D�8E�D�E�D�AE�
E?�'E:�:E?r�rFNc
���K�|����d{V��d}	|j���}|D�]�\}}	tj|��}nC#tjtf$r*}t�d|��|dz
}Yd}~�Wd}~wwxYw|�	dd��}|j
|z}	tj||�
d��fg��}
d}|
�r|
���\}}
tj|�����}|
rd|
ind}	|�|	||�	���d{V��}�nj#t$t&f$�rU}t)|t&��r:|jt,krt/d
|	�d|
�d|����|�|jt0kr�t3|��}|�R|xjdz
c_d
}t�d|	|
t9|��|j||dd���Yd}~��-|
p%t;j|�����}tA|��D](\}}|�d|��}||d<|
�!||f���)t�d|	|
t9|��t9|����Yd}~���d}~wwxYwtEj
d��r't�#d|	|j$|j%��|
��|r"tMj'i|�d|i�tPd���|dz
}���dS#t.$rU}t�d|t9|��t9|��|z
|��tSd|��|���|�d}~wtT$r^}|�+���d{V��t�d|t9|��|��tSd|��|���|�d}~wwxYw)NrzSkipping malformed message: %sr&r[�UNKNOWNr�TzNats-Msg-Id)rQzsubject=z message_id=z: FzhDropping oversized NATS message: subject=%s message_id=%s size=%d oversized_total=%d error=%s preview=%r���.zKSplitting oversized NATS message: subject=%s message_id=%s size=%d parts=%dr�z"Published to %s, stream=%s, seq=%szagent-nats-sendingr�z<NATS stream full, %d/%d messages published, %d re-queued: %szStream at capacity: )�	publishedz,NATS publish failed after %d/%d messages: %szFailed to publish messages: ),r�r��	jetstreamr�r��JSONDecodeError�UnicodeDecodeErrorrH�warning�pop�NATS_SUBJECT_PREFIX�collections�dequerB�popleftr�r��publishrrr0r
�_JS_ERR_STREAM_FULLr"�_JS_ERR_MSG_TOO_LARGErDr�r�r3�hashlib�sha1�	hexdigest�	enumerate�appendrrI�stream�seqrr��_reporter_gen_natsrr�r�)rOr�r��jsr��	msg_bytesr=r_r[�subject�pending�fully_delivered�part�dedup_idr�rQ�ackrC�base_key�indexr@�	child_keys                      r
r�zNATSGatewayAPI.send_messagesPs+�����$�$�&�&�&�&�&�&�&�&�&��	�A	���#�#�%�%�B� (�c
�c
���9��!�Z�	�2�2�F�F���,�.@�A�����N�N�#C�Q�G�G�G���N�I��H�H�H�H��������� ���H�i�8�8���2�V�;��&�+��f�j�j��6�6�7�8����#'���B�%,�_�_�%6�%6�N�D�(�"�j��.�.�5�5�7�7�G�;C�M�}�h�7�7��G�7!�$&�J�J�#�W�g�%/�%�%����������1�-�@�3!�3!�3!�%�a��7�7�&� �z�-@�@�@�
'2�%C�w�%C�%C�3;�%C�%C�?@�%C�%C�'"�'"�()�!)� !�z�-B�B�B� %�!1�$�!7�!7��!�>�!�3�3�q�8�3�3�.3�O�"�L�L�!7�!(� (� #�G��� $� 7� !� '����
�
�
�
�%�H�H�H�H�$�I���W�(=�(=�(G�(G�(I�(I�!�,5�V�+<�+<�>�>�K�E�4�+3�(=�(=�e�(=�(=�I�1:�D��.�#�N�N�D�)�+<�=�=�=�=����>�#�$���L�L���K�K�
���!���������g3!����h�u�W�~�~�����@�#��J��G�	���{�B�R#���$�4�6�4�8�V�4�4�*�2�����
�Q��	�	�Gc
�c
��J�
	�
	�
	�
�N�N�N���H�
�
��H�
�
�	�)��
�
�
�+�*�q�*�*�#�����
������	�	�	��+�+�-�-���������N�N�>���H�
�
��	
�
�
�+�2�q�2�2�#�����
�����	���s��!L$�A�L$�B�- B�
L$�B�BL$�/E�
L$�J6�!B,J1�
L$�BJ1�+L$�1J6�6A,L$�$
O)�.AM>�>
O)�AO$�$O)c��K�|j�F	|j����d{V��n#t$rYnwxYwd|_dS#d|_wxYwdSrN)r��closer�r�s r
r�zNATSGatewayAPI._close�s������8��	
 �
�h�n�n�&�&�&�&�&�&�&�&�&�&���
�
�
���
���� ������4������� �s!�+�A�
8�A�8�A�	A
c��>K�|����d{V��dSrN)r�r�s r
rzNATSGatewayAPI.close�s,�����k�k�m�m���������r	)rrrr#r�r�r�r�r�r�rp�staticmethodr�r�r1r�r�rcr�r�rrr	r
r�r�s���������)���L�9��7���O���$�$�$�
�#�#��\�#�&#�#�#�JE�D��u�e�|�1D�,E�E�$�E�E�E�E�N � � �����r	r�)Cr�r�rr�r�r��urllib.errorr{r��nats.errors�nats.js.errorsr��errors�MaxPayloadErrorrr
rr�ImportErrorr��urllib.request�abcrr�loggingr�typingrr�r��defence360agent.api.serverrrrr� defence360agent.contracts.configr�"defence360agent.contracts.messagesrr�&defence360agent.internals.global_scoper�defence360agent.internals.iaidrr�2defence360agent.internals.message_status_publisherrr�!defence360agent.utils.async_utilsr�defence360agent.utils.jsonr rrHr�r	rrr"r2r<rDrJrLrer�r�rr	r
�<module>r+s���
�
�
�
�������������	�	�	�	�����������K�K�K����������I��;�6���G�N�+�M�M�������I�
�
�
�
�
�y�
�
�
������	������
��������#�#�#�#�#�#�#�#�����������������������������������2�1�1�1�1�1�E�E�E�E�E�E�E�E�4�4�4�4�4�4���������N�M�M�M�M�M�M�M�:�:�:�:�:�:�8�8�8�8�8�8�	��8�	�	���C�E�E���S�U�U��
����O�O�O�O�O�)�O�O�O�;�t�;�;�;�;�2�T�����"!�Y�!�4�!�!�!�!�$�$�$�$�$��c�$�$�$�428�28�28�28�28�'�28�28�28�j8�8�8�8�8�.�8�8�8�va�a�a�a�a�a�a�a�a�as�+A
�
#A0�/A0defence360agent/api/server/__pycache__/send_message.cpython-311.pyc0000644000000000000000000006060300000000000022201 0ustar  �

=VX�B؄����ddlZddlZddlZddlZddlZddlZddlZ	ddlZddl	Zddl
ZdZejj
ZejjjZn)#e$r!dZGd�de��ZGd�de��ZYnwxYwddlZddlmZmZdd	lmZdd
lmZddlZddlZddlmZmZm 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.m/Z/ddl0m1Z1ddl2m3Z3ee4��Z5e.��Z6e.��Z7dZ8dZ9Gd�de��Z:de;fd�Z<de;fd�Z=deddfd�Z>Gd�dee��Z?Gd �d!e?��Z@Gd"�d#e@��ZAGd$�d%��ZBdS)&�NTFc��eZdZdS)�_NATSMaxPayloadErrorN)�__name__�
__module__�__qualname__���\/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/server/send_message.pyrrs�������r	rc��eZdZdZdS)�
_NATSAPIErrorN)rrr�err_coderr	r
rrs���������r	r)�ABC�abstractmethod)�	getLogger)�Optional)�API�APIError�
APITokenError�FGWSendMessgeException�NATSSendMessageException)�Core)�
estimate_size�Message)�g)�IndependentAgentIDAPI�IAIDTokenError)�Gen�	publisher)�AsyncIterate)�ServerJSONEncoderi]'iF'c��eZdZdZdS)�_StreamFullzDStream is at capacity: re-queue the rest, the connection is healthy.N)rrr�__doc__rr	r
r"r"Bs������N�N�N�Nr	r"�itemc�0�	�d}d}|���D]M\}�	t�	ttf��r,t	�	��dkrt�	��}||kr||}}�N|�dS||�	t�	t��r2t	�	��dz}i|�|�	d|�i�i|�|�	|d�i�gSt�	��}t	|��dz}�	fd�|d|�D��}�	fd�||d�D��}i|�||i�i|�||i�gS)a%Split a single sub-message on its largest list/dict field, chosen by
    serialized byte size (not element count) so the heaviest field is the one
    that shrinks. Returns two sub-messages, or None when nothing inside can be
    split further (no list/dict field holds at least two elements).Nr��c�"��i|]}|�|��Srr��.0�k�values  �r
�
<dictcomp>z(_split_largest_field.<locals>.<dictcomp>Zs���,�,�,�A�A�u�Q�x�,�,�,r	c�"��i|]}|�|��Srrr)s  �r
r-z(_split_largest_field.<locals>.<dictcomp>[s���-�-�-�Q�Q��a��-�-�-r	)�items�
isinstance�list�dict�lenr)
r$�field�largest�key�size�mid�keys�left�rightr,s
         @r
�_split_largest_fieldr<Fsc���

�E��G��j�j�l�l�+�+�
��U��e�d�D�\�*�*�	+�s�5�z�z�A�~�~� ��'�'�D��g�~�~�!$�d�w����}��t���K�E��%����L��%�j�j�A�o��,�4�,���d�s�d��,�,�.J��.J�u�e�C�D�D�k�.J�.J�K�K���;�;�D�

�d�)�)�q�.�C�,�,�,�,��d�s�d��,�,�,�D�-�-�-�-�$�s�t�t�*�-�-�-�E�!�t�!�U�D�!�!�#9�d�#9�E�5�#9�#9�:�:r	�loadedc�����d��}t|t��rEt|��dkr2t|��dz}i��d|d|�i�i��d||d�i�gSt|t��r8t|��dkr%t	|d��}|��fd�|D��SdS)z�Split an oversized message into smaller parts. Returns a list of parts,
    or None when the message carries a single irreducible record.r/r&r'Nrc�"��g|]}i��d|gi���S)r/r)r*�halfr=s  �r
�
<listcomp>z$_split_oversized.<locals>.<listcomp>ls+���C�C�C�D�/�v�/�w���/�/�C�C�Cr	)�getr0r1r3r<)r=r/r8�halvess`   r
�_split_oversizedrD_s����
�J�J�w���E��%����
�3�u�:�:��>�>��%�j�j�A�o��,�v�,�w��d�s�d��,�,�,�v�,�w��c�d�d��,�,�
�	
��%����D�3�u�:�:��?�?�%�e�A�h�/�/����C�C�C�C�F�C�C�C�C��4r	�ex�returnc��@K�t�d|��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)�logger�debug)rEs r
�_nats_error_cbrJps"�����L�L��R� � � � � r	c�R�eZdZdZedefd���Zdeddfd�Zdede	ddfd	�Z
dS)
�BaseSendMessageAPIz/api/v2/send-message/{method}rFc��
K�dS�Nr)�self�message_method�headers�	post_datas    r
�
_send_requestz BaseSendMessageAPI._send_request}s�����r	�resultNc���d|vr"td�|�����|ddkr5td�|�d�������dS)N�statusz unexpected server response: {!r}�okzserver error: {}�msg)r�formatrB)rOrTs  r
�check_responsez!BaseSendMessageAPI.check_response�sk���6�!�!��=�D�D�V�L�L�M�M�M��(��t�#�#��-�4�4�V�Z�Z��5F�5F�G�G�H�H�H�$�#r	�methodrRc��K�	tj���d{V��}n$#t$r}td|�����d}~wwxYwd|d�}|�|||���d{V��}|�|��dS)NzIAID token error occurred zapplication/json)zContent-TypezX-Auth)r�	get_tokenrrrSrZ)rOr[rR�token�erQrTs       r
�	send_datazBaseSendMessageAPI.send_data�s�����	B�/�9�;�;�;�;�;�;�;�;�E�E���	B�	B�	B�� @�Q� @� @�A�A�A�����	B����/��
�
���)�)�&�'�9�E�E�E�E�E�E�E�E�����F�#�#�#�#�#s��
?�:�?)rrr�URLrr2rSrZ�str�bytesr`rr	r
rLrLzs�������
)�C��
��
�
�
��^�
�I�T�I�d�I�I�I�I�
$�c�
$�e�
$��
$�
$�
$�
$�
$�
$r	rLc��eZdZejZddedefd�Zdeddfd�Zde	eddfd	�Z
d
eddfd�Zd�Z
d
eddfd�ZdS)�SendMessageAPIN�rpm_ver�base_urlc�z�||_||_d|_d|_i|_|r	||_dS|j|_dS)N�)�	_executorrf�product_name�	server_id�licenserg�	_BASE_URL)rOrfrg�executors    r
�__init__zSendMessageAPI.__init__�sF��!����������������	+�$�D�M�M�M� �N�D�M�M�Mr	rkrFc��||_dSrN)rk)rOrks  r
�set_product_namezSendMessageAPI.set_product_name�s��(����r	rlc��||_dSrN)rl)rOrls  r
�
set_server_idzSendMessageAPI.set_server_id�s
��"����r	rmc��||_dSrN)rm)rOrms  r
�set_licensezSendMessageAPI.set_license�s
������r	c���K�tj�|j|j�|���z||d���}|�||j����d{V��S)N�r[�POST)�datarQr[)ro)�urllib�request�RequestrgrarY�
async_requestrj)rOrPrQrRr|s     r
rSzSendMessageAPI._send_request�st�����.�(�(��M�D�H�O�O�>�O�B�B�B����	)�
�
���'�'��$�.�'�I�I�I�I�I�I�I�I�Ir	�messagec��lK�d|vrtj��|d<d|vrtj��j|d<d|vrd|d<|j|j|j|j|jd�}tj
|t������}|�
|j|���d{V��dS)N�	timestamp�
message_idr[�
INCIDENT_LIST)�payloadrfr�rl�name)�cls)�time�uuid�uuid4�hexr�rfr�rlrk�json�dumpsr �encoder`r[)rOr�	data2sendrRs    r
�send_messagezSendMessageAPI.send_message�s������g�%�%�#'�9�;�;�G�K� ��w�&�&�$(�J�L�L�$4�G�L�!��7�"�"� /�G�H�����|�!�,����%�
�
�	��J�y�.?�@�@�@�G�G�I�I�	��n�n�W�^�Y�7�7�7�7�7�7�7�7�7�7�7r	)NN)rrrr�DEFAULT_SOCKET_TIMEOUT�_SOCKET_TIMEOUTrbrprrrrtr2rvrSrr�rr	r
rere�s��������1�O�	+�	+��	+�s�	+�	+�	+�	+�)�S�)�T�)�)�)�)�#�x��}�#��#�#�#�#��4��D�����J�J�J�8�'�8�d�8�8�8�8�8�8r	rec�F�eZdZdefd�Zdeeeefddfd�Z	dS)�FileBasedGatewayAPIrFc��K�|4�d{V��tjtj|���d{V��}|dd�|���D��d�cddd���d{V��S#1�d{V��swxYwYdS)Nr[c�&�i|]\}}|dk�||��Srxr)r*r+�vs   r
r-z8FileBasedGatewayAPI._prepare_message.<locals>.<dictcomp>�s#��J�J�J�$�!�Q�A��M�M��A�M�M�Mr	)r[rz)�asyncio�	to_threadr��loadsr/)rOr�	semaphorer=s    r
�_prepare_messagez$FileBasedGatewayAPI._prepare_message�s�����	�	�	�	�	�	�	�	�"�,�T�Z��A�A�A�A�A�A�A�A�F� ��*�J�J�&�,�,�.�.�J�J�J���	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	�	s�AA*�*
A4�7A4�messagesNc��n��K�d}tj|�����fd�t|��2���d{V��}tj|��d{V��}|D]N}i|�di���d|�dd��i�}tj|td����Otjtj
|���d{V��}tjdd	��}tj
�|d
��}	|	ddg}
tj|
tjjtjjtjjd
���d{V��}t%j|�����}|�|����d{V��\}
}t-jd��r*t.�dt3|��|
|��|jdkr`t.�d|�������t;t=d|����������dS)N�c��T�K�g|3d{V��	\}}��|�����"6SrN)r�)r*�_rXrOr�s   ��r
rAz5FileBasedGatewayAPI.send_messages.<locals>.<listcomp>�sd�����
�
�
�
�
�
�
�
�
��a��
�!�!�#�y�1�1�
�
�
�
s�(rzr[rizagent-fgw-sending��stage�I360_MESSAGE_GATEWAY_BIN_PATHz
/usr/libexec/zimunify-message-gatewayz	send-manyz"--producer=i360-agent-non-resident)�stdin�stdout�stderr)�input�DEBUGzMessage sent to fgw: %s %s %srzError sending message: )r��	Semaphorer�gatherrBr�report�_reporter_gen_fgwr�r�r��os�getenv�path�join�create_subprocess_exec�
subprocess�PIPE�base64�	b64encoder��communicaterrH�infor3�
returncode�error�decoderrb)rOr��max_threads�tasks�prepared_messagesrX�flat�dumped_messages�
bin_file_path�bin_file�command�process�b64datar�r�r�s`              @r
�
send_messagesz!FileBasedGatewayAPI.send_messages�s����������%�k�2�2�	�
�
�
�
�
� ,�X� 6� 6�
�
�
�
�
�
�
�
�
��#*�.�%�"8�8�8�8�8�8�8��$�	�	�C�K�c�g�g�f�b�)�)�K�8�S�W�W�X�r�5J�5J�K�K�D����'�/B�
�
�
�
�
�!(� 1��J�)�!
�!
�
�
�
�
�
�
���	�+�_�
�
�
��7�<�<�
�/H�I�I��
��0�
�� �6�
��$�)��%�*��%�*�	
�
�
�
�
�
�
�
�
���"�?�#9�#9�#;�#;�<�<��&�2�2��2�A�A�A�A�A�A�A�A�����5��>�>�	��K�K�/��X�����
�
�
����"�"��L�L�D�6�=�=�?�?�D�D�E�E�E�(��?�f�m�m�o�o�?�?�@�@���
�#�"r	)
rrrr2r�r1�tuple�floatrcr�rr	r
r�r��s_�������D�����/�D��u�e�|�1D�,E�/�$�/�/�/�/�/�/r	r�c��eZdZdZdZdZdZdZdZdZ	d�Z
ed���Zd	�Z
d
eeeefddfd
�Zd�Zd�ZdS)�NATSGatewayAPIz�Publishes messages to the embedded NATS server via localhost TCP.

    Connects to nats://127.0.0.1:<port> with an auth token read from
    a file written by the resident-agent on startup.
    zimunify.api.i��z/var/run/imunify360/nats.tokenz/var/run/imunify360/nats.addrr�c�0�d|_d|_d|_dS)Nr)�_nc�_last_connect_attempt�_oversized_dropped�rOs r
rpzNATSGatewayAPI.__init__s�����%&��"�"#����r	c��tjdtj��}	t	|��5}|������}ddd��n#1swxYwY|r|Sn#t$rYnwxYwttjdttj
������}d|��S)zBRead NATS listen address from addr file, fall back to env/default.�I360_NATS_ADDR_PATHN�I360_NATS_PORTz
127.0.0.1:)r�r�r��DEFAULT_ADDR_PATH�open�read�strip�OSError�intrb�DEFAULT_PORT)�	addr_path�f�addr�ports    r
�
_read_addrzNATSGatewayAPI._read_addrs���I�!�>�#C�
�
�	�	��i���
(�A��v�v�x�x�~�~�'�'��
(�
(�
(�
(�
(�
(�
(�
(�
(�
(�
(����
(�
(�
(�
(��
���
���	�	�	��D�	������I�&��N�,G�(H�(H�I�I�
�
��#�D�"�"�"s4�A3�'A#�A3�#A'�'A3�*A'�+A3�3
B�?Bc��K�|j�|jjrdStstd���t	j��}||jz
}||jkrtd|j|z
d�d����||_|����d{V��|�	��}tjd|j��}	t|��5}|������}ddd��n#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)r^�connect_timeout�max_reconnect_attempts�error_cbzConnected to NATS at %szFailed to connect to NATS: )r��is_connected�	_has_natsrr��	monotonicr��MIN_RECONNECT_INTERVAL�_closer�r�r��DEFAULT_TOKEN_PATHr�r�r��nats�connect�CONNECT_TIMEOUTrJrHr��	Exception)rO�now�
since_lastr��
token_pathr�r^r_s        r
�_ensure_connectedz NATSGatewayAPI._ensure_connected+s
�����8��D�H�$9���F��	G�*�+E�F�F�F��n�����4�5�5�
���3�3�3�*�P��0�:�=�O�P�P�P���
�&)��"��k�k�m�m����������� � ���Y�5�t�7N�O�O�
�	��j�!�!�
)�Q��������(�(��
)�
)�
)�
)�
)�
)�
)�
)�
)�
)�
)����
)�
)�
)�
)�!�\� �$� � �� $� 4�'(�'����������D�H�
�K�K�1�4�8�8�8�8�8���	�	�	�*�1�a�1�1����
�����	���s=�E�'D�8E�D�E�D�AE�
E?�'E:�:E?r�rFNc
���K�|����d{V��d}	|j���}|D�]�\}}	tj|��}nC#tjtf$r*}t�d|��|dz
}Yd}~�Wd}~wwxYw|�	dd��}|j
|z}	tj||�
d��fg��}
d}|
�r|
���\}}
tj|�����}|
rd|
ind}	|�|	||�	���d{V��}�nj#t$t&f$�rU}t)|t&��r:|jt,krt/d
|	�d|
�d|����|�|jt0kr�t3|��}|�R|xjdz
c_d
}t�d|	|
t9|��|j||dd���Yd}~��-|
p%t;j|�����}tA|��D](\}}|�d|��}||d<|
�!||f���)t�d|	|
t9|��t9|����Yd}~���d}~wwxYwtEj
d��r't�#d|	|j$|j%��|
��|r"tMj'i|�d|i�tPd���|dz
}���dS#t.$rU}t�d|t9|��t9|��|z
|��tSd|��|���|�d}~wtT$r^}|�+���d{V��t�d|t9|��|��tSd|��|���|�d}~wwxYw)NrzSkipping malformed message: %sr&r[�UNKNOWNr�TzNats-Msg-Id)rQzsubject=z message_id=z: FzhDropping oversized NATS message: subject=%s message_id=%s size=%d oversized_total=%d error=%s preview=%r���.zKSplitting oversized NATS message: subject=%s message_id=%s size=%d parts=%dr�z"Published to %s, stream=%s, seq=%szagent-nats-sendingr�z<NATS stream full, %d/%d messages published, %d re-queued: %szStream at capacity: )�	publishedz,NATS publish failed after %d/%d messages: %szFailed to publish messages: ),r�r��	jetstreamr�r��JSONDecodeError�UnicodeDecodeErrorrH�warning�pop�NATS_SUBJECT_PREFIX�collections�dequerB�popleftr�r��publishrrr0r
�_JS_ERR_STREAM_FULLr"�_JS_ERR_MSG_TOO_LARGErDr�r�r3�hashlib�sha1�	hexdigest�	enumerate�appendrrI�stream�seqrr��_reporter_gen_natsrr�r�)rOr�r��jsr��	msg_bytesr=r_r[�subject�pending�fully_delivered�part�dedup_idr�rQ�ackrC�base_key�indexr@�	child_keys                      r
r�zNATSGatewayAPI.send_messagesPs+�����$�$�&�&�&�&�&�&�&�&�&��	�A	���#�#�%�%�B� (�c
�c
���9��!�Z�	�2�2�F�F���,�.@�A�����N�N�#C�Q�G�G�G���N�I��H�H�H�H��������� ���H�i�8�8���2�V�;��&�+��f�j�j��6�6�7�8����#'���B�%,�_�_�%6�%6�N�D�(�"�j��.�.�5�5�7�7�G�;C�M�}�h�7�7��G�7!�$&�J�J�#�W�g�%/�%�%����������1�-�@�3!�3!�3!�%�a��7�7�&� �z�-@�@�@�
'2�%C�w�%C�%C�3;�%C�%C�?@�%C�%C�'"�'"�()�!)� !�z�-B�B�B� %�!1�$�!7�!7��!�>�!�3�3�q�8�3�3�.3�O�"�L�L�!7�!(� (� #�G��� $� 7� !� '����
�
�
�
�%�H�H�H�H�$�I���W�(=�(=�(G�(G�(I�(I�!�,5�V�+<�+<�>�>�K�E�4�+3�(=�(=�e�(=�(=�I�1:�D��.�#�N�N�D�)�+<�=�=�=�=����>�#�$���L�L���K�K�
���!���������g3!����h�u�W�~�~�����@�#��J��G�	���{�B�R#���$�4�6�4�8�V�4�4�*�2�����
�Q��	�	�Gc
�c
��J�
	�
	�
	�
�N�N�N���H�
�
��H�
�
�	�)��
�
�
�+�*�q�*�*�#�����
������	�	�	��+�+�-�-���������N�N�>���H�
�
��	
�
�
�+�2�q�2�2�#�����
�����	���s��!L$�A�L$�B�- B�
L$�B�BL$�/E�
L$�J6�!B,J1�
L$�BJ1�+L$�1J6�6A,L$�$
O)�.AM>�>
O)�AO$�$O)c��K�|j�F	|j����d{V��n#t$rYnwxYwd|_dS#d|_wxYwdSrN)r��closer�r�s r
r�zNATSGatewayAPI._close�s������8��	
 �
�h�n�n�&�&�&�&�&�&�&�&�&�&���
�
�
���
���� ������4������� �s!�+�A�
8�A�8�A�	A
c��>K�|����d{V��dSrN)r�r�s r
rzNATSGatewayAPI.close�s,�����k�k�m�m���������r	)rrrr#r�r�r�r�r�r�rp�staticmethodr�r�r1r�r�rcr�r�rrr	r
r�r�s���������)���L�9��7���O���$�$�$�
�#�#��\�#�&#�#�#�JE�D��u�e�|�1D�,E�E�$�E�E�E�E�N � � �����r	r�)Cr�r�rr�r�r��urllib.errorr{r��nats.errors�nats.js.errorsr��errors�MaxPayloadErrorrr
rr�ImportErrorr��urllib.request�abcrr�loggingr�typingrr�r��defence360agent.api.serverrrrr� defence360agent.contracts.configr�"defence360agent.contracts.messagesrr�&defence360agent.internals.global_scoper�defence360agent.internals.iaidrr�2defence360agent.internals.message_status_publisherrr�!defence360agent.utils.async_utilsr�defence360agent.utils.jsonr rrHr�r	rrr"r2r<rDrJrLrer�r�rr	r
�<module>r+s���
�
�
�
�������������	�	�	�	�����������K�K�K����������I��;�6���G�N�+�M�M�������I�
�
�
�
�
�y�
�
�
������	������
��������#�#�#�#�#�#�#�#�����������������������������������2�1�1�1�1�1�E�E�E�E�E�E�E�E�4�4�4�4�4�4���������N�M�M�M�M�M�M�M�:�:�:�:�:�:�8�8�8�8�8�8�	��8�	�	���C�E�E���S�U�U��
����O�O�O�O�O�)�O�O�O�;�t�;�;�;�;�2�T�����"!�Y�!�4�!�!�!�!�$�$�$�$�$��c�$�$�$�428�28�28�28�28�'�28�28�28�j8�8�8�8�8�.�8�8�8�va�a�a�a�a�a�a�a�a�as�+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&not_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.py0000644000000000000000000004411600000000000015642 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.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()

# 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."""


def _split_largest_field(item: dict):
    """Split a single sub-message on its largest list/dict field, chosen by
    serialized byte size (not element count) so the heaviest field is the one
    that shrinks. Returns two sub-messages, or None when nothing inside can be
    split further (no list/dict field holds at least two elements)."""
    field = None
    largest = 0
    for key, value in item.items():
        if isinstance(value, (list, dict)) and len(value) > 1:
            size = estimate_size(value)
            if size > largest:
                field, largest = key, size
    if field is None:
        return None
    value = item[field]
    if isinstance(value, list):
        mid = len(value) // 2
        return [{**item, field: value[:mid]}, {**item, field: value[mid:]}]
    keys = list(value)
    mid = len(keys) // 2
    left = {k: value[k] for k in keys[:mid]}
    right = {k: value[k] for k in keys[mid:]}
    return [{**item, field: left}, {**item, field: right}]


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:]},
        ]
    if isinstance(items, list) and len(items) == 1:
        halves = _split_largest_field(items[0])
        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:
        # 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
        if "method" not in message:
            message["method"] = "INCIDENT_LIST"

        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()}")
            )


class NATSGatewayAPI:
    """Publishes messages to the embedded NATS server via localhost TCP.

    Connects to nats://127.0.0.1:<port> 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

    @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
                method = loaded.pop("method", "UNKNOWN")
                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"))]
                )
                fully_delivered = True
                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.
                            self._oversized_dropped += 1
                            fully_delivered = False
                            logger.error(
                                "Dropping oversized NATS message: subject=%s"
                                " message_id=%s size=%d oversized_total=%d"
                                " error=%s preview=%r",
                                subject,
                                dedup_id,
                                len(payload),
                                self._oversized_dropped,
                                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 status report per logical message, not per fragment: a
                # split message's fragments share the parent's reporter id, so
                # reporting each would inflate the delivery-tracking cardinality.
                # Skip the report when any fragment was dropped: the message did
                # not fully reach NATS, so tracking it as sent overstates
                # delivery. The drop is still counted and logged above.
                if fully_delivered:
                    publisher.report(
                        {**loaded, "method": method},
                        _reporter_gen_nats,
                        stage="agent-nats-sending",
                    )
                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  �

֦2�P��(��8�dZGd�d��Ze��ZdS)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�
MIDDLEWARE�MIDDLEWARE_EXCLUDE�MODULES_WITH_MODELS�MIGRATIONS_DIRS�MIGRATIONS_ATTACHED_DBS���Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/application/__init__.pyrrsB���������L��I��J������O� ���rrN)rr�apprrr�<module>rsH��3�3�!�!�!�!�!�!�!�!�(�k�m�m���rdefence360agent/application/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000223100000000000021520 0ustar  �

֦2�P��(��8�dZGd�d��Ze��ZdS)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�
MIDDLEWARE�MIDDLEWARE_EXCLUDE�MODULES_WITH_MODELS�MIGRATIONS_DIRS�MIGRATIONS_ATTACHED_DBS���Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/application/__init__.pyrrsB���������L��I��J������O� ���rrN)rr�apprrr�<module>rsH��3�3�!�!�!�!�!�!�!�!�(�k�m�m���rdefence360agent/application/__pycache__/determine_hosting_panel.cpython-311.opt-1.pyc0000644000000000000000000000572300000000000025617 0ustar  �

����a�����dZddlZddlmZddlmZdZdZdZdZ	ej
e��Zd	e
fd
�Zd�Zd�Zd
�Zd�Zd�ZdS)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_modulec�F�t��r&t|�d���}|���St��r&t|�d���}|���St��r&t|�d���}|���St��r&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.panel�defence360agentz.subsys.panels.no_cp.panel)�is_generic_panel_installedr�GenericPanel�is_plesk_installed�Plesk�is_cpanel_installed�cPanel�is_directadmin_installed�DirectAdmin�NoCP�NoControlPanel)r�modules  �h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/application/determine_hosting_panel.py�get_hosting_panelrs5��"�#�#���+�K�K�K�L�L���"�"�$�$�$�	�	�	���+�I�I�I�J�J���|�|�~�~��	�	�	�
��+�J�J�J�K�K���}�}����	!�	#�	#�	���<�<�<�
�
���!�!�#�#�#�	�)�	)�	)��+�I�I�I�J�J���{�{�}�}��
�k�E�E�E�
F�
F�F�� � �"�"�"�c�*�tt��S�N)�_is_panel_installed�CPANEL_FILE�rrrr3s���{�+�+�+rc�*�tt��Sr)r�DA_FILErrrrr7����w�'�'�'rc�*�tt��Sr)r�GP_FILErrrrr;rrc�*�tt��Sr)r�
PLESK_FILErrrr
r
?s���z�*�*�*rc�D�t|�����Sr)r�is_file)�
panel_files rrrCs���
���#�#�%�%�%r)�__doc__�logging�	importlibr�pathlibrrrr!r�	getLogger�__name__�logger�strrrrrr
rrrr�<module>r-s���������#�#�#�#�#�#�������(��
.��
�
�
6��	��	�8�	$�	$��#�3�#�#�#�#�D,�,�,�(�(�(�(�(�(�+�+�+�&�&�&�&�&rdefence360agent/application/__pycache__/determine_hosting_panel.cpython-311.pyc0000644000000000000000000000572300000000000024660 0ustar  �

����a�����dZddlZddlmZddlmZdZdZdZdZ	ej
e��Zd	e
fd
�Zd�Zd�Zd
�Zd�Zd�ZdS)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_modulec�F�t��r&t|�d���}|���St��r&t|�d���}|���St��r&t|�d���}|���St��r&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.panel�defence360agentz.subsys.panels.no_cp.panel)�is_generic_panel_installedr�GenericPanel�is_plesk_installed�Plesk�is_cpanel_installed�cPanel�is_directadmin_installed�DirectAdmin�NoCP�NoControlPanel)r�modules  �h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/application/determine_hosting_panel.py�get_hosting_panelrs5��"�#�#���+�K�K�K�L�L���"�"�$�$�$�	�	�	���+�I�I�I�J�J���|�|�~�~��	�	�	�
��+�J�J�J�K�K���}�}����	!�	#�	#�	���<�<�<�
�
���!�!�#�#�#�	�)�	)�	)��+�I�I�I�J�J���{�{�}�}��
�k�E�E�E�
F�
F�F�� � �"�"�"�c�*�tt��S�N)�_is_panel_installed�CPANEL_FILE�rrrr3s���{�+�+�+rc�*�tt��Sr)r�DA_FILErrrrr7����w�'�'�'rc�*�tt��Sr)r�GP_FILErrrrr;rrc�*�tt��Sr)r�
PLESK_FILErrrr
r
?s���z�*�*�*rc�D�t|�����Sr)r�is_file)�
panel_files rrrCs���
���#�#�%�%�%r)�__doc__�logging�	importlibr�pathlibrrrr!r�	getLogger�__name__�logger�strrrrrr
rrrr�<module>r-s���������#�#�#�#�#�#�������(��
.��
�
�
6��	��	�8�	$�	$��#�3�#�#�#�#�D,�,�,�(�(�(�(�(�(�+�+�+�&�&�&�&�&rdefence360agent/application/__pycache__/settings.cpython-311.opt-1.pyc0000644000000000000000000000610600000000000022565 0ustar  �

�̌��]K�	��dZddlZddlmZddlmZddlmZddlm	Z	ddl
mZddlm
Z
dd	lmZmZdd
lmZddlmZdd
lmZdefd�Zeeeddejdddf	d�ZdS)z"Set settings of application object�N)�Path)�files)�tags)�eula)�g)�simplification)�SchemaValidator�validate_middleware)�init_validator)�update_wp_rules_on_sites�)�app�
is_updatedc��DK�|rtj���d{V��dSdS)N)r�update)�indexrs  �Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/application/settings.py�update_eula_datars<�������k�m�m������������Fc	�*�tj�d��dkrdt_|t
_||||��\t
_t
_t
_	t
xj
tgz
c_
|rt
xj
|z
c_
tt�����jj}	t
xj|	dzgz
c_|rt
xj|z
c_|rt
xj|z
c_|��t%j��|s`t$j�t$jt.��t$j�t$jt2��dSdS)N�DEBUG�trueT�
migrations)�os�environ�getrrr�SCHEMA_PATHS�	VALIDATOR�
MIDDLEWARE�MIDDLEWARE_EXCLUDE�MODULES_WITH_MODELSrr�__file__�resolve�parent�MIGRATIONS_DIRS�MIGRATIONS_ATTACHED_DBSr�	configure�Index�add_hook�EULAr�WP_RULESr)
r�
validator_cls�validate_middleware_wrap�schema_paths�models_modules�set_sentry_tags�migration_dirs�migrations_attached_dbs�resident�av_paths
          rr'r'sg��
�z�~�~�g���&�(�(����#�C��<J�N��/��=�=�9�C�M�3�>�3�#9�����/�/����2����>�1����8�n�n�$�$�&�&�-�4�G����G�l�2�3�3����.����~�-����?��#�#�'>�>�#�#��O����	�O�����G�
����U�Z�)9�:�:�:�
����U�^�-E�F�F�F�F�F�G�Gr)�__doc__r�pathlibr�defence360agentr�defence360agent.applicationr�defence360agent.contractsr�&defence360agent.internals.global_scoper�defence360agent.modelr�"defence360agent.rpc_tools.validater	r
�!defence360agent.simple_rpc.schemar� defence360agent.wordpress.pluginr�r�boolr�fillr'�rr�<module>rCs>��(�(�	�	�	�	�������!�!�!�!�!�!�,�,�,�,�,�,�*�*�*�*�*�*�4�4�4�4�4�4�0�0�0�0�0�0���������=�<�<�<�<�<�E�E�E�E�E�E��������d�����"�!�0����I�� �
�G�G�G�G�G�Grdefence360agent/application/__pycache__/settings.cpython-311.pyc0000644000000000000000000000610600000000000021626 0ustar  �

�̌��]K�	��dZddlZddlmZddlmZddlmZddlm	Z	ddl
mZddlm
Z
dd	lmZmZdd
lmZddlmZdd
lmZdefd�Zeeeddejdddf	d�ZdS)z"Set settings of application object�N)�Path)�files)�tags)�eula)�g)�simplification)�SchemaValidator�validate_middleware)�init_validator)�update_wp_rules_on_sites�)�app�
is_updatedc��DK�|rtj���d{V��dSdS)N)r�update)�indexrs  �Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/application/settings.py�update_eula_datars<�������k�m�m������������Fc	�*�tj�d��dkrdt_|t
_||||��\t
_t
_t
_	t
xj
tgz
c_
|rt
xj
|z
c_
tt�����jj}	t
xj|	dzgz
c_|rt
xj|z
c_|rt
xj|z
c_|��t%j��|s`t$j�t$jt.��t$j�t$jt2��dSdS)N�DEBUG�trueT�
migrations)�os�environ�getrrr�SCHEMA_PATHS�	VALIDATOR�
MIDDLEWARE�MIDDLEWARE_EXCLUDE�MODULES_WITH_MODELSrr�__file__�resolve�parent�MIGRATIONS_DIRS�MIGRATIONS_ATTACHED_DBSr�	configure�Index�add_hook�EULAr�WP_RULESr)
r�
validator_cls�validate_middleware_wrap�schema_paths�models_modules�set_sentry_tags�migration_dirs�migrations_attached_dbs�resident�av_paths
          rr'r'sg��
�z�~�~�g���&�(�(����#�C��<J�N��/��=�=�9�C�M�3�>�3�#9�����/�/����2����>�1����8�n�n�$�$�&�&�-�4�G����G�l�2�3�3����.����~�-����?��#�#�'>�>�#�#��O����	�O�����G�
����U�Z�)9�:�:�:�
����U�^�-E�F�F�F�F�F�G�Gr)�__doc__r�pathlibr�defence360agentr�defence360agent.applicationr�defence360agent.contractsr�&defence360agent.internals.global_scoper�defence360agent.modelr�"defence360agent.rpc_tools.validater	r
�!defence360agent.simple_rpc.schemar� defence360agent.wordpress.pluginr�r�boolr�fillr'�rr�<module>rCs>��(�(�	�	�	�	�������!�!�!�!�!�!�,�,�,�,�,�,�*�*�*�*�*�*�4�4�4�4�4�4�0�0�0�0�0�0���������=�<�<�<�<�<�E�E�E�E�E�E��������d�����"�!�0����I�� �
�G�G�G�G�G�Grdefence360agent/application/__pycache__/tags.cpython-311.opt-1.pyc0000644000000000000000000001361600000000000021667 0ustar  �

���a*7�����ddlZddlZddlZddlmZddlmZddlmZ	ddl
mZddlm
Z
ddlmZddlmZmZdd	lmZeje��Zed
��Zd�Zd�Zd
�Zddd�ZdS)�N)�Path)�sentry)�Core)�
LicenseCLN)�IndependentAgentIDAPI)�
hosting_panel)�stub_unexpected_error�is_root_user)�	IPEchoAPIz/var/imunify360/.sentry_tagsc�r�t�tjtj����dS�N)�SENTRY_TAGS_CACHE_PATH�
write_text�json�dumpsr�_TAGS���U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/application/tags.py�dump_sentry_tagsrs(���%�%�d�j���&>�&>�?�?�?�?�?rc�f�t���r�	tjt�����t
_dS#tjtf$r+}t�
dt|��Yd}~nd}~wt$rYnwxYwtd���dS)Nz%Sentry cache file %s is malformed: %sF)�dump)
r�existsr�loads�	read_textrr�JSONDecodeError�FileNotFoundError�logger�warning�PermissionError�fill)�es r�cached_fillr#s����$�$�&�&��
	��:�&<�&F�&F�&H�&H�I�I�F�L��F���$�&7�8�	�	�	��N�N�7�&��
�
�
�
�
�
�
�
�����
�	�	�	��D�	�����e������s�5A�B�(!B�
B�Bc����d}d}dtfd��dtfd��t��fd���}|��}tj|��|dkrtj|��dStj|��dS)N�PRIMARY_IDS�CSF_COOP�returnc��	tjddgdtj���dS#ttjtjf$rYdSwxYw)Nzfirewall-cmdz--state�)�timeout�stderrTF)�
subprocess�check_output�DEVNULL�IOError�CalledProcessError�TimeoutExpiredrrr�_is_firewalld_runningz3_set_additional_tags.<locals>._is_firewalld_running.sn��	��#���+��!�)�
�
�
�
�
�4����)��%�
�	�	�	�
�5�5�	���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.<locals>._is_csf_running=ss��	��)� �*�-�j�6H����C�C��"�:�#@�A�	�	�	��5�5�	����%�S�0�
�>�c�I�	
s�"%�?�?c�8�����rdS���rdSdS)N�csf�	firewalld�iptablesr)r5r2s��r�_get_current_firewallz3_set_additional_tags.<locals>._get_current_firewallHs3����?���	��5� � �"�"�	��;��zrr7)�boolr	r�set_firewall_type�set_strategy)�PRIMARY_IDS_STRATEGY�CSF_COOP_STRATEGYr:�fwr5r2s    @@r�_set_additional_tagsrA*s�����(��"��
�4�
�
�
�
�	
�T�	
�	
�	
�	
����������
�	�	 �	 �B�
��R� � � �	�U�{�{���-�.�.�.�.�.���0�1�1�1�1�1rTr'c���td���}tjtj��tjtj��tjtj��tj	tj����t��sdStj
tj����tjt!j����tjtt&j������tj|����tj��t/��|rt1��dSdS)Nc�2�tj��jSr
)r�HostingPanel�NAMErrr�_get_hosting_panelz fill.<locals>._get_hosting_panelZs���)�+�+�0�0r)r	r�set_av_version�Config�
AV_VERSION�set_core_version�CORE_VERSION�set_version�VERSION�set_product_namer�get_product_namer
�
set_server_id�
get_server_id�set_iaidr�get_iaid�set_ipr�	server_ip�set_hosting_panel�set_test_envrAr)rrFs  rr!r!Ys7���1�1���1���&�+�,�,�,�
��F�/�0�0�0�
��v�~�&�&�&�
��J�7�9�9�:�:�:��>�>����
���1�3�3�4�4�4�
�O�)�2�4�4�5�5�5�
�M�<�'�	�(;�<�<�>�>�?�?�?�
��/�/�1�1�2�2�2�
������������������r)T)r'N)r�loggingr,�pathlibr�defence360agent.contractsr� defence360agent.contracts.configrrH�!defence360agent.contracts.licenser�defence360agent.internals.iaidr�defence360agent.subsys.panelsr�defence360agent.utilsr	r
�defence360agent.utils.ipechor�	getLogger�__name__rrrr#rAr!rrr�<module>rcsB��������������������,�,�,�,�,�,�;�;�;�;�;�;�8�8�8�8�8�8�@�@�@�@�@�@�7�7�7�7�7�7���������3�2�2�2�2�2�	��	�8�	$�	$����<�=�=��@�@�@�
�
�
� ,2�,2�,2�^������rdefence360agent/application/__pycache__/tags.cpython-311.pyc0000644000000000000000000001361600000000000020730 0ustar  �

���a*7�����ddlZddlZddlZddlmZddlmZddlmZ	ddl
mZddlm
Z
ddlmZddlmZmZdd	lmZeje��Zed
��Zd�Zd�Zd
�Zddd�ZdS)�N)�Path)�sentry)�Core)�
LicenseCLN)�IndependentAgentIDAPI)�
hosting_panel)�stub_unexpected_error�is_root_user)�	IPEchoAPIz/var/imunify360/.sentry_tagsc�r�t�tjtj����dS�N)�SENTRY_TAGS_CACHE_PATH�
write_text�json�dumpsr�_TAGS���U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/application/tags.py�dump_sentry_tagsrs(���%�%�d�j���&>�&>�?�?�?�?�?rc�f�t���r�	tjt�����t
_dS#tjtf$r+}t�
dt|��Yd}~nd}~wt$rYnwxYwtd���dS)Nz%Sentry cache file %s is malformed: %sF)�dump)
r�existsr�loads�	read_textrr�JSONDecodeError�FileNotFoundError�logger�warning�PermissionError�fill)�es r�cached_fillr#s����$�$�&�&��
	��:�&<�&F�&F�&H�&H�I�I�F�L��F���$�&7�8�	�	�	��N�N�7�&��
�
�
�
�
�
�
�
�����
�	�	�	��D�	�����e������s�5A�B�(!B�
B�Bc����d}d}dtfd��dtfd��t��fd���}|��}tj|��|dkrtj|��dStj|��dS)N�PRIMARY_IDS�CSF_COOP�returnc��	tjddgdtj���dS#ttjtjf$rYdSwxYw)Nzfirewall-cmdz--state�)�timeout�stderrTF)�
subprocess�check_output�DEVNULL�IOError�CalledProcessError�TimeoutExpiredrrr�_is_firewalld_runningz3_set_additional_tags.<locals>._is_firewalld_running.sn��	��#���+��!�)�
�
�
�
�
�4����)��%�
�	�	�	�
�5�5�	���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.<locals>._is_csf_running=ss��	��)� �*�-�j�6H����C�C��"�:�#@�A�	�	�	��5�5�	����%�S�0�
�>�c�I�	
s�"%�?�?c�8�����rdS���rdSdS)N�csf�	firewalld�iptablesr)r5r2s��r�_get_current_firewallz3_set_additional_tags.<locals>._get_current_firewallHs3����?���	��5� � �"�"�	��;��zrr7)�boolr	r�set_firewall_type�set_strategy)�PRIMARY_IDS_STRATEGY�CSF_COOP_STRATEGYr:�fwr5r2s    @@r�_set_additional_tagsrA*s�����(��"��
�4�
�
�
�
�	
�T�	
�	
�	
�	
����������
�	�	 �	 �B�
��R� � � �	�U�{�{���-�.�.�.�.�.���0�1�1�1�1�1rTr'c���td���}tjtj��tjtj��tjtj��tj	tj����t��sdStj
tj����tjt!j����tjtt&j������tj|����tj��t/��|rt1��dSdS)Nc�2�tj��jSr
)r�HostingPanel�NAMErrr�_get_hosting_panelz fill.<locals>._get_hosting_panelZs���)�+�+�0�0r)r	r�set_av_version�Config�
AV_VERSION�set_core_version�CORE_VERSION�set_version�VERSION�set_product_namer�get_product_namer
�
set_server_id�
get_server_id�set_iaidr�get_iaid�set_ipr�	server_ip�set_hosting_panel�set_test_envrAr)rrFs  rr!r!Ys7���1�1���1���&�+�,�,�,�
��F�/�0�0�0�
��v�~�&�&�&�
��J�7�9�9�:�:�:��>�>����
���1�3�3�4�4�4�
�O�)�2�4�4�5�5�5�
�M�<�'�	�(;�<�<�>�>�?�?�?�
��/�/�1�1�2�2�2�
������������������r)T)r'N)r�loggingr,�pathlibr�defence360agent.contractsr� defence360agent.contracts.configrrH�!defence360agent.contracts.licenser�defence360agent.internals.iaidr�defence360agent.subsys.panelsr�defence360agent.utilsr	r
�defence360agent.utils.ipechor�	getLogger�__name__rrrr#rAr!rrr�<module>rcsB��������������������,�,�,�,�,�,�;�;�;�;�;�;�8�8�8�8�8�8�@�@�@�@�@�@�7�7�7�7�7�7���������3�2�2�2�2�2�	��	�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  �

s�����s���dS)N�r��W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/__init__.py�<module>rs���rdefence360agent/contracts/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000030400000000000021214 0ustar  �

s�����s���dS)N�r��W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/__init__.py�<module>rs���rdefence360agent/contracts/__pycache__/config.cpython-311.opt-1.pyc0000644000000000000000000020714400000000000021674 0ustar  �

�~6O9K����
�UdZddlZddlZddlZddlmZddlmZmZddl	m
Z
ddlmZddl
m
Z
mZddlmZdd	lmZdd
lmZmZmZmZmZmZmZmZmZmZmZddlm 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/m0Z0e0j1ddd���Z2ej3e4��Z5e0j6d��Z7dZ8dZ9e0j1dde2���Z:dZ;eej<�1dd����Z=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_fd0�Z`ej<fd,e^d-ead.ed/eafd1�Zbd2�Zcd3�Zdd4�Ze	d�d5e^dzd/eee^dzffd6�Zfd7�ZgGd8�d9��ZhGd:�d;��Zid<d=�d>�Zjejkd"�?��d@���Zlejkd"�?��dA���ZmdBdCdDdd<dE�dDdd<dE�dFd<dG�dFd<dG�dH�idI�iZnGdJ�dKeo��ZpGdL�dM��ZqGdN�dOe��ZrGdP�dQere��ZsGdR�dSee.��ZtGdT�dU��ZuGdV�dWeret�X��ZvGdY�dZev��ZwGd[�d\eret�X��Zx	d�d5eee^e_fd]ee^d/erfd^�Zyd/eafd_�Zzej{eve&eqj|eReqj}�`���a��Z~ej{eve&eqjeReqj��`���a��Z�Gdb�dcev��Z�Gdd�de��Z�Gdf�dge���Z�Gdh�die���Z�Gdj�dke���Z�Gdl�dme���Z�Gdn�doe ��Z�Gdp�dq��Z�Gdr�ds��Z�Gdt�du��Z�Gdv�dw��Z�Gdx�dy��Z�Gdz�d{��Z�Gd|�d}��Z�Gd~�d��Z�Gd��d���Z�Gd��d���Z�Gd��d���Z�Gd��d���Z�Gd��d���Z�d��Z�Gd��d���Z�Gd��d���Z�Gd��d���Z�e
d�e�j�����Z�e
e^e�d�<Gd��d���Z�Gd��d���Z�Gd��d�ev��Z�Gd��d���Z�Gd��d���Z�Gd��d���Z�Gd��d���Z�d��Z�Gd��d���Z�Gd��d�eo��Z�Gd��d���Z�eyZ�Gd��d���Z�Gd��d�e^e��Z�d�dCdFd�dG�dDdd<eTeUeXeWe[eVe\eYeZg	d��d��idI�iZ�Gd��d�ev��Z�Gd��d���Z�d��Z�Gd��d���Z�d5e^d/eafd��Z�d5e^d/e
fd��Z�d5e^d/eafd��Z�Gd��d���Z�Gd��d���Z�dS)�z5
All the config settings for defence360 in one place
�N)�abstractmethod)�bisect_left�bisect_right)�
ContextVar)�deepcopy)�datetime�	timedelta)�Enum)�Path)�Any�Callable�Dict�List�Mapping�Optional�Protocol�Sequence�Tuple�Union�
_ProtocolMeta)�	Validator)�CachedConfigReader�ConfigError�ConfigReader�UserConfigReader�WriteOnlyConfigReader)�config_cleanup)�__version__)�	Singleton�dict_deep_update�importerz
imav._versionr)�module�name�default�im360z'/var/imunify360/myimunify-freemium.flag�
MY_IMUNIFYzim360._versionz../.�IM360_CONFIG_SCHEMA_PATHz4/opt/imunify360/venv/share/imunify360/config_schema/)�notify�cleanup)�none�day�week�month�ii��FULL�MINIMAL���DENY�ALLOWa4############################################################################
# DO NOT MODIFY THIS FILE!!!                                               #
# USE /etc/sysconfig/imunify360/imunify360.config.d/ TO OVERRIDE DEFAULTS  #
############################################################################
a�############################################################################
# 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)�acronis�r1soft�
clusterlogics�sample)�
cloudlinux�cloudlinux_on_premisez./var/run/defence360agent/generic_sensor.sock.2�varr$�env�returnc��	t||��S#t$r|cYSt$r(}td�|����|�d}~wwxYw)Nz{}: integer required)�int�KeyError�
ValueError�format)r>r$r?�es    �U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/config.py�int_from_envvarrHqsu��D��3�s�8�}�}������������D�D�D��/�6�6�s�;�;�<�<�!�C�����D���s��A�	A�#A�Ac���d}d}	||}|���}||vrdS||vrdStd�|||z�����#t$r|cYSwxYw)N)�true�t�yes�y�1)�false�f�no�n�0TFz{}: should be one of {})�lowerrDrErC)r>r$r?�	TRUE_VALS�
FALSE_VALS�vals      rG�bool_from_envvarrXzs���/�I�/�J�
��#�h���i�i�k�k���)����4��*����5��%�,�,�S�)�j�2H�I�I�
�
�	
�������������s�A�A#�"A#c��tj�tj�t��|��S�N)�os�path�join�dirname�__file__��relpaths rG�
_self_rel2absrb�s&��
�7�<�<������1�1�7�;�;�;�c��tj�tj�t	t
����|��SrZ)r[r\r]r^rb�
AGENT_CONFr`s rG�conf_rel2absrf�s.��
�7�<�<�����
�j�(A�(A�B�B�G�L�L�Lrcc���	t|d��5}|������cddd��S#1swxYwYdS#t$rYdSwxYw)z1Returns content for existing file, otherwise None�rN)�open�read�strip�OSError)r\rPs  rG�_slurp_filerm�s����
�$��_�_�	$���6�6�8�8�>�>�#�#�	$�	$�	$�	$�	$�	$�	$�	$�	$�	$�	$�	$����	$�	$�	$�	$�	$�	$�������t�t����s3�A�&A�A�A	�	A�A	�
A�
A �A �usernamec�V�t|������}|�|��}|�|�|��}|�||fSt�d||��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)�
ConfigFile�config_to_dict�get�logger�debug�UserType�ROOT)�section�optionrn�user_config�user_section�
user_value�root_configs       rG�choose_value_from_configr~�s����h�/�/�/�>�>�@�@�K��?�?�7�+�+�L���!�%�%�f�-�-�
��!��x�'�'�
�L�L�5�w��G�G�G��,�,�-�-�/�/�K��w���'���6�6rcc�J�tj�t��S)zG
    Just checks if this is server with MyImunify Freemium license
    )r[r\�exists�FREEMIUM_FEATURE_FLAG�rcrG�is_mi_freemium_licenser��s���7�>�>�/�0�0�0rcc��eZdZdd�Zd�ZdS)�
FromConfigNc�>�||_||_||_d|_dSrZ)rxry�_config_cls�_config_instance)�selfrxry�
config_clss    rG�__init__zFromConfig.__init__�s&��������%��� $����rcc���|j�4|j�t��|_n|���|_|j���|j}|j�
||jS|SrZ)r�r�rqrrrxry)r��instance�owner�
section_values    rG�__get__zFromConfig.__get__�sm��� �(���'�(2����%�%�(,�(8�(8�(:�(:��%��-�<�<�>�>�t�|�L�
��;�"� ���-�-��rc�NN)�__name__�
__module__�__qualname__r�r�r�rcrGr�r��s7������%�%�%�%�
�
�
�
�
rcr�c�8�eZdZed��Zedd�d�Zd�ZdS)�FromFlagFile�/var/imunify360.��coercer$c�0�||_||_||_dSrZ)r#r�r$)r�r#r�r$s    rGr�zFromFlagFile.__init__�s����	��������rcc��|j|jz}|���r.|�|���p|j��SdSrZ)�LOCATIONr#r�r��	read_textr$)r�r�r�r\s    rGr�zFromFlagFile.__get__�sP���}�t�y�(���;�;�=�=�	A��;�;�t�~�~�/�/�?�4�<�@�@�@�	A�	ArcN)r�r�r�rr��boolr�r�r�rcrGr�r��sV�������t�%�&�&�H�'+�S������
A�A�A�A�Arcr�T��rootc��|rdnd}i}tjtg��D]0}t||d���}|��}t	||d����1|S)N�get_root_config�get_non_root_configc��iSrZr�r�rcrG�<lambda>z1_get_combined_validation_schema.<locals>.<lambda>�s���rcF)�allow_overwrite)r!�iter_modules�CONFIG_VALIDATORS_DIR_PATH�getattrr )r��	func_name�combined_schemar"�
get_schema�schemas      rG�_get_combined_validation_schemar��st��%)�D�!�!�/D�I��O��'�)C�(D�E�E�I�I���V�Y�
�
�;�;�
��������&�%�H�H�H�H�H��rc)�maxsizec��t��SrZ�r�r�rcrG�config_schema_rootr��s��*�,�,�,rcc�"�td���S)NFr�r�r�rcrG�config_schema_non_rootr��s��*��6�6�6�6rc�CUSTOM_BILLING�dict�string)�typer$�nullable�boolean)r�r$)�upgrade_url�upgrade_url_360�billing_notifications�
ip_license)r�r�r$c��eZdZdS)�ConfigValidationErrorN)r�r�r�r�rcrGr�r�s�������Drcr�c��eZdZdZesdezndZeZeZ	e
Zej
�dd��ZdZdZdZd	Zd
ZdZdZdZd
Zej�ed��ZdZej�ee��Zej�ee��ZdZdZ ej�ed��Z!dZ"dZ#dZ$dZ%dZ&esdndZ'dZ(e)d��Z*dZ+dS)�Core�
imunify360z%s agentzimunify antivirus�IMUNIFY360_API_URLzhttps://api.imunify360.com�
z.el9�z/var/imunify360/tmpzimunify360-merged.configz&imunify360-merged-nonprivileged.configzimunify360.configz/etc/imunify360rz�/etc/sysconfig/imunify360i�i�zimunify360.config.dz.imunify360.backup_configz
hooks.yamlzcustom_billing.configz/var/imunify360/hookszimunify360-agentzimunify-antiviruszunified-access-logger.confz#/etc/sysconfig/imunify360/.go_agent�<N),r�r�r��PRODUCT�ANTIVIRUS_MODE�NAME�
av_version�
AV_VERSION�core_version�CORE_VERSION�_version�VERSIONr[�environrs�API_BASE_URL�DEFAULT_SOCKET_TIMEOUT�DIST�
FILE_UMASK�TMPDIR�MERGED_CONFIG_FILE_NAME�%MERGED_NONPRIVILEGED_CONFIG_FILE_NAME�USER_CONFIG_FILE_NAME�LOCAL_CONFIG_FILE_NAME�
CONFIG_DIRr\r]�USER_CONFDIR�GLOBAL_CONFDIR�MERGED_CONFIG_FILE_PATH�%MERGED_NONPRIVILEGED_CONFIG_FILE_PATH�MERGED_CONFIG_FILE_PERMISSION�+MERGED_NONPRIVILEGED_CONFIG_FILE_PERMISSION�LOCAL_CONFIG_FILE_PATH�
CONFIG_D_NAME�BACKUP_CONFIGFILENAME�HOOKS_CONFIGFILENAME�CUSTOM_BILLING_CONFIGFILENAME�INBOX_HOOKS_DIR�SVC_NAME�$UNIFIED_ACCESS_LOGGER_CONFIGFILENAMEr�GO_FLAG_FILE�%SIGNAL_HANDLER_MIGRATION_TIMEOUT_SECSr�rcrGr�r�	sV�������G�'5�N�:����;N�D��J��L��G��:�>�>��:���L� ���D��J�
"�F�8��0�*�0��0��"�J��7�<�<�
�M�:�:�L�0�N� �g�l�l��/����-/�G�L�L��=�-�-�)�%*�!�27�/��W�\�\�.�:M�N�N��)�M�7��'��$;�!�-�O�#1�I���6I�
�,H�(��4�=�>�>�L�,.�)�)�)rcr�c� �eZdZe	ddededefd���Ze				ddededed	ed
eddfd���Zedd
���Z	e	dded
edefd���Z
edeedefd���Z
dedeedefd�Zdedeededdfd�ZdS)�IConfigTF�	normalize�
force_readr@c��t�rZ��NotImplementedError�r�r�r�s   rGrrzIConfig.config_to_dict7�
��"�!rc�data�validate�	overwrite�without_defaultsNc��t�rZr��r�r�r�r�r�r�s      rG�dict_to_configzIConfig.dict_to_config=s
��"�!rcc��t�rZr��r�s rGr�zIConfig.validateH���!�!rc�configc��t�rZr��r�rr�s   rGr�zIConfig.normalizeLr�rc�	timestampc��t�rZr��r�rs  rG�modified_sincezIConfig.modified_sinceRr�rcrxryc��|r;|����|i���|��SdSrZ)rrrs)r�rxrys   rGrszIConfig.getVs@���	F��&�&�(�(�,�,�W�b�9�9�=�=�f�E�E�E��trc�valuec�@�|r|�|||ii��dSdSrZ)r�)r�rxryrs    rG�setzIConfig.set[s7���	<�����6�5�/� :�;�;�;�;�;�	<�	<rc�TF�TTFF�r@N�F)r�r�r�rr�r�rrrr�r�r�r�floatr�strrrsr
r�rcrGr�r�6s��������9>�"�"��"�26�"�	
�"�"�"��^�"�
����!&�
"�"��"��"��	"�
�"��
"�
�"�"�"��^�"��"�"�"��^�"��8=�"�"��"�15�"�	
�"�"�"��^�"�
�"����"�D�"�"�"��^�"��3����
��#�����
<�3�<���
�<�c�<�d�<�<�<�<�<�<rcr�c��eZdZUeed<dS)�IConfigFiler\N)r�r�r�r�__annotations__r�rcrGrr`s�������

�I�I�I�I�Ircrc��eZdZdZdS)�ProtocolSingletonze
    Needed to avoid metaclass conflict when implementing protocols that are
    also Singletons
    N)r�r�r��__doc__r�rcrGrrds���������rcrc��eZdZd�Zededefd���Zededefd���Z	ededefd���Z
dededefd	�Zd
S)�
Normalizerc��d|_i|_t�|��|_|�|j��|_dSrZ)�_config�_normalized_config�ConfigsValidator�get_validation_schema�_schema�_get_schema_without_defaults�_schema_without_defaults)r��validation_schemas  rGr�zNormalizer.__init__lsP��*.���(*���'�=�=��
�
���)-�(I�(I��L�)
�)
��%�%�%rc�_dictr@c�D���fd�|���D��S)Nc�x��i|]6\}}|dv�	|t|t��r��|��n|��7S))r$�default_setter)�
isinstancer�r)�.0�keyr�clss   �rG�
<dictcomp>z;Normalizer._get_schema_without_defaults.<locals>.<dictcomp>xs]���
�
�
���U��7�7�7�	
��%��&�&���1�1�%�8�8�8��7�7�7rc��items)r)r"s` rGrz'Normalizer._get_schema_without_defaultsvs8���
�
�
�
�$�k�k�m�m�	
�
�
�	
rcrc���i}|���D]W\}}t|t��r6|���D] \}}|�||�|i��|<�!�P|r|||<�X|SrZ)r,r&r��
setdefault)r�
new_configrx�optionsryrs      rG�remove_nullzNormalizer.remove_null�s���:<�
� &�����		.�		.��G�W��'�4�(�(�
.�%,�]�]�_�_�K�K�M�F�E��(�EJ�
�-�-�g�r�:�:�6�B��K��
.�'.�
�7�#���rcc��t|��}|�|��}|jrt|j���|�td|�����|S)NzCerberus returned None for )�ConfigValidator�
normalized�errorsr�)rr��	validatorr4s    rG�_normalize_with_schemaz!Normalizer._normalize_with_schema�sa��#�F�+�+�	�%.�%9�%9�&�%A�%A�
���	:�'�	�(8�9�9�9���'�(N�f�(N�(N�O�O�O��rcr�c��|r|jn|j}|r+|�|��}|�||��S||jkr|jS|�||��}||_||_|jSrZ)r rr1r7rr)r�rr�r�r4s     rGr�zNormalizer.normalize�s���-=�O�D�)�)�4�<�	��	?��%�%�f�-�-�F��.�.�v�v�>�>�>��T�\�!�!��*�*��0�0���@�@�
����",����&�&rcN)
r�r�r�r��classmethodrr�r�staticmethodr1r7r�r�r�rcrGrrks�������
�
�
��
��
�T�
�
�
��[�
���G�������\����w��4�����\��
'��
'�4�
'�D�
'�
'�
'�
'�
'�
'rcrc
����eZdZdZddddddd�dededeeegeffded	e	d
e
f�fd�Zd�Zddede
fd�Zdde	de
fd�Z				d dede	de	de	de	ddfd�Zd�Zd�Zd�Zdeede	fd�Z�xZS)!�Config�NT)r\�
config_readerr!�
disclaimer�cached�permissionsr\r>r!r?r@rAc���t�����|s|sJ�|rtnt}|p|||p|j|���|_|jj|_|pt|_t|j��|_
dS)N)r?rA)�superr�rr�
DISCLAIMER�_config_readerr\r�r!r�_normalizer)	r�r\r>r!r?r@rA�config_reader_cls�	__class__s	        �rGr�zConfig.__init__�s����	���������$�}�$�$�$�28�J�.�.�l��+�
�/@�/@��!�4�T�_�#�0
�0
�0
���
�'�,��	�!2�!H�6H���%�d�&<�=�=����rcc�Z�d�|jj|j|j���S)NzW<{classname}(config_reader={config_reader!r}, validation_schema={validation_schema!r})>)�	classnamer>r!)rErHr�rEr!r�s rG�__repr__zConfig.__repr__�s6��
��&��n�1��-�"�4��
�
�		
rcFrr@c�8�|j�||��SrZ)rFr�rs   rGr�zConfig.normalize�s����)�)�&�2B�C�C�Crcr�c��|j�|���}|r|�|��}t|��S)zr
        Converts config file to dict

        :return dict: dictionary (key (section) / value (options))
        )r�)rE�read_config_filer�r)r�r�r�rs    rGrrzConfig.config_to_dict�sD���$�5�5��5�L�L���	,��^�^�F�+�+�F�����rcr�r�r�r�r�c�r�|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
        )r�r�r�r�N)�_dict_to_config_overwrite�_dict_to_configr�s      rGr�zConfig.dict_to_config�st��(�
	��*�*��!�#�!1�	
+�
�
�
�
�
�
� � ��!�#�!1�	
!�
�
�
�
�
rcc��|r t�||j��|r|�||���}|j�|��dS�N�r�)rr�r!r�rE�write_config_file)r�r�r�r�r�s     rGrPz Config._dict_to_config_overwritesb���	D��%�%�d�D�,B�C�C�C��	K��>�>�$�9I�>�J�J�D���-�-�d�3�3�3�3�3rcc� �t|j�����}t||��rW|r t�||j��|r|�||���}|j�|��dSdSrS)	rrErNr rr�r!r�rU)r�r�r�r�r�rs      rGrQzConfig._dict_to_config	s����$�-�>�>�@�@�A�A���F�D�)�)�	:��
J� �)�)�&�$�2H�I�I�I��
�����-=�(����
��1�1�&�9�9�9�9�9�	:�	:rcc��	|j�d���}nB#t$r5}d}t�d||��t||i��|�d}~wwxYw	t�||j��dS#t$r5}d}t�d||��t||i��|�d}~wwxYw)z/
        :raises ConfigsValidatorError
        F)�
ignore_errorszError during config validationz%s: %sNz+Imunify360 config does not match the scheme)
rErNrrt�error�ConfigsValidatorErrorrr�r!r�)r��config_dictrF�messages    rGr�zConfig.validates���	@��-�>�>�#�?���K�K���	@�	@�	@�6�G��L�L��7�A�.�.�.�'��w��8�8�a�?�����	@����
	@��%�%�k�4�3I�J�J�J�J�J��$�	@�	@�	@�C�G��L�L��7�A�.�.�.�'��w��8�8�a�?�����	@���s,��
A�0A�A�! B�
C�
0B=�=Crc�6�|j�|��SrZ)rErrs  rGrzConfig.modified_since(����"�1�1�)�<�<�<rcrrr)r�r�r�rDrrrrr
r�rBr�rKr�r�rrr�rPrQr�rrr�
__classcell__�rHs@rGr<r<�s���������J��&*�CG����>�>�>��	>�
$�>�!��(�2�w�;�*?�!?�@�
>��>��>��>�>�>�>�>�>�.	
�	
�	
�D�D��D�D�D�D�D�D�
 �
 ��
 �$�
 �
 �
 �
 ����!&�
!�!��!��!��	!�
�!��
!�
�!�!�!�!�F4�4�4�	:�	:�	:�@�@�@�(=����=�D�=�=�=�=�=�=�=�=rcr<)�	metaclassc���eZdZ�fd�Z�xZS)�
UserConfigc����||_tj�tj|tj��}t���|t||��t���dS)N)r\r>r!)rnr[r\r]r�r�r�rCr�rr�)r�rnr\rHs   �rGr�zUserConfig.__init__-sl��� ��
��w�|�|���x��)C�
�
��	������*�4��:�:�4�	�	
�	
�	
�	
�	
rc�r�r�r�r�r_r`s@rGrcrc,s8�������	
�	
�	
�	
�	
�	
�	
�	
�	
rcrcc
���eZdZdddd�dededef�fd�Z	dd	ed
edefd�Z				dd
eded	edededdfd�Z	dd�Z
	ddededefd�Zdee
defd�Z�xZS)�SystemConfigN)�local_config�
merged_config�nonpriv_merged_configrhrirjc����t�����|p
t��|_|p
t	��|_|p
t
��|_dSrZ)rCr��LocalConfig�
_local_config�MergedConfig�_merged_config�MergedNonPrivilegedConfig�_nonpriv_merged_config)r�rhrirjrHs    �rGr�zSystemConfig.__init__:sZ���	��������)�:�[�]�]���+�=�|�~�~���!�@�%>�%@�%@�	
�#�#�#rcTFr�r�r@c�:�|j�||���S)N�r�r�)rorrr�s   rGrrzSystemConfig.config_to_dictHs)���"�1�1��J�2�
�
�	
rcr�r�r�r�c�D�|j�|||||���dS�N)r�r�r�r�)rmr�r�s      rGr�zSystemConfig.dict_to_configOs=��	
��)�)�����-�	*�	
�	
�	
�	
�	
rcc�j�|j���|j���dSrZ)ror�rqr�s rGr�zSystemConfig.validate_s2����$�$�&�&�&��#�,�,�.�.�.�.�.rcrc�:�|j�||���S)N)rr�)ror�rs   rGr�zSystemConfig.normalizecs*���"�,�,��,<�-�
�
�	
rcrc�6�|j�|��SrZ)rorrs  rGrzSystemConfig.modified_sincejr^rcr�TTFTr
r)r�r�r�r<r�r�r�rrrr�r�r�rrrr_r`s@rGrgrg9sw������� $� $�(,�
�
�
��
��	
�
 &�
�
�
�
�
�
�:?�
�
��
�26�
�	
�
�
�
�
����!%�

�
��
��
��	
�
�
��

�
�
�
�
�
� /�/�/�/�
9>�
�
��
�15�
�	
�
�
�
�
�=����=�D�=�=�=�=�=�=�=�=rcrgr\c��|r%t|t��st|���S|rt|���St	��S)Nrp�r\)r&rBrcr<rg)rnr\s  rG�config_file_factoryr|nsQ����
�8�S�1�1���8�,�,�,�,�	
���4� � � � ��~�~�rcc��tt�����}|jD]}|�|��rdS�dS)NTF)�Merger�get_layer_names�layersr)r�merger�layers   rG�any_layer_modified_sincer�zsS��
�F�*�*�,�,�
-�
-�F���������	�*�*�	��4�4�	��5rc)r\r?rA)r>c
���eZdZdZejddddd�dedeee	geffde
f�fd�Z				dd
ededed
ededdf�fd�
Z
�xZS)rlzt
    Config (/etc/sysconfig/imunify360/imunify360.config) should contain
    options changed by a customer only
    NF�r\r>r!r?r@r>r!r?c�T��t���|||||���dS)Nr��rCr�)r�r\r>r!r?r@rHs      �rGr�zLocalConfig.__init__�s?���	������'�/�!��	�	
�	
�	
�	
�	
rcTr�r�r�r�r�r@c�P��t���|||||���Sru)rCr�)r�r�r�r�r�r�rHs      �rGr�zLocalConfig.dict_to_config�s5����w�w�%�%�����-�&�
�
�	
rcry)r�r�r�rr�r�rrrr
rr�r�r�r_r`s@rGrlrl�s����������
(�&*�CG���
�
�
�$�	
�
!��(�2�w�;�*?�!?�@�
��

�
�
�
�
�
�(���!%�

�
��
��
��	
�
�
��

�
�
�
�
�
�
�
�
�
�
�
rcrlc��eZdZej�ejej��Z	dZ
d	d�Zed���Z
d	d�Zedefd���ZdS)
�
BaseMergerz90-local.configFc�<��|�_�fd�|D���_dS)Nc�v��g|]5}ttj��j|�������6S)r{)r<r[r\r]�DIR)r'r#r�s  �rG�
<listcomp>z'BaseMerger.__init__.<locals>.<listcomp>�sC���
�
�
�:>�F�����T�X�t�4�4�5�5�5�
�
�
rc)�_include_defaultsr�)r��names�include_defaultss`  rGr�zBaseMerger.__init__�s9���!1���
�
�
�
�BG�
�
�
����rcc��tj�|j��r&t	tj|j����ngSrZ)r[r\�isdirr��sorted�listdir�r)s rGrzBaseMerger.get_layer_names�s6��.0�g�m�m�C�G�.D�.D�L�v�b�j���)�)�*�*�*�"�Lrcc����g}|jr>tt���id���}|�|��|�fd�|jD��z
}|�|��S)NFrTc�>��g|]}|�d������S)Frs)rr)r'r�r�s  �rGr�z.BaseMerger.configs_to_dict.<locals>.<listcomp>�s<���
�
�
��
� � �5�Z� �H�H�
�
�
rc)r�rr�r��appendr��_build_effective_config)r�r��layer_dict_list�defaultss `  rG�configs_to_dictzBaseMerger.configs_to_dict�s�������!�	-�!�"4�5�5�?�?��U�@���H�
�"�"�8�,�,�,��
�
�
�
���
�
�
�	
���+�+�O�<�<�<rcr�c��i}|D]O}|���D]8\}}|��||vri||<|���D]\}}|�||||<��9�P|SrZr+)r)r��	effective�
layer_dictrxr0ryrs        rGr�z"BaseMerger._build_effective_config�s���%'�	�)�	;�	;�J�$.�$4�$4�$6�$6�
;�
;� ����?���)�+�+�)+�I�g�&�%,�]�]�_�_�;�;�M�F�E��(�5:�	�'�*�6�2��;�
;��rcNr)r�r�r�r[r\r]r�r�r�r��LOCAL_CONFIG_NAMEr�r9rr��listr�r�rcrGr�r��s�������
�'�,�,�t�*�D�,>�
?�
?�C�)��
�
�
�
��M�M��[�M�=�=�=�=���d�����[���rcr�c�$��eZdZdef�fd�Z�xZS)�
MutableMergerr�c���t||j��}|d|�}t���|d���dS�NT�r�)rr�rCr��r�r��idxrHs   �rGr�zMutableMerger.__init__�sE����%��!7�8�8���d�s�d���
��������6�6�6�6�6rc�r�r�r�rr�r_r`s@rGr�r��sD�������7�h�7�7�7�7�7�7�7�7�7�7rcr�c�$��eZdZdef�fd�Z�xZS)�ImmutableMergerr�c���t||j��}||d�}t���|d���dS�NFr�)rr�rCr�r�s   �rGr�zImmutableMerger.__init__�sE����5�$�"8�9�9���c�d�d���
��������7�7�7�7�7rcr�r`s@rGr�r��sD�������8�h�8�8�8�8�8�8�8�8�8�8rcr�c�$��eZdZdef�fd�Z�xZS)�
NonBaseMergerr�c�N��t���|d���dSr�r��r�r�rHs  �rGr�zNonBaseMerger.__init__�s&���
��������7�7�7�7�7rcr�r`s@rGr�r��sD�������8�h�8�8�8�8�8�8�8�8�8�8rcr�c�n��eZdZddgddgdgdgd�Z�fd�Zed	���Zed
edefd���Z	�xZ
S)
r~N�user_override_proactive_defense�num_days�limit�enable�enable_scan_modsec)�PROACTIVE_DEFENCE�PERMISSIONS�INCIDENT_LOGGING�ERROR_REPORTING�MALWARE_SCANNINGc�N��t���|d���dSr�r�r�s  �rGr�zMerger.__init__s&���
��������6�6�6�6�6rcc���||�����}|���}|jD]I}tj�|j��s#t�d|j��dS�J	t�	|��tt��}|�|d���}|�
|��\}}t���|d���t!���|dd���dS#t"t$f$r&}t�d|��Yd}~dSd}~wwxYw)NzGAborting merged config update: Config layer %s disappeared during mergeFrT)r�)r�r�zConfig file is invalid! %s)rr�r�r[r\�lexistsrt�warningrr�rr�r��_split_settingsrnr�rprZr�)r)r�r[r��
normalizer�	priv_dict�nonpriv_dictrFs        rG�update_merged_configzMerger.update_merged_configs�����S�(�(�*�*�+�+���,�,�.�.���]�	�	�E��7�?�?�5�:�.�.�
����?��J����
���

�	��%�%�k�2�2�2�$�$6�7�7�J�$�.�.��e�/���K�'*�&9�&9�+�&F�&F�#�I�|��N�N�)�)�)�e�)�D�D�D�%�'�'�6�6��u��
7�
�
�
�
�
��&�'<�=�	<�	<�	<��N�N�7��;�;�;�;�;�;�;�;�;�����	<���s�D*�*E!�;E�E!r[r@c��t|��}i}|j���D]T\}}||vr�
|�t||��||<�%|D],}|||vr ||vri||<||||||<�-�U||fS)z�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.
        )r�NONPRIVILEGED_SETTINGSr,)r)r[r�r�rxr0rys       rGr�zMerger._split_settings>s����[�)�)�	�(*�� #� :� @� @� B� B�
	�
	��G�W��k�)�)����(0��W�1E�(F�(F��W�%�%�%���F���W�!5�5�5�"�,�6�6�46�L��1�8C�G�8L�"�9��W�-�f�5��	��,�&�&rc)r�r�r�r�r�r9r�r��tupler�r_r`s@rGr~r~�s��������!�-�
�
��
�

�
�
!�
����&7�7�7�7�7��)�)��[�)�V�'�$�'�5�'�'�'��[�'�'�'�'�'rcr~c�,��eZdZedd��fd�
Zd�Z�xZS)r3T��
allow_unknown�purge_readonlyc�@��t��j|||d�|��dS)z�
        Initialises ConfigValidator(Validator)
        for more details on Validator params please check
        https://docs.python-cerberus.org/en/stable/validation-rules.html
        r�Nr�)r�r�r��args�kwargsrHs     �rGr�zConfigValidator.__init__[sB���	�����
�'�)�	
�	
��		
�	
�	
�	
�	
rcc�h�|j�di���dd��rdS|S)Nr&r�FT)�
root_documentrs)r�rs  rG�(_normalize_coerce_user_override_pd_rulesz8ConfigValidator._normalize_coerce_user_override_pd_rulesns7����!�!�,��3�3�7�7��%�H�H�	��4��rc)r�r�r�r�r�r�r_r`s@rGr3r3ZsY�������%��	
�
�
�
�
�
�
�&������rcr3c�"�eZdZdejzZdS)�	Packagingz/opt/imunify360/venv/share/%sN)r�r�r�r�r��DATADIRr�rcrGr�r�ts������-���<�G�G�Grcr�c��eZdZdZdZdZdZdZede	��Z
edee
d�	���������Zed
d��Zedee
d�	���������ZdS)
�	SimpleRpciz(/var/run/defence360agent/simple_rpc.sockz1/var/run/defence360agent/non_root_simple_rpc.sockz.imunify360_token_{suffix}��I360_SOCKET_ACTIVATION�IMUNIFY360_INACTIVITY_TIMEOUT���minutes�I360_RPC_MAX_CONNECTIONS�I360_RPC_READ_TIMEOUTN)r�r�r��CLIENT_TIMEOUT�SOCKET_PATH�NON_ROOT_SOCKET_PATH�TOKEN_FILE_TMPL�
TOKEN_MASKrXr��SOCKET_ACTIVATIONrHrBr	�
total_seconds�INACTIVITY_TIMEOUT�MAX_CONCURRENT_CONNECTIONS�READ_TIMEOUTr�rcrGr�r�xs��������N�<�K�N��2�O��J�(�(� �����)��'���I�I�a� � � �.�.�0�0�1�1����"1��"�C�"�"��#�?����I�I�a� � � �.�.�0�0�1�1���L�L�Lrcr�c�p�eZdZdej�dej�d�Zdej�d�Zdej�dej�d�ZdS)�Modelz/var/�/z.dbz
/proactive.dbz-resident.dbN)r�r�r�r�r��PATH�PROACTIVE_PATH�
RESIDENT_PATHr�rcrGr�r��sN�������"�l�l�l�D�L�L�L�9�D�D�/3�|�|�|�=�N�N�04����d�l�l�l�K�M�M�Mrcr�c��eZdZed������Zed������ZdZgZdZ	dS)�FilesUpdate�r���N)
r�r�r�r	r��PERIOD�TIMEOUT�SOCKET_TIMEOUT�DISABLED�DAYS_TO_KEEPr�rcrGr�r��s_������
�Y�r�
"�
"�
"�
0�
0�
2�
2�F��i��#�#�#�1�1�3�3�G��N�
�H��L�L�Lrcr�c�,�eZdZdZdZed���ZdS)�CountryInfoz2/var/imunify360/files/geo/v1/GeoLite2-Country.mmdbz>/var/imunify360/files/geo/v1/GeoLite2-Country-Locations-en.csvc�,�d�|��S)Nz2/var/imunify360/files/geo/v1/CountrySubnets-{}.txt)rE)�country_codes rG�country_subnets_filez CountryInfo.country_subnets_file�s��C�J�J��
�
�	
rcN)r�r�r��DB�LOCATIONS_DBr:r�r�rcrGr�r��s?������	=�B�	I���
�
��\�
�
�
rcr�c�j�eZdZejdedejz����Ze	dd��Z
dS)�Sentry�IMUNITY360_SENTRY_DSNz	%s/sentryr�r�N)r�r�r�r[�getenvrmr�r��DSNr��ENABLEr�rcrGrr�sL������
�"�)����[�9�;L�-L�!M�!M���C��Z�)�8�
4�
4�F�F�Frcrc��eZdZdZdZdZdZdZedd��Z	edd��Z
edd��Zedd	��Zedd
��Z
edd��Zedd��Zedd
��ZdZdZdZedd���Zedd���Zedd���Zedd��Zedd��Zedd��Zedd��Zedd��Zedd��Zedd��Zed��Zedd��Z d S)!�Malwarei,r�r�r�r��max_targets_per_scan_type�max_path_len�enable_scan_inotify�enable_scan_pure_ftpd�sends_file_for_analysis�cloud_assisted_scan�
rapid_scan�crontabsz$/var/imunify360/aibolit/scans.picklei�z/var/imunify360/cleanup_storage�MALWARE_CLEANUP�trim_file_instead_of_removal�rxry�keep_original_files_days�scan_modified_files�max_signature_size_to_scan�max_cloudscan_size_to_scan�max_mrs_upload_file�,rapid_scan_rescan_unchanging_files_frequency�	hyperscan�MALWARE_DATABASE_SCANr��enable_scan_cpanel�disable_cloudav�
db_timeoutN)!r�r�r��SCAN_CHECK_PERIOD�CONSECUTIVE_ERROR_LIMIT�INOTIFY_SCAN_PERIOD�CONFIG_CHECK_PERIOD�CONFLICTS_CHECK_PERIODr��MAX_TARGETS_PER_SCAN_TYPE�MAX_PATH_LEN�INOTIFY_ENABLED�	PURE_SCAN�
SEND_FILES�CLOUD_ASSISTED_SCAN�
RAPID_SCAN�CRONTABS_SCAN_ENABLED�
SCANS_PATH�FILE_PREVIEW_BYTES_NUM�CLEANUP_STORAGE�CLEANUP_TRIM�CLEANUP_KEEP�SCAN_MODIFIED_FILES�MAX_SIGNATURE_SIZE_TO_SCAN�MAX_CLOUDSCAN_SIZE_TO_SCAN�MAX_MRS_UPLOAD_FILE�,RAPID_SCAN_RESCAN_UNCHANGING_FILES_FREQUENCY�	HYPERSCAN�DATABASE_SCAN_ENABLED�CPANEL_SCAN_ENABLEDr��CLEANUP_DISABLE_CLOUDAV�MDS_DB_TIMEOUTr�rcrGr	r	�s��������� ������ �� *�
��7�!�!���:�0�.�A�A�L� �j�!3�5J�K�K�O��
�-�/F�G�G�I���.�0I�J�J�J�$�*�%7�9N�O�O����.��=�=�J�&�J�'9�:�F�F��7�J�'��7�O��:�!�-����L��:�!�)����L�%�*�"�$�����
",���8�"�"��",���8�"�"��%�*�%7�9N�O�O��3=�:��J�4�4�0��
�-�{�;�;�I�&�J�'>��I�I��$�*�%7�9M�N�N��*�l�+<�=�=���Z� 7��F�F�N�N�Nrcr	c�p�eZdZdZed��Zed��Zeded���Zed��Z	dS)	�MalwareTunezc
    Experimental and testing-only purpose settings
     we don't want to expose to customers.
    �use_json�no_check_known_hashes�rapid_scan_basedir_overridez/homer��no_auto_upgradeN)
r�r�r�rr��USE_JSON_REPORT�NO_CHECK_KNOWN_HASHESr�RAPID_SCAN_BASEDIR_OVERRIDE�NO_AUTO_UPGRADEr�rcrGr=r=�sj��������
#�l�:�.�.�O�(�L�)@�A�A��".�,�%�d�G�#�#�#��#�l�#4�5�5�O�O�Orcr=c��eZdZeZeZeZeZdS)�MalwareScanScheduleIntervalN)r�r�r��NONE�DAY�WEEK�MONTHr�rcrGrGrGs"�������D�

�C��D��E�E�ErcrGc��eZdZdZdZdZedd���Zedd���Zedd���Z	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_SCHEDULE�intervalr�hour�day_of_week�day_of_monthN)r�r�r��CMD�	CRON_PATH�CRON_STRINGr��INTERVAL�HOUR�DAY_OF_WEEK�DAY_OF_MONTHr�rcrGrMrMs�������
D�C�3�I��K�
�z�'�����H��:�'�����D��*�'�����K��:�'�����L�L�LrcrMc���eZdZedd���Zedd���Zedd���Zedd���Zedd���Zedd���Z	edd	���Z
d
S)�MalwareScanIntensity�MALWARE_SCAN_INTENSITY�cpur�io�ram�
user_scan_cpu�user_scan_io�
user_scan_ram�resident_ramN)r�r�r�r��CPU�IO�RAM�USER_CPU�USER_IO�USER_RAM�RESIDENT_RAMr�rcrGr[r[(s�������
�*�(�����C�
��(��
�
�
�B��*�(�����C��z�(�����H��j�(�����G��z�(�����H��:�(�����L�L�Lrcr[c�\�eZdZedd���Zedd���Zedd���ZdS)�FileBasedResourceLimits�RESOURCE_MANAGEMENT�	cpu_limitr�io_limit�	ram_limitN)r�r�r�r�rdrerfr�rcrGrlrlGsf������
�*�%�����C�
��%��
�
�
�B��*�%�����C�C�Crcrlc�(�eZdZedd���ZdS)�
KernelCare�
KERNELCARE�edfrN)r�r�r�r��EDFr�rcrGrrrrVs*������
�*������C�C�Crcrrc���tj}|�Rtjdtjdtjdtjdi}|�tj	d��S|S)Nr2r.r�r�)
r	r6rGrHrKrJrIrsrMrV)r�freqs  rG�get_rapid_rescan_frequencyrx]sU���@�E��}�'�,�a�'�-�q�'�,�a�'�+�R�	
���x�x�+�4�a�8�8�8��Lrcc��eZdZdZej�ed��Zej�ed��Zej�edd��Z	ej�edd��Z
ej�edd��Zej�edd��Zej�edd	��Z
d
S)�MalwareSignaturesz/var/imunify360/files/sigs/v1/�rfxn�i360�aibolitzai-bolit-hoster-full.dbrzmds-ai-bolit-hoster.dbz	procu2.dbz
mds-procu2.dbN)r�r�r��_dirr[r\r]�RFXNr|�AI_BOLIT_HOSTER�AI_BOLIT_HYPERSCAN�MDS_AI_BOLIT_HOSTER�PROCU_DB�MDS_PROCU_DBr�rcrGrzrzjs�������+�D�
�7�<�<��f�%�%�D�
�7�<�<��f�%�%�D��g�l�l�4��4M�N�N�O�����d�I�{�C�C���'�,�,��i�1�����w�|�|�D�)�[�9�9�H��7�<�<��i��A�A�L�L�Lrcrzc�J�eZdZedd���Zedd���ZdZdZdS)�Logger�LOGGER�max_log_file_sizer�backup_counti�i�N)r�r�r�r��MAX_LOG_FILE_SIZE�BACKUP_COUNT�LOG_DIR_PERM�
LOG_FILE_PERMr�rcrGr�r�xsU������"�
��"������:������L�
�L��M�M�Mrcr�c��eZdZdZdZdS)rvr��non_rootN)r�r�r�rw�NON_ROOTr�rcrGrvrv�s�������D��H�H�Hrcrv�caller_type)r$c��eZdZdZdZdS)�UIRole�client�adminN)r�r�r��CLIENT�ADMINr�rcrGr�r��s������
�F��E�E�Ercr�c��eZdZdZdZdS)�NoCPz/etc/imunify360/scripts/domainsr2N)r�r�r��
CLIENT_SCRIPT�LATEST_VERSIONr�rcrGr�r��s������5�M��N�N�Nrcr�c���eZdZ�fd�Z�xZS)�CustomBillingConfigc���tj�dtj��}t���|t���dS)Nr��r\r!)r[r\r]r�r�rCr��CONFIG_SCHEMA_CUSTOM_BILLING)r�r\rHs  �rGr�zCustomBillingConfig.__init__�sS����w�|�|�'��)K�
�
��	������)E�	�	
�	
�	
�	
�	
rcrer`s@rGr�r��s8�������
�
�
�
�
�
�
�
�
rcr�c�~�eZdZedde���Zedde���Zedde���Zedde���ZdS)�
CustomBillingr�r��rxryr�r�r�r�N)	r�r�r�r�r��UPGRADE_URL�UPGRADE_URL_360�
NOTIFICATIONS�
IP_LICENSEr�rcrGr�r��s��������*� ��&����K�
!�j� � �&����O�
�J� �&�&����M�
�� ��&����J�J�Jrcr�c���eZdZedd���Zedd���Zedd���Zedd���Zedd���Zedd���Z	edd	���Z
d
S)�PermissionsConfigr��user_ignore_listr�allow_malware_scan�user_override_malware_actionsr��*allow_local_malware_ignore_list_management�use_plesk_service_plan�allow_wp_waf_rules_managementN)r�r�r�r��USER_IGNORE_LIST�ALLOW_MALWARE_SCAN�USER_OVERRIDE_MALWARE_ACTIONS�USER_OVERRIDE_PROACTIVE_DEFENSE�*ALLOW_LOCAL_MALWARE_IGNORE_LIST_MANAGEMENT�USE_PLESK_SERVICE_PLAN�ALLOW_WP_WAF_RULES_MANAGEMENTr�rcrGr�r��s�������!�z��!�����$���#�����%/�J��&E�%�%�%�!�'1�j��0�'�'�'�#�2<���;�2�2�2�.�(�Z��'�����%/�J��.�%�%�%�!�!�!rcr�c�B�eZdZedd���Zedd���ZdS)�MyImunifyConfigr&r�r�purchase_page_urlN)r�r�r�r��ENABLED�PURCHASE_PAGE_URLr�rcrGr�r��sK�������j������G�#�
��"������rcr�c�B�eZdZedd���Zedd���ZdS)�ControlPanelConfig�
CONTROL_PANEL�smart_advice_allowedr�advice_email_notificationN)r�r�r�r��SMART_ADVICE_ALLOWED�ADVICE_EMAIL_NOTIFICATIONr�rcrGr�r��sL������%�:��%�����!+�
��*�!�!�!���rcr�c������gd�}tjtjtjtjtjtjd��|����|������di���dd���d<���fd���fd�|D��}t||j��S)	N)
�BACKUP_RESTORErr�r�r�r�r&r�rN�	WORDPRESS))rNrO)rNrP)rNrQ)rNrR)r��default_action)r��moder��waf_enabledT)r�r�c�@��t��|i����}t�	�|i����}i}|���D]?\}}|�|��}|���||fd��r|||<�:|||<�@|S)NT)rrsr,)
rx�
admin_options�user_options�resulting_dictry�admin_valuer|�
admin_dict�overridable�	user_dicts
       ���rG�normalize_sectionz0effective_user_config.<locals>.normalize_section!s���� ������!<�!<�=�=�
��	�
�
�g�r� :� :�;�;����#0�#6�#6�#8�#8�		5�		5��F�K�%�)�)�&�1�1�J��&��O�O�W�f�$5�t�<�<�'�*4��v�&�&�)4��v�&�&��rcc�(��i|]}|�|����Sr�r�)r'rxr�s  �rGr*z)effective_user_config.<locals>.<dictcomp>2s4������07��"�"�7�+�+���rc)r�r�r�rrrs�fm_config_cleanuprn)�admin_configrz�allowed_sections�effective_configr�r�r�r�s    @@@@rG�effective_user_configr��s���������� �:��:��:��:��:��<�1��K�4�,�,�.�.�J��*�*�,�,�I�1;����R�1�1�	�c�-�����,�-��������"����;K������-�{�/C�D�D�Drcc�:�eZdZdxZ\ZZZZZeeeefZ	e
re	neZdS)�
HookEvents)�agent�licensezmalware-scanningzmalware-cleanupzmalware-detectedN)r�r�r��IM360_EVENTS�AGENT�LICENSEr�r�MALWARE_DETECTED�IMAV_EVENTSr��EVENTSr�rcrGr�r�9sS������	�
�L��
�����	����	�K�+�
<�[�[��F�F�Frcr�c�0�eZdZdeeeffd�Zd�ZdS)rZ�configs_to_errorsc��||_dSrZ)r�)r�r�s  rGr�zConfigsValidatorError.__init__Qs��!2����rcc��g}|j���D]\}}|�|�d|����� d�|��S)Nz: �
)r�r,r�r])r�r5rrYs    rGrKzConfigsValidatorError.__repr__Ts^����!�3�9�9�;�;�	2�	2�M�F�E��M�M�V�0�0��0�0�1�1�1�1��y�y�� � � rcN)r�r�r�rr<rr�rKr�rcrGrZrZPsG������3�$�v�s�{�*;�3�3�3�3�!�!�!�!�!rcrZc��eZdZdZed���Zed���Zeefdede	ee
fddfd���Zede	e
e
fde
fd	���ZdS)
rz@A class that has methods to validate configs bypassing the cachec�F�t�����dS)zN
        Validate merged config
        :raises ConfigsValidatorError
        N)rgr�r�s rG�validate_system_configz'ConfigsValidator.validate_system_config^s ��	�����!�!�!�!�!rcc��i}tt�����jD]H}	|����#t$r$}|�|j��Yd}~�Ad}~wwxYw|rt	|���dS)zf
        Validate all config layers, collect all errors
        :raises ConfigsValidatorError
        N)r~rr�r�rZ�updater�)r)r�r�rFs    rG�validate_config_layersz'ConfigsValidator.validate_config_layersfs������F�2�2�4�4�5�5�<�	>�	>�E�
>���� � � � ��(�
>�
>�
>�!�(�(��)<�=�=�=�=�=�=�=�=�����
>�����	;�'�(9�:�:�:�	;�	;s�A�
A4�A/�/A4r[r!r@Nc��|�|��}t|��}|�|��st|j���dS)z�
        Validate config represented by a dict
        :param config_dict: config to validate
        :param validation_schema: schema to validate config against
        :raises ConfigValidationError
        N)rr3r�r�r5)r)r[r!r��vs     rGr�zConfigsValidator.validatevsS���*�*�+<�=�=���F�#�#���z�z�+�&�&�	2�'���1�1�1�	2�	2rcc�8�t|��r
|��S|SrZ)�callable)r!s rGrz&ConfigsValidator.get_validation_schema�s*���%�&�&�	'�$�$�&�&�&� � rc)r�r�r�rr9r�r�r�r�rr
r�r:rrr�rcrGrr[s�������J�J��"�"��[�"��
;�
;��[�
;��4F�2�2��2�!��x��0�2�
�	2�2�2��[�2�"�!� ��(�!2�3�!�	�!�!�!��\�!�!�!rcrc�(�eZdZedd���ZdS)�
AdminContacts�ADMIN_CONTACTS�enable_icontact_notificationsrN)r�r�r�r��ENABLE_ICONTACT_NOTIFICATIONSr�rcrGr�r��s-������$.�J� �.�%�%�%�!�!�!rcr�c� �eZdZdZdZdZd�ZdS)�IContactMessageType�MalwareFound�ScanNotScheduled�Genericc��|jSrZ)rr�s rG�__str__zIContactMessageType.__str__�s
���z�rcN)r�r�r��
MALWARE_FOUND�SCAN_NOT_SCHEDULED�GENERICr�r�rcrGr�r��s3������"�M�+���G�����rcr��
BACKUP_SYSTEMF)r�r$r��allowed)�enabled�
backup_systemc�f��eZdZdZej�dej��e	d��fd�
Z
�xZS)�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 <init|disable> <backup-system>
#   or activate/deactivate appropriate feature in UI.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    r�r�c�N��t���||���dS)Nr�r�)r�r\r!rHs   �rGr�zBackupConfig.__init__�s)���	�����d�6G��H�H�H�H�Hrc)r�r�r�rDr[r\r]r�r��CONFIG_SCHEMA_BACKUP_SYSTEMr�r_r`s@rGrr�sv��������J�(�W�\�\�'��)C�
�
�6�
I�I�I�I�I�I�I�I�I�I�Ircrc��eZdZedde���Zedde���Zedd���Zedd���Ze	d	���Z
d
S)�
BackupRestorerrr�rr��cl_backup_allowedr�cl_on_premise_backup_allowedc�*�t|j��SrZ)�_get_backend_system�_BACKUP_SYSTEMr�s rGrzBackupRestore.backup_system�s��"�3�#5�6�6�6rcN)r�r�r�r�rr�r�CL_BACKUP_ALLOWED�CL_ON_PREMISE_BACKUP_ALLOWEDr9rr�rcrGr	r	�s��������j��	�l����G� �Z�������N�
#�
� �"�����$.�:� �-�$�$�$� �
�7�7��[�7�7�7rcr	c�t�ddlm}|ttfvrt}n|�dS|�|d���S)zo
    Get backup module from its name
    :param name: backup system name
    :return: backup system module
    r)�backup_backendsNT)�async_)�restore_infectedr�
CLOUDLINUX�CLOUDLINUX_ON_PREMISE�ACRONIS�backend)r#rs  rGr
r
�sQ��1�0�0�0�0�0��
�1�2�2�2����	
���t��"�"�4��"�5�5�5rcc��eZdZdZdZdhZdS)�
AcronisBackupzacronis-installer.log)i� i��i�)iZixN)r�r�r��LOG_NAME�PORTS�RANGEr�rcrGrrs!������'�H� �E�
�N�E�E�Ercrc�F�tdd|��\}}tjo|S)zs
    Checks is Agent should try restore malware file firts
    and returns user that set this action in config
    r��try_restore_from_backup_first)r~r	r�)rn�try_restore�_s   rG� should_try_autorestore_maliciousr"s/��
.��;�X���N�K��� �0�[�0rcc�v�tdd|��\}}tj��t|���z
}|S)Nr��max_days_in_backup)�days)r~r�nowr	)rn�max_daysr!�untils    rG�"choose_use_backups_start_from_dater)s?��*��.����K�H�a�
�L�N�N�Y�H�5�5�5�5�E��Lrcc�0�tdd|���\}}|S)Nr��generic_user_notificationsrp)r~)rn�should_sendr!s   rG�should_send_user_notificationsr-"s+��-��$�����N�K��
�rcc��eZdZedd��Zedd��Zedd��Zedd��Zedd��ZdS)�	Wordpressr��security_plugin_enabledr��waf_default�ai_bot_protection�ai_bot_protection_presetN)	r�r�r�r��SECURITY_PLUGIN_ENABLED�WAF_ENABLED�WAF_DEFAULT�AI_BOT_PROTECTION�AI_BOT_PROTECTION_PRESETr�rcrGr/r/+sw������(�j��.�����*�[�-�8�8�K��*�[�-�8�8�K�"�
�;�0C�D�D��)�z��/� � ���rcr/c��eZdZdZdZdZdZdS)�
HackerTrapr�zmalware_found_b64.listzmalware_standalone_b64.listz%/opt/imunify360/proactive/dangerlist/N)r�r�r�r�r��SA_NAME�DIR_PDr�rcrGr:r:7s"������
�C�#�D�+�G�
4�F�F�Frcr:rZr�)�r�	functools�loggingr[�abcr�bisectrr�contextvarsr�copyrrr	�enumr
�pathlibr�typingrr
rrrrrrrrr�cerberusr�)defence360agent.contracts.config_providerrrrrr�+defence360agent.feature_management.checkersrr��defence360agent._versionrr��defence360agent.utilsrr r!rsr��	getLoggerr�rtr�r�r��MY_IMUNIFY_KEYr�rer�r��NOTIFY�CLEANUPrHrIrJrK�DEFAULT_INTENSITY_CPU�DEFAULT_INTENSITY_IO�DEFAULT_INTENSITY_RAM�DEFAULT_INTENSITY_RESIDENT_RAM�%DEFAULT_RESOURCE_MANAGEMENT_CPU_LIMIT�$DEFAULT_RESOURCE_MANAGEMENT_IO_LIMIT�%DEFAULT_RESOURCE_MANAGEMENT_RAM_LIMIT�MODSEC_RULESET_FULL�MODSEC_RULESET_MINIMAL�_DOS_DETECTOR_DEFAULT_LIMIT�_DOS_DETECTOR_MIN_LIMIT�_DOS_DETECTOR_MIN_INTERVAL�PORT_BLOCKING_MODE_DENY�PORT_BLOCKING_MODE_ALLOW�DO_NOT_MODIFY_DISCLAMER�DEFAULT_CONFIG_DISCLAMER�CPANEL�PLESK�DIRECTADMINr�R1SOFT�
CLUSTERLOGICS�SAMPLE_BACKENDrr�GENERIC_SENSOR_SOCKET_PATHrrBrHr�rXrbrfrmr~r�r�r�r��	lru_cacher�r�r��	Exceptionr�r�r�rrrr<rcrgr|r��partialr�r�rnr�r�rprlr�r�r�r�r~r3r�r�r�r�r�rr	r=rGrMr[rlrrrxrzr�rvrwr�rr�r�r�r�r�r�r�r�r�rZrrqr�r�rrr	r
rr"r)r-r/r:r�rcrG�<module>ris��������������	�	�	�	�������,�,�,�,�,�,�,�,�"�"�"�"�"�"�������(�(�(�(�(�(�(�(�����������������������������������������������������������������A�@�@�@�@�@�G�G�G�G�G�G�G�G�G�G�
�X�\�������
�
��	�8�	$�	$��$�X�_�W�-�-�-��A�����8�<��-�������
�!�T��J�N�N�"�>������&����7���c�4��������!%��()�%�'(�$�(+�%���"��!������ ��"������>����{�!E�������$I�!�
�!�M��<>�:�D�D��D�s�D��D�#�D�D�D�D�-/�J�
�
�	�
��
�")�
�	�
�
�
�
�(<�<�<�M�M�M����-1�7�7�"�T�z�7�
�3��d�
�?��7�7�7�7�&1�1�1���������(A�A�A�A�A�A�A�A�-1���������Q����-�-� ��-����Q����7�7� ��7�
��!�� ���!�� � � �
/8�D�%I�%I�#,��>�>�

�

��!�� ��,	�	�	�	�	�I�	�	�	�*/�*/�*/�*/�*/�*/�*/�*/�Z'<�'<�'<�'<�'<�h�'<�'<�'<�T�����'�8���������
�y����;'�;'�;'�;'�;'�;'�;'�;'�|@=�@=�@=�@=�@=�W� 1�@=�@=�@=�@=�F

�

�

�

�

��

�

�

�2=�2=�2=�2=�2=�7�&7�2=�2=�2=�2=�lGK����u�S�#�X��'��6>�s�m��������4�����!�y� �
�'�'�
�
)�*��6��������.�I�-�
�'�'�
�
7�*��D��������%
�%
�%
�%
�%
�&�%
�%
�%
�P(�(�(�(�(�(�(�(�V7�7�7�7�7�J�7�7�7�8�8�8�8�8�j�8�8�8�8�8�8�8�8�J�8�8�8�
\'�\'�\'�\'�\'�Z�\'�\'�\'�~�����i����4=�=�=�=�=�=�=�=���������4L�L�L�L�L�L�L�L���������&
�
�
�
�
�
�
�
�5�5�5�5�5�5�5�5�5G�5G�5G�5G�5G�5G�5G�5G�p6�6�6�6�6�6�6�6�����������������4��������>����������������
�
�
�B�B�B�B�B�B�B�B����������������� *�z�-���O�O�O��Z��_�O�O�O���������
��������
�
�
�
�
�&�
�
�
���������.��������<����������������DE�DE�DE�N=�=�=�=�=�=�=�=�.!�!�!�!�!�I�!�!�!�3!�3!�3!�3!�3!�3!�3!�3!�l!�
��������������#�t������"� ���
!�� �������)�!�"�
�	��
�
�,�1����<I�I�I�I�I�6�I�I�I�:7�7�7�7�7�7�7�7�.6�6�6�"��������1�s�1�t�1�1�1�1����������S��T�����	�	�	�	�	�	�	�	�5�5�5�5�5�5�5�5�5�5rcdefence360agent/contracts/__pycache__/config.cpython-311.pyc0000644000000000000000000020714400000000000020735 0ustar  �

�~6O9K����
�UdZddlZddlZddlZddlmZddlmZmZddl	m
Z
ddlmZddl
m
Z
mZddlmZdd	lmZdd
lmZmZmZmZmZmZmZmZmZmZmZddlm 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/m0Z0e0j1ddd���Z2ej3e4��Z5e0j6d��Z7dZ8dZ9e0j1dde2���Z:dZ;eej<�1dd����Z=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_fd0�Z`ej<fd,e^d-ead.ed/eafd1�Zbd2�Zcd3�Zdd4�Ze	d�d5e^dzd/eee^dzffd6�Zfd7�ZgGd8�d9��ZhGd:�d;��Zid<d=�d>�Zjejkd"�?��d@���Zlejkd"�?��dA���ZmdBdCdDdd<dE�dDdd<dE�dFd<dG�dFd<dG�dH�idI�iZnGdJ�dKeo��ZpGdL�dM��ZqGdN�dOe��ZrGdP�dQere��ZsGdR�dSee.��ZtGdT�dU��ZuGdV�dWeret�X��ZvGdY�dZev��ZwGd[�d\eret�X��Zx	d�d5eee^e_fd]ee^d/erfd^�Zyd/eafd_�Zzej{eve&eqj|eReqj}�`���a��Z~ej{eve&eqjeReqj��`���a��Z�Gdb�dcev��Z�Gdd�de��Z�Gdf�dge���Z�Gdh�die���Z�Gdj�dke���Z�Gdl�dme���Z�Gdn�doe ��Z�Gdp�dq��Z�Gdr�ds��Z�Gdt�du��Z�Gdv�dw��Z�Gdx�dy��Z�Gdz�d{��Z�Gd|�d}��Z�Gd~�d��Z�Gd��d���Z�Gd��d���Z�Gd��d���Z�Gd��d���Z�Gd��d���Z�d��Z�Gd��d���Z�Gd��d���Z�Gd��d���Z�e
d�e�j�����Z�e
e^e�d�<Gd��d���Z�Gd��d���Z�Gd��d�ev��Z�Gd��d���Z�Gd��d���Z�Gd��d���Z�Gd��d���Z�d��Z�Gd��d���Z�Gd��d�eo��Z�Gd��d���Z�eyZ�Gd��d���Z�Gd��d�e^e��Z�d�dCdFd�dG�dDdd<eTeUeXeWe[eVe\eYeZg	d��d��idI�iZ�Gd��d�ev��Z�Gd��d���Z�d��Z�Gd��d���Z�d5e^d/eafd��Z�d5e^d/e
fd��Z�d5e^d/eafd��Z�Gd��d���Z�Gd��d���Z�dS)�z5
All the config settings for defence360 in one place
�N)�abstractmethod)�bisect_left�bisect_right)�
ContextVar)�deepcopy)�datetime�	timedelta)�Enum)�Path)�Any�Callable�Dict�List�Mapping�Optional�Protocol�Sequence�Tuple�Union�
_ProtocolMeta)�	Validator)�CachedConfigReader�ConfigError�ConfigReader�UserConfigReader�WriteOnlyConfigReader)�config_cleanup)�__version__)�	Singleton�dict_deep_update�importerz
imav._versionr)�module�name�default�im360z'/var/imunify360/myimunify-freemium.flag�
MY_IMUNIFYzim360._versionz../.�IM360_CONFIG_SCHEMA_PATHz4/opt/imunify360/venv/share/imunify360/config_schema/)�notify�cleanup)�none�day�week�month�ii��FULL�MINIMAL���DENY�ALLOWa4############################################################################
# DO NOT MODIFY THIS FILE!!!                                               #
# USE /etc/sysconfig/imunify360/imunify360.config.d/ TO OVERRIDE DEFAULTS  #
############################################################################
a�############################################################################
# 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)�acronis�r1soft�
clusterlogics�sample)�
cloudlinux�cloudlinux_on_premisez./var/run/defence360agent/generic_sensor.sock.2�varr$�env�returnc��	t||��S#t$r|cYSt$r(}td�|����|�d}~wwxYw)Nz{}: integer required)�int�KeyError�
ValueError�format)r>r$r?�es    �U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/config.py�int_from_envvarrHqsu��D��3�s�8�}�}������������D�D�D��/�6�6�s�;�;�<�<�!�C�����D���s��A�	A�#A�Ac���d}d}	||}|���}||vrdS||vrdStd�|||z�����#t$r|cYSwxYw)N)�true�t�yes�y�1)�false�f�no�n�0TFz{}: should be one of {})�lowerrDrErC)r>r$r?�	TRUE_VALS�
FALSE_VALS�vals      rG�bool_from_envvarrXzs���/�I�/�J�
��#�h���i�i�k�k���)����4��*����5��%�,�,�S�)�j�2H�I�I�
�
�	
�������������s�A�A#�"A#c��tj�tj�t��|��S�N)�os�path�join�dirname�__file__��relpaths rG�
_self_rel2absrb�s&��
�7�<�<������1�1�7�;�;�;�c��tj�tj�t	t
����|��SrZ)r[r\r]r^rb�
AGENT_CONFr`s rG�conf_rel2absrf�s.��
�7�<�<�����
�j�(A�(A�B�B�G�L�L�Lrcc���	t|d��5}|������cddd��S#1swxYwYdS#t$rYdSwxYw)z1Returns content for existing file, otherwise None�rN)�open�read�strip�OSError)r\rPs  rG�_slurp_filerm�s����
�$��_�_�	$���6�6�8�8�>�>�#�#�	$�	$�	$�	$�	$�	$�	$�	$�	$�	$�	$�	$����	$�	$�	$�	$�	$�	$�������t�t����s3�A�&A�A�A	�	A�A	�
A�
A �A �usernamec�V�t|������}|�|��}|�|�|��}|�||fSt�d||��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)�
ConfigFile�config_to_dict�get�logger�debug�UserType�ROOT)�section�optionrn�user_config�user_section�
user_value�root_configs       rG�choose_value_from_configr~�s����h�/�/�/�>�>�@�@�K��?�?�7�+�+�L���!�%�%�f�-�-�
��!��x�'�'�
�L�L�5�w��G�G�G��,�,�-�-�/�/�K��w���'���6�6rcc�J�tj�t��S)zG
    Just checks if this is server with MyImunify Freemium license
    )r[r\�exists�FREEMIUM_FEATURE_FLAG�rcrG�is_mi_freemium_licenser��s���7�>�>�/�0�0�0rcc��eZdZdd�Zd�ZdS)�
FromConfigNc�>�||_||_||_d|_dSrZ)rxry�_config_cls�_config_instance)�selfrxry�
config_clss    rG�__init__zFromConfig.__init__�s&��������%��� $����rcc���|j�4|j�t��|_n|���|_|j���|j}|j�
||jS|SrZ)r�r�rqrrrxry)r��instance�owner�
section_values    rG�__get__zFromConfig.__get__�sm��� �(���'�(2����%�%�(,�(8�(8�(:�(:��%��-�<�<�>�>�t�|�L�
��;�"� ���-�-��rc�NN)�__name__�
__module__�__qualname__r�r�r�rcrGr�r��s7������%�%�%�%�
�
�
�
�
rcr�c�8�eZdZed��Zedd�d�Zd�ZdS)�FromFlagFile�/var/imunify360.��coercer$c�0�||_||_||_dSrZ)r#r�r$)r�r#r�r$s    rGr�zFromFlagFile.__init__�s����	��������rcc��|j|jz}|���r.|�|���p|j��SdSrZ)�LOCATIONr#r�r��	read_textr$)r�r�r�r\s    rGr�zFromFlagFile.__get__�sP���}�t�y�(���;�;�=�=�	A��;�;�t�~�~�/�/�?�4�<�@�@�@�	A�	ArcN)r�r�r�rr��boolr�r�r�rcrGr�r��sV�������t�%�&�&�H�'+�S������
A�A�A�A�Arcr�T��rootc��|rdnd}i}tjtg��D]0}t||d���}|��}t	||d����1|S)N�get_root_config�get_non_root_configc��iSrZr�r�rcrG�<lambda>z1_get_combined_validation_schema.<locals>.<lambda>�s���rcF)�allow_overwrite)r!�iter_modules�CONFIG_VALIDATORS_DIR_PATH�getattrr )r��	func_name�combined_schemar"�
get_schema�schemas      rG�_get_combined_validation_schemar��st��%)�D�!�!�/D�I��O��'�)C�(D�E�E�I�I���V�Y�
�
�;�;�
��������&�%�H�H�H�H�H��rc)�maxsizec��t��SrZ�r�r�rcrG�config_schema_rootr��s��*�,�,�,rcc�"�td���S)NFr�r�r�rcrG�config_schema_non_rootr��s��*��6�6�6�6rc�CUSTOM_BILLING�dict�string)�typer$�nullable�boolean)r�r$)�upgrade_url�upgrade_url_360�billing_notifications�
ip_license)r�r�r$c��eZdZdS)�ConfigValidationErrorN)r�r�r�r�rcrGr�r�s�������Drcr�c��eZdZdZesdezndZeZeZ	e
Zej
�dd��ZdZdZdZd	Zd
ZdZdZdZd
Zej�ed��ZdZej�ee��Zej�ee��ZdZdZ ej�ed��Z!dZ"dZ#dZ$dZ%dZ&esdndZ'dZ(e)d��Z*dZ+dS)�Core�
imunify360z%s agentzimunify antivirus�IMUNIFY360_API_URLzhttps://api.imunify360.com�
z.el9�z/var/imunify360/tmpzimunify360-merged.configz&imunify360-merged-nonprivileged.configzimunify360.configz/etc/imunify360rz�/etc/sysconfig/imunify360i�i�zimunify360.config.dz.imunify360.backup_configz
hooks.yamlzcustom_billing.configz/var/imunify360/hookszimunify360-agentzimunify-antiviruszunified-access-logger.confz#/etc/sysconfig/imunify360/.go_agent�<N),r�r�r��PRODUCT�ANTIVIRUS_MODE�NAME�
av_version�
AV_VERSION�core_version�CORE_VERSION�_version�VERSIONr[�environrs�API_BASE_URL�DEFAULT_SOCKET_TIMEOUT�DIST�
FILE_UMASK�TMPDIR�MERGED_CONFIG_FILE_NAME�%MERGED_NONPRIVILEGED_CONFIG_FILE_NAME�USER_CONFIG_FILE_NAME�LOCAL_CONFIG_FILE_NAME�
CONFIG_DIRr\r]�USER_CONFDIR�GLOBAL_CONFDIR�MERGED_CONFIG_FILE_PATH�%MERGED_NONPRIVILEGED_CONFIG_FILE_PATH�MERGED_CONFIG_FILE_PERMISSION�+MERGED_NONPRIVILEGED_CONFIG_FILE_PERMISSION�LOCAL_CONFIG_FILE_PATH�
CONFIG_D_NAME�BACKUP_CONFIGFILENAME�HOOKS_CONFIGFILENAME�CUSTOM_BILLING_CONFIGFILENAME�INBOX_HOOKS_DIR�SVC_NAME�$UNIFIED_ACCESS_LOGGER_CONFIGFILENAMEr�GO_FLAG_FILE�%SIGNAL_HANDLER_MIGRATION_TIMEOUT_SECSr�rcrGr�r�	sV�������G�'5�N�:����;N�D��J��L��G��:�>�>��:���L� ���D��J�
"�F�8��0�*�0��0��"�J��7�<�<�
�M�:�:�L�0�N� �g�l�l��/����-/�G�L�L��=�-�-�)�%*�!�27�/��W�\�\�.�:M�N�N��)�M�7��'��$;�!�-�O�#1�I���6I�
�,H�(��4�=�>�>�L�,.�)�)�)rcr�c� �eZdZe	ddededefd���Ze				ddededed	ed
eddfd���Zedd
���Z	e	dded
edefd���Z
edeedefd���Z
dedeedefd�Zdedeededdfd�ZdS)�IConfigTF�	normalize�
force_readr@c��t�rZ��NotImplementedError�r�r�r�s   rGrrzIConfig.config_to_dict7�
��"�!rc�data�validate�	overwrite�without_defaultsNc��t�rZr��r�r�r�r�r�r�s      rG�dict_to_configzIConfig.dict_to_config=s
��"�!rcc��t�rZr��r�s rGr�zIConfig.validateH���!�!rc�configc��t�rZr��r�rr�s   rGr�zIConfig.normalizeLr�rc�	timestampc��t�rZr��r�rs  rG�modified_sincezIConfig.modified_sinceRr�rcrxryc��|r;|����|i���|��SdSrZ)rrrs)r�rxrys   rGrszIConfig.getVs@���	F��&�&�(�(�,�,�W�b�9�9�=�=�f�E�E�E��trc�valuec�@�|r|�|||ii��dSdSrZ)r�)r�rxryrs    rG�setzIConfig.set[s7���	<�����6�5�/� :�;�;�;�;�;�	<�	<rc�TF�TTFF�r@N�F)r�r�r�rr�r�rrrr�r�r�r�floatr�strrrsr
r�rcrGr�r�6s��������9>�"�"��"�26�"�	
�"�"�"��^�"�
����!&�
"�"��"��"��	"�
�"��
"�
�"�"�"��^�"��"�"�"��^�"��8=�"�"��"�15�"�	
�"�"�"��^�"�
�"����"�D�"�"�"��^�"��3����
��#�����
<�3�<���
�<�c�<�d�<�<�<�<�<�<rcr�c��eZdZUeed<dS)�IConfigFiler\N)r�r�r�r�__annotations__r�rcrGrr`s�������

�I�I�I�I�Ircrc��eZdZdZdS)�ProtocolSingletonze
    Needed to avoid metaclass conflict when implementing protocols that are
    also Singletons
    N)r�r�r��__doc__r�rcrGrrds���������rcrc��eZdZd�Zededefd���Zededefd���Z	ededefd���Z
dededefd	�Zd
S)�
Normalizerc��d|_i|_t�|��|_|�|j��|_dSrZ)�_config�_normalized_config�ConfigsValidator�get_validation_schema�_schema�_get_schema_without_defaults�_schema_without_defaults)r��validation_schemas  rGr�zNormalizer.__init__lsP��*.���(*���'�=�=��
�
���)-�(I�(I��L�)
�)
��%�%�%rc�_dictr@c�D���fd�|���D��S)Nc�x��i|]6\}}|dv�	|t|t��r��|��n|��7S))r$�default_setter)�
isinstancer�r)�.0�keyr�clss   �rG�
<dictcomp>z;Normalizer._get_schema_without_defaults.<locals>.<dictcomp>xs]���
�
�
���U��7�7�7�	
��%��&�&���1�1�%�8�8�8��7�7�7rc��items)r)r"s` rGrz'Normalizer._get_schema_without_defaultsvs8���
�
�
�
�$�k�k�m�m�	
�
�
�	
rcrc���i}|���D]W\}}t|t��r6|���D] \}}|�||�|i��|<�!�P|r|||<�X|SrZ)r,r&r��
setdefault)r�
new_configrx�optionsryrs      rG�remove_nullzNormalizer.remove_null�s���:<�
� &�����		.�		.��G�W��'�4�(�(�
.�%,�]�]�_�_�K�K�M�F�E��(�EJ�
�-�-�g�r�:�:�6�B��K��
.�'.�
�7�#���rcc��t|��}|�|��}|jrt|j���|�td|�����|S)NzCerberus returned None for )�ConfigValidator�
normalized�errorsr�)rr��	validatorr4s    rG�_normalize_with_schemaz!Normalizer._normalize_with_schema�sa��#�F�+�+�	�%.�%9�%9�&�%A�%A�
���	:�'�	�(8�9�9�9���'�(N�f�(N�(N�O�O�O��rcr�c��|r|jn|j}|r+|�|��}|�||��S||jkr|jS|�||��}||_||_|jSrZ)r rr1r7rr)r�rr�r�r4s     rGr�zNormalizer.normalize�s���-=�O�D�)�)�4�<�	��	?��%�%�f�-�-�F��.�.�v�v�>�>�>��T�\�!�!��*�*��0�0���@�@�
����",����&�&rcN)
r�r�r�r��classmethodrr�r�staticmethodr1r7r�r�r�rcrGrrks�������
�
�
��
��
�T�
�
�
��[�
���G�������\����w��4�����\��
'��
'�4�
'�D�
'�
'�
'�
'�
'�
'rcrc
����eZdZdZddddddd�dededeeegeffded	e	d
e
f�fd�Zd�Zddede
fd�Zdde	de
fd�Z				d dede	de	de	de	ddfd�Zd�Zd�Zd�Zdeede	fd�Z�xZS)!�Config�NT)r\�
config_readerr!�
disclaimer�cached�permissionsr\r>r!r?r@rAc���t�����|s|sJ�|rtnt}|p|||p|j|���|_|jj|_|pt|_t|j��|_
dS)N)r?rA)�superr�rr�
DISCLAIMER�_config_readerr\r�r!r�_normalizer)	r�r\r>r!r?r@rA�config_reader_cls�	__class__s	        �rGr�zConfig.__init__�s����	���������$�}�$�$�$�28�J�.�.�l��+�
�/@�/@��!�4�T�_�#�0
�0
�0
���
�'�,��	�!2�!H�6H���%�d�&<�=�=����rcc�Z�d�|jj|j|j���S)NzW<{classname}(config_reader={config_reader!r}, validation_schema={validation_schema!r})>)�	classnamer>r!)rErHr�rEr!r�s rG�__repr__zConfig.__repr__�s6��
��&��n�1��-�"�4��
�
�		
rcFrr@c�8�|j�||��SrZ)rFr�rs   rGr�zConfig.normalize�s����)�)�&�2B�C�C�Crcr�c��|j�|���}|r|�|��}t|��S)zr
        Converts config file to dict

        :return dict: dictionary (key (section) / value (options))
        )r�)rE�read_config_filer�r)r�r�r�rs    rGrrzConfig.config_to_dict�sD���$�5�5��5�L�L���	,��^�^�F�+�+�F�����rcr�r�r�r�r�c�r�|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
        )r�r�r�r�N)�_dict_to_config_overwrite�_dict_to_configr�s      rGr�zConfig.dict_to_config�st��(�
	��*�*��!�#�!1�	
+�
�
�
�
�
�
� � ��!�#�!1�	
!�
�
�
�
�
rcc��|r t�||j��|r|�||���}|j�|��dS�N�r�)rr�r!r�rE�write_config_file)r�r�r�r�r�s     rGrPz Config._dict_to_config_overwritesb���	D��%�%�d�D�,B�C�C�C��	K��>�>�$�9I�>�J�J�D���-�-�d�3�3�3�3�3rcc� �t|j�����}t||��rW|r t�||j��|r|�||���}|j�|��dSdSrS)	rrErNr rr�r!r�rU)r�r�r�r�r�rs      rGrQzConfig._dict_to_config	s����$�-�>�>�@�@�A�A���F�D�)�)�	:��
J� �)�)�&�$�2H�I�I�I��
�����-=�(����
��1�1�&�9�9�9�9�9�	:�	:rcc��	|j�d���}nB#t$r5}d}t�d||��t||i��|�d}~wwxYw	t�||j��dS#t$r5}d}t�d||��t||i��|�d}~wwxYw)z/
        :raises ConfigsValidatorError
        F)�
ignore_errorszError during config validationz%s: %sNz+Imunify360 config does not match the scheme)
rErNrrt�error�ConfigsValidatorErrorrr�r!r�)r��config_dictrF�messages    rGr�zConfig.validates���	@��-�>�>�#�?���K�K���	@�	@�	@�6�G��L�L��7�A�.�.�.�'��w��8�8�a�?�����	@����
	@��%�%�k�4�3I�J�J�J�J�J��$�	@�	@�	@�C�G��L�L��7�A�.�.�.�'��w��8�8�a�?�����	@���s,��
A�0A�A�! B�
C�
0B=�=Crc�6�|j�|��SrZ)rErrs  rGrzConfig.modified_since(����"�1�1�)�<�<�<rcrrr)r�r�r�rDrrrrr
r�rBr�rKr�r�rrr�rPrQr�rrr�
__classcell__�rHs@rGr<r<�s���������J��&*�CG����>�>�>��	>�
$�>�!��(�2�w�;�*?�!?�@�
>��>��>��>�>�>�>�>�>�.	
�	
�	
�D�D��D�D�D�D�D�D�
 �
 ��
 �$�
 �
 �
 �
 ����!&�
!�!��!��!��	!�
�!��
!�
�!�!�!�!�F4�4�4�	:�	:�	:�@�@�@�(=����=�D�=�=�=�=�=�=�=�=rcr<)�	metaclassc���eZdZ�fd�Z�xZS)�
UserConfigc����||_tj�tj|tj��}t���|t||��t���dS)N)r\r>r!)rnr[r\r]r�r�r�rCr�rr�)r�rnr\rHs   �rGr�zUserConfig.__init__-sl��� ��
��w�|�|���x��)C�
�
��	������*�4��:�:�4�	�	
�	
�	
�	
�	
rc�r�r�r�r�r_r`s@rGrcrc,s8�������	
�	
�	
�	
�	
�	
�	
�	
�	
rcrcc
���eZdZdddd�dededef�fd�Z	dd	ed
edefd�Z				dd
eded	edededdfd�Z	dd�Z
	ddededefd�Zdee
defd�Z�xZS)�SystemConfigN)�local_config�
merged_config�nonpriv_merged_configrhrirjc����t�����|p
t��|_|p
t	��|_|p
t
��|_dSrZ)rCr��LocalConfig�
_local_config�MergedConfig�_merged_config�MergedNonPrivilegedConfig�_nonpriv_merged_config)r�rhrirjrHs    �rGr�zSystemConfig.__init__:sZ���	��������)�:�[�]�]���+�=�|�~�~���!�@�%>�%@�%@�	
�#�#�#rcTFr�r�r@c�:�|j�||���S)N�r�r�)rorrr�s   rGrrzSystemConfig.config_to_dictHs)���"�1�1��J�2�
�
�	
rcr�r�r�r�c�D�|j�|||||���dS�N)r�r�r�r�)rmr�r�s      rGr�zSystemConfig.dict_to_configOs=��	
��)�)�����-�	*�	
�	
�	
�	
�	
rcc�j�|j���|j���dSrZ)ror�rqr�s rGr�zSystemConfig.validate_s2����$�$�&�&�&��#�,�,�.�.�.�.�.rcrc�:�|j�||���S)N)rr�)ror�rs   rGr�zSystemConfig.normalizecs*���"�,�,��,<�-�
�
�	
rcrc�6�|j�|��SrZ)rorrs  rGrzSystemConfig.modified_sincejr^rcr�TTFTr
r)r�r�r�r<r�r�r�rrrr�r�r�rrrr_r`s@rGrgrg9sw������� $� $�(,�
�
�
��
��	
�
 &�
�
�
�
�
�
�:?�
�
��
�26�
�	
�
�
�
�
����!%�

�
��
��
��	
�
�
��

�
�
�
�
�
� /�/�/�/�
9>�
�
��
�15�
�	
�
�
�
�
�=����=�D�=�=�=�=�=�=�=�=rcrgr\c��|r%t|t��st|���S|rt|���St	��S)Nrp�r\)r&rBrcr<rg)rnr\s  rG�config_file_factoryr|nsQ����
�8�S�1�1���8�,�,�,�,�	
���4� � � � ��~�~�rcc��tt�����}|jD]}|�|��rdS�dS)NTF)�Merger�get_layer_names�layersr)r�merger�layers   rG�any_layer_modified_sincer�zsS��
�F�*�*�,�,�
-�
-�F���������	�*�*�	��4�4�	��5rc)r\r?rA)r>c
���eZdZdZejddddd�dedeee	geffde
f�fd�Z				dd
ededed
ededdf�fd�
Z
�xZS)rlzt
    Config (/etc/sysconfig/imunify360/imunify360.config) should contain
    options changed by a customer only
    NF�r\r>r!r?r@r>r!r?c�T��t���|||||���dS)Nr��rCr�)r�r\r>r!r?r@rHs      �rGr�zLocalConfig.__init__�s?���	������'�/�!��	�	
�	
�	
�	
�	
rcTr�r�r�r�r�r@c�P��t���|||||���Sru)rCr�)r�r�r�r�r�r�rHs      �rGr�zLocalConfig.dict_to_config�s5����w�w�%�%�����-�&�
�
�	
rcry)r�r�r�rr�r�rrrr
rr�r�r�r_r`s@rGrlrl�s����������
(�&*�CG���
�
�
�$�	
�
!��(�2�w�;�*?�!?�@�
��

�
�
�
�
�
�(���!%�

�
��
��
��	
�
�
��

�
�
�
�
�
�
�
�
�
�
�
rcrlc��eZdZej�ejej��Z	dZ
d	d�Zed���Z
d	d�Zedefd���ZdS)
�
BaseMergerz90-local.configFc�<��|�_�fd�|D���_dS)Nc�v��g|]5}ttj��j|�������6S)r{)r<r[r\r]�DIR)r'r#r�s  �rG�
<listcomp>z'BaseMerger.__init__.<locals>.<listcomp>�sC���
�
�
�:>�F�����T�X�t�4�4�5�5�5�
�
�
rc)�_include_defaultsr�)r��names�include_defaultss`  rGr�zBaseMerger.__init__�s9���!1���
�
�
�
�BG�
�
�
����rcc��tj�|j��r&t	tj|j����ngSrZ)r[r\�isdirr��sorted�listdir�r)s rGrzBaseMerger.get_layer_names�s6��.0�g�m�m�C�G�.D�.D�L�v�b�j���)�)�*�*�*�"�Lrcc����g}|jr>tt���id���}|�|��|�fd�|jD��z
}|�|��S)NFrTc�>��g|]}|�d������S)Frs)rr)r'r�r�s  �rGr�z.BaseMerger.configs_to_dict.<locals>.<listcomp>�s<���
�
�
��
� � �5�Z� �H�H�
�
�
rc)r�rr�r��appendr��_build_effective_config)r�r��layer_dict_list�defaultss `  rG�configs_to_dictzBaseMerger.configs_to_dict�s�������!�	-�!�"4�5�5�?�?��U�@���H�
�"�"�8�,�,�,��
�
�
�
���
�
�
�	
���+�+�O�<�<�<rcr�c��i}|D]O}|���D]8\}}|��||vri||<|���D]\}}|�||||<��9�P|SrZr+)r)r��	effective�
layer_dictrxr0ryrs        rGr�z"BaseMerger._build_effective_config�s���%'�	�)�	;�	;�J�$.�$4�$4�$6�$6�
;�
;� ����?���)�+�+�)+�I�g�&�%,�]�]�_�_�;�;�M�F�E��(�5:�	�'�*�6�2��;�
;��rcNr)r�r�r�r[r\r]r�r�r�r��LOCAL_CONFIG_NAMEr�r9rr��listr�r�rcrGr�r��s�������
�'�,�,�t�*�D�,>�
?�
?�C�)��
�
�
�
��M�M��[�M�=�=�=�=���d�����[���rcr�c�$��eZdZdef�fd�Z�xZS)�
MutableMergerr�c���t||j��}|d|�}t���|d���dS�NT�r�)rr�rCr��r�r��idxrHs   �rGr�zMutableMerger.__init__�sE����%��!7�8�8���d�s�d���
��������6�6�6�6�6rc�r�r�r�rr�r_r`s@rGr�r��sD�������7�h�7�7�7�7�7�7�7�7�7�7rcr�c�$��eZdZdef�fd�Z�xZS)�ImmutableMergerr�c���t||j��}||d�}t���|d���dS�NFr�)rr�rCr�r�s   �rGr�zImmutableMerger.__init__�sE����5�$�"8�9�9���c�d�d���
��������7�7�7�7�7rcr�r`s@rGr�r��sD�������8�h�8�8�8�8�8�8�8�8�8�8rcr�c�$��eZdZdef�fd�Z�xZS)�
NonBaseMergerr�c�N��t���|d���dSr�r��r�r�rHs  �rGr�zNonBaseMerger.__init__�s&���
��������7�7�7�7�7rcr�r`s@rGr�r��sD�������8�h�8�8�8�8�8�8�8�8�8�8rcr�c�n��eZdZddgddgdgdgd�Z�fd�Zed	���Zed
edefd���Z	�xZ
S)
r~N�user_override_proactive_defense�num_days�limit�enable�enable_scan_modsec)�PROACTIVE_DEFENCE�PERMISSIONS�INCIDENT_LOGGING�ERROR_REPORTING�MALWARE_SCANNINGc�N��t���|d���dSr�r�r�s  �rGr�zMerger.__init__s&���
��������6�6�6�6�6rcc���||�����}|���}|jD]I}tj�|j��s#t�d|j��dS�J	t�	|��tt��}|�|d���}|�
|��\}}t���|d���t!���|dd���dS#t"t$f$r&}t�d|��Yd}~dSd}~wwxYw)NzGAborting merged config update: Config layer %s disappeared during mergeFrT)r�)r�r�zConfig file is invalid! %s)rr�r�r[r\�lexistsrt�warningrr�rr�r��_split_settingsrnr�rprZr�)r)r�r[r��
normalizer�	priv_dict�nonpriv_dictrFs        rG�update_merged_configzMerger.update_merged_configs�����S�(�(�*�*�+�+���,�,�.�.���]�	�	�E��7�?�?�5�:�.�.�
����?��J����
���

�	��%�%�k�2�2�2�$�$6�7�7�J�$�.�.��e�/���K�'*�&9�&9�+�&F�&F�#�I�|��N�N�)�)�)�e�)�D�D�D�%�'�'�6�6��u��
7�
�
�
�
�
��&�'<�=�	<�	<�	<��N�N�7��;�;�;�;�;�;�;�;�;�����	<���s�D*�*E!�;E�E!r[r@c��t|��}i}|j���D]T\}}||vr�
|�t||��||<�%|D],}|||vr ||vri||<||||||<�-�U||fS)z�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.
        )r�NONPRIVILEGED_SETTINGSr,)r)r[r�r�rxr0rys       rGr�zMerger._split_settings>s����[�)�)�	�(*�� #� :� @� @� B� B�
	�
	��G�W��k�)�)����(0��W�1E�(F�(F��W�%�%�%���F���W�!5�5�5�"�,�6�6�46�L��1�8C�G�8L�"�9��W�-�f�5��	��,�&�&rc)r�r�r�r�r�r9r�r��tupler�r_r`s@rGr~r~�s��������!�-�
�
��
�

�
�
!�
����&7�7�7�7�7��)�)��[�)�V�'�$�'�5�'�'�'��[�'�'�'�'�'rcr~c�,��eZdZedd��fd�
Zd�Z�xZS)r3T��
allow_unknown�purge_readonlyc�@��t��j|||d�|��dS)z�
        Initialises ConfigValidator(Validator)
        for more details on Validator params please check
        https://docs.python-cerberus.org/en/stable/validation-rules.html
        r�Nr�)r�r�r��args�kwargsrHs     �rGr�zConfigValidator.__init__[sB���	�����
�'�)�	
�	
��		
�	
�	
�	
�	
rcc�h�|j�di���dd��rdS|S)Nr&r�FT)�
root_documentrs)r�rs  rG�(_normalize_coerce_user_override_pd_rulesz8ConfigValidator._normalize_coerce_user_override_pd_rulesns7����!�!�,��3�3�7�7��%�H�H�	��4��rc)r�r�r�r�r�r�r_r`s@rGr3r3ZsY�������%��	
�
�
�
�
�
�
�&������rcr3c�"�eZdZdejzZdS)�	Packagingz/opt/imunify360/venv/share/%sN)r�r�r�r�r��DATADIRr�rcrGr�r�ts������-���<�G�G�Grcr�c��eZdZdZdZdZdZdZede	��Z
edee
d�	���������Zed
d��Zedee
d�	���������ZdS)
�	SimpleRpciz(/var/run/defence360agent/simple_rpc.sockz1/var/run/defence360agent/non_root_simple_rpc.sockz.imunify360_token_{suffix}��I360_SOCKET_ACTIVATION�IMUNIFY360_INACTIVITY_TIMEOUT���minutes�I360_RPC_MAX_CONNECTIONS�I360_RPC_READ_TIMEOUTN)r�r�r��CLIENT_TIMEOUT�SOCKET_PATH�NON_ROOT_SOCKET_PATH�TOKEN_FILE_TMPL�
TOKEN_MASKrXr��SOCKET_ACTIVATIONrHrBr	�
total_seconds�INACTIVITY_TIMEOUT�MAX_CONCURRENT_CONNECTIONS�READ_TIMEOUTr�rcrGr�r�xs��������N�<�K�N��2�O��J�(�(� �����)��'���I�I�a� � � �.�.�0�0�1�1����"1��"�C�"�"��#�?����I�I�a� � � �.�.�0�0�1�1���L�L�Lrcr�c�p�eZdZdej�dej�d�Zdej�d�Zdej�dej�d�ZdS)�Modelz/var/�/z.dbz
/proactive.dbz-resident.dbN)r�r�r�r�r��PATH�PROACTIVE_PATH�
RESIDENT_PATHr�rcrGr�r��sN�������"�l�l�l�D�L�L�L�9�D�D�/3�|�|�|�=�N�N�04����d�l�l�l�K�M�M�Mrcr�c��eZdZed������Zed������ZdZgZdZ	dS)�FilesUpdate�r���N)
r�r�r�r	r��PERIOD�TIMEOUT�SOCKET_TIMEOUT�DISABLED�DAYS_TO_KEEPr�rcrGr�r��s_������
�Y�r�
"�
"�
"�
0�
0�
2�
2�F��i��#�#�#�1�1�3�3�G��N�
�H��L�L�Lrcr�c�,�eZdZdZdZed���ZdS)�CountryInfoz2/var/imunify360/files/geo/v1/GeoLite2-Country.mmdbz>/var/imunify360/files/geo/v1/GeoLite2-Country-Locations-en.csvc�,�d�|��S)Nz2/var/imunify360/files/geo/v1/CountrySubnets-{}.txt)rE)�country_codes rG�country_subnets_filez CountryInfo.country_subnets_file�s��C�J�J��
�
�	
rcN)r�r�r��DB�LOCATIONS_DBr:r�r�rcrGr�r��s?������	=�B�	I���
�
��\�
�
�
rcr�c�j�eZdZejdedejz����Ze	dd��Z
dS)�Sentry�IMUNITY360_SENTRY_DSNz	%s/sentryr�r�N)r�r�r�r[�getenvrmr�r��DSNr��ENABLEr�rcrGrr�sL������
�"�)����[�9�;L�-L�!M�!M���C��Z�)�8�
4�
4�F�F�Frcrc��eZdZdZdZdZdZdZedd��Z	edd��Z
edd��Zedd	��Zedd
��Z
edd��Zedd��Zedd
��ZdZdZdZedd���Zedd���Zedd���Zedd��Zedd��Zedd��Zedd��Zedd��Zedd��Zedd��Zed��Zedd��Z d S)!�Malwarei,r�r�r�r��max_targets_per_scan_type�max_path_len�enable_scan_inotify�enable_scan_pure_ftpd�sends_file_for_analysis�cloud_assisted_scan�
rapid_scan�crontabsz$/var/imunify360/aibolit/scans.picklei�z/var/imunify360/cleanup_storage�MALWARE_CLEANUP�trim_file_instead_of_removal�rxry�keep_original_files_days�scan_modified_files�max_signature_size_to_scan�max_cloudscan_size_to_scan�max_mrs_upload_file�,rapid_scan_rescan_unchanging_files_frequency�	hyperscan�MALWARE_DATABASE_SCANr��enable_scan_cpanel�disable_cloudav�
db_timeoutN)!r�r�r��SCAN_CHECK_PERIOD�CONSECUTIVE_ERROR_LIMIT�INOTIFY_SCAN_PERIOD�CONFIG_CHECK_PERIOD�CONFLICTS_CHECK_PERIODr��MAX_TARGETS_PER_SCAN_TYPE�MAX_PATH_LEN�INOTIFY_ENABLED�	PURE_SCAN�
SEND_FILES�CLOUD_ASSISTED_SCAN�
RAPID_SCAN�CRONTABS_SCAN_ENABLED�
SCANS_PATH�FILE_PREVIEW_BYTES_NUM�CLEANUP_STORAGE�CLEANUP_TRIM�CLEANUP_KEEP�SCAN_MODIFIED_FILES�MAX_SIGNATURE_SIZE_TO_SCAN�MAX_CLOUDSCAN_SIZE_TO_SCAN�MAX_MRS_UPLOAD_FILE�,RAPID_SCAN_RESCAN_UNCHANGING_FILES_FREQUENCY�	HYPERSCAN�DATABASE_SCAN_ENABLED�CPANEL_SCAN_ENABLEDr��CLEANUP_DISABLE_CLOUDAV�MDS_DB_TIMEOUTr�rcrGr	r	�s��������� ������ �� *�
��7�!�!���:�0�.�A�A�L� �j�!3�5J�K�K�O��
�-�/F�G�G�I���.�0I�J�J�J�$�*�%7�9N�O�O����.��=�=�J�&�J�'9�:�F�F��7�J�'��7�O��:�!�-����L��:�!�)����L�%�*�"�$�����
",���8�"�"��",���8�"�"��%�*�%7�9N�O�O��3=�:��J�4�4�0��
�-�{�;�;�I�&�J�'>��I�I��$�*�%7�9M�N�N��*�l�+<�=�=���Z� 7��F�F�N�N�Nrcr	c�p�eZdZdZed��Zed��Zeded���Zed��Z	dS)	�MalwareTunezc
    Experimental and testing-only purpose settings
     we don't want to expose to customers.
    �use_json�no_check_known_hashes�rapid_scan_basedir_overridez/homer��no_auto_upgradeN)
r�r�r�rr��USE_JSON_REPORT�NO_CHECK_KNOWN_HASHESr�RAPID_SCAN_BASEDIR_OVERRIDE�NO_AUTO_UPGRADEr�rcrGr=r=�sj��������
#�l�:�.�.�O�(�L�)@�A�A��".�,�%�d�G�#�#�#��#�l�#4�5�5�O�O�Orcr=c��eZdZeZeZeZeZdS)�MalwareScanScheduleIntervalN)r�r�r��NONE�DAY�WEEK�MONTHr�rcrGrGrGs"�������D�

�C��D��E�E�ErcrGc��eZdZdZdZdZedd���Zedd���Zedd���Z	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_SCHEDULE�intervalr�hour�day_of_week�day_of_monthN)r�r�r��CMD�	CRON_PATH�CRON_STRINGr��INTERVAL�HOUR�DAY_OF_WEEK�DAY_OF_MONTHr�rcrGrMrMs�������
D�C�3�I��K�
�z�'�����H��:�'�����D��*�'�����K��:�'�����L�L�LrcrMc���eZdZedd���Zedd���Zedd���Zedd���Zedd���Zedd���Z	edd	���Z
d
S)�MalwareScanIntensity�MALWARE_SCAN_INTENSITY�cpur�io�ram�
user_scan_cpu�user_scan_io�
user_scan_ram�resident_ramN)r�r�r�r��CPU�IO�RAM�USER_CPU�USER_IO�USER_RAM�RESIDENT_RAMr�rcrGr[r[(s�������
�*�(�����C�
��(��
�
�
�B��*�(�����C��z�(�����H��j�(�����G��z�(�����H��:�(�����L�L�Lrcr[c�\�eZdZedd���Zedd���Zedd���ZdS)�FileBasedResourceLimits�RESOURCE_MANAGEMENT�	cpu_limitr�io_limit�	ram_limitN)r�r�r�r�rdrerfr�rcrGrlrlGsf������
�*�%�����C�
��%��
�
�
�B��*�%�����C�C�Crcrlc�(�eZdZedd���ZdS)�
KernelCare�
KERNELCARE�edfrN)r�r�r�r��EDFr�rcrGrrrrVs*������
�*������C�C�Crcrrc���tj}|�Rtjdtjdtjdtjdi}|�tj	d��S|S)Nr2r.r�r�)
r	r6rGrHrKrJrIrsrMrV)r�freqs  rG�get_rapid_rescan_frequencyrx]sU���@�E��}�'�,�a�'�-�q�'�,�a�'�+�R�	
���x�x�+�4�a�8�8�8��Lrcc��eZdZdZej�ed��Zej�ed��Zej�edd��Z	ej�edd��Z
ej�edd��Zej�edd��Zej�edd	��Z
d
S)�MalwareSignaturesz/var/imunify360/files/sigs/v1/�rfxn�i360�aibolitzai-bolit-hoster-full.dbrzmds-ai-bolit-hoster.dbz	procu2.dbz
mds-procu2.dbN)r�r�r��_dirr[r\r]�RFXNr|�AI_BOLIT_HOSTER�AI_BOLIT_HYPERSCAN�MDS_AI_BOLIT_HOSTER�PROCU_DB�MDS_PROCU_DBr�rcrGrzrzjs�������+�D�
�7�<�<��f�%�%�D�
�7�<�<��f�%�%�D��g�l�l�4��4M�N�N�O�����d�I�{�C�C���'�,�,��i�1�����w�|�|�D�)�[�9�9�H��7�<�<��i��A�A�L�L�Lrcrzc�J�eZdZedd���Zedd���ZdZdZdS)�Logger�LOGGER�max_log_file_sizer�backup_counti�i�N)r�r�r�r��MAX_LOG_FILE_SIZE�BACKUP_COUNT�LOG_DIR_PERM�
LOG_FILE_PERMr�rcrGr�r�xsU������"�
��"������:������L�
�L��M�M�Mrcr�c��eZdZdZdZdS)rvr��non_rootN)r�r�r�rw�NON_ROOTr�rcrGrvrv�s�������D��H�H�Hrcrv�caller_type)r$c��eZdZdZdZdS)�UIRole�client�adminN)r�r�r��CLIENT�ADMINr�rcrGr�r��s������
�F��E�E�Ercr�c��eZdZdZdZdS)�NoCPz/etc/imunify360/scripts/domainsr2N)r�r�r��
CLIENT_SCRIPT�LATEST_VERSIONr�rcrGr�r��s������5�M��N�N�Nrcr�c���eZdZ�fd�Z�xZS)�CustomBillingConfigc���tj�dtj��}t���|t���dS)Nr��r\r!)r[r\r]r�r�rCr��CONFIG_SCHEMA_CUSTOM_BILLING)r�r\rHs  �rGr�zCustomBillingConfig.__init__�sS����w�|�|�'��)K�
�
��	������)E�	�	
�	
�	
�	
�	
rcrer`s@rGr�r��s8�������
�
�
�
�
�
�
�
�
rcr�c�~�eZdZedde���Zedde���Zedde���Zedde���ZdS)�
CustomBillingr�r��rxryr�r�r�r�N)	r�r�r�r�r��UPGRADE_URL�UPGRADE_URL_360�
NOTIFICATIONS�
IP_LICENSEr�rcrGr�r��s��������*� ��&����K�
!�j� � �&����O�
�J� �&�&����M�
�� ��&����J�J�Jrcr�c���eZdZedd���Zedd���Zedd���Zedd���Zedd���Zedd���Z	edd	���Z
d
S)�PermissionsConfigr��user_ignore_listr�allow_malware_scan�user_override_malware_actionsr��*allow_local_malware_ignore_list_management�use_plesk_service_plan�allow_wp_waf_rules_managementN)r�r�r�r��USER_IGNORE_LIST�ALLOW_MALWARE_SCAN�USER_OVERRIDE_MALWARE_ACTIONS�USER_OVERRIDE_PROACTIVE_DEFENSE�*ALLOW_LOCAL_MALWARE_IGNORE_LIST_MANAGEMENT�USE_PLESK_SERVICE_PLAN�ALLOW_WP_WAF_RULES_MANAGEMENTr�rcrGr�r��s�������!�z��!�����$���#�����%/�J��&E�%�%�%�!�'1�j��0�'�'�'�#�2<���;�2�2�2�.�(�Z��'�����%/�J��.�%�%�%�!�!�!rcr�c�B�eZdZedd���Zedd���ZdS)�MyImunifyConfigr&r�r�purchase_page_urlN)r�r�r�r��ENABLED�PURCHASE_PAGE_URLr�rcrGr�r��sK�������j������G�#�
��"������rcr�c�B�eZdZedd���Zedd���ZdS)�ControlPanelConfig�
CONTROL_PANEL�smart_advice_allowedr�advice_email_notificationN)r�r�r�r��SMART_ADVICE_ALLOWED�ADVICE_EMAIL_NOTIFICATIONr�rcrGr�r��sL������%�:��%�����!+�
��*�!�!�!���rcr�c������gd�}tjtjtjtjtjtjd��|����|������di���dd���d<���fd���fd�|D��}t||j��S)	N)
�BACKUP_RESTORErr�r�r�r�r&r�rN�	WORDPRESS))rNrO)rNrP)rNrQ)rNrR)r��default_action)r��moder��waf_enabledT)r�r�c�@��t��|i����}t�	�|i����}i}|���D]?\}}|�|��}|���||fd��r|||<�:|||<�@|S)NT)rrsr,)
rx�
admin_options�user_options�resulting_dictry�admin_valuer|�
admin_dict�overridable�	user_dicts
       ���rG�normalize_sectionz0effective_user_config.<locals>.normalize_section!s���� ������!<�!<�=�=�
��	�
�
�g�r� :� :�;�;����#0�#6�#6�#8�#8�		5�		5��F�K�%�)�)�&�1�1�J��&��O�O�W�f�$5�t�<�<�'�*4��v�&�&�)4��v�&�&��rcc�(��i|]}|�|����Sr�r�)r'rxr�s  �rGr*z)effective_user_config.<locals>.<dictcomp>2s4������07��"�"�7�+�+���rc)r�r�r�rrrs�fm_config_cleanuprn)�admin_configrz�allowed_sections�effective_configr�r�r�r�s    @@@@rG�effective_user_configr��s���������� �:��:��:��:��:��<�1��K�4�,�,�.�.�J��*�*�,�,�I�1;����R�1�1�	�c�-�����,�-��������"����;K������-�{�/C�D�D�Drcc�:�eZdZdxZ\ZZZZZeeeefZ	e
re	neZdS)�
HookEvents)�agent�licensezmalware-scanningzmalware-cleanupzmalware-detectedN)r�r�r��IM360_EVENTS�AGENT�LICENSEr�r�MALWARE_DETECTED�IMAV_EVENTSr��EVENTSr�rcrGr�r�9sS������	�
�L��
�����	����	�K�+�
<�[�[��F�F�Frcr�c�0�eZdZdeeeffd�Zd�ZdS)rZ�configs_to_errorsc��||_dSrZ)r�)r�r�s  rGr�zConfigsValidatorError.__init__Qs��!2����rcc��g}|j���D]\}}|�|�d|����� d�|��S)Nz: �
)r�r,r�r])r�r5rrYs    rGrKzConfigsValidatorError.__repr__Ts^����!�3�9�9�;�;�	2�	2�M�F�E��M�M�V�0�0��0�0�1�1�1�1��y�y�� � � rcN)r�r�r�rr<rr�rKr�rcrGrZrZPsG������3�$�v�s�{�*;�3�3�3�3�!�!�!�!�!rcrZc��eZdZdZed���Zed���Zeefdede	ee
fddfd���Zede	e
e
fde
fd	���ZdS)
rz@A class that has methods to validate configs bypassing the cachec�F�t�����dS)zN
        Validate merged config
        :raises ConfigsValidatorError
        N)rgr�r�s rG�validate_system_configz'ConfigsValidator.validate_system_config^s ��	�����!�!�!�!�!rcc��i}tt�����jD]H}	|����#t$r$}|�|j��Yd}~�Ad}~wwxYw|rt	|���dS)zf
        Validate all config layers, collect all errors
        :raises ConfigsValidatorError
        N)r~rr�r�rZ�updater�)r)r�r�rFs    rG�validate_config_layersz'ConfigsValidator.validate_config_layersfs������F�2�2�4�4�5�5�<�	>�	>�E�
>���� � � � ��(�
>�
>�
>�!�(�(��)<�=�=�=�=�=�=�=�=�����
>�����	;�'�(9�:�:�:�	;�	;s�A�
A4�A/�/A4r[r!r@Nc��|�|��}t|��}|�|��st|j���dS)z�
        Validate config represented by a dict
        :param config_dict: config to validate
        :param validation_schema: schema to validate config against
        :raises ConfigValidationError
        N)rr3r�r�r5)r)r[r!r��vs     rGr�zConfigsValidator.validatevsS���*�*�+<�=�=���F�#�#���z�z�+�&�&�	2�'���1�1�1�	2�	2rcc�8�t|��r
|��S|SrZ)�callable)r!s rGrz&ConfigsValidator.get_validation_schema�s*���%�&�&�	'�$�$�&�&�&� � rc)r�r�r�rr9r�r�r�r�rr
r�r:rrr�rcrGrr[s�������J�J��"�"��[�"��
;�
;��[�
;��4F�2�2��2�!��x��0�2�
�	2�2�2��[�2�"�!� ��(�!2�3�!�	�!�!�!��\�!�!�!rcrc�(�eZdZedd���ZdS)�
AdminContacts�ADMIN_CONTACTS�enable_icontact_notificationsrN)r�r�r�r��ENABLE_ICONTACT_NOTIFICATIONSr�rcrGr�r��s-������$.�J� �.�%�%�%�!�!�!rcr�c� �eZdZdZdZdZd�ZdS)�IContactMessageType�MalwareFound�ScanNotScheduled�Genericc��|jSrZ)rr�s rG�__str__zIContactMessageType.__str__�s
���z�rcN)r�r�r��
MALWARE_FOUND�SCAN_NOT_SCHEDULED�GENERICr�r�rcrGr�r��s3������"�M�+���G�����rcr��
BACKUP_SYSTEMF)r�r$r��allowed)�enabled�
backup_systemc�f��eZdZdZej�dej��e	d��fd�
Z
�xZS)�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 <init|disable> <backup-system>
#   or activate/deactivate appropriate feature in UI.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    r�r�c�N��t���||���dS)Nr�r�)r�r\r!rHs   �rGr�zBackupConfig.__init__�s)���	�����d�6G��H�H�H�H�Hrc)r�r�r�rDr[r\r]r�r��CONFIG_SCHEMA_BACKUP_SYSTEMr�r_r`s@rGrr�sv��������J�(�W�\�\�'��)C�
�
�6�
I�I�I�I�I�I�I�I�I�I�Ircrc��eZdZedde���Zedde���Zedd���Zedd���Ze	d	���Z
d
S)�
BackupRestorerrr�rr��cl_backup_allowedr�cl_on_premise_backup_allowedc�*�t|j��SrZ)�_get_backend_system�_BACKUP_SYSTEMr�s rGrzBackupRestore.backup_system�s��"�3�#5�6�6�6rcN)r�r�r�r�rr�r�CL_BACKUP_ALLOWED�CL_ON_PREMISE_BACKUP_ALLOWEDr9rr�rcrGr	r	�s��������j��	�l����G� �Z�������N�
#�
� �"�����$.�:� �-�$�$�$� �
�7�7��[�7�7�7rcr	c�t�ddlm}|ttfvrt}n|�dS|�|d���S)zo
    Get backup module from its name
    :param name: backup system name
    :return: backup system module
    r)�backup_backendsNT)�async_)�restore_infectedr�
CLOUDLINUX�CLOUDLINUX_ON_PREMISE�ACRONIS�backend)r#rs  rGr
r
�sQ��1�0�0�0�0�0��
�1�2�2�2����	
���t��"�"�4��"�5�5�5rcc��eZdZdZdZdhZdS)�
AcronisBackupzacronis-installer.log)i� i��i�)iZixN)r�r�r��LOG_NAME�PORTS�RANGEr�rcrGrrs!������'�H� �E�
�N�E�E�Ercrc�F�tdd|��\}}tjo|S)zs
    Checks is Agent should try restore malware file firts
    and returns user that set this action in config
    r��try_restore_from_backup_first)r~r	r�)rn�try_restore�_s   rG� should_try_autorestore_maliciousr"s/��
.��;�X���N�K��� �0�[�0rcc�v�tdd|��\}}tj��t|���z
}|S)Nr��max_days_in_backup)�days)r~r�nowr	)rn�max_daysr!�untils    rG�"choose_use_backups_start_from_dater)s?��*��.����K�H�a�
�L�N�N�Y�H�5�5�5�5�E��Lrcc�0�tdd|���\}}|S)Nr��generic_user_notificationsrp)r~)rn�should_sendr!s   rG�should_send_user_notificationsr-"s+��-��$�����N�K��
�rcc��eZdZedd��Zedd��Zedd��Zedd��Zedd��ZdS)�	Wordpressr��security_plugin_enabledr��waf_default�ai_bot_protection�ai_bot_protection_presetN)	r�r�r�r��SECURITY_PLUGIN_ENABLED�WAF_ENABLED�WAF_DEFAULT�AI_BOT_PROTECTION�AI_BOT_PROTECTION_PRESETr�rcrGr/r/+sw������(�j��.�����*�[�-�8�8�K��*�[�-�8�8�K�"�
�;�0C�D�D��)�z��/� � ���rcr/c��eZdZdZdZdZdZdS)�
HackerTrapr�zmalware_found_b64.listzmalware_standalone_b64.listz%/opt/imunify360/proactive/dangerlist/N)r�r�r�r�r��SA_NAME�DIR_PDr�rcrGr:r:7s"������
�C�#�D�+�G�
4�F�F�Frcr:rZr�)�r�	functools�loggingr[�abcr�bisectrr�contextvarsr�copyrrr	�enumr
�pathlibr�typingrr
rrrrrrrrr�cerberusr�)defence360agent.contracts.config_providerrrrrr�+defence360agent.feature_management.checkersrr��defence360agent._versionrr��defence360agent.utilsrr r!rsr��	getLoggerr�rtr�r�r��MY_IMUNIFY_KEYr�rer�r��NOTIFY�CLEANUPrHrIrJrK�DEFAULT_INTENSITY_CPU�DEFAULT_INTENSITY_IO�DEFAULT_INTENSITY_RAM�DEFAULT_INTENSITY_RESIDENT_RAM�%DEFAULT_RESOURCE_MANAGEMENT_CPU_LIMIT�$DEFAULT_RESOURCE_MANAGEMENT_IO_LIMIT�%DEFAULT_RESOURCE_MANAGEMENT_RAM_LIMIT�MODSEC_RULESET_FULL�MODSEC_RULESET_MINIMAL�_DOS_DETECTOR_DEFAULT_LIMIT�_DOS_DETECTOR_MIN_LIMIT�_DOS_DETECTOR_MIN_INTERVAL�PORT_BLOCKING_MODE_DENY�PORT_BLOCKING_MODE_ALLOW�DO_NOT_MODIFY_DISCLAMER�DEFAULT_CONFIG_DISCLAMER�CPANEL�PLESK�DIRECTADMINr�R1SOFT�
CLUSTERLOGICS�SAMPLE_BACKENDrr�GENERIC_SENSOR_SOCKET_PATHrrBrHr�rXrbrfrmr~r�r�r�r��	lru_cacher�r�r��	Exceptionr�r�r�rrrr<rcrgr|r��partialr�r�rnr�r�rprlr�r�r�r�r~r3r�r�r�r�r�rr	r=rGrMr[rlrrrxrzr�rvrwr�rr�r�r�r�r�r�r�r�r�rZrrqr�r�rrr	r
rr"r)r-r/r:r�rcrG�<module>ris��������������	�	�	�	�������,�,�,�,�,�,�,�,�"�"�"�"�"�"�������(�(�(�(�(�(�(�(�����������������������������������������������������������������A�@�@�@�@�@�G�G�G�G�G�G�G�G�G�G�
�X�\�������
�
��	�8�	$�	$��$�X�_�W�-�-�-��A�����8�<��-�������
�!�T��J�N�N�"�>������&����7���c�4��������!%��()�%�'(�$�(+�%���"��!������ ��"������>����{�!E�������$I�!�
�!�M��<>�:�D�D��D�s�D��D�#�D�D�D�D�-/�J�
�
�	�
��
�")�
�	�
�
�
�
�(<�<�<�M�M�M����-1�7�7�"�T�z�7�
�3��d�
�?��7�7�7�7�&1�1�1���������(A�A�A�A�A�A�A�A�-1���������Q����-�-� ��-����Q����7�7� ��7�
��!�� ���!�� � � �
/8�D�%I�%I�#,��>�>�

�

��!�� ��,	�	�	�	�	�I�	�	�	�*/�*/�*/�*/�*/�*/�*/�*/�Z'<�'<�'<�'<�'<�h�'<�'<�'<�T�����'�8���������
�y����;'�;'�;'�;'�;'�;'�;'�;'�|@=�@=�@=�@=�@=�W� 1�@=�@=�@=�@=�F

�

�

�

�

��

�

�

�2=�2=�2=�2=�2=�7�&7�2=�2=�2=�2=�lGK����u�S�#�X��'��6>�s�m��������4�����!�y� �
�'�'�
�
)�*��6��������.�I�-�
�'�'�
�
7�*��D��������%
�%
�%
�%
�%
�&�%
�%
�%
�P(�(�(�(�(�(�(�(�V7�7�7�7�7�J�7�7�7�8�8�8�8�8�j�8�8�8�8�8�8�8�8�J�8�8�8�
\'�\'�\'�\'�\'�Z�\'�\'�\'�~�����i����4=�=�=�=�=�=�=�=���������4L�L�L�L�L�L�L�L���������&
�
�
�
�
�
�
�
�5�5�5�5�5�5�5�5�5G�5G�5G�5G�5G�5G�5G�5G�p6�6�6�6�6�6�6�6�����������������4��������>����������������
�
�
�B�B�B�B�B�B�B�B����������������� *�z�-���O�O�O��Z��_�O�O�O���������
��������
�
�
�
�
�&�
�
�
���������.��������<����������������DE�DE�DE�N=�=�=�=�=�=�=�=�.!�!�!�!�!�I�!�!�!�3!�3!�3!�3!�3!�3!�3!�3!�l!�
��������������#�t������"� ���
!�� �������)�!�"�
�	��
�
�,�1����<I�I�I�I�I�6�I�I�I�:7�7�7�7�7�7�7�7�.6�6�6�"��������1�s�1�t�1�1�1�1����������S��T�����	�	�	�	�	�	�	�	�5�5�5�5�5�5�5�5�5�5rcdefence360agent/contracts/__pycache__/config_provider.cpython-311.opt-1.pyc0000644000000000000000000005342600000000000023610 0ustar  �

�
��M������ddlZddlZddlZddlZddlmZddlmZddlm	Z	ddl
mZmZm
Z
ddlZddlZddlmZddlmZeje��ZdZGd	�d
e
��ZGd�de��ZGd
�d��Zdeedeefd�Zdedefd�Zdededefd�ZGd�d��Z Gd�de ��Z!Gd�de!��Z"Gd�d e!��Z#dS)!�N)�abstractmethod)�suppress)�dedent)�Mapping�Optional�Protocol)�atomic_rewrite)�open_dir_no_symlinksic�~�eZdZe	ddedefd���Zededdfd	���Zed
ee	defd���Z
dS)
�IConfigProviderFT�
force_read�
ignore_errorsc��t��N��NotImplementedError)�selfr
rs   �^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/config_provider.py�read_config_filez IConfigProvider.read_config_files
��"�!��config�returnNc��t�rr)rrs  r�write_config_filez!IConfigProvider.write_config_file���!�!r�	timestampc��t�rr�rrs  r�modified_sincezIConfigProvider.modified_since!rr�FT)�__name__�
__module__�__qualname__r�boolrrrr�floatr�rrrrs��������>B�"�"��"�7;�"�"�"��^�"�
�"��"�D�"�"�"��^�"��"����"�D�"�"�"��^�"�"�"rrc��eZdZdS)�ConfigErrorN)r!r"r#r&rrr(r(&s�������Drr(c��eZdZdZd�Zd�ZdS)�JsonMessagez�Pretty-print given *obj* as JSON.

    To be used for logging. Example:

      logging.info("object: %s", JsonMessage(obj))

    c��||_dSr)�_obj)r�objs  r�__init__zJsonMessage.__init__3s
����	�	�	rc�8�tj|jd���S)NT)�	sort_keys)�json�dumpsr,�rs r�__str__zJsonMessage.__str__6s���z�$�)�t�4�4�4�4rN)r!r"r#�__doc__r.r4r&rrr*r**s<�����������5�5�5�5�5rr*�prev_section�sectionc�^����pi��pi���������z
}��������z
}�fd�|D���fd�|D����fd���������zD��d�S)z*Return difference between config sections.c�"��i|]}|�|��Sr&r&)�.0�vr6s  �r�
<dictcomp>z diff_section.<locals>.<dictcomp>As���
;�
;�
;�Q�a��a��
;�
;�
;rc�"��i|]}|�|��Sr&r&)r:r;r7s  �rr<z diff_section.<locals>.<dictcomp>Bs���
4�
4�
4��a����
4�
4�
4rc�V��i|]%}�|�|k�|�|�|f��&Sr&r&)r:r;r6r7s  ��rr<z diff_section.<locals>.<dictcomp>DsE���
�
�
���A��'�!�*�,�,�
��Q�����,�,�,�,r)�-�+�?��keys)r6r7�removed_settings�added_settingss``  r�diff_sectionrF:s������%�2�L��m��G�#�(�(�*�*�W�\�\�^�^�;���\�\�^�^�l�&7�&7�&9�&9�9�N�
;�
;�
;�
;�*:�
;�
;�
;�
4�
4�
4�
4�^�
4�
4�
4�
�
�
�
�
�"�'�'�)�)�G�L�L�N�N�:�
�
�
�		�	�	r�	prev_conf�confc#�b��K���������z
}�fd�|D��V���������z
}�fd�|D��V���fd���������zD��V�dS)z,Compare *prev_conf* with the current *conf*.c�"��i|]}|�|��Sr&r&)r:r7rGs  �rr<zdiff_config.<locals>.<dictcomp>Os ���
G�
G�
G�7�7�I�g�&�
G�
G�
Grc�"��i|]}|�|��Sr&r&)r:r7rHs  �rr<zdiff_config.<locals>.<dictcomp>Qs���
@�
@�
@�g�7�D��M�
@�
@�
@rc�n��i|]1}�|�|k�|t�|�|����2Sr&)rF)r:r7rHrGs  ��rr<zdiff_config.<locals>.<dictcomp>SsL��������W���g��.�.�	��i��0�$�w�-�@�@�.�.�.rNrB)rGrH�removed_sections�added_sectionss``  r�diff_configrOLs������� �~�~�'�'�$�)�)�+�+�5��
G�
G�
G�
G�6F�
G�
G�
G�G�G�G��Y�Y�[�[�9�>�>�#3�#3�3�N�
@�
@�
@�
@��
@�
@�
@�@�@�@������!���(�(�4�9�9�;�;�6��������r�	main_conf�	base_confrc���t||��\}}}i}|���D]�\}}||���vr|||<||���vr�|�|i���||d��|�|i���d�||d���D������|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|]\}}||d��S)�r&)r:�kr;s   rr<z"exclude_equals.<locals>.<dictcomp>ts"��C�C�C�T�Q���A�a�D�C�C�CrrA)rO�itemsrC�
setdefault�update)rPrQ�_�added�changed�resultr7�values        r�exclude_equalsr^Zs���$$�I�y�9�9��A�u�g�
�F�#�/�/�+�+�������e�j�j�l�l�"�"�#�F�7�O��g�l�l�n�n�$�$����g�r�*�*�1�1�'�'�2B�3�2G�H�H�H����g�r�*�*�1�1�C�C�W�W�%5�c�%:�%@�%@�%B�%B�C�C�C�
�
�
���Mrc��eZdZdZdd�Zd�Zd�Z	dd	ed
edefd�Z	d
e
defd�Zd�Zd�Z
de
fd�Zde
fd�Zdeedefd�ZdS)�ConfigReaderzM
    ConfigFile file for settings page.
    Location config file is PATH
    �Nc�0�||_||_||_dSr)�path�
disclaimer�permissions)rrcrdres    rr.zConfigReader.__init__s����	�$���&����rc�N�d�|jj|j���S)Nz<{classname}({path})>)�	classnamerc)�format�	__class__r#rcr3s r�__repr__zConfigReader.__repr__�s+��&�-�-��n�1��	�.�
�
�	
rc��d|j��S)NzConfigReader at )rcr3s rr4zConfigReader.__str__�s��-�$�)�-�-�-rFTr
rrc�2�	tj�|j��tkrt	d���|j}t|d��5}t�d|��|���}ddd��n#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 large�rzReading config file %sNzUnable to decode config file)
�osrc�getsize�_MAX_CONFIG_SIZEr(�open�logger�info�read�UnicodeDecodeError�FileNotFoundError�load_config_body�error)rr
r�filename�config_file�text�es       rrzConfigReader.read_config_file�su��
	��w���t�y�)�)�,<�<�<�!�"<�=�=�=��y�H��h��$�$�
*�����4�h�?�?�?�"�'�'�)�)��
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*����
*�
*�
*�
*���"�	E�	E�	E��<�=�=�1�D����� �	�	�	��I�I�I�	����	��(�(��.�.�.���	�	�	��L�L��O�O�O��
��	�	�	�	�	�	��G�����		���sf�AB�0B�B�B�B�B�B�
C	�'B7�7C	�C	�
C"�"
D�,D�	D�D�Dr{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(�
isinstance�dictrhrc)rr{rr|s    rrwzConfigReader.load_config_body�s���	��^�D�)�)�F�F���~�	�	�	��E��E�E�E����
�����	����
�>��I��&�$�'�'�	��)�)/���	�4�)@�)@���
�
�
s��?�:�?c��dSrr&r3s r�
_pre_writezConfigReader._pre_write�����rc��dSrr&r3s r�_post_writezConfigReader._post_write�r�rc��d}|jr|t|j��z
}|dz
}|tj|d���z
}|S)Nra�
F)�default_flow_style)rdrr�dump�rr�config_texts   r�_serialize_configzConfigReader._serialize_config�sN�����?�	 ��6�$�/�2�2�2�K��4��K��t�y��E�B�B�B�B���rc��|���|�|��}t|j|d|j���|���|S)NF)�backupre)r�r�r	rcrer�r�s   rrzConfigReader.write_config_file�sd���������,�,�V�4�4����I�{�5�d�>N�	
�	
�	
�	
�	
�������rrc��dS)NTr&rs  rrzConfigReader.modified_since�s���tr�raNr )r!r"r#r5r.rjr4r$r�r�strrwr�r�r�rrr%rr&rrr`r`ys#��������
'�'�'�'�

�
�
�
.�.�.�?C�����7;��	
�����4�S��T�����&
�
�
�
�
�
��3������3����������D������rr`c�b��eZdZd�fd�	Zd�Z	ddedef�fd	�
Zdd�Zdee	d
efd
�Z
�xZS)�CachedConfigReaderraNc���t���||��d|_d|_i|_||_dSr)�superr.�mtime�size�_configre�rrcrdreris    �rr.zCachedConfigReader.__init__�s@���
������z�*�*�*�&*��
�%)��	����&����rc�f�d�|jj|j|j|j���S)Nz9{classname} <'{path}', modified at {mtime}, {size} bytes>)rgrcr�r�)rhrir#rcr�r�r3s rr4zCachedConfigReader.__str__�s7��G�N�N��.�5��Y��j��Y�	
O�
�
�	
rFTr
rc�H��|�|j��s|r�|j}	t���|���|_|j�Wtt
||j����}t|��r&tj	d|gtt|���R�n]#t$rP}tj|��t�d|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)rr�r�r�r�listrO�anyrrrs�mapr*r(�
sentry_sdk�capture_exception�warning�_refresh_stat_cache)rr
r�prev_config�diffsrxris      �rrz#CachedConfigReader.read_config_file�sW������t�z�*�*�	'�j�	'��,�K�
�$�w�w�7�7�"/� 8� � ����:�)� ��[�$�,�!G�!G�H�H�E��5�z�z����K� ��!��e�4�4��������	
 �	
 �	
 ��,�U�3�3�3����@�����-�-����
%� ��K� � � � � �����	
 ����*
�$�$�&�&�&��|�s�'B,�,
D�6AD�Drc��	tj|j��}|j|_|j|_dS#t$rd|_d|_YdSwxYw)z-Sync cached mtime/size with the file on disk.�N)rn�statrc�st_mtimer��st_sizer�rv)rr�s  rr�z&CachedConfigReader._refresh_stat_cache	s]��	��7�4�9�%�%�D���D�J���D�I�I�I�� �	�	�	��D�J��D�I�I�I�I�	���s�15�A�Arc��|�d}	tj|j��}|j|j}}n#t
$rd\}}YnwxYw||kp
||jkS)z�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
        Nr�)r�r�)rnr�rcr�r�rvr�)rrr�r�r�s     rrz!CachedConfigReader.modified_sincesz�����I�	<��7�4�9�%�%�D�!%�
�t�|�g�H�H��!�	)�	)�	)� (��H�g�g�g�	)�����)�#�;�w�$�)�';�;s�.�A�Ar�r )rN)r!r"r#r.r4r$rr�rr%r�
__classcell__�ris@rr�r��s��������'�'�'�'�'�'�
�
�
�?C�!�!��!�7;�!�!�!�!�!�!�F����<����<�D�<�<�<�<�<�<�<�<rr�c�0��eZdZd�fd�	Zd�Z�fd�Z�xZS)�WriteOnlyConfigReaderraNc�v��t���|||��|���dSr)r�r.r�r�s    �rr.zWriteOnlyConfigReader.__init__&s9���
������z�;�7�7�7�
	
� � �"�"�"�"�"rc��|jSr)r�)rrY�__s   rrz&WriteOnlyConfigReader.read_config_file.s
���|�rc���t���|��}|�|��|_|���|Sr)r�rrwr�r�)rrr�ris   �rrz'WriteOnlyConfigReader.write_config_file1sG����g�g�/�/��7�7���,�,�[�9�9���� � �"�"�"��rr�)r!r"r#r.rrr�r�s@rr�r�%se�������#�#�#�#�#�#������������rr�c�P��eZdZdZdZdZ�fd�Zd�Zdede	defd	�Z
de	fd
�Z�xZS)�UserConfigReaderawPer-user config reader that resists TOCTOU symlink attacks.

    The user-specific subdirectory ``<USER_CONFDIR>/<username>/`` and the
    config file inside it must end up owned by ``root:<user-gid>`` 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.
    i�i�c�X��t���|��||_dSr)r�r.�username)rrcr�ris   �rr.zUserConfigReader.__init__Ms&���
��������� ��
�
�
rc��d|j��S)NzConfig of user )r�r3s rr4zUserConfigReader.__str__Qs��0���0�0�0r�	parent_fd�namerc��tt��5tj||j|���ddd��n#1swxYwYtj|tjtjztjz|���S)a�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.
        )�mode�dir_fdN�r�)	r�FileExistsErrorrn�mkdir�DIR_PERMISSIONSrq�O_RDONLY�O_DIRECTORY�
O_NOFOLLOW)rr�r�s   r�_open_user_subdirz"UserConfigReader._open_user_subdirTs����o�
&�
&�	H�	H��H�T�� 4�Y�G�G�G�G�	H�	H�	H�	H�	H�	H�	H�	H�	H�	H�	H����	H�	H�	H�	H��w���K�"�.�(�2�=�8��
�
�
�	
s�>�A�Ac	��tj|j��j}tj�|j��\}}tj�|��\}}t|��}	|�||��}	t	j	|d|��t	j
||j��|�|��}	t||	dd||j|���t	j|tjtjz|���}
	t	j	|
d|��t	j
|
|j��t	j|
��n#t	j|
��wxYw	t	j|��n#t	j|��wxYw	t	j|��n#t	j|��wxYw|	S)NrF)r��uid�gidrer�r�)�pwd�getpwnamr��pw_gidrnrc�splitr
r��chown�fchmodr�r�r	�FILE_PERMISSIONSrqr�r��close)rrr��confdir�basename�userconfdirr�r��user_fdr��file_fds           rrz"UserConfigReader.write_config_filees����l�4�=�)�)�0���G�M�M�$�)�4�4���� "��
�
�g� 6� 6���X�)��5�5�	�*	 ��,�,�Y��A�A�G�&
"����!�S�)�)�)��	�'�4�#7�8�8�8�"�4�4�V�<�<����� ��� $� 5�"������'���K�"�-�/�"�����
&��H�W�a��-�-�-��I�g�t�'<�=�=�=��H�W�%�%�%�%��B�H�W�%�%�%�%����%����!�!�!�!�����!�!�!�!����!��H�Y������B�H�Y���������s=�8G�BF�0E#�F�#E9�9F�=G�F(�(G�G)
r!r"r#r5r�r�r.r4�intr�r�rr�r�s@rr�r�8s����������"�O���!�!�!�!�!�1�1�1�
�3�
�c�
�c�
�
�
�
�"6�3�6�6�6�6�6�6�6�6rr�)$r1�loggingrnr��abcr�
contextlibr�textwrapr�typingrrrr�r�defence360agent.utilsr	�defence360agent.utils.fd_opsr
�	getLoggerr!rrrpr�	Exceptionr(r*r�rFrOr^r`r�r�r�r&rr�<module>r�s�����������	�	�	�	�
�
�
�
�������������������.�.�.�.�.�.�.�.�.�.���������0�0�0�0�0�0�=�=�=�=�=�=�	��	�8�	$�	$����
"�
"�
"�
"�
"�h�
"�
"�
"� 	�	�	�	�	�)�	�	�	�
5�
5�
5�
5�
5�
5�
5�
5� �x��~���������$�4��t��������$��4�����>X�X�X�X�X�X�X�X�vN<�N<�N<�N<�N<��N<�N<�N<�b�����.����&c�c�c�c�c�)�c�c�c�c�crdefence360agent/contracts/__pycache__/config_provider.cpython-311.pyc0000644000000000000000000005342600000000000022651 0ustar  �

�
��M������ddlZddlZddlZddlZddlmZddlmZddlm	Z	ddl
mZmZm
Z
ddlZddlZddlmZddlmZeje��ZdZGd	�d
e
��ZGd�de��ZGd
�d��Zdeedeefd�Zdedefd�Zdededefd�ZGd�d��Z Gd�de ��Z!Gd�de!��Z"Gd�d e!��Z#dS)!�N)�abstractmethod)�suppress)�dedent)�Mapping�Optional�Protocol)�atomic_rewrite)�open_dir_no_symlinksic�~�eZdZe	ddedefd���Zededdfd	���Zed
ee	defd���Z
dS)
�IConfigProviderFT�
force_read�
ignore_errorsc��t��N��NotImplementedError)�selfr
rs   �^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/config_provider.py�read_config_filez IConfigProvider.read_config_files
��"�!��config�returnNc��t�rr)rrs  r�write_config_filez!IConfigProvider.write_config_file���!�!r�	timestampc��t�rr�rrs  r�modified_sincezIConfigProvider.modified_since!rr�FT)�__name__�
__module__�__qualname__r�boolrrrr�floatr�rrrrs��������>B�"�"��"�7;�"�"�"��^�"�
�"��"�D�"�"�"��^�"��"����"�D�"�"�"��^�"�"�"rrc��eZdZdS)�ConfigErrorN)r!r"r#r&rrr(r(&s�������Drr(c��eZdZdZd�Zd�ZdS)�JsonMessagez�Pretty-print given *obj* as JSON.

    To be used for logging. Example:

      logging.info("object: %s", JsonMessage(obj))

    c��||_dSr)�_obj)r�objs  r�__init__zJsonMessage.__init__3s
����	�	�	rc�8�tj|jd���S)NT)�	sort_keys)�json�dumpsr,�rs r�__str__zJsonMessage.__str__6s���z�$�)�t�4�4�4�4rN)r!r"r#�__doc__r.r4r&rrr*r**s<�����������5�5�5�5�5rr*�prev_section�sectionc�^����pi��pi���������z
}��������z
}�fd�|D���fd�|D����fd���������zD��d�S)z*Return difference between config sections.c�"��i|]}|�|��Sr&r&)�.0�vr6s  �r�
<dictcomp>z diff_section.<locals>.<dictcomp>As���
;�
;�
;�Q�a��a��
;�
;�
;rc�"��i|]}|�|��Sr&r&)r:r;r7s  �rr<z diff_section.<locals>.<dictcomp>Bs���
4�
4�
4��a����
4�
4�
4rc�V��i|]%}�|�|k�|�|�|f��&Sr&r&)r:r;r6r7s  ��rr<z diff_section.<locals>.<dictcomp>DsE���
�
�
���A��'�!�*�,�,�
��Q�����,�,�,�,r)�-�+�?��keys)r6r7�removed_settings�added_settingss``  r�diff_sectionrF:s������%�2�L��m��G�#�(�(�*�*�W�\�\�^�^�;���\�\�^�^�l�&7�&7�&9�&9�9�N�
;�
;�
;�
;�*:�
;�
;�
;�
4�
4�
4�
4�^�
4�
4�
4�
�
�
�
�
�"�'�'�)�)�G�L�L�N�N�:�
�
�
�		�	�	r�	prev_conf�confc#�b��K���������z
}�fd�|D��V���������z
}�fd�|D��V���fd���������zD��V�dS)z,Compare *prev_conf* with the current *conf*.c�"��i|]}|�|��Sr&r&)r:r7rGs  �rr<zdiff_config.<locals>.<dictcomp>Os ���
G�
G�
G�7�7�I�g�&�
G�
G�
Grc�"��i|]}|�|��Sr&r&)r:r7rHs  �rr<zdiff_config.<locals>.<dictcomp>Qs���
@�
@�
@�g�7�D��M�
@�
@�
@rc�n��i|]1}�|�|k�|t�|�|����2Sr&)rF)r:r7rHrGs  ��rr<zdiff_config.<locals>.<dictcomp>SsL��������W���g��.�.�	��i��0�$�w�-�@�@�.�.�.rNrB)rGrH�removed_sections�added_sectionss``  r�diff_configrOLs������� �~�~�'�'�$�)�)�+�+�5��
G�
G�
G�
G�6F�
G�
G�
G�G�G�G��Y�Y�[�[�9�>�>�#3�#3�3�N�
@�
@�
@�
@��
@�
@�
@�@�@�@������!���(�(�4�9�9�;�;�6��������r�	main_conf�	base_confrc���t||��\}}}i}|���D]�\}}||���vr|||<||���vr�|�|i���||d��|�|i���d�||d���D������|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|]\}}||d��S)�r&)r:�kr;s   rr<z"exclude_equals.<locals>.<dictcomp>ts"��C�C�C�T�Q���A�a�D�C�C�CrrA)rO�itemsrC�
setdefault�update)rPrQ�_�added�changed�resultr7�values        r�exclude_equalsr^Zs���$$�I�y�9�9��A�u�g�
�F�#�/�/�+�+�������e�j�j�l�l�"�"�#�F�7�O��g�l�l�n�n�$�$����g�r�*�*�1�1�'�'�2B�3�2G�H�H�H����g�r�*�*�1�1�C�C�W�W�%5�c�%:�%@�%@�%B�%B�C�C�C�
�
�
���Mrc��eZdZdZdd�Zd�Zd�Z	dd	ed
edefd�Z	d
e
defd�Zd�Zd�Z
de
fd�Zde
fd�Zdeedefd�ZdS)�ConfigReaderzM
    ConfigFile file for settings page.
    Location config file is PATH
    �Nc�0�||_||_||_dSr)�path�
disclaimer�permissions)rrcrdres    rr.zConfigReader.__init__s����	�$���&����rc�N�d�|jj|j���S)Nz<{classname}({path})>)�	classnamerc)�format�	__class__r#rcr3s r�__repr__zConfigReader.__repr__�s+��&�-�-��n�1��	�.�
�
�	
rc��d|j��S)NzConfigReader at )rcr3s rr4zConfigReader.__str__�s��-�$�)�-�-�-rFTr
rrc�2�	tj�|j��tkrt	d���|j}t|d��5}t�d|��|���}ddd��n#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 large�rzReading config file %sNzUnable to decode config file)
�osrc�getsize�_MAX_CONFIG_SIZEr(�open�logger�info�read�UnicodeDecodeError�FileNotFoundError�load_config_body�error)rr
r�filename�config_file�text�es       rrzConfigReader.read_config_file�su��
	��w���t�y�)�)�,<�<�<�!�"<�=�=�=��y�H��h��$�$�
*�����4�h�?�?�?�"�'�'�)�)��
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*����
*�
*�
*�
*���"�	E�	E�	E��<�=�=�1�D����� �	�	�	��I�I�I�	����	��(�(��.�.�.���	�	�	��L�L��O�O�O��
��	�	�	�	�	�	��G�����		���sf�AB�0B�B�B�B�B�B�
C	�'B7�7C	�C	�
C"�"
D�,D�	D�D�Dr{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(�
isinstance�dictrhrc)rr{rr|s    rrwzConfigReader.load_config_body�s���	��^�D�)�)�F�F���~�	�	�	��E��E�E�E����
�����	����
�>��I��&�$�'�'�	��)�)/���	�4�)@�)@���
�
�
s��?�:�?c��dSrr&r3s r�
_pre_writezConfigReader._pre_write�����rc��dSrr&r3s r�_post_writezConfigReader._post_write�r�rc��d}|jr|t|j��z
}|dz
}|tj|d���z
}|S)Nra�
F)�default_flow_style)rdrr�dump�rr�config_texts   r�_serialize_configzConfigReader._serialize_config�sN�����?�	 ��6�$�/�2�2�2�K��4��K��t�y��E�B�B�B�B���rc��|���|�|��}t|j|d|j���|���|S)NF)�backupre)r�r�r	rcrer�r�s   rrzConfigReader.write_config_file�sd���������,�,�V�4�4����I�{�5�d�>N�	
�	
�	
�	
�	
�������rrc��dS)NTr&rs  rrzConfigReader.modified_since�s���tr�raNr )r!r"r#r5r.rjr4r$r�r�strrwr�r�r�rrr%rr&rrr`r`ys#��������
'�'�'�'�

�
�
�
.�.�.�?C�����7;��	
�����4�S��T�����&
�
�
�
�
�
��3������3����������D������rr`c�b��eZdZd�fd�	Zd�Z	ddedef�fd	�
Zdd�Zdee	d
efd
�Z
�xZS)�CachedConfigReaderraNc���t���||��d|_d|_i|_||_dSr)�superr.�mtime�size�_configre�rrcrdreris    �rr.zCachedConfigReader.__init__�s@���
������z�*�*�*�&*��
�%)��	����&����rc�f�d�|jj|j|j|j���S)Nz9{classname} <'{path}', modified at {mtime}, {size} bytes>)rgrcr�r�)rhrir#rcr�r�r3s rr4zCachedConfigReader.__str__�s7��G�N�N��.�5��Y��j��Y�	
O�
�
�	
rFTr
rc�H��|�|j��s|r�|j}	t���|���|_|j�Wtt
||j����}t|��r&tj	d|gtt|���R�n]#t$rP}tj|��t�d|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)rr�r�r�r�listrO�anyrrrs�mapr*r(�
sentry_sdk�capture_exception�warning�_refresh_stat_cache)rr
r�prev_config�diffsrxris      �rrz#CachedConfigReader.read_config_file�sW������t�z�*�*�	'�j�	'��,�K�
�$�w�w�7�7�"/� 8� � ����:�)� ��[�$�,�!G�!G�H�H�E��5�z�z����K� ��!��e�4�4��������	
 �	
 �	
 ��,�U�3�3�3����@�����-�-����
%� ��K� � � � � �����	
 ����*
�$�$�&�&�&��|�s�'B,�,
D�6AD�Drc��	tj|j��}|j|_|j|_dS#t$rd|_d|_YdSwxYw)z-Sync cached mtime/size with the file on disk.�N)rn�statrc�st_mtimer��st_sizer�rv)rr�s  rr�z&CachedConfigReader._refresh_stat_cache	s]��	��7�4�9�%�%�D���D�J���D�I�I�I�� �	�	�	��D�J��D�I�I�I�I�	���s�15�A�Arc��|�d}	tj|j��}|j|j}}n#t
$rd\}}YnwxYw||kp
||jkS)z�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
        Nr�)r�r�)rnr�rcr�r�rvr�)rrr�r�r�s     rrz!CachedConfigReader.modified_sincesz�����I�	<��7�4�9�%�%�D�!%�
�t�|�g�H�H��!�	)�	)�	)� (��H�g�g�g�	)�����)�#�;�w�$�)�';�;s�.�A�Ar�r )rN)r!r"r#r.r4r$rr�rr%r�
__classcell__�ris@rr�r��s��������'�'�'�'�'�'�
�
�
�?C�!�!��!�7;�!�!�!�!�!�!�F����<����<�D�<�<�<�<�<�<�<�<rr�c�0��eZdZd�fd�	Zd�Z�fd�Z�xZS)�WriteOnlyConfigReaderraNc�v��t���|||��|���dSr)r�r.r�r�s    �rr.zWriteOnlyConfigReader.__init__&s9���
������z�;�7�7�7�
	
� � �"�"�"�"�"rc��|jSr)r�)rrY�__s   rrz&WriteOnlyConfigReader.read_config_file.s
���|�rc���t���|��}|�|��|_|���|Sr)r�rrwr�r�)rrr�ris   �rrz'WriteOnlyConfigReader.write_config_file1sG����g�g�/�/��7�7���,�,�[�9�9���� � �"�"�"��rr�)r!r"r#r.rrr�r�s@rr�r�%se�������#�#�#�#�#�#������������rr�c�P��eZdZdZdZdZ�fd�Zd�Zdede	defd	�Z
de	fd
�Z�xZS)�UserConfigReaderawPer-user config reader that resists TOCTOU symlink attacks.

    The user-specific subdirectory ``<USER_CONFDIR>/<username>/`` and the
    config file inside it must end up owned by ``root:<user-gid>`` 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.
    i�i�c�X��t���|��||_dSr)r�r.�username)rrcr�ris   �rr.zUserConfigReader.__init__Ms&���
��������� ��
�
�
rc��d|j��S)NzConfig of user )r�r3s rr4zUserConfigReader.__str__Qs��0���0�0�0r�	parent_fd�namerc��tt��5tj||j|���ddd��n#1swxYwYtj|tjtjztjz|���S)a�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.
        )�mode�dir_fdN�r�)	r�FileExistsErrorrn�mkdir�DIR_PERMISSIONSrq�O_RDONLY�O_DIRECTORY�
O_NOFOLLOW)rr�r�s   r�_open_user_subdirz"UserConfigReader._open_user_subdirTs����o�
&�
&�	H�	H��H�T�� 4�Y�G�G�G�G�	H�	H�	H�	H�	H�	H�	H�	H�	H�	H�	H����	H�	H�	H�	H��w���K�"�.�(�2�=�8��
�
�
�	
s�>�A�Ac	��tj|j��j}tj�|j��\}}tj�|��\}}t|��}	|�||��}	t	j	|d|��t	j
||j��|�|��}	t||	dd||j|���t	j|tjtjz|���}
	t	j	|
d|��t	j
|
|j��t	j|
��n#t	j|
��wxYw	t	j|��n#t	j|��wxYw	t	j|��n#t	j|��wxYw|	S)NrF)r��uid�gidrer�r�)�pwd�getpwnamr��pw_gidrnrc�splitr
r��chown�fchmodr�r�r	�FILE_PERMISSIONSrqr�r��close)rrr��confdir�basename�userconfdirr�r��user_fdr��file_fds           rrz"UserConfigReader.write_config_filees����l�4�=�)�)�0���G�M�M�$�)�4�4���� "��
�
�g� 6� 6���X�)��5�5�	�*	 ��,�,�Y��A�A�G�&
"����!�S�)�)�)��	�'�4�#7�8�8�8�"�4�4�V�<�<����� ��� $� 5�"������'���K�"�-�/�"�����
&��H�W�a��-�-�-��I�g�t�'<�=�=�=��H�W�%�%�%�%��B�H�W�%�%�%�%����%����!�!�!�!�����!�!�!�!����!��H�Y������B�H�Y���������s=�8G�BF�0E#�F�#E9�9F�=G�F(�(G�G)
r!r"r#r5r�r�r.r4�intr�r�rr�r�s@rr�r�8s����������"�O���!�!�!�!�!�1�1�1�
�3�
�c�
�c�
�
�
�
�"6�3�6�6�6�6�6�6�6�6rr�)$r1�loggingrnr��abcr�
contextlibr�textwrapr�typingrrrr�r�defence360agent.utilsr	�defence360agent.utils.fd_opsr
�	getLoggerr!rrrpr�	Exceptionr(r*r�rFrOr^r`r�r�r�r&rr�<module>r�s�����������	�	�	�	�
�
�
�
�������������������.�.�.�.�.�.�.�.�.�.���������0�0�0�0�0�0�=�=�=�=�=�=�	��	�8�	$�	$����
"�
"�
"�
"�
"�h�
"�
"�
"� 	�	�	�	�	�)�	�	�	�
5�
5�
5�
5�
5�
5�
5�
5� �x��~���������$�4��t��������$��4�����>X�X�X�X�X�X�X�X�vN<�N<�N<�N<�N<��N<�N<�N<�b�����.����&c�c�c�c�c�)�c�c�c�c�crdefence360agent/contracts/__pycache__/eula.cpython-311.opt-1.pyc0000644000000000000000000000731100000000000021347 0ustar  �

烽*v������ddlZddlZddlmZddlmZddlmZddl	m
Z
mZdZerdndZ
d	Zd
Zddedeed
efd�Zded
efd�Zd
efd�Zdd�Zdd�Zd
efd�Zd
efd�Zd
efd�ZdS)�N)�Optional)�files)�ANTIVIRUS_MODE)�Eula�run_in_executorz
message{}.txtz-av�z
eula{}.txtz
updated{}.txt�path�errors�returnc��t||���5}|������cddd��S#1swxYwYdS)N�r
)�open�read�strip)r	r
�fs   �S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/eula.py�	_readfilers���	
�d�6�	"�	"�	"� �a��v�v�x�x�~�~��� � � � � � � � � � � � ���� � � � � � s�&A�A	�A	�templatec��tj�tj�tj��|�t����S�N)	�osr	�joinr�Index�
files_path�EULA�format�_SUFFIX)rs r�	_get_pathrs=��
�7�<�<�
����u�z�*�*�H�O�O�G�,D�,D����c��hK�ttj��tj���d{V��S)z9Return True if latest EULA was accepted, False otherwise.N)r�asyncio�get_event_loopr�is_accepted�rrr#r#s4���� ��!7�!9�!9�4�;K�L�L�L�L�L�L�L�L�Lrc��lK�ttj��tj���d{V��dS)z
Accepts EULA.N)rr!r"r�acceptr$rrr&r& s9����
�'�0�2�2�D�K�
@�
@�@�@�@�@�@�@�@�@�@rc��ZK�ttj��d����d{V��dS)z$Updates latest EULA date from files.c�D�tjt�����S)N)�updated)r�
get_or_creater)r$rr�<lambda>zupdate.<locals>.<lambda>(s��$�*<�W�Y�Y�*O�*O�*O�rN)rr!r"r$rr�updater,%sR����
��� � �"O�"O�����������rc�H�ttt��d���S)zReturn main text of the EULA.�ignorer
)rr�_TEXT_TEMPLATEr$rr�textr0,s���Y�~�.�.�x�@�@�@�@rc�D�ttt����S)z)Return a message inviting to accept EULA.)rr�_MESSAGE_TEMPLATEr$rr�messager31����Y�0�1�1�2�2�2rc�D�ttt����S)zReturn last EULA's update time.)rr�_UPDATED_TEMPLATEr$rrr)r)6r4rr)rN)r!�os.pathr�typingr�defence360agentr� defence360agent.contracts.configr�$defence360agent.model.simplificationrrr2rr/r6�strrr�boolr#r&r,r0r3r)r$rr�<module>r>s�����������������!�!�!�!�!�!�;�;�;�;�;�;�F�F�F�F�F�F�F�F�$��!�
)�%�%�r����#�� � �C� ��#�� �#� � � � �
��������M�4�M�M�M�M�
A�A�A�A�
����A�c�A�A�A�A�
3��3�3�3�3�
3��3�3�3�3�3�3rdefence360agent/contracts/__pycache__/eula.cpython-311.pyc0000644000000000000000000000731100000000000020410 0ustar  �

烽*v������ddlZddlZddlmZddlmZddlmZddl	m
Z
mZdZerdndZ
d	Zd
Zddedeed
efd�Zded
efd�Zd
efd�Zdd�Zdd�Zd
efd�Zd
efd�Zd
efd�ZdS)�N)�Optional)�files)�ANTIVIRUS_MODE)�Eula�run_in_executorz
message{}.txtz-av�z
eula{}.txtz
updated{}.txt�path�errors�returnc��t||���5}|������cddd��S#1swxYwYdS)N�r
)�open�read�strip)r	r
�fs   �S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/eula.py�	_readfilers���	
�d�6�	"�	"�	"� �a��v�v�x�x�~�~��� � � � � � � � � � � � ���� � � � � � s�&A�A	�A	�templatec��tj�tj�tj��|�t����S�N)	�osr	�joinr�Index�
files_path�EULA�format�_SUFFIX)rs r�	_get_pathrs=��
�7�<�<�
����u�z�*�*�H�O�O�G�,D�,D����c��hK�ttj��tj���d{V��S)z9Return True if latest EULA was accepted, False otherwise.N)r�asyncio�get_event_loopr�is_accepted�rrr#r#s4���� ��!7�!9�!9�4�;K�L�L�L�L�L�L�L�L�Lrc��lK�ttj��tj���d{V��dS)z
Accepts EULA.N)rr!r"r�acceptr$rrr&r& s9����
�'�0�2�2�D�K�
@�
@�@�@�@�@�@�@�@�@�@rc��ZK�ttj��d����d{V��dS)z$Updates latest EULA date from files.c�D�tjt�����S)N)�updated)r�
get_or_creater)r$rr�<lambda>zupdate.<locals>.<lambda>(s��$�*<�W�Y�Y�*O�*O�*O�rN)rr!r"r$rr�updater,%sR����
��� � �"O�"O�����������rc�H�ttt��d���S)zReturn main text of the EULA.�ignorer
)rr�_TEXT_TEMPLATEr$rr�textr0,s���Y�~�.�.�x�@�@�@�@rc�D�ttt����S)z)Return a message inviting to accept EULA.)rr�_MESSAGE_TEMPLATEr$rr�messager31����Y�0�1�1�2�2�2rc�D�ttt����S)zReturn last EULA's update time.)rr�_UPDATED_TEMPLATEr$rrr)r)6r4rr)rN)r!�os.pathr�typingr�defence360agentr� defence360agent.contracts.configr�$defence360agent.model.simplificationrrr2rr/r6�strrr�boolr#r&r,r0r3r)r$rr�<module>r>s�����������������!�!�!�!�!�!�;�;�;�;�;�;�F�F�F�F�F�F�F�F�$��!�
)�%�%�r����#�� � �C� ��#�� �#� � � � �
��������M�4�M�M�M�M�
A�A�A�A�
����A�c�A�A�A�A�
3��3�3�3�3�
3��3�3�3�3�3�3rdefence360agent/contracts/__pycache__/hook_events.cpython-311.opt-1.pyc0000644000000000000000000001177700000000000022760 0ustar  �

�3%�������ddlmZddlmZd\ZZGd�de��ZGd�de��ZGd�d	e��ZGd
�de��Z	Gd�d
e��Z
Gd�de��ZGd�d��ZdS)�)�
HookEvents)�Message)�started�finishedc��eZdZdZdZd�ZdS)�_HookEventBaseNc�|�d�|���D��}|jj�dt|���d�S)Nc�&�i|]\}}|dk�||��S)�DUMP�)�.0�k�vs   �Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/hook_events.py�
<dictcomp>z+_HookEventBase.__repr__.<locals>.<dictcomp>s#��A�A�A�T�Q��Q�&�[�[�A�q�[�[�[��(�))�items�	__class__�__qualname__�repr)�self�filtereds  r�__repr__z_HookEventBase.__repr__
s?��A�A�T�Z�Z�\�\�A�A�A���.�-�A�A��X���A�A�A�Ar)�__name__�
__module__r�event�subtyperrrrrr	s2�������E��G�B�B�B�B�Brrc��eZdZejZdS)�_AgentN)rrrr�AGENTrrrrr!r!s��������E�E�Err!c��eZdZejZdS)�_LicenseN)rrrr�LICENSErrrrr$r$s��������E�E�Err$c��eZdZejZdS)�_MalwareScanningN)rrrr�MALWARE_SCANNINGrrrrr'r'��������'�E�E�Err'c��eZdZejZdS)�_MalwareDetectedN)rrrr�MALWARE_DETECTEDrrrrr+r+r)rr+c��eZdZejZdS)�_MalwareCleanupN)rrrr�MALWARE_CLEANUPrrrrr.r."s�������&�E�E�Err.c�&�eZdZGd�de��ZGd�de��ZGd�de��ZGd�de��ZGd	�d
e��Z	Gd�de
��ZGd
�de
��ZGd�de
��ZGd�de��ZGd�de��ZdS)�	HookEventc��eZdZeZdS)�HookEvent.AgentStartedN�rrr�STARTEDrrrr�AgentStartedr3'����������rr6c��eZdZdZdS)�HookEvent.AgentMisconfig�	misconfigN�rrrrrrr�AgentMisconfigr9*s���������rr<c��eZdZdZdS)�HookEvent.LicenseExpired�expiredNr;rrr�LicenseExpiredr>-����������rr@c��eZdZdZdS)�HookEvent.LicenseExpiring�expiringNr;rrr�LicenseExpiringrC0����������rrEc��eZdZdZdS)�HookEvent.LicenseRenewed�renewedNr;rrr�LicenseRenewedrH3rArrJc��eZdZeZdS)� HookEvent.MalwareScanningStartedNr4rrr�MalwareScanningStartedrL6r7rrMc��eZdZeZdS)�!HookEvent.MalwareScanningFinishedN�rrr�FINISHEDrrrr�MalwareScanningFinishedrO9����������rrRc��eZdZdZdS)�!HookEvent.MalwareDetectedCritical�criticalNr;rrr�MalwareDetectedCriticalrU<rFrrWc��eZdZeZdS)�HookEvent.MalwareCleanupStartedNr4rrr�MalwareCleanupStartedrY?r7rrZc��eZdZeZdS)� HookEvent.MalwareCleanupFinishedNrPrrr�MalwareCleanupFinishedr\BrSrr]N)rrrr!r6r<r$r@rErJr'rMrRr+rWr.rZr]rrrr1r1&s������������v���������������������������(������������������!1���������"2���������"2�����������������������rr1N)
� defence360agent.contracts.configr�"defence360agent.contracts.messagesrr5rQrr!r$r'r+r.r1rrr�<module>r`sk��8�7�7�7�7�7�6�6�6�6�6�6�)����B�B�B�B�B�W�B�B�B������^���������~����(�(�(�(�(�~�(�(�(�(�(�(�(�(�~�(�(�(�'�'�'�'�'�n�'�'�'����������rdefence360agent/contracts/__pycache__/hook_events.cpython-311.pyc0000644000000000000000000001177700000000000022021 0ustar  �

�3%�������ddlmZddlmZd\ZZGd�de��ZGd�de��ZGd�d	e��ZGd
�de��Z	Gd�d
e��Z
Gd�de��ZGd�d��ZdS)�)�
HookEvents)�Message)�started�finishedc��eZdZdZdZd�ZdS)�_HookEventBaseNc�|�d�|���D��}|jj�dt|���d�S)Nc�&�i|]\}}|dk�||��S)�DUMP�)�.0�k�vs   �Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/hook_events.py�
<dictcomp>z+_HookEventBase.__repr__.<locals>.<dictcomp>s#��A�A�A�T�Q��Q�&�[�[�A�q�[�[�[��(�))�items�	__class__�__qualname__�repr)�self�filtereds  r�__repr__z_HookEventBase.__repr__
s?��A�A�T�Z�Z�\�\�A�A�A���.�-�A�A��X���A�A�A�Ar)�__name__�
__module__r�event�subtyperrrrrr	s2�������E��G�B�B�B�B�Brrc��eZdZejZdS)�_AgentN)rrrr�AGENTrrrrr!r!s��������E�E�Err!c��eZdZejZdS)�_LicenseN)rrrr�LICENSErrrrr$r$s��������E�E�Err$c��eZdZejZdS)�_MalwareScanningN)rrrr�MALWARE_SCANNINGrrrrr'r'��������'�E�E�Err'c��eZdZejZdS)�_MalwareDetectedN)rrrr�MALWARE_DETECTEDrrrrr+r+r)rr+c��eZdZejZdS)�_MalwareCleanupN)rrrr�MALWARE_CLEANUPrrrrr.r."s�������&�E�E�Err.c�&�eZdZGd�de��ZGd�de��ZGd�de��ZGd�de��ZGd	�d
e��Z	Gd�de
��ZGd
�de
��ZGd�de
��ZGd�de��ZGd�de��ZdS)�	HookEventc��eZdZeZdS)�HookEvent.AgentStartedN�rrr�STARTEDrrrr�AgentStartedr3'����������rr6c��eZdZdZdS)�HookEvent.AgentMisconfig�	misconfigN�rrrrrrr�AgentMisconfigr9*s���������rr<c��eZdZdZdS)�HookEvent.LicenseExpired�expiredNr;rrr�LicenseExpiredr>-����������rr@c��eZdZdZdS)�HookEvent.LicenseExpiring�expiringNr;rrr�LicenseExpiringrC0����������rrEc��eZdZdZdS)�HookEvent.LicenseRenewed�renewedNr;rrr�LicenseRenewedrH3rArrJc��eZdZeZdS)� HookEvent.MalwareScanningStartedNr4rrr�MalwareScanningStartedrL6r7rrMc��eZdZeZdS)�!HookEvent.MalwareScanningFinishedN�rrr�FINISHEDrrrr�MalwareScanningFinishedrO9����������rrRc��eZdZdZdS)�!HookEvent.MalwareDetectedCritical�criticalNr;rrr�MalwareDetectedCriticalrU<rFrrWc��eZdZeZdS)�HookEvent.MalwareCleanupStartedNr4rrr�MalwareCleanupStartedrY?r7rrZc��eZdZeZdS)� HookEvent.MalwareCleanupFinishedNrPrrr�MalwareCleanupFinishedr\BrSrr]N)rrrr!r6r<r$r@rErJr'rMrRr+rWr.rZr]rrrr1r1&s������������v���������������������������(������������������!1���������"2���������"2�����������������������rr1N)
� defence360agent.contracts.configr�"defence360agent.contracts.messagesrr5rQrr!r$r'r+r.r1rrr�<module>r`sk��8�7�7�7�7�7�6�6�6�6�6�6�)����B�B�B�B�B�W�B�B�B������^���������~����(�(�(�(�(�~�(�(�(�(�(�(�(�(�~�(�(�(�'�'�'�'�'�n�'�'�'����������rdefence360agent/contracts/__pycache__/hooks.cpython-311.opt-1.pyc0000644000000000000000000002167300000000000021553 0ustar  �

¬*˯�;���ddlZddlZddlmZmZddlmZddlmZGd�d��Z	Gd�de��Z
Gd	�d
e��ZdS)�N)�Config�Core)�ConfigReader)�antivirus_modec� �eZdZed���Zedd���Zedd���Zed���Zed���Zed���Z	ed	���Z
ed
���Zed���Zedd
���Z
edd���Zedd���ZdS)�Schemac��d|id�S)N�dict)�type�schema�default�)�datas �T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/hooks.pyr
zSchema.dict
s�����
�
�	
�Nc�$�dddi|rd|ini�dgd�S)N�listr�string�regexF�rr�nullabler
r)rs r�list_of_stringszSchema.list_of_stringss?������',�4�G�U�#�#�"����
�
�	
rTc�B�|rdnd}t�|��S)Nz^.+@(.+\.)+.+|default$z^.+@(.+\.)+.+$)rr)�default_enabledrs  r�list_of_emailszSchema.list_of_emailss.��*9�O�%�%�>O�	��%�%�e�,�,�,rc��ddtddd�iS)N�period�integer�)r�coerce�minr
)�intrrrrz
Schema.period%s'��
�!����	��
�	
rc��d|d�S)Nr)rrr�rs rrz
Schema.string0s��� �
�
�	
rc��dddd�iS)N�enabled�booleanF)rr
rrrrr&zSchema.enabled7s!��
�!� ���
�	
rc���dt�it����dt���i�|rt���ni���iS)N�ADMIN�admin_emails)rr
r&rr�rs r�adminzSchema.admin@sn��
�V�[�[���n�n�&�&��"�F�$9�$9�$;�$;���+1�8�v�}�}����b����
�	
rc���dt�it����dt�d��i�|rt���ni���iS)N�SCRIPT�scriptsz^\/.+$)rr
r&rrr+s r�scriptz
Schema.scriptLsp��
�f�k�k���n�n�&�&���v�5�5�i�@�@���+1�8�v�}�}����b����
�	
rc��dt�it����|rt���ni���iS)N�USER)rr
r&rr+s r�userzSchema.userXsQ��
�F�K�K���n�n�&�&��*0�8�v�}�}����b����
�	
rFc�l�t�it�|������S�Nr+)rr
r0r+s r�
target_scriptzSchema.target_scriptcs3���{�{�
��-�-�v�-�.�.�
�
�
�	
rc��t�it�|����t�|������Sr5�rr
r,r0r+s r�target_admin_and_scriptzSchema.target_admin_and_scriptksI���{�{�
��,�,�f�,�-�-�
��-�-�v�-�.�.�
�
�
�	
rc��t�it�|����t�|������Sr5r8r+s r�
target_allzSchema.target_alltsK���{�{�
��,�,�f�,�-�-�
��-�-�v�-�.�.�
�
�
�	
r)N)T)F)�__name__�
__module__�__qualname__�staticmethodr
rrrrr&r,r0r3r6r9r;rrrrr	so�������
�
��\�
��	
�	
�	
��\�	
��-�-�-��\�-��
�
��\�
��
�
��\�
��
�
��\�
��	
�	
��\�	
��	
�	
��\�	
��
�
��\�
��
�
�
��\�
��
�
�
��\�
��
�
�
��\�
�
�
rrc��eZdZdZd�ZdS)�HooksConfigReader�_imunifyc��tj|jd��tj|jdt	j|j��j��dS)Ni�r)�os�chmod�path�chown�grp�getgrnam�
GROUP_NAME�gr_gid)�selfs r�_post_writezHooksConfigReader._post_write�sA��
����E�"�"�"�
����A�s�|�D�O�<�<�C�D�D�D�D�DrN)r<r=r>rJrMrrrrArAs-�������J�E�E�E�E�ErrAc�t��eZdZej�ejej��f�fd�	Z	d�Z
d�Z�xZS)�HooksConfigc
� ��tj�r�t�t�d���dddd�t�d���d���dt�t�d���t���t�d���d	���dgd
�t�t�d���t���t�d���t���t���t���t���t���d���id
�n�t�t���t���t���t���t���t���d���id�}t���
||t|�����dS)NF)rrT)rr
rr$)�default_emails�notify_from_email�localer)�username�emailsrSrr+)�REALTIME_MALWARE_FOUND�USER_SCAN_MALWARE_FOUND�SCRIPT_BLOCKED�USER_SCAN_STARTED�CUSTOM_SCAN_STARTED�USER_SCAN_FINISHED�CUSTOM_SCAN_FINISHED�CUSTOM_SCAN_MALWARE_FOUND)r,�users�rulesr
)rWrYrZr[r\r])r_r
)rF�validation_schema�
config_reader)r�disabledrr
rrr9r;r6�super�__init__rA)rLrFr`�	__class__s   �rrdzHooksConfig.__init__�s`���d�&�];
����*0�*?�*?�,1�+@�+�+�%-�'+�(,�.�.�
#)�-�-��-�">�">�
�
���#�$�k�k�(.�
�
�u�
�(E�(E�&,�&;�&;�&=�&=�&,�m�m�T�m�&B�&B�����!%�!��� ���#�:�:�$�:�G�G�39�3D�3D�3F�3F�*0�*H�*H�#'�+I�+�+�.4�-A�-A�-C�-C�/5�/C�/C�/E�/E�.4�.B�.B�.D�.D�06�0D�0D�0F�0F�"�:�:�<�<�����$�Y-
�-
�-
�` ���39�3G�3G�3I�3I�-3�-A�-A�-C�-C�/5�/C�/C�/E�/E�.4�.B�.B�.D�.D�06�0D�0D�0F�0F�5;�5I�5I�5K�5K�
��	�	����a	�|	������/�+�D�1�1�	�	
�	
�	
�	
�	
rc�Z�|���}|�dd��|S�Nr^)�config_to_dict�pop�rLrs  r�getzHooksConfig.get�s,���"�"�$�$������$�����rc�\�|�dd��|�|��dSrg)ri�dict_to_configrjs  r�updatezHooksConfig.update�s0������$�������D�!�!�!�!�!r)
r<r=r>rDrF�joinr�GLOBAL_CONFDIR�HOOKS_CONFIGFILENAMErdrkrn�
__classcell__)res@rrOrO�s{��������7�<�<�� 3�T�5N�O�O�E
�E
�E
�E
�E
�E
�N���
"�"�"�"�"�"�"rrO)rHrD� defence360agent.contracts.configrr�)defence360agent.contracts.config_providerr�defence360agent.utilsrrrArOrrr�<module>rvs���
�
�
�
�	�	�	�	�9�9�9�9�9�9�9�9�B�B�B�B�B�B�0�0�0�0�0�0�s
�s
�s
�s
�s
�s
�s
�s
�lE�E�E�E�E��E�E�E�O"�O"�O"�O"�O"�&�O"�O"�O"�O"�O"rdefence360agent/contracts/__pycache__/hooks.cpython-311.pyc0000644000000000000000000002167300000000000020614 0ustar  �

¬*˯�;���ddlZddlZddlmZmZddlmZddlmZGd�d��Z	Gd�de��Z
Gd	�d
e��ZdS)�N)�Config�Core)�ConfigReader)�antivirus_modec� �eZdZed���Zedd���Zedd���Zed���Zed���Zed���Z	ed	���Z
ed
���Zed���Zedd
���Z
edd���Zedd���ZdS)�Schemac��d|id�S)N�dict)�type�schema�default�)�datas �T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/hooks.pyr
zSchema.dict
s�����
�
�	
�Nc�$�dddi|rd|ini�dgd�S)N�listr�string�regexF�rr�nullabler
r)rs r�list_of_stringszSchema.list_of_stringss?������',�4�G�U�#�#�"����
�
�	
rTc�B�|rdnd}t�|��S)Nz^.+@(.+\.)+.+|default$z^.+@(.+\.)+.+$)rr)�default_enabledrs  r�list_of_emailszSchema.list_of_emailss.��*9�O�%�%�>O�	��%�%�e�,�,�,rc��ddtddd�iS)N�period�integer�)r�coerce�minr
)�intrrrrz
Schema.period%s'��
�!����	��
�	
rc��d|d�S)Nr)rrr�rs rrz
Schema.string0s��� �
�
�	
rc��dddd�iS)N�enabled�booleanF)rr
rrrrr&zSchema.enabled7s!��
�!� ���
�	
rc���dt�it����dt���i�|rt���ni���iS)N�ADMIN�admin_emails)rr
r&rr�rs r�adminzSchema.admin@sn��
�V�[�[���n�n�&�&��"�F�$9�$9�$;�$;���+1�8�v�}�}����b����
�	
rc���dt�it����dt�d��i�|rt���ni���iS)N�SCRIPT�scriptsz^\/.+$)rr
r&rrr+s r�scriptz
Schema.scriptLsp��
�f�k�k���n�n�&�&���v�5�5�i�@�@���+1�8�v�}�}����b����
�	
rc��dt�it����|rt���ni���iS)N�USER)rr
r&rr+s r�userzSchema.userXsQ��
�F�K�K���n�n�&�&��*0�8�v�}�}����b����
�	
rFc�l�t�it�|������S�Nr+)rr
r0r+s r�
target_scriptzSchema.target_scriptcs3���{�{�
��-�-�v�-�.�.�
�
�
�	
rc��t�it�|����t�|������Sr5�rr
r,r0r+s r�target_admin_and_scriptzSchema.target_admin_and_scriptksI���{�{�
��,�,�f�,�-�-�
��-�-�v�-�.�.�
�
�
�	
rc��t�it�|����t�|������Sr5r8r+s r�
target_allzSchema.target_alltsK���{�{�
��,�,�f�,�-�-�
��-�-�v�-�.�.�
�
�
�	
r)N)T)F)�__name__�
__module__�__qualname__�staticmethodr
rrrrr&r,r0r3r6r9r;rrrrr	so�������
�
��\�
��	
�	
�	
��\�	
��-�-�-��\�-��
�
��\�
��
�
��\�
��
�
��\�
��	
�	
��\�	
��	
�	
��\�	
��
�
��\�
��
�
�
��\�
��
�
�
��\�
��
�
�
��\�
�
�
rrc��eZdZdZd�ZdS)�HooksConfigReader�_imunifyc��tj|jd��tj|jdt	j|j��j��dS)Ni�r)�os�chmod�path�chown�grp�getgrnam�
GROUP_NAME�gr_gid)�selfs r�_post_writezHooksConfigReader._post_write�sA��
����E�"�"�"�
����A�s�|�D�O�<�<�C�D�D�D�D�DrN)r<r=r>rJrMrrrrArAs-�������J�E�E�E�E�ErrAc�t��eZdZej�ejej��f�fd�	Z	d�Z
d�Z�xZS)�HooksConfigc
� ��tj�r�t�t�d���dddd�t�d���d���dt�t�d���t���t�d���d	���dgd
�t�t�d���t���t�d���t���t���t���t���t���d���id
�n�t�t���t���t���t���t���t���d���id�}t���
||t|�����dS)NF)rrT)rr
rr$)�default_emails�notify_from_email�localer)�username�emailsrSrr+)�REALTIME_MALWARE_FOUND�USER_SCAN_MALWARE_FOUND�SCRIPT_BLOCKED�USER_SCAN_STARTED�CUSTOM_SCAN_STARTED�USER_SCAN_FINISHED�CUSTOM_SCAN_FINISHED�CUSTOM_SCAN_MALWARE_FOUND)r,�users�rulesr
)rWrYrZr[r\r])r_r
)rF�validation_schema�
config_reader)r�disabledrr
rrr9r;r6�super�__init__rA)rLrFr`�	__class__s   �rrdzHooksConfig.__init__�s`���d�&�];
����*0�*?�*?�,1�+@�+�+�%-�'+�(,�.�.�
#)�-�-��-�">�">�
�
���#�$�k�k�(.�
�
�u�
�(E�(E�&,�&;�&;�&=�&=�&,�m�m�T�m�&B�&B�����!%�!��� ���#�:�:�$�:�G�G�39�3D�3D�3F�3F�*0�*H�*H�#'�+I�+�+�.4�-A�-A�-C�-C�/5�/C�/C�/E�/E�.4�.B�.B�.D�.D�06�0D�0D�0F�0F�"�:�:�<�<�����$�Y-
�-
�-
�` ���39�3G�3G�3I�3I�-3�-A�-A�-C�-C�/5�/C�/C�/E�/E�.4�.B�.B�.D�.D�06�0D�0D�0F�0F�5;�5I�5I�5K�5K�
��	�	����a	�|	������/�+�D�1�1�	�	
�	
�	
�	
�	
rc�Z�|���}|�dd��|S�Nr^)�config_to_dict�pop�rLrs  r�getzHooksConfig.get�s,���"�"�$�$������$�����rc�\�|�dd��|�|��dSrg)ri�dict_to_configrjs  r�updatezHooksConfig.update�s0������$�������D�!�!�!�!�!r)
r<r=r>rDrF�joinr�GLOBAL_CONFDIR�HOOKS_CONFIGFILENAMErdrkrn�
__classcell__)res@rrOrO�s{��������7�<�<�� 3�T�5N�O�O�E
�E
�E
�E
�E
�E
�N���
"�"�"�"�"�"�"rrO)rHrD� defence360agent.contracts.configrr�)defence360agent.contracts.config_providerr�defence360agent.utilsrrrArOrrr�<module>rvs���
�
�
�
�	�	�	�	�9�9�9�9�9�9�9�9�B�B�B�B�B�B�0�0�0�0�0�0�s
�s
�s
�s
�s
�s
�s
�s
�lE�E�E�E�E��E�E�E�O"�O"�O"�O"�O"�&�O"�O"�O"�O"�O"rdefence360agent/contracts/__pycache__/license.cpython-311.opt-1.pyc0000644000000000000000000007707400000000000022060 0ustar  �

n�V,����ddlZddlZddlZddlZddlZddlZddlZddlZddlZddl	Z	ddl
mZddlmZddl
mZddlmZddlmZddlmZddlmZdd	lmZdd
lmZmZmZmZmZddlmZdd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/dZ0edd��Z1ed��xZ2�3��s*ed��xZ2�3��sed��Z2e)e(ej4���ej5��Z6e)e(ej4���ej5��Z7Gd�de8��Z9Gd�d��Z:d�Z;de<d e=fd!�Z>dS)"�N)�suppress)�JSONDecodeError)�Path)�TimeoutExpired)�Optional)�OperationalError)�is_cpanel_installed)�sentry)�ANTIVIRUS_MODE�Core�
CustomBilling�int_from_envvar�logger)�	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)�period�on_dropc��eZdZdZdS)�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@s������C�C�C�Cr%rc�.�eZdZdZdZeed�ZdZdZdZdZ	dZ
d	Zd
ZdZ
gd�ZiZd
Zeeed���dedededeeeeeffd�����Zed8dedefd���Zedeeeefdeeeeffd���Zed���Zee e!j"e#���d���de$fd�����Z%edeefd���Z&ed���Z'ed ���Z(ed!���Z)edefd"���Z*ed9d#���Z+ed9d$efd%���Z,ed&���Z-ed'���Z.ed(���Z/ed)���Z0ed*���Z1ed+���Z2ed,eedeefd-���Z3ed.���Z4ed/���Z5ed0���Z6edefd1���Z7edefd2���Z8edefd3���Z9edefd4���Z:edefd5���Z;ed6���Z<edefd7���Z=d
S):�
LicenseCLN)�id�status�group�limit�token_created_utc�token_expire_utc)r)r*r,r-r.�group_id�permissions)��z!/usr/share/imunify360/cln-pub.key)z)/usr/share/imunify360/alt-license-pub.keyz/var/imunify360/license.jsonz!/var/imunify360/license-free.jsonz9https://cln.cloudlinux.com/console/purchase/ImunifyAvPlusz8https://www.cloudlinux.com/upgrade-imunify-{user_count}/z6../../../scripts14/purchase_imunifyavplus_init_IMUNIFYz3../../../scripts14/purchase_imunify360_init_IMUNIFY)r1��Nr2)�	max_tries�pubkey_path�content�	signature�returnc	�$�g}d}tjd���5}|�|��|���tddd|d|jg}	t
j|tjtj|d�	��}|j	d
krd}nb|�
d|j	�d|j�d
|j����n4#t$r'}|�
d|j����Yd}~nd}~wwxYwddd��n#1swxYwY||pdfS)z�Verify that `content` is correctly signed with public key from file
        `pubkey_path` with resulting `signature`. Returns a tuple with (success, error_list).
        FT)�delete�dgstz-sha512z-verifyz
-signature�)�stdout�stderr�input�timeoutrz1Signature verification failed - openssl returned z
. stdout: z
, stderr: z openssl command failed: missing N)�tempfile�NamedTemporaryFile�write�flush�OPENSSL_BIN�name�
subprocess�run�PIPE�
returncode�appendr>r?�FileNotFoundError�filename)	r6r7r8�errors�result�sig_file�cmd�p�es	         r&�_verify_signaturezLicenseCLN._verify_signaturevs�������
�
(��
5�
5�
5�	���N�N�9�%�%�%��N�N�����������
��C�
��N��%�?�%�?�!�������<�1�$�$�!�F�F��M�M�B�,-�L�B�B�#$�8�B�B�78�x�B�B������
%�
O�
O�
O��
�
�M���M�M�N�N�N�N�N�N�N�N�����
O����)	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�@�v�~��%�%s;�=D�-C�<D�
C2�C-�(D�-C2�2D�D�Dr1�versionc��g}|j|D]�}||}t|t��rE|�d�d�|���D�������d|�|�d���||�t
|������d�|�����S)N�c3�*K�|]\}}|�d|��V��dS)�=Nr$)�.0�subkey�subvalues   r&�	<genexpr>z2LicenseCLN._get_signature_input.<locals>.<genexpr>�sH������,�F�H�"�.�.�H�.�.������r%�null)�VERIFY_FIELDS_MAP�
isinstance�dictrL�join�items�str�encode)�cls�licenserV�parts�key�values      r&�_get_signature_inputzLicenseCLN._get_signature_input�s������(��1�	)�	)�C��C�L�E��%��&�&�

)�����G�G���05���
�
���������������V�$�$�$�$����S��Z�Z�(�(�(�(��w�w�u�~�~�$�$�&�&�&r%�signature_listc�F��
�g�
�
�fd�}|D]y\}}tj|��}	��||���}n#t$rY�>wxYw|�j||��r|dfcS�jD]}||||��r|dfccS��z�
D]}	t
jd|	���dS)zc
        Verify signatures in license

        :return: signature, is_alternative, version
        c�V���j|i|��\}}|r��|��|S�N)rU�extend)�args�kwargs�successrO�
all_errorsrgs    ��r&�verify_and_collect_errorsz=LicenseCLN._find_signature.<locals>.verify_and_collect_errors�s?���3�c�3�T�D�V�D�D�O�G�V��
*��!�!�&�)�)�)��Nr%)rVFTz%s�NF)�base64�	b64decoderl�KeyError�_PUBKEY_FILE�_ALTERNATIVE_PUBKEY_FILESr�warning)rg�
license_tokenrmrv�signrVr8r7�
alt_pubkey�errorrus`         @r&�_find_signaturezLicenseCLN._find_signature�s6����!#�
�	�	�	�	�	�	�,�	&�	&�M�D�'��(��.�.�I�
��2�2�!�7�3�������
�
�
���
����)�(��)9�7�I�N�N�
#��U�{�"�"�"�!�;�
&�
&�
�,�,�Z��)�L�L�&���:�%�%�%�%�%�&�
&� �	(�	(�E��N�4��'�'�'�'��{s�?�
A�Ac	���i}	t|��5}tj|��}t|t��s%tjd|����|cddd��S|�|d�|�dg��D����\}}|�d��}|rD|�||dfg��\}}	|�%td��|�
dd��n(d|vr$|�
d��td	��|�td
��|cddd��S||d<||d<|cddd��S#1swxYwYn�#t$rtj
d
��Ynpt$r}
tjd|
��Yd}
~
nMd}
~
wt t"t$t&jt*f$r}
tjd|
��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 Nc��g|]}|df��S�r1r$)r[rs  r&�
<listcomp>z*LicenseCLN._load_token.<locals>.<listcomp>�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 signaturer�is_alternativez'Failed to load license: not registered?zFailed to load license: %s)�open�json�loadrarbrr�r��get�throttled_log_error�pop�throttled_log_no_v2rM�inforr}�OSErrorrz�UnicodeDecodeError�binascii�Error�	TypeError)rg�path�default�fr~r8r��v2_sign�_sign�_rTs           r&�_load_tokenzLicenseCLN._load_token�s�����;	:��d���)
%�q� $�	�!���
�!�-��6�6�#��L�L�(�=�+����#�)
%�)
%�)
%�)
%�)
%�)
%�)
%�)
%�-0�,?�,?�!���$1�$5�$5�l�B�$G�$G����-�-�)�	�>�(�+�+�N�;�;����"�2�2�%��!��~� � �H�E�1��}�+�C����&�)�)�-��>�>�>��"�m�3�3�!�%�%�m�4�4�4�'�'�����$�'�(L�M�M�M�"�K)
%�)
%�)
%�)
%�)
%�)
%�)
%�)
%�N)2�
�f�%�2@�
�.�/�$�S)
%�)
%�)
%�)
%�)
%�)
%�)
%�)
%�)
%�)
%�)
%�)
%����)
%�)
%�)
%�)
%�)
%��V!�	C�	C�	C��K�A�B�B�B�B�B��	<�	<�	<�
�N�7��;�;�;�;�;�;�;�;���������N��
�	:�	:�	:�
�L�5�q�9�9�9�9�9�9�9�9�����	:�����sf�E�AE�E�"CE�.E�;E�E�E�E�E�E�G,�?	G,�F"�"+G,�
G'�'G,)�seconds)�maxsizec��i}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_files�lfs    r&�	get_tokenzLicenseCLN.get_token&sr���	��
%�S�
�� 6�7�7��#�$�	�
 �	!�	!�B�����+�+�I��
!� � � � �
!��r%c�P�|����d��S)z$
        :return: server id
        r)�r�r��rgs r&�
get_server_idzLicenseCLN.get_server_id=s ��
�}�}���"�"�4�(�(�(r%c�D�t|�����S)z1
        :return: bool: if we have token
        )�boolr�r�s r&�
is_registeredzLicenseCLN.is_registeredDs��
�C�M�M�O�O�$�$�$r%c�b�to(|���o|���S)ze
        :return: Return true only if we have valid ImunifyAV+ or
        Imunify360 license
        )r�is_valid�is_freer�s r&�is_valid_av_pluszLicenseCLN.is_valid_av_plusKs'���H�#�,�,�.�.�H�#�+�+�-�-�6G�Hr%c�N�tsdS|���tkSrw)rr��
AV_DEFAULT_IDr�s r&r�zLicenseCLN.is_freeSs&���	��5�� � �"�"�m�3�3r%c�<�tsdS|���S)zCCloud-assisted cleanup is a paid feature (Imunify360 / ImunifyAV+).T)rr�r�s r&�!is_cloud_assisted_cleanup_allowedz,LicenseCLN.is_cloud_assisted_cleanup_allowedYs#���	��4��#�#�%�%�%r%c�R�|p|���}|sdStrF|�dd���d��o|dt	j��kS|ddvo6|dt	j��ko|jdup|j|dkS)	z�License check based on license token

        return True - if license token is valid for this server
        return False - if license token is invalid
        Fr*rX�okr.�r��ok-trialNr,)r�rr��
startswith�time�users_count�rg�tokens  r&r�zLicenseCLN.is_valid`s����(��������	��5��	��	�	�(�B�'�'�2�2�4�8�8�=��,�-�����<�
�
�(�O�1�1�
O��(�)�T�Y�[�[�8�
O���D�(�M�C�O�u�W�~�,M�	
r%�
permissionc��|p|���}|sdS||�di��x}vo||dkS)z�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
        Fr0�ENABLEDr�)rgr�r��perms    r&�has_permissionzLicenseCLN.has_permissionwsW���(��������	��5�
�5�9�9�]�B�#?�#?�?�4�@�
.��Z� �I�-�	
r%c�~�|���}|s |�d���|d|d<|jdz}tjtjztjz}d}tt��5tj	|��ddd��n#1swxYwYtj
tj|||��d��5}tj
||��ddd��n#1swxYwYtj|dd�	��tj||j��|j���t%j|�����t%j|�����	|�||��dS#t0$rYdSwxYw)
zb
        Write new license token to file
        :param token: new token
        :return:
        r,N�saved_user_limitz.tmpi��w�root�_imunify)�userr+)r�r�r��os�O_WRONLY�O_CREAT�O_EXCLrrM�unlink�fdopenr�r��dump�shutil�chown�rename�cache_clearr
�
set_server_idr��set_product_name�get_product_name�
renew_hookr)rgr��	old_token�	temp_file�flags�moder�s       r&�updatezLicenseCLN.update�s���M�M�O�O�	��	7�U�Y�Y�w�/�/�;�(-�g��E�$�%��%��.�	���b�j�(�2�9�4����
�'�
(�
(�	!�	!��I�i� � � �	!�	!�	!�	!�	!�	!�	!�	!�	!�	!�	!����	!�	!�	!�	!�
�Y�r�w�y�%��6�6��
<�
<�	 ���I�e�Q����	 �	 �	 �	 �	 �	 �	 �	 �	 �	 �	 ����	 �	 �	 �	 �	��Y�V�:�>�>�>�>�
�	�)�S�.�/�/�/��
�!�!�#�#�#���S�.�.�0�0�1�1�1���� 4� 4� 6� 6�7�7�7�	��N�N�9�e�,�,�,�,�,���	�	�	��D�D�	���s6�=B�B"�%B"�C4�4C8�;C8�F.�.
F<�;F<c����gd�}��d��}|����}t��fd�|D����}|r=tj||���}ddlm}tj||��d���dSdS)	N)�license_expire_utcr*r,r)r�c�h��g|].}��|����|��k��/Sr$�r�)r[�elemr�r�s  ��r&r�z)LicenseCLN.renew_hook.<locals>.<listcomp>�s4���O�O�O��U�Y�Y�t�_�_�	�
�
�d� 3� 3�
3�O�O�Or%)�exp_timerhr)�
execute_hooksT)�return_exceptions)	r��fill_license_type�anyr�LicenseRenewed�defence360agent.hooks.executer��asyncio�gather)	rgr�r��important_keysr��license_type�	condition�license_updatedr�s	 ``      r&r�zLicenseCLN.renew_hook�s�����H�H�H���9�9�1�2�2���,�,�U�3�3���O�O�O�O�O��O�O�O�
�
�	��	�'�6�!�<����O�
D�C�C�C�C�C��N��
�o�.�.�$�
�
�
�
�
�
�
	�	r%c�6�tt��5tj|j��ddd��n#1swxYwY|j���tjd��tj	|�
����dS)zY
        Delete license token along with old-style license data
        :return:
        N)rrMr�r�r�r�r�r
r�r�r�r�s r&r;zLicenseCLN.delete�s����'�
(�
(�	)�	)��I�c�'�(�(�(�	)�	)�	)�	)�	)�	)�	)�	)�	)�	)�	)����	)�	)�	)�	)��
�!�!�#�#�#���T�"�"�"���� 4� 4� 6� 6�7�7�7�7�7s
�;�?�?c�d�|�d��}ddddd�}|�|��S)Nr*�
imunify360�imunify360Trial�	imunifyAV�
imunifyAVPlus)r�r��ok-av�ok-avpr�)rgr�r��license_type_to_products    r&r�zLicenseCLN.fill_license_type�sB���y�y��*�*���)� �%�	#
�#
��'�*�*�<�8�8�8r%c�P�|�|�����Srp)r�r�r�s r&�get_license_typezLicenseCLN.get_license_type�s���$�$�S�]�]�_�_�5�5�5r%c��|���}|�dd������d��rdSdS)Nr)rXzip-TF)r�r��lowerr�r�s  r&�is_ip_license_typezLicenseCLN.is_ip_license_type�sI���
�
�����9�9�T�2���$�$�&�&�1�1�%�8�8�	��4��ur%�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})r�rr��VERSION_THRESHOLDS�replacere)rgr��nr�
user_count�	thresholds      r&�format_upgrade_urlzLicenseCLN.format_upgrade_url�s����	 ����O���u�V�R� � ��
�9��J�J� �3�
)�
)�	��	�>�>�!*�J��E�"�)�
�#�+�+�N�C�
�O�O�L�L��#�+�+�H�d�;�;��#�+�+��s��
�1�1�1�5�5�
�
���r%c�6�|dkrdS|dkrdS|dkrdSdS)z1Get recommended license tier based on user count.r1zSingle userr3zUp to 30 usersr4zUp to 250 userszUnlimited usersr$)rgrs  r&� _get_license_tier_recommendationz+LicenseCLN._get_license_tier_recommendations:����?�?� �=�
�2�
�
�#�#�
�3�
�
�$�$�$�$r%c�b�|�	dS|�|��}|dkrdnd}d|�d|�d|�d	�S)
z<Format enhanced message when user count exceeds saved limit.Nz�WARNING: License is invalid for current server. Unable to determine user count; please check KB article: https://cloudlinux.zendesk.com/hc/en-us/articles/r1r��usersz9WARNING: License is invalid for current server. Detected � u → purchase the "z=" Imunify360 license. Pricing: https://imunify360.com/pricing)r	)rgr�	tier_name�	user_words    r&� _format_license_exceeded_messagez+LicenseCLN._format_license_exceeded_message
sv����D�
�
��8�8��D�D�	�(�A�o�o�F�F�7�	�
6�"�
6�
6�%.�
6�
6�&�
6�
6�
6�	
r%c	�6�|���}|�d��dv}|�dd��}trtjrtjsd}tr|r|sd}|r�|���|�dd��|�d��|�d��|j||�|��d	�}tsW|�d
���B|j�;|j|�d
��kr|�	|j��|d<nddi}d|d<d|d
<tr�dg}|�d��r|D]}||dvrd|d<�|r+tj
otjdup
tjdu|d<t��}|�
tj��p#|dp|�d��p|j|d<|�
tj��p|dp|dp
t��|d
<|sd|d<n ts|�dd��|d<|���rd|d<|���|d<|S)Nr*r��messagez�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/r�rr,r))r*�
expiration�
user_limitr)rrr�r�F�upgrade_url�upgrade_url_360zuser limits�
ip_license�buy_url�upgrade_license_url�redirect_urlT�demo�eligible_for_imunify_patch)r�r�rr
�UPGRADE_URL�
NOTIFICATIONSr�r�r�r�
IP_LICENSE�UPGRADE_URL_360rr�AV_PLUS_BUY_URL�upgrade_url_default�is_demo�is_eligible_for_imunify_patch)rgr��key_360rr��ignored_messages�msg�
plesk_urlss        r&�license_infozLicenseCLN.license_info!s���
�
�����)�)�H�%�%�);�;���)�)�I�t�,�,���	��)�	�"�/�	�
�G��	�g�	�g�	�<�
��	%��,�,�.�.�#�i�i�(<�a�@�@�#�i�i��0�0��i�i��o�o�!�o�"� #� 5� 5�e� <� <���D�#�
��I�I�0�1�1�=��O�/��O�e�i�i�0B�&C�&C�C�C�"%�"F�"F��O�#�#��Y����e�$�D�"��]��"&��
���	�� ���x�x�	�"�"�
/�+�/�/�C��d�9�o�-�-�*.��Y����
�%2�%=�&�!�-�T�9�A�$�4�D�@��\�"�0�1�1�J��&�&�}�'@�A�A�'��i�(�'��9�9�]�+�+�'��&�	
����&�&�}�'D�E�E�)��3�4�)��i�(�)�'�(�(�	
�"�#��	B�#'�D�� � ��	B�#(�9�9�]�D�#A�#A�D�� ��;�;�=�=�	 ��D��L�
�-�-�/�/�	
�(�	
��r%c�*�|jduo
|jdkS)Nr1)r�r�s r&�is_vpszLicenseCLN.is_vps~s����d�*�C�s��!�/C�Cr%c���dg}dg}t��r4|�|j��|�|j��tj|vo
tj|vSrp)r	rL�CPANEL_UPGRADE_URL�CPANEL_UPGRADE_URL_360r
rr)rg�upgrade_urls�upgrade_urls_360s   r&�is_custom_reseller_configuredz(LicenseCLN.is_custom_reseller_configured�sw��-1�F��15���� � �	@����� 6�7�7�7��#�#�C�$>�?�?�?�
�%��5�
B��-�1A�A�
�	
r%c�|�|���o(|���o|���Srp)r*r�r0r�s r&r#z(LicenseCLN.is_eligible_for_imunify_patch�s;��
�J�J�L�L�
8����
�
�
8��5�5�7�7�7�	
r%c���tstjS|����dd��}|dkrdS|dvrdStjd|��dS)	Nr*rXr�z
imunify.av)r�r�r�zimunify.av+zUnknown license %szUnknown license)rr�NAMEr�r�rr�)rg�license_statuss  r&r�zLicenseCLN.get_product_name�si���	��9�������,�,�X�r�:�:���W�$�$��<�
�;�
;�
;� �=��L�-�~�>�>�>�$�$r%c�@�tj�d��S)Nz/var/imunify360/demo)r�r��isfiler�s r&r"zLicenseCLN.is_demo�s���w�~�~�4�5�5�5r%c�h�|���}|�dd��tkS)Nr,r)r�r��UNLIMITED_USERS_COUNTr�s  r&�is_unlimitedzLicenseCLN.is_unlimited�s)���
�
�����y�y��!�$�$�(=�=�=r%c���|j�|j�d���S|jD]*}|j|kr|j�|���cS�+|j�d���S)Nr1)rr)r��IM360_BUY_URL_TEMPLATE�formatr)rgrs  r&�get_im360_buy_urlzLicenseCLN.get_im360_buy_url�s����?�"��-�4�4��4�B�B�B��/�	O�	O�I���)�+�+��1�8�8�I�8�N�N�N�N�N�,��)�0�0�K�0�H�H�Hr%r�rp)>r r!r"�VERIFY_FIELDS_V1�VERIFY_FIELDS_V2r`r{r|r�r�r r;r,r-r�_tokenr��staticmethodrrre�bytes�tupler�r�listrU�classmethod�intrlr�r�r�datetime�	timedelta�_CACHE_LICENSE_TOKEN_TIMEOUTrbr�r�r�r�r�r�r�r�r�r�r;r�r�r�rr	rr(r*r0r#r�r"r9r=r$r%r&r(r(Ds*���������������
7�L�!��3�M�<��C��	C��	A��	>��&����
�F��K��
�X�n��*�*�*�)&��)&�#(�)&�5:�)&�	�t�X�d�3�i�(�(�	)�)&�)&�)&�+�*��\�)&�V�'�'�C�'��'�'�'��[�'�"�%�,0��s�C�x��,A�%�	�x��}�d�"�	#�%�%�%��[�%�N�F�F��[�F�P��[����#?�@�@�@�!�����$�������[��&�)�h�s�m�)�)�)��[�)��%�%��[�%��I�I��[�I��4�4��[�4�
�&�$�&�&�&��[�&��
�
�
��[�
�,�

�

��

�

�

��[�

�����[��B����[��$�	8�	8��[�	8��9�9��[�9��6�6��[�6�����[��� �h�s�m� ���
� � � ��[� �D�	%�	%��[�	%��
�
��[�
�&�Z�Z��[�Z�x�D�t�D�D�D��[�D��
�d�
�
�
��[�
��
�d�
�
�
��[�
��%��%�%�%��[�%��6��6�6�6��[�6��>�>��[�>��I�#�I�I�I��[�I�I�Ir%r(c�B�tj}tjdd��}t	��r�t
jtjkr�t|��stj	Sd}d}	tj��}tj
|��r|}ntjd|��n,#t $r}tjd|��Yd}~nd}~wwxYw|dkrd|��}nd|��}||zSt���d	|��zd
|��t'|��zzS)NrrXz<https://store.cpanel.net/index.php?rp=/store/partner-addons/zJServer IP is IPv6 (%s), cPanel Store requires IPv4. Omitting IP parameter.zFailed to get server IP: %sr1z/imunify360-for-cpanel-solo&customfield%5B55%5D=z imunify360&customfield%5B375%5D=z?iaid=z&users=)r(r�rr�r	r
rr,�_eligible_for_new_upgrade_linksr-r�	server_ipr�is_valid_ipv4_addrrr�rr}r=r�)rr�base_urlrL�iprT�suffixs       r&r!r!�sq����A��5�����D�	���+!�
�%��)F�F�F�/�t�4�4�	5��4�4�

K�	��	�
	=��$�&�&�B��$�R�(�(�
��	�	���-�������
�	=�	=�	=��N�8�!�<�<�<�<�<�<�<�<�����	=����
��6�6�M�)�M�M�
�F�D�	�C�C�F��&� � �	�$�$�&�&�
�4�/�/�	�
�A�-�-�$�q�'�'�
!�	"�s�*?B*�*
C�4C�Crr9c��tjd|��t|��dkrtjd��dS	t	|dd��}n%#t
$rtjd��YdSwxYwd}||kS)Nz,checking if iaid: %s is eligible for upgraderz(receive empty iaid, fallback to old linkF�z%iaid is not hex, fallback to old link�)r�debug�lenr}rF�
ValueError)r�
hex_bucket�hex_mids   r&rKrK�s���
�L�?��F�F�F�
�4�y�y�A�~�~���A�B�B�B��u����a��"�%�%�
�
��������>�?�?�?��u�u������G����s�A�A9�8A9)?r�rxr�rGr�r�r�rHrBr��
contextlibrr�pathlibrr�typingr�peeweer�3defence360agent.application.determine_hosting_panelr	�defence360agent.contractsr
� defence360agent.contracts.configrrr
rr�%defence360agent.contracts.hook_eventsr�&defence360agent.internals.global_scoper�0defence360agent.subsys.panels.plesk.upgrade_urlsr�defence360agent.utilsrr�defence360agent.utils.commonrr�defence360agent.utils.ipechorr�defence360agent.utils.validaterr�r8rIrF�existsr}r�r�r��	Exceptionrr(r!rer�rKr$r%r&�<module>ris_������
�
�
�
�������������	�	�	�	�
�
�
�
������������������� � � � � � �������%�%�%�%�%�%�������#�#�#�#�#�#�������-�,�,�,�,�,���������������<�;�;�;�;�;�4�4�4�4�4�4�������8�7�7�7�7�7�7�7�9�9�9�9�9�9�9�9�<�<�<�<�<�<�<�<�-�-�-�-�-�-��
�"�� /��,�� � ���t�<�=�=�=��E�E�G�G�/��4� >�?�?�?�K�G�G�I�I�/��d�-�.�.��E�j�j��f�n�E�E�E�
�L����F�j�j��f�n�E�E�E�
�L����
D�D�D�D�D�9�D�D�D�t	I�t	I�t	I�t	I�t	I�t	I�t	I�t	I�n5�5�5�p
 �#�
 �$�
 �
 �
 �
 �
 �
 r%defence360agent/contracts/__pycache__/license.cpython-311.pyc0000644000000000000000000007707400000000000021121 0ustar  �

n�V,����ddlZddlZddlZddlZddlZddlZddlZddlZddlZddl	Z	ddl
mZddlmZddl
mZddlmZddlmZddlmZddlmZdd	lmZdd
lmZmZmZmZmZddlmZdd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/dZ0edd��Z1ed��xZ2�3��s*ed��xZ2�3��sed��Z2e)e(ej4���ej5��Z6e)e(ej4���ej5��Z7Gd�de8��Z9Gd�d��Z:d�Z;de<d e=fd!�Z>dS)"�N)�suppress)�JSONDecodeError)�Path)�TimeoutExpired)�Optional)�OperationalError)�is_cpanel_installed)�sentry)�ANTIVIRUS_MODE�Core�
CustomBilling�int_from_envvar�logger)�	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)�period�on_dropc��eZdZdZdS)�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@s������C�C�C�Cr%rc�.�eZdZdZdZeed�ZdZdZdZdZ	dZ
d	Zd
ZdZ
gd�ZiZd
Zeeed���dedededeeeeeffd�����Zed8dedefd���Zedeeeefdeeeeffd���Zed���Zee e!j"e#���d���de$fd�����Z%edeefd���Z&ed���Z'ed ���Z(ed!���Z)edefd"���Z*ed9d#���Z+ed9d$efd%���Z,ed&���Z-ed'���Z.ed(���Z/ed)���Z0ed*���Z1ed+���Z2ed,eedeefd-���Z3ed.���Z4ed/���Z5ed0���Z6edefd1���Z7edefd2���Z8edefd3���Z9edefd4���Z:edefd5���Z;ed6���Z<edefd7���Z=d
S):�
LicenseCLN)�id�status�group�limit�token_created_utc�token_expire_utc)r)r*r,r-r.�group_id�permissions)��z!/usr/share/imunify360/cln-pub.key)z)/usr/share/imunify360/alt-license-pub.keyz/var/imunify360/license.jsonz!/var/imunify360/license-free.jsonz9https://cln.cloudlinux.com/console/purchase/ImunifyAvPlusz8https://www.cloudlinux.com/upgrade-imunify-{user_count}/z6../../../scripts14/purchase_imunifyavplus_init_IMUNIFYz3../../../scripts14/purchase_imunify360_init_IMUNIFY)r1��Nr2)�	max_tries�pubkey_path�content�	signature�returnc	�$�g}d}tjd���5}|�|��|���tddd|d|jg}	t
j|tjtj|d�	��}|j	d
krd}nb|�
d|j	�d|j�d
|j����n4#t$r'}|�
d|j����Yd}~nd}~wwxYwddd��n#1swxYwY||pdfS)z�Verify that `content` is correctly signed with public key from file
        `pubkey_path` with resulting `signature`. Returns a tuple with (success, error_list).
        FT)�delete�dgstz-sha512z-verifyz
-signature�)�stdout�stderr�input�timeoutrz1Signature verification failed - openssl returned z
. stdout: z
, stderr: z openssl command failed: missing N)�tempfile�NamedTemporaryFile�write�flush�OPENSSL_BIN�name�
subprocess�run�PIPE�
returncode�appendr>r?�FileNotFoundError�filename)	r6r7r8�errors�result�sig_file�cmd�p�es	         r&�_verify_signaturezLicenseCLN._verify_signaturevs�������
�
(��
5�
5�
5�	���N�N�9�%�%�%��N�N�����������
��C�
��N��%�?�%�?�!�������<�1�$�$�!�F�F��M�M�B�,-�L�B�B�#$�8�B�B�78�x�B�B������
%�
O�
O�
O��
�
�M���M�M�N�N�N�N�N�N�N�N�����
O����)	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�@�v�~��%�%s;�=D�-C�<D�
C2�C-�(D�-C2�2D�D�Dr1�versionc��g}|j|D]�}||}t|t��rE|�d�d�|���D�������d|�|�d���||�t
|������d�|�����S)N�c3�*K�|]\}}|�d|��V��dS)�=Nr$)�.0�subkey�subvalues   r&�	<genexpr>z2LicenseCLN._get_signature_input.<locals>.<genexpr>�sH������,�F�H�"�.�.�H�.�.������r%�null)�VERIFY_FIELDS_MAP�
isinstance�dictrL�join�items�str�encode)�cls�licenserV�parts�key�values      r&�_get_signature_inputzLicenseCLN._get_signature_input�s������(��1�	)�	)�C��C�L�E��%��&�&�

)�����G�G���05���
�
���������������V�$�$�$�$����S��Z�Z�(�(�(�(��w�w�u�~�~�$�$�&�&�&r%�signature_listc�F��
�g�
�
�fd�}|D]y\}}tj|��}	��||���}n#t$rY�>wxYw|�j||��r|dfcS�jD]}||||��r|dfccS��z�
D]}	t
jd|	���dS)zc
        Verify signatures in license

        :return: signature, is_alternative, version
        c�V���j|i|��\}}|r��|��|S�N)rU�extend)�args�kwargs�successrO�
all_errorsrgs    ��r&�verify_and_collect_errorsz=LicenseCLN._find_signature.<locals>.verify_and_collect_errors�s?���3�c�3�T�D�V�D�D�O�G�V��
*��!�!�&�)�)�)��Nr%)rVFTz%s�NF)�base64�	b64decoderl�KeyError�_PUBKEY_FILE�_ALTERNATIVE_PUBKEY_FILESr�warning)rg�
license_tokenrmrv�signrVr8r7�
alt_pubkey�errorrus`         @r&�_find_signaturezLicenseCLN._find_signature�s6����!#�
�	�	�	�	�	�	�,�	&�	&�M�D�'��(��.�.�I�
��2�2�!�7�3�������
�
�
���
����)�(��)9�7�I�N�N�
#��U�{�"�"�"�!�;�
&�
&�
�,�,�Z��)�L�L�&���:�%�%�%�%�%�&�
&� �	(�	(�E��N�4��'�'�'�'��{s�?�
A�Ac	���i}	t|��5}tj|��}t|t��s%tjd|����|cddd��S|�|d�|�dg��D����\}}|�d��}|rD|�||dfg��\}}	|�%td��|�
dd��n(d|vr$|�
d��td	��|�td
��|cddd��S||d<||d<|cddd��S#1swxYwYn�#t$rtj
d
��Ynpt$r}
tjd|
��Yd}
~
nMd}
~
wt t"t$t&jt*f$r}
tjd|
��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 Nc��g|]}|df��S�r1r$)r[rs  r&�
<listcomp>z*LicenseCLN._load_token.<locals>.<listcomp>�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 signaturer�is_alternativez'Failed to load license: not registered?zFailed to load license: %s)�open�json�loadrarbrr�r��get�throttled_log_error�pop�throttled_log_no_v2rM�inforr}�OSErrorrz�UnicodeDecodeError�binascii�Error�	TypeError)rg�path�default�fr~r8r��v2_sign�_sign�_rTs           r&�_load_tokenzLicenseCLN._load_token�s�����;	:��d���)
%�q� $�	�!���
�!�-��6�6�#��L�L�(�=�+����#�)
%�)
%�)
%�)
%�)
%�)
%�)
%�)
%�-0�,?�,?�!���$1�$5�$5�l�B�$G�$G����-�-�)�	�>�(�+�+�N�;�;����"�2�2�%��!��~� � �H�E�1��}�+�C����&�)�)�-��>�>�>��"�m�3�3�!�%�%�m�4�4�4�'�'�����$�'�(L�M�M�M�"�K)
%�)
%�)
%�)
%�)
%�)
%�)
%�)
%�N)2�
�f�%�2@�
�.�/�$�S)
%�)
%�)
%�)
%�)
%�)
%�)
%�)
%�)
%�)
%�)
%�)
%����)
%�)
%�)
%�)
%�)
%��V!�	C�	C�	C��K�A�B�B�B�B�B��	<�	<�	<�
�N�7��;�;�;�;�;�;�;�;���������N��
�	:�	:�	:�
�L�5�q�9�9�9�9�9�9�9�9�����	:�����sf�E�AE�E�"CE�.E�;E�E�E�E�E�E�G,�?	G,�F"�"+G,�
G'�'G,)�seconds)�maxsizec��i}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_files�lfs    r&�	get_tokenzLicenseCLN.get_token&sr���	��
%�S�
�� 6�7�7��#�$�	�
 �	!�	!�B�����+�+�I��
!� � � � �
!��r%c�P�|����d��S)z$
        :return: server id
        r)�r�r��rgs r&�
get_server_idzLicenseCLN.get_server_id=s ��
�}�}���"�"�4�(�(�(r%c�D�t|�����S)z1
        :return: bool: if we have token
        )�boolr�r�s r&�
is_registeredzLicenseCLN.is_registeredDs��
�C�M�M�O�O�$�$�$r%c�b�to(|���o|���S)ze
        :return: Return true only if we have valid ImunifyAV+ or
        Imunify360 license
        )r�is_valid�is_freer�s r&�is_valid_av_pluszLicenseCLN.is_valid_av_plusKs'���H�#�,�,�.�.�H�#�+�+�-�-�6G�Hr%c�N�tsdS|���tkSrw)rr��
AV_DEFAULT_IDr�s r&r�zLicenseCLN.is_freeSs&���	��5�� � �"�"�m�3�3r%c�<�tsdS|���S)zCCloud-assisted cleanup is a paid feature (Imunify360 / ImunifyAV+).T)rr�r�s r&�!is_cloud_assisted_cleanup_allowedz,LicenseCLN.is_cloud_assisted_cleanup_allowedYs#���	��4��#�#�%�%�%r%c�R�|p|���}|sdStrF|�dd���d��o|dt	j��kS|ddvo6|dt	j��ko|jdup|j|dkS)	z�License check based on license token

        return True - if license token is valid for this server
        return False - if license token is invalid
        Fr*rX�okr.�r��ok-trialNr,)r�rr��
startswith�time�users_count�rg�tokens  r&r�zLicenseCLN.is_valid`s����(��������	��5��	��	�	�(�B�'�'�2�2�4�8�8�=��,�-�����<�
�
�(�O�1�1�
O��(�)�T�Y�[�[�8�
O���D�(�M�C�O�u�W�~�,M�	
r%�
permissionc��|p|���}|sdS||�di��x}vo||dkS)z�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
        Fr0�ENABLEDr�)rgr�r��perms    r&�has_permissionzLicenseCLN.has_permissionwsW���(��������	��5�
�5�9�9�]�B�#?�#?�?�4�@�
.��Z� �I�-�	
r%c�~�|���}|s |�d���|d|d<|jdz}tjtjztjz}d}tt��5tj	|��ddd��n#1swxYwYtj
tj|||��d��5}tj
||��ddd��n#1swxYwYtj|dd�	��tj||j��|j���t%j|�����t%j|�����	|�||��dS#t0$rYdSwxYw)
zb
        Write new license token to file
        :param token: new token
        :return:
        r,N�saved_user_limitz.tmpi��w�root�_imunify)�userr+)r�r�r��os�O_WRONLY�O_CREAT�O_EXCLrrM�unlink�fdopenr�r��dump�shutil�chown�rename�cache_clearr
�
set_server_idr��set_product_name�get_product_name�
renew_hookr)rgr��	old_token�	temp_file�flags�moder�s       r&�updatezLicenseCLN.update�s���M�M�O�O�	��	7�U�Y�Y�w�/�/�;�(-�g��E�$�%��%��.�	���b�j�(�2�9�4����
�'�
(�
(�	!�	!��I�i� � � �	!�	!�	!�	!�	!�	!�	!�	!�	!�	!�	!����	!�	!�	!�	!�
�Y�r�w�y�%��6�6��
<�
<�	 ���I�e�Q����	 �	 �	 �	 �	 �	 �	 �	 �	 �	 �	 ����	 �	 �	 �	 �	��Y�V�:�>�>�>�>�
�	�)�S�.�/�/�/��
�!�!�#�#�#���S�.�.�0�0�1�1�1���� 4� 4� 6� 6�7�7�7�	��N�N�9�e�,�,�,�,�,���	�	�	��D�D�	���s6�=B�B"�%B"�C4�4C8�;C8�F.�.
F<�;F<c����gd�}��d��}|����}t��fd�|D����}|r=tj||���}ddlm}tj||��d���dSdS)	N)�license_expire_utcr*r,r)r�c�h��g|].}��|����|��k��/Sr$�r�)r[�elemr�r�s  ��r&r�z)LicenseCLN.renew_hook.<locals>.<listcomp>�s4���O�O�O��U�Y�Y�t�_�_�	�
�
�d� 3� 3�
3�O�O�Or%)�exp_timerhr)�
execute_hooksT)�return_exceptions)	r��fill_license_type�anyr�LicenseRenewed�defence360agent.hooks.executer��asyncio�gather)	rgr�r��important_keysr��license_type�	condition�license_updatedr�s	 ``      r&r�zLicenseCLN.renew_hook�s�����H�H�H���9�9�1�2�2���,�,�U�3�3���O�O�O�O�O��O�O�O�
�
�	��	�'�6�!�<����O�
D�C�C�C�C�C��N��
�o�.�.�$�
�
�
�
�
�
�
	�	r%c�6�tt��5tj|j��ddd��n#1swxYwY|j���tjd��tj	|�
����dS)zY
        Delete license token along with old-style license data
        :return:
        N)rrMr�r�r�r�r�r
r�r�r�r�s r&r;zLicenseCLN.delete�s����'�
(�
(�	)�	)��I�c�'�(�(�(�	)�	)�	)�	)�	)�	)�	)�	)�	)�	)�	)����	)�	)�	)�	)��
�!�!�#�#�#���T�"�"�"���� 4� 4� 6� 6�7�7�7�7�7s
�;�?�?c�d�|�d��}ddddd�}|�|��S)Nr*�
imunify360�imunify360Trial�	imunifyAV�
imunifyAVPlus)r�r��ok-av�ok-avpr�)rgr�r��license_type_to_products    r&r�zLicenseCLN.fill_license_type�sB���y�y��*�*���)� �%�	#
�#
��'�*�*�<�8�8�8r%c�P�|�|�����Srp)r�r�r�s r&�get_license_typezLicenseCLN.get_license_type�s���$�$�S�]�]�_�_�5�5�5r%c��|���}|�dd������d��rdSdS)Nr)rXzip-TF)r�r��lowerr�r�s  r&�is_ip_license_typezLicenseCLN.is_ip_license_type�sI���
�
�����9�9�T�2���$�$�&�&�1�1�%�8�8�	��4��ur%�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})r�rr��VERSION_THRESHOLDS�replacere)rgr��nr�
user_count�	thresholds      r&�format_upgrade_urlzLicenseCLN.format_upgrade_url�s����	 ����O���u�V�R� � ��
�9��J�J� �3�
)�
)�	��	�>�>�!*�J��E�"�)�
�#�+�+�N�C�
�O�O�L�L��#�+�+�H�d�;�;��#�+�+��s��
�1�1�1�5�5�
�
���r%c�6�|dkrdS|dkrdS|dkrdSdS)z1Get recommended license tier based on user count.r1zSingle userr3zUp to 30 usersr4zUp to 250 userszUnlimited usersr$)rgrs  r&� _get_license_tier_recommendationz+LicenseCLN._get_license_tier_recommendations:����?�?� �=�
�2�
�
�#�#�
�3�
�
�$�$�$�$r%c�b�|�	dS|�|��}|dkrdnd}d|�d|�d|�d	�S)
z<Format enhanced message when user count exceeds saved limit.Nz�WARNING: License is invalid for current server. Unable to determine user count; please check KB article: https://cloudlinux.zendesk.com/hc/en-us/articles/r1r��usersz9WARNING: License is invalid for current server. Detected � u → purchase the "z=" Imunify360 license. Pricing: https://imunify360.com/pricing)r	)rgr�	tier_name�	user_words    r&� _format_license_exceeded_messagez+LicenseCLN._format_license_exceeded_message
sv����D�
�
��8�8��D�D�	�(�A�o�o�F�F�7�	�
6�"�
6�
6�%.�
6�
6�&�
6�
6�
6�	
r%c	�6�|���}|�d��dv}|�dd��}trtjrtjsd}tr|r|sd}|r�|���|�dd��|�d��|�d��|j||�|��d	�}tsW|�d
���B|j�;|j|�d
��kr|�	|j��|d<nddi}d|d<d|d
<tr�dg}|�d��r|D]}||dvrd|d<�|r+tj
otjdup
tjdu|d<t��}|�
tj��p#|dp|�d��p|j|d<|�
tj��p|dp|dp
t��|d
<|sd|d<n ts|�dd��|d<|���rd|d<|���|d<|S)Nr*r��messagez�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/r�rr,r))r*�
expiration�
user_limitr)rrr�r�F�upgrade_url�upgrade_url_360zuser limits�
ip_license�buy_url�upgrade_license_url�redirect_urlT�demo�eligible_for_imunify_patch)r�r�rr
�UPGRADE_URL�
NOTIFICATIONSr�r�r�r�
IP_LICENSE�UPGRADE_URL_360rr�AV_PLUS_BUY_URL�upgrade_url_default�is_demo�is_eligible_for_imunify_patch)rgr��key_360rr��ignored_messages�msg�
plesk_urlss        r&�license_infozLicenseCLN.license_info!s���
�
�����)�)�H�%�%�);�;���)�)�I�t�,�,���	��)�	�"�/�	�
�G��	�g�	�g�	�<�
��	%��,�,�.�.�#�i�i�(<�a�@�@�#�i�i��0�0��i�i��o�o�!�o�"� #� 5� 5�e� <� <���D�#�
��I�I�0�1�1�=��O�/��O�e�i�i�0B�&C�&C�C�C�"%�"F�"F��O�#�#��Y����e�$�D�"��]��"&��
���	�� ���x�x�	�"�"�
/�+�/�/�C��d�9�o�-�-�*.��Y����
�%2�%=�&�!�-�T�9�A�$�4�D�@��\�"�0�1�1�J��&�&�}�'@�A�A�'��i�(�'��9�9�]�+�+�'��&�	
����&�&�}�'D�E�E�)��3�4�)��i�(�)�'�(�(�	
�"�#��	B�#'�D�� � ��	B�#(�9�9�]�D�#A�#A�D�� ��;�;�=�=�	 ��D��L�
�-�-�/�/�	
�(�	
��r%c�*�|jduo
|jdkS)Nr1)r�r�s r&�is_vpszLicenseCLN.is_vps~s����d�*�C�s��!�/C�Cr%c���dg}dg}t��r4|�|j��|�|j��tj|vo
tj|vSrp)r	rL�CPANEL_UPGRADE_URL�CPANEL_UPGRADE_URL_360r
rr)rg�upgrade_urls�upgrade_urls_360s   r&�is_custom_reseller_configuredz(LicenseCLN.is_custom_reseller_configured�sw��-1�F��15���� � �	@����� 6�7�7�7��#�#�C�$>�?�?�?�
�%��5�
B��-�1A�A�
�	
r%c�|�|���o(|���o|���Srp)r*r�r0r�s r&r#z(LicenseCLN.is_eligible_for_imunify_patch�s;��
�J�J�L�L�
8����
�
�
8��5�5�7�7�7�	
r%c���tstjS|����dd��}|dkrdS|dvrdStjd|��dS)	Nr*rXr�z
imunify.av)r�r�r�zimunify.av+zUnknown license %szUnknown license)rr�NAMEr�r�rr�)rg�license_statuss  r&r�zLicenseCLN.get_product_name�si���	��9�������,�,�X�r�:�:���W�$�$��<�
�;�
;�
;� �=��L�-�~�>�>�>�$�$r%c�@�tj�d��S)Nz/var/imunify360/demo)r�r��isfiler�s r&r"zLicenseCLN.is_demo�s���w�~�~�4�5�5�5r%c�h�|���}|�dd��tkS)Nr,r)r�r��UNLIMITED_USERS_COUNTr�s  r&�is_unlimitedzLicenseCLN.is_unlimited�s)���
�
�����y�y��!�$�$�(=�=�=r%c���|j�|j�d���S|jD]*}|j|kr|j�|���cS�+|j�d���S)Nr1)rr)r��IM360_BUY_URL_TEMPLATE�formatr)rgrs  r&�get_im360_buy_urlzLicenseCLN.get_im360_buy_url�s����?�"��-�4�4��4�B�B�B��/�	O�	O�I���)�+�+��1�8�8�I�8�N�N�N�N�N�,��)�0�0�K�0�H�H�Hr%r�rp)>r r!r"�VERIFY_FIELDS_V1�VERIFY_FIELDS_V2r`r{r|r�r�r r;r,r-r�_tokenr��staticmethodrrre�bytes�tupler�r�listrU�classmethod�intrlr�r�r�datetime�	timedelta�_CACHE_LICENSE_TOKEN_TIMEOUTrbr�r�r�r�r�r�r�r�r�r�r;r�r�r�rr	rr(r*r0r#r�r"r9r=r$r%r&r(r(Ds*���������������
7�L�!��3�M�<��C��	C��	A��	>��&����
�F��K��
�X�n��*�*�*�)&��)&�#(�)&�5:�)&�	�t�X�d�3�i�(�(�	)�)&�)&�)&�+�*��\�)&�V�'�'�C�'��'�'�'��[�'�"�%�,0��s�C�x��,A�%�	�x��}�d�"�	#�%�%�%��[�%�N�F�F��[�F�P��[����#?�@�@�@�!�����$�������[��&�)�h�s�m�)�)�)��[�)��%�%��[�%��I�I��[�I��4�4��[�4�
�&�$�&�&�&��[�&��
�
�
��[�
�,�

�

��

�

�

��[�

�����[��B����[��$�	8�	8��[�	8��9�9��[�9��6�6��[�6�����[��� �h�s�m� ���
� � � ��[� �D�	%�	%��[�	%��
�
��[�
�&�Z�Z��[�Z�x�D�t�D�D�D��[�D��
�d�
�
�
��[�
��
�d�
�
�
��[�
��%��%�%�%��[�%��6��6�6�6��[�6��>�>��[�>��I�#�I�I�I��[�I�I�Ir%r(c�B�tj}tjdd��}t	��r�t
jtjkr�t|��stj	Sd}d}	tj��}tj
|��r|}ntjd|��n,#t $r}tjd|��Yd}~nd}~wwxYw|dkrd|��}nd|��}||zSt���d	|��zd
|��t'|��zzS)NrrXz<https://store.cpanel.net/index.php?rp=/store/partner-addons/zJServer IP is IPv6 (%s), cPanel Store requires IPv4. Omitting IP parameter.zFailed to get server IP: %sr1z/imunify360-for-cpanel-solo&customfield%5B55%5D=z imunify360&customfield%5B375%5D=z?iaid=z&users=)r(r�rr�r	r
rr,�_eligible_for_new_upgrade_linksr-r�	server_ipr�is_valid_ipv4_addrrr�rr}r=r�)rr�base_urlrL�iprT�suffixs       r&r!r!�sq����A��5�����D�	���+!�
�%��)F�F�F�/�t�4�4�	5��4�4�

K�	��	�
	=��$�&�&�B��$�R�(�(�
��	�	���-�������
�	=�	=�	=��N�8�!�<�<�<�<�<�<�<�<�����	=����
��6�6�M�)�M�M�
�F�D�	�C�C�F��&� � �	�$�$�&�&�
�4�/�/�	�
�A�-�-�$�q�'�'�
!�	"�s�*?B*�*
C�4C�Crr9c��tjd|��t|��dkrtjd��dS	t	|dd��}n%#t
$rtjd��YdSwxYwd}||kS)Nz,checking if iaid: %s is eligible for upgraderz(receive empty iaid, fallback to old linkF�z%iaid is not hex, fallback to old link�)r�debug�lenr}rF�
ValueError)r�
hex_bucket�hex_mids   r&rKrK�s���
�L�?��F�F�F�
�4�y�y�A�~�~���A�B�B�B��u����a��"�%�%�
�
��������>�?�?�?��u�u������G����s�A�A9�8A9)?r�rxr�rGr�r�r�rHrBr��
contextlibrr�pathlibrr�typingr�peeweer�3defence360agent.application.determine_hosting_panelr	�defence360agent.contractsr
� defence360agent.contracts.configrrr
rr�%defence360agent.contracts.hook_eventsr�&defence360agent.internals.global_scoper�0defence360agent.subsys.panels.plesk.upgrade_urlsr�defence360agent.utilsrr�defence360agent.utils.commonrr�defence360agent.utils.ipechorr�defence360agent.utils.validaterr�r8rIrF�existsr}r�r�r��	Exceptionrr(r!rer�rKr$r%r&�<module>ris_������
�
�
�
�������������	�	�	�	�
�
�
�
������������������� � � � � � �������%�%�%�%�%�%�������#�#�#�#�#�#�������-�,�,�,�,�,���������������<�;�;�;�;�;�4�4�4�4�4�4�������8�7�7�7�7�7�7�7�9�9�9�9�9�9�9�9�<�<�<�<�<�<�<�<�-�-�-�-�-�-��
�"�� /��,�� � ���t�<�=�=�=��E�E�G�G�/��4� >�?�?�?�K�G�G�I�I�/��d�-�.�.��E�j�j��f�n�E�E�E�
�L����F�j�j��f�n�E�E�E�
�L����
D�D�D�D�D�9�D�D�D�t	I�t	I�t	I�t	I�t	I�t	I�t	I�t	I�n5�5�5�p
 �#�
 �$�
 �
 �
 �
 �
 �
 r%defence360agent/contracts/__pycache__/messages.cpython-311.opt-1.pyc0000644000000000000000000010061500000000000022231 0ustar  �

�9�;�MF���ddlZddlZddlZddlmZddlmZddlmZ	Gd�de
��ZGd�d��ZGd	�d
��Z
Gd�d��Ze��ZGd
�de��ZGd�de
��ZGd�de
��ZGd�de
��ZGd�dee
��ZGd�de��ZGd�d��ZGd�de��ZGd�de��ZGd�d e��ZGd!�d"ee��ZGd#�d$ee��ZGd%�d&e��ZGd'�d(ee��ZGd)�d*ee��ZGd+�d,ee��Z Gd-�d.e��Z!Gd/�d0ee��Z"Gd1�d2e��Z#Gd3�d4e
��Z$Gd5�d6e��Z%Gd7�d8ee��Z&Gd9�d:ee��Z'Gd;�d<ee��Z(Gd=�d>e��Z)Gd?�d@e��Z*GdA�dBee��Z+dCe,dDe-dEe,fdF�Z.dGe-dHe-fdI�Z/GdJ�dKee��Z0GdL�dMeee��Z1GdN�dOe��Z2e-ej3�4dPdQ����Z5dEe-fdR�Z6dEe-fdS�Z7GdT�dU��Z8GdV�dWe��Z9GdX�dYee��Z:GdZ�d[e��Z;Gd\�d]ee��Z<Gd^�d_ee��Z=Gd`�daee��Z>Gdb�dcee��Z?dS)d�N)�Enum)�List)�Corec��eZdZdS)�MessageNotFoundErrorN��__name__�
__module__�__qualname__���W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/messages.pyrr
��������Dr
rc��eZdZdZd�Zd�ZdS)�UnknownMessagez&
    Used as stub for MessageType
    c� �td���)NzMessage class is not found.)r��selfs r�__init__zUnknownMessage.__init__s��"�#@�A�A�Ar
c��dS)N�Unknownr)r�names  r�__getattr__zUnknownMessage.__getattr__s���yr
N)r	r
r�__doc__rrrr
rrrs?��������B�B�B�����r
rc�8��eZdZgZ�fd�Zed���Z�xZS)�MessageTc�n��t��jdi|��|j�|��dS)Nr)�super�__init_subclass__�_subclasses�append)�cls�kwargs�	__class__s  �rrzMessageT.__init_subclass__s<���!����!�+�+�F�+�+�+�����s�#�#�#�#�#r
c�*�t|j��S�N)�tupler �r"s r�get_subclasseszMessageT.get_subclasses!s���S�_�%�%�%r
)r	r
rr r�classmethodr)�
__classcell__�r$s@rrrsX��������K�$�$�$�$�$��&�&��[�&�&�&�&�&r
rc��eZdZdZd�ZdS)�_MessageTypea
    Used to get specific message class. For example,
    >>> _MessageType().ConfigUpdate
    <class 'defence360agent.contracts.messages.ConfigUpdate'>
    >>> _MessageType().NotExistMessage
    <class 'defence360agent.contracts.messages.UnknownMessage'>
    >>>
    c�f�t���D]}|j|kr|cS�tSr&)�Messager)r	r)rr�subclss   rrz_MessageType.__getattr__0s>���,�,�.�.�	�	�F���$�&�&��
�
�
�'��r
N)r	r
rrrrr
rr.r.&s-������������r
r.c��eZdZdZdZdS)�ReportTarget�api�connN)r	r
r�API�PERSISTENT_CONNECTIONrr
rr3r3;s������
�C�"���r
r3c�<�eZdZdZejZedefd���Z	dS)�
ReportablezD
    Mixin class for messages that should be sent to the server
    �methodc�d�|���D]}|t|d��kr|cS�dS)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   r�get_subclass_with_methodz#Reportable.get_subclass_with_methodGsH���*�*�,�,�	 �	 �H����+;�<�<�<�<�����=��tr
N)
r	r
rrr3r7�TARGETr*�strr@rr
rr9r9@sM���������
/�F���c�����[���r
r9c�.�eZdZdZedefd���ZdS)�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|dg��pt|d��g}||vr|cS�-td�|�����)N�RECEIVED_ACTIONSr<z*Message class is not found for "{}" action)r=r>r�format)r"rEr?�received_actionss    r�get_subclass_with_actionz!Received.get_subclass_with_action^s����*�*�,�,�	 �	 �H�&�x�1C�R�H�H� ���"2�3�3�M���)�)�)�����*�"�8�?�?��G�G�
�
�	
r
N)r	r
rrr*rBrJrr
rrDrDVsE���������	
�c�	
�	
�	
��[�	
�	
�	
r
rDc�^�eZdZdZedd���Zedefd���Zedd���ZdS)�LockableN�returnc��K�|j�tj��|_|j����d{V��dSr&)�_lock�asyncio�Lock�acquirer(s rrRzLockable.acquirensG�����9������C�I��i���!�!�!�!�!�!�!�!�!�!�!r
c�F�|jduo|j���Sr&)rO�lockedr(s rrTzLockable.lockedts"���y��$�;���)9�)9�);�);�;r
c�J�|j�|j���dSdSr&)rO�releaser(s rrVzLockable.releasexs,���9� ��I��������!� r
�rMN)	r	r
rrOr*rR�boolrTrVrr
rrLrLks~�������E��"�"�"��[�"�
�<�t�<�<�<��[�<�� � � ��[� � � r
rLc�`��eZdZdZdZdZdZdZdZd�fd	�Z	e
d
���Zd�Zd�Z
d
�Z�xZS)r0zj
    Base class for messages to be passed as
    a parameter to plugins.MessageSink.process_message()
    ��
�<�di@rMNc�j��|jr
|j|d<tt|��j|i|��dS)Nr:)r<rr0r)r�argsr#r$s   �rrzMessage.__init__�sB�����	1�!�0�D��N�%��g�t���%�t�6�v�6�6�6�6�6r
c�>�d�|���D��S)Nc�&�i|]\}}|dk�||��S)r:r��.0�k�vs   r�
<dictcomp>z#Message.payload.<locals>.<dictcomp>�s#��?�?�?���A��h����1���r
��itemsrs r�payloadzMessage.payload�s��?�?������?�?�?�?r
c�X�	||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)�KeyError�AttributeError)rr�excs   rrzMessage.__getattr__�s@��	0���:����	0�	0�	0� ��&�&�C�/�����	0���s�
�
)�$�)c����fd�����D��}d��jj|��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.c�P��i|]"\}}|t|�j�j�����#S���
fold_limit�	str_limit)�_fold_repr_value�_FOLD_LIST_THRESHOLD�_SHORTEN_STR_THRESHOLD)rcrdrers   �rrfz$Message.__repr__.<locals>.<dictcomp>�sP���
�
�
���1�
����4��5����
�
�
r
�{}({}))rhrHr$r)r�
folded_msgs` r�__repr__zMessage.__repr__�sP���

�
�
�
��
�
���

�
�
�
����t�~�:�J�G�G�Gr
c�*�|���Sr&)ryrs r�__str__zMessage.__str__�s���}�}���r
rW)r	r
rrr<�PRIORITY�PROCESSING_TIME_THRESHOLDrurvr�propertyrirryr{r+r,s@rr0r0~s�����������N��H� "���� ��7�7�7�7�7�7�
�@�@��X�@�
0�
0�
0�
H�
H�
H�������r
r0c�4��eZdZ�fd�Zed���Z�xZS)�MessageListc�L��t���|���dS)N��list)rr)r�msg_listr$s  �rrzMessageList.__init__�s$���
�����h��'�'�'�'�'r
c��|jSr&r�rs rrizMessageList.payload�s
���y�r
)r	r
rrr~rir+r,s@rr�r��sS�������(�(�(�(�(�����X�����r
r�c��eZdZdZdefd�ZdS)�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�|jjd�t|�dg��������S)Nrw�<{} item(s)>rh)rHr$r�len�getrs rryzShortenReprListMixin.__repr__�sG������N�'��!�!�#�d�h�h�w��&;�&;�"<�"<�=�=�
�
�	
r
N)r	r
rr�dictryrr
rr�r��s9��������
�t�
�
�
�
�
�
r
r�c�"�eZdZdZeZdefd�ZdS)�
Accumulatablez�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.rMc��dS)zAReturn True if this message is worth collecting, False otherwise.Trrs r�
do_accumulatezAccumulatable.do_accumulate�s���tr
N)r	r
rrr��
LIST_CLASSrXr�rr
rr�r��s@������5�5��J��t������r
r�c��eZdZdS)�ServerConnectedNrrr
rr�r��rr
r�c��eZdZdS)�ServerReconnectedNrrr
rr�r��rr
r�c�*��eZdZdZdZdZ�fd�Z�xZS)�Pingzr
    Will send this message on connected, reconnected events
    to provide central server with agent version
    �PINGrc�f��t�����tj|d<dS)N�version)rr�
CoreConfig�VERSION)rr$s �rrz
Ping.__init__�s)���
��������$�,��Y���r
)r	r
rrr<r|rr+r,s@rr�r��sN���������
�N��H�-�-�-�-�-�-�-�-�-r
r�c�&��eZdZdZdZ�fd�Z�xZS)�Ackzd
    Notify Server that a persistent message with *seq_number* has been
    received by Agent.

    �ACKc�`��t��jdi|��t|���|d<dS)N)�per_seq�_metar)rrr�)r�
seq_numberr#r$s   �rrzAck.__init__�s8��������"�"�6�"�"�"��Z�0�0�0��W�
�
�
r
)r	r
rrr<rr+r,s@rr�r��sI����������N�1�1�1�1�1�1�1�1�1r
r�c��eZdZdZdZdS)�NoopzG
    Sending NOOP to the agent to track the message in agent logs.
    �NOOPN�r	r
rrr<rr
rr�r��s���������N�N�Nr
r�c�*�eZdZdZdZejZd�ZdS)�ServerConfigz.
    Information about server environment
    �
SERVER_CONFIGc�@�d�|jj��S�Nz{}()�rHr$rrs rryzServerConfig.__repr__����}�}�T�^�8�9�9�9r
N�	r	r
rrr<r3r6rAryrr
rr�r�s<��������%�N�
�
�F�:�:�:�:�:r
r�c� �eZdZdZejZdS)�WpSecurityPluginStats�WP_SECURITY_PLUGIN_STATSN)r	r
rr<r3r6rArr
rr�r�s������/�N�
�
�F�F�Fr
r�c�*�eZdZdZdZejZd�ZdS)�
DomainListz*
    Information about server domains
    �DOMAIN_LISTc�@�d�|jj��Sr�r�rs rryzDomainList.__repr__r�r
Nr�rr
rr�r�s<��������#�N�
�
�F�:�:�:�:�:r
r�c��eZdZdZd�Zd�ZdS)�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)rr�r�s   rrzFilesUpdated.__init__(s��(��\��)��]���r
c�\�d�|jj|d|d��S)z?
        Do not flood console.log with large sequences
        z+{}({{'files_type':'{}', 'files_index':{}}})r�r�r�rs rryzFilesUpdated.__repr__1s4��=�C�C��N�'�������
�
�	
r
N)r	r
rrrryrr
rr�r�#s<��������*�*�*�
�
�
�
�
r
r�c��eZdZdZdZdS)�UpdateFilesz9
    Update files by getting message from the server
    �UPDATENr�rr
rr�r�<s���������N�N�Nr
r�c��eZdZdZdS)�ConfigUpdate�
CONFIG_UPDATEN�r	r
rr<rr
rr�r�D�������$�N�N�Nr
r�c��eZdZdZdS)�Rejectz�
    Kinda message filtering facility.
    Raised in order to stop message processing through plugins.
    Takes reason of reject as argument.
    N)r	r
rrrr
rr�r�Hs��������	�Dr
r�c��eZdZdZdS)�Health�HEALTHNr�rr
rr�r�Rs�������N�N�Nr
r�c��eZdZdZdS)�
CommandInvoke�COMMAND_INVOKENr�rr
rr�r�V�������%�N�N�Nr
r�c��eZdZdZdS)�
ScanFailed�SCAN_FAILEDNr�rr
rr�r�Zs������"�N�N�Nr
r�c��eZdZdZdS)�
CleanupFailed�CLEANUP_FAILEDNr�rr
rr�r�^r�r
r�c��eZdZdZdZdS)�RestoreFromBackupTaskz5
    Creates a task to restore files from backup
    �MALWARE_RESTORE_FROM_BACKUPNr�rr
rr�r�bs��������3�N�N�Nr
r�c	�B�eZdZdZhd�Zededededefd���Z	dS)	�cPanelEvent�PANEL_EVENT>�plan�exclude�new_pkg�
imunify360_av�imunify360_proactive�username�hook�ts�fieldsc����fd�|���D��}|dkr%d|vr!d|vr|d|dkr|d|d<�||||d���S)Nc�v��i|]5\}}|����jv� |���|��6Sr)�lower�ALLOWED_FIELDS)rcrdrer"s   �rrfz/cPanelEvent.from_hook_event.<locals>.<dictcomp>xsG���
�
�
���1��w�w�y�y�C�.�.�.�
�G�G�I�I�q�.�.�.r
�Modify�user�newuser�old_username)r�r��data�	timestamprg)r"r�r�r�r�r�s`     r�from_hook_eventzcPanelEvent.from_hook_eventts����
�
�
�
������
�
�
��
�H����&� � ��V�#�#��v��&��"3�3�3�#)�&�>�D�� ��s�$����	
�
�
�
�	
r
N)
r	r
rr<r�r*rB�floatr�r�rr
rr�r�jsj������"�N����N��
��
�"%�
�+0�
�:>�
�
�
��[�
�
�
r
r�c��eZdZdZdS)�IContactSent�
ICONTACT_SENTNr�rr
rr�r��r�r
r��s�limitrMc��|dksJ�t|��|kr$|d|dzdz
��d||dzdzd���n|S)z1Shorten *s* string if its length exceeds *limit*.�N��z...)r�)r�r�s  r�_shorten_strr��sf���1�9�9�9�9��q�6�6�E�>�>�
�
�u��z�A�~�
��9�9�1�e�V�q�[�1�_�%6�%6�#7�9�9�9�
�r
rrrsc�.���t|t��rt|���St|t��rVt	|���kr"d�t	|����S��fd�|���D��St|ttttf��rZt	|���kr"d�t	|����St|����fd�|D����S|S)Nr�c�<��i|]\}}|t|�������Srp�rt)rcrdrerrrss   ��rrfz$_fold_repr_value.<locals>.<dictcomp>�s?���
�
�
���1�
���j�I�N�N�N�
�
�
r
c3�<�K�|]}t|�����V��dS)rqNr�)rcrerrrss  ��r�	<genexpr>z#_fold_repr_value.<locals>.<genexpr>�sF�����
�
��
�Q�:��K�K�K�
�
�
�
�
�
r
)�
isinstancerBr�r�r�rHrhr�r'�set�	frozenset�type)�valuerrrss ``rrtrt�s/�����%����.��E�9�-�-�-��%����
��u�:�:�
�"�"�!�(�(��U���4�4�4�
�
�
�
�
����
�
�
�
�
�	
��%�$��s�I�6�7�7�
��u�:�:�
�"�"�!�(�(��U���4�4�4��t�E�{�{�
�
�
�
�
��
�
�
�
�
�	
��Lr
c��eZdZdZdZdS)�
BackupInfoz(Information about enabled backup backend�BACKUP_INFONr�rr
rrr�s������2�2�"�N�N�Nr
rc��eZdZdZdS)�
MDSReportList�
MDS_SCAN_LISTNr�rr
rrr�r�r
rc��eZdZeZdS)�	MDSReportN)r	r
rrr�rr
rr
r
�s�������J�J�Jr
r
�IMUNIFY360_MAX_MESSAGE_SIZEic��ddlm}	ttj||��������S#ttf$r1tt|�������cYSwxYw)Nr)�ServerJSONEncoderr()	�defence360agent.utils.jsonr
r��json�dumps�encode�	TypeError�
ValueError�repr)�objr
s  r�serialized_sizer�s���<�<�<�<�<�<�'��4�:�c�'8�9�9�9�@�@�B�B�C�C�C���z�"�'�'�'��4��9�9�#�#�%�%�&�&�&�&�&�'���s�4=�?A?�>A?c�6�|�dSt|t��rdSt|t��r-tdt	t|����dz��St|t��rdSt|t
��r�|���rR|���r>t	|��dz|�	d��z|�	d	��zSt	tj|����St|ttf��rdtd
�|D����zSt|t��r.dtd�|���D����zSt#|��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.Nr���r��r��"�\c3�:K�|]}t|��dzV��dS�r�N��
estimate_size)rcres  rr�z estimate_size.<locals>.<genexpr>�s/����9�9��}�Q�'�'�!�+�9�9�9�9�9�9r
c3�K�|]P\}}tt|t��r|nt|����dzt|��zdzV��QdSr)r r�rBrbs   rr�z estimate_size.<locals>.<genexpr>�s����
�
�
��1�	
�z�!�S�1�1�=�!�!�s�1�v�v�>�>��
��A���
��
�
�
�
�
�
�
r
)r�rX�int�maxr�rBr��isascii�isprintable�countrrr�r'�sumr�rhr)rs rr r �s����{��q��#�t�����q��#�s���*��2�s�3�s�8�8�}�}�q�(�)�)�)��#�u�����r��#�s���$��;�;�=�=�	C�S�_�_�.�.�	C��s�8�8�a�<�#�)�)�C�.�.�0�3�9�9�T�?�?�B�B��4�:�c�?�?�#�#�#��#��e�}�%�%�:��3�9�9�S�9�9�9�9�9�9�9��#�t���
��3�
�
�
�	�	���
�
�
�
�
�
�	
��3���r
c��eZdZdZdZdZdZedefd���Z	ede
efd���Zede
defd���Zed	���Zede
efd
���ZdS)�
Splittablez�
    A message list could be split into multiple batches.
    The split is possible for a list itself along with internal resources.
    NrMc��tSr&)�MAX_MESSAGE_SIZEr(s r�_max_message_sizezSplittable._max_message_size�s���r
�messagesc#�JK�|jr�|jr}|D]x}|�|j��x}�|V��#t|��}|�||��D]/}|���}|||j<||��}|V��0�ydSt
|��Ed{V��dS)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_SIZEr�r�_size_bounded_batches�copy�iter)r"r-�messagerh�
message_class�batchr��new_messages        r�_split_itemszSplittable._split_itemss������?�	&�s�~�	&�#�	
*�	
*��$�[�[���9�9�9�E�B�!�M�M�M�M�$(��M�M�M�!$�!:�!:�5�'�!J�!J�*�*��&�|�|�~�~��05��S�_�-�&3�m�D�&9�&9��)�)�)�)�)�	*�	
*�	
*��H�~�~�%�%�%�%�%�%�%�%�%r
�is_dictc�D�t|r|d|din|��S)z�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.rr�r)r"�unitr9r4s    r�
_unit_sizezSplittable._unit_sizes)���7�D�d�1�g�t�A�w�/�/��E�E�Er
c#��
K�|���}t|t���
�
r!t|�����n|}�
fd�}g}d}|D]g}|�|�
|��}	|r2||	z|kst
|��|jkr||��V�gd}}|�|��||	z
}�h|r||��V�dSdS)z�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.c�D���rt|��nt|��Sr&)r�r�)�bufferr9s �r�buildz/Splittable._size_bounded_batches.<locals>.build's���#*�<�4��<�<�<��V���<r
rN)	r,r�r�r�rhr<r�r0r!)r"rhr4�budget�unitsr@r?�sizer;�	unit_sizer9s          @rr1z Splittable._size_bounded_batchess"�����
�&�&�(�(���U�D�)�)��'.�9��U�[�[�]�]�#�#�#�E��	=�	=�	=�	=�	=������	�	�D����t�W�g�>�>�I��
%��y� �6�)�)�S��[�[�C�N�-J�-J��e�F�m�m�#�#�#�!�1����M�M�$�����I��D�D��	 ��%��-�-������	 �	 r
c#�DK�|jpt|��}|���}g}d}|�|��D]Q}t	|��}|r$||z|kst|��|kr|V�gd}}|�|��||z
}�R|r|V�dSdS)Nr)�	LIST_SIZEr�r,r8r r!)r"r-�	list_sizerAr?rCr4�message_sizes        r�batchedzSplittable.batched8s������M�2�S��]�]�	��&�&�(�(�������'�'��1�1�	!�	!�G�(��1�1�L��
%��|�#�f�,�,��F���y�0H�0H�����!�1����M�M�'�"�"�"��L� �D�D��	��L�L�L�L�L�	�	r
)r	r
rrrFr0r/r*r"r,rr�r8rXr<r1rIrr
rr)r)�s���������
�I��J��K�� �#� � � ��[� ��&�D��$7�&�&�&��[�&�&�F�t�F��F�F�F��[�F�� � ��[� �2��t�M�2�����[���r
r)c��eZdZdZdZdS)�EnsureServiceStatez-Ensure the service has the appropriate status�ENSURE_SERVICE_STATENr�rr
rrKrKKs������7�7�+�N�N�Nr
rKc��eZdZdZdZdS)�SensorWordpressIncidentListzAggregated incident list�
INCIDENT_LISTNr�rr
rrNrNQs������"�"�$�N�N�Nr
rNc��eZdZdZdS)�WordpressPluginAction�WP_SECURITY_PLUGIN_ACTIONNr�rr
rrQrQWs������0�N�N�Nr
rQc�*�eZdZdZdZejZd�ZdS)�WordpressPluginTelemetryzX
    Information about telemetry event related to Imunify Security WordPress plugin
    �WP_SECURITY_PLUGIN_EVENTc�@�d�|jj��Sr�r�rs rryz!WordpressPluginTelemetry.__repr__cr�r
Nr�rr
rrTrT[s<��������0�N�
�
�F�:�:�:�:�:r
rTc��eZdZdZdZdS)�WPRuleDisabledz#WordPress protection rule disabled.�
RULE_DISABLEDNr�rr
rrXrXgs������-�-�$�N�N�Nr
rXc��eZdZdZdZdS)�
WPRuleEnabledz%WordPress protection rule re-enabled.�RULE_ENABLEDNr�rr
rr[r[ms������/�/�#�N�N�Nr
r[c��eZdZdZdS)�GeneralMetrics�GENERAL_METRICSNr�rr
rr^r^ss������&�N�N�Nr
r^)@rPr�os�enumr�typingr� defence360agent.contracts.configrr��	Exceptionrrrr.�MessageTyper3r9rDrLr�r0r�r�r�r�r�r�r�r�r�r�r�r�r�r�r�r�r�r�r�r�r�r�rBr"r�rtrrr
�environr�r+rr r)rKrNrQrTrXr[r^rr
r�<module>rgs#����������	�	�	�	�������������?�?�?�?�?�?�	�	�	�	�	�9�	�	�	�	�	�	�	�	�	�	�	�	&�	&�	&�	&�	&�	&�	&�	&���������$�l�n�n��#�#�#�#�#�4�#�#�#�
���������,
�
�
�
�
�x�
�
�
�* � � � � �x� � � �&4�4�4�4�4�d�H�4�4�4�n�����'����
�
�
�
�
�
�
�
�	�	�	�	�	�G�	�	�	�	�	�	�	�	�g�	�	�	�
	�	�	�	�	��	�	�	�-�-�-�-�-�7�J�-�-�-�1�1�1�1�1�'�:�1�1�1������7����	:�	:�	:�	:�	:�7�J�	:�	:�	:������G�Z����
	:�	:�	:�	:�	:��*�	:�	:�	:�
�
�
�
�
�7�
�
�
�2�����'�8����%�%�%�%�%�7�%�%�%�	�	�	�	�	�Y�	�	�	������W����&�&�&�&�&�G�Z�&�&�&�#�#�#�#�#��*�#�#�#�&�&�&�&�&�G�Z�&�&�&�3�3�3�3�3�G�3�3�3�"
�"
�"
�"
�"
�'�"
�"
�"
�J%�%�%�%�%�7�J�%�%�%��C����������3��3�����(#�#�#�#�#��*�#�#�#�%�%�%�%�%�(�'�:�%�%�%������
�����3��J�N�N�0�+�>�>����
'�C�'�'�'�'� �#� � � � �DU�U�U�U�U�U�U�U�p,�,�,�,�,��,�,�,�%�%�%�%�%�+�z�%�%�%�1�1�1�1�1�G�1�1�1�	:�	:�	:�	:�	:�w�
�	:�	:�	:�%�%�%�%�%�W�j�%�%�%�$�$�$�$�$�G�Z�$�$�$�'�'�'�'�'�[�*�'�'�'�'�'r
defence360agent/contracts/__pycache__/messages.cpython-311.pyc0000644000000000000000000010061500000000000021272 0ustar  �

�9�;�MF���ddlZddlZddlZddlmZddlmZddlmZ	Gd�de
��ZGd�d��ZGd	�d
��Z
Gd�d��Ze��ZGd
�de��ZGd�de
��ZGd�de
��ZGd�de
��ZGd�dee
��ZGd�de��ZGd�d��ZGd�de��ZGd�de��ZGd�d e��ZGd!�d"ee��ZGd#�d$ee��ZGd%�d&e��ZGd'�d(ee��ZGd)�d*ee��ZGd+�d,ee��Z Gd-�d.e��Z!Gd/�d0ee��Z"Gd1�d2e��Z#Gd3�d4e
��Z$Gd5�d6e��Z%Gd7�d8ee��Z&Gd9�d:ee��Z'Gd;�d<ee��Z(Gd=�d>e��Z)Gd?�d@e��Z*GdA�dBee��Z+dCe,dDe-dEe,fdF�Z.dGe-dHe-fdI�Z/GdJ�dKee��Z0GdL�dMeee��Z1GdN�dOe��Z2e-ej3�4dPdQ����Z5dEe-fdR�Z6dEe-fdS�Z7GdT�dU��Z8GdV�dWe��Z9GdX�dYee��Z:GdZ�d[e��Z;Gd\�d]ee��Z<Gd^�d_ee��Z=Gd`�daee��Z>Gdb�dcee��Z?dS)d�N)�Enum)�List)�Corec��eZdZdS)�MessageNotFoundErrorN��__name__�
__module__�__qualname__���W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/messages.pyrr
��������Dr
rc��eZdZdZd�Zd�ZdS)�UnknownMessagez&
    Used as stub for MessageType
    c� �td���)NzMessage class is not found.)r��selfs r�__init__zUnknownMessage.__init__s��"�#@�A�A�Ar
c��dS)N�Unknownr)r�names  r�__getattr__zUnknownMessage.__getattr__s���yr
N)r	r
r�__doc__rrrr
rrrs?��������B�B�B�����r
rc�8��eZdZgZ�fd�Zed���Z�xZS)�MessageTc�n��t��jdi|��|j�|��dS)Nr)�super�__init_subclass__�_subclasses�append)�cls�kwargs�	__class__s  �rrzMessageT.__init_subclass__s<���!����!�+�+�F�+�+�+�����s�#�#�#�#�#r
c�*�t|j��S�N)�tupler �r"s r�get_subclasseszMessageT.get_subclasses!s���S�_�%�%�%r
)r	r
rr r�classmethodr)�
__classcell__�r$s@rrrsX��������K�$�$�$�$�$��&�&��[�&�&�&�&�&r
rc��eZdZdZd�ZdS)�_MessageTypea
    Used to get specific message class. For example,
    >>> _MessageType().ConfigUpdate
    <class 'defence360agent.contracts.messages.ConfigUpdate'>
    >>> _MessageType().NotExistMessage
    <class 'defence360agent.contracts.messages.UnknownMessage'>
    >>>
    c�f�t���D]}|j|kr|cS�tSr&)�Messager)r	r)rr�subclss   rrz_MessageType.__getattr__0s>���,�,�.�.�	�	�F���$�&�&��
�
�
�'��r
N)r	r
rrrrr
rr.r.&s-������������r
r.c��eZdZdZdZdS)�ReportTarget�api�connN)r	r
r�API�PERSISTENT_CONNECTIONrr
rr3r3;s������
�C�"���r
r3c�<�eZdZdZejZedefd���Z	dS)�
ReportablezD
    Mixin class for messages that should be sent to the server
    �methodc�d�|���D]}|t|d��kr|cS�dS)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   r�get_subclass_with_methodz#Reportable.get_subclass_with_methodGsH���*�*�,�,�	 �	 �H����+;�<�<�<�<�����=��tr
N)
r	r
rrr3r7�TARGETr*�strr@rr
rr9r9@sM���������
/�F���c�����[���r
r9c�.�eZdZdZedefd���ZdS)�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|dg��pt|d��g}||vr|cS�-td�|�����)N�RECEIVED_ACTIONSr<z*Message class is not found for "{}" action)r=r>r�format)r"rEr?�received_actionss    r�get_subclass_with_actionz!Received.get_subclass_with_action^s����*�*�,�,�	 �	 �H�&�x�1C�R�H�H� ���"2�3�3�M���)�)�)�����*�"�8�?�?��G�G�
�
�	
r
N)r	r
rrr*rBrJrr
rrDrDVsE���������	
�c�	
�	
�	
��[�	
�	
�	
r
rDc�^�eZdZdZedd���Zedefd���Zedd���ZdS)�LockableN�returnc��K�|j�tj��|_|j����d{V��dSr&)�_lock�asyncio�Lock�acquirer(s rrRzLockable.acquirensG�����9������C�I��i���!�!�!�!�!�!�!�!�!�!�!r
c�F�|jduo|j���Sr&)rO�lockedr(s rrTzLockable.lockedts"���y��$�;���)9�)9�);�);�;r
c�J�|j�|j���dSdSr&)rO�releaser(s rrVzLockable.releasexs,���9� ��I��������!� r
�rMN)	r	r
rrOr*rR�boolrTrVrr
rrLrLks~�������E��"�"�"��[�"�
�<�t�<�<�<��[�<�� � � ��[� � � r
rLc�`��eZdZdZdZdZdZdZdZd�fd	�Z	e
d
���Zd�Zd�Z
d
�Z�xZS)r0zj
    Base class for messages to be passed as
    a parameter to plugins.MessageSink.process_message()
    ��
�<�di@rMNc�j��|jr
|j|d<tt|��j|i|��dS)Nr:)r<rr0r)r�argsr#r$s   �rrzMessage.__init__�sB�����	1�!�0�D��N�%��g�t���%�t�6�v�6�6�6�6�6r
c�>�d�|���D��S)Nc�&�i|]\}}|dk�||��S)r:r��.0�k�vs   r�
<dictcomp>z#Message.payload.<locals>.<dictcomp>�s#��?�?�?���A��h����1���r
��itemsrs r�payloadzMessage.payload�s��?�?������?�?�?�?r
c�X�	||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)�KeyError�AttributeError)rr�excs   rrzMessage.__getattr__�s@��	0���:����	0�	0�	0� ��&�&�C�/�����	0���s�
�
)�$�)c����fd�����D��}d��jj|��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.c�P��i|]"\}}|t|�j�j�����#S���
fold_limit�	str_limit)�_fold_repr_value�_FOLD_LIST_THRESHOLD�_SHORTEN_STR_THRESHOLD)rcrdrers   �rrfz$Message.__repr__.<locals>.<dictcomp>�sP���
�
�
���1�
����4��5����
�
�
r
�{}({}))rhrHr$r)r�
folded_msgs` r�__repr__zMessage.__repr__�sP���

�
�
�
��
�
���

�
�
�
����t�~�:�J�G�G�Gr
c�*�|���Sr&)ryrs r�__str__zMessage.__str__�s���}�}���r
rW)r	r
rrr<�PRIORITY�PROCESSING_TIME_THRESHOLDrurvr�propertyrirryr{r+r,s@rr0r0~s�����������N��H� "���� ��7�7�7�7�7�7�
�@�@��X�@�
0�
0�
0�
H�
H�
H�������r
r0c�4��eZdZ�fd�Zed���Z�xZS)�MessageListc�L��t���|���dS)N��list)rr)r�msg_listr$s  �rrzMessageList.__init__�s$���
�����h��'�'�'�'�'r
c��|jSr&r�rs rrizMessageList.payload�s
���y�r
)r	r
rrr~rir+r,s@rr�r��sS�������(�(�(�(�(�����X�����r
r�c��eZdZdZdefd�ZdS)�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�|jjd�t|�dg��������S)Nrw�<{} item(s)>rh)rHr$r�len�getrs rryzShortenReprListMixin.__repr__�sG������N�'��!�!�#�d�h�h�w��&;�&;�"<�"<�=�=�
�
�	
r
N)r	r
rr�dictryrr
rr�r��s9��������
�t�
�
�
�
�
�
r
r�c�"�eZdZdZeZdefd�ZdS)�
Accumulatablez�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.rMc��dS)zAReturn True if this message is worth collecting, False otherwise.Trrs r�
do_accumulatezAccumulatable.do_accumulate�s���tr
N)r	r
rrr��
LIST_CLASSrXr�rr
rr�r��s@������5�5��J��t������r
r�c��eZdZdS)�ServerConnectedNrrr
rr�r��rr
r�c��eZdZdS)�ServerReconnectedNrrr
rr�r��rr
r�c�*��eZdZdZdZdZ�fd�Z�xZS)�Pingzr
    Will send this message on connected, reconnected events
    to provide central server with agent version
    �PINGrc�f��t�����tj|d<dS)N�version)rr�
CoreConfig�VERSION)rr$s �rrz
Ping.__init__�s)���
��������$�,��Y���r
)r	r
rrr<r|rr+r,s@rr�r��sN���������
�N��H�-�-�-�-�-�-�-�-�-r
r�c�&��eZdZdZdZ�fd�Z�xZS)�Ackzd
    Notify Server that a persistent message with *seq_number* has been
    received by Agent.

    �ACKc�`��t��jdi|��t|���|d<dS)N)�per_seq�_metar)rrr�)r�
seq_numberr#r$s   �rrzAck.__init__�s8��������"�"�6�"�"�"��Z�0�0�0��W�
�
�
r
)r	r
rrr<rr+r,s@rr�r��sI����������N�1�1�1�1�1�1�1�1�1r
r�c��eZdZdZdZdS)�NoopzG
    Sending NOOP to the agent to track the message in agent logs.
    �NOOPN�r	r
rrr<rr
rr�r��s���������N�N�Nr
r�c�*�eZdZdZdZejZd�ZdS)�ServerConfigz.
    Information about server environment
    �
SERVER_CONFIGc�@�d�|jj��S�Nz{}()�rHr$rrs rryzServerConfig.__repr__����}�}�T�^�8�9�9�9r
N�	r	r
rrr<r3r6rAryrr
rr�r�s<��������%�N�
�
�F�:�:�:�:�:r
r�c� �eZdZdZejZdS)�WpSecurityPluginStats�WP_SECURITY_PLUGIN_STATSN)r	r
rr<r3r6rArr
rr�r�s������/�N�
�
�F�F�Fr
r�c�*�eZdZdZdZejZd�ZdS)�
DomainListz*
    Information about server domains
    �DOMAIN_LISTc�@�d�|jj��Sr�r�rs rryzDomainList.__repr__r�r
Nr�rr
rr�r�s<��������#�N�
�
�F�:�:�:�:�:r
r�c��eZdZdZd�Zd�ZdS)�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)rr�r�s   rrzFilesUpdated.__init__(s��(��\��)��]���r
c�\�d�|jj|d|d��S)z?
        Do not flood console.log with large sequences
        z+{}({{'files_type':'{}', 'files_index':{}}})r�r�r�rs rryzFilesUpdated.__repr__1s4��=�C�C��N�'�������
�
�	
r
N)r	r
rrrryrr
rr�r�#s<��������*�*�*�
�
�
�
�
r
r�c��eZdZdZdZdS)�UpdateFilesz9
    Update files by getting message from the server
    �UPDATENr�rr
rr�r�<s���������N�N�Nr
r�c��eZdZdZdS)�ConfigUpdate�
CONFIG_UPDATEN�r	r
rr<rr
rr�r�D�������$�N�N�Nr
r�c��eZdZdZdS)�Rejectz�
    Kinda message filtering facility.
    Raised in order to stop message processing through plugins.
    Takes reason of reject as argument.
    N)r	r
rrrr
rr�r�Hs��������	�Dr
r�c��eZdZdZdS)�Health�HEALTHNr�rr
rr�r�Rs�������N�N�Nr
r�c��eZdZdZdS)�
CommandInvoke�COMMAND_INVOKENr�rr
rr�r�V�������%�N�N�Nr
r�c��eZdZdZdS)�
ScanFailed�SCAN_FAILEDNr�rr
rr�r�Zs������"�N�N�Nr
r�c��eZdZdZdS)�
CleanupFailed�CLEANUP_FAILEDNr�rr
rr�r�^r�r
r�c��eZdZdZdZdS)�RestoreFromBackupTaskz5
    Creates a task to restore files from backup
    �MALWARE_RESTORE_FROM_BACKUPNr�rr
rr�r�bs��������3�N�N�Nr
r�c	�B�eZdZdZhd�Zededededefd���Z	dS)	�cPanelEvent�PANEL_EVENT>�plan�exclude�new_pkg�
imunify360_av�imunify360_proactive�username�hook�ts�fieldsc����fd�|���D��}|dkr%d|vr!d|vr|d|dkr|d|d<�||||d���S)Nc�v��i|]5\}}|����jv� |���|��6Sr)�lower�ALLOWED_FIELDS)rcrdrer"s   �rrfz/cPanelEvent.from_hook_event.<locals>.<dictcomp>xsG���
�
�
���1��w�w�y�y�C�.�.�.�
�G�G�I�I�q�.�.�.r
�Modify�user�newuser�old_username)r�r��data�	timestamprg)r"r�r�r�r�r�s`     r�from_hook_eventzcPanelEvent.from_hook_eventts����
�
�
�
������
�
�
��
�H����&� � ��V�#�#��v��&��"3�3�3�#)�&�>�D�� ��s�$����	
�
�
�
�	
r
N)
r	r
rr<r�r*rB�floatr�r�rr
rr�r�jsj������"�N����N��
��
�"%�
�+0�
�:>�
�
�
��[�
�
�
r
r�c��eZdZdZdS)�IContactSent�
ICONTACT_SENTNr�rr
rr�r��r�r
r��s�limitrMc��|dksJ�t|��|kr$|d|dzdz
��d||dzdzd���n|S)z1Shorten *s* string if its length exceeds *limit*.�N��z...)r�)r�r�s  r�_shorten_strr��sf���1�9�9�9�9��q�6�6�E�>�>�
�
�u��z�A�~�
��9�9�1�e�V�q�[�1�_�%6�%6�#7�9�9�9�
�r
rrrsc�.���t|t��rt|���St|t��rVt	|���kr"d�t	|����S��fd�|���D��St|ttttf��rZt	|���kr"d�t	|����St|����fd�|D����S|S)Nr�c�<��i|]\}}|t|�������Srp�rt)rcrdrerrrss   ��rrfz$_fold_repr_value.<locals>.<dictcomp>�s?���
�
�
���1�
���j�I�N�N�N�
�
�
r
c3�<�K�|]}t|�����V��dS)rqNr�)rcrerrrss  ��r�	<genexpr>z#_fold_repr_value.<locals>.<genexpr>�sF�����
�
��
�Q�:��K�K�K�
�
�
�
�
�
r
)�
isinstancerBr�r�r�rHrhr�r'�set�	frozenset�type)�valuerrrss ``rrtrt�s/�����%����.��E�9�-�-�-��%����
��u�:�:�
�"�"�!�(�(��U���4�4�4�
�
�
�
�
����
�
�
�
�
�	
��%�$��s�I�6�7�7�
��u�:�:�
�"�"�!�(�(��U���4�4�4��t�E�{�{�
�
�
�
�
��
�
�
�
�
�	
��Lr
c��eZdZdZdZdS)�
BackupInfoz(Information about enabled backup backend�BACKUP_INFONr�rr
rrr�s������2�2�"�N�N�Nr
rc��eZdZdZdS)�
MDSReportList�
MDS_SCAN_LISTNr�rr
rrr�r�r
rc��eZdZeZdS)�	MDSReportN)r	r
rrr�rr
rr
r
�s�������J�J�Jr
r
�IMUNIFY360_MAX_MESSAGE_SIZEic��ddlm}	ttj||��������S#ttf$r1tt|�������cYSwxYw)Nr)�ServerJSONEncoderr()	�defence360agent.utils.jsonr
r��json�dumps�encode�	TypeError�
ValueError�repr)�objr
s  r�serialized_sizer�s���<�<�<�<�<�<�'��4�:�c�'8�9�9�9�@�@�B�B�C�C�C���z�"�'�'�'��4��9�9�#�#�%�%�&�&�&�&�&�'���s�4=�?A?�>A?c�6�|�dSt|t��rdSt|t��r-tdt	t|����dz��St|t��rdSt|t
��r�|���rR|���r>t	|��dz|�	d��z|�	d	��zSt	tj|����St|ttf��rdtd
�|D����zSt|t��r.dtd�|���D����zSt#|��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.Nr���r��r��"�\c3�:K�|]}t|��dzV��dS�r�N��
estimate_size)rcres  rr�z estimate_size.<locals>.<genexpr>�s/����9�9��}�Q�'�'�!�+�9�9�9�9�9�9r
c3�K�|]P\}}tt|t��r|nt|����dzt|��zdzV��QdSr)r r�rBrbs   rr�z estimate_size.<locals>.<genexpr>�s����
�
�
��1�	
�z�!�S�1�1�=�!�!�s�1�v�v�>�>��
��A���
��
�
�
�
�
�
�
r
)r�rX�int�maxr�rBr��isascii�isprintable�countrrr�r'�sumr�rhr)rs rr r �s����{��q��#�t�����q��#�s���*��2�s�3�s�8�8�}�}�q�(�)�)�)��#�u�����r��#�s���$��;�;�=�=�	C�S�_�_�.�.�	C��s�8�8�a�<�#�)�)�C�.�.�0�3�9�9�T�?�?�B�B��4�:�c�?�?�#�#�#��#��e�}�%�%�:��3�9�9�S�9�9�9�9�9�9�9��#�t���
��3�
�
�
�	�	���
�
�
�
�
�
�	
��3���r
c��eZdZdZdZdZdZedefd���Z	ede
efd���Zede
defd���Zed	���Zede
efd
���ZdS)�
Splittablez�
    A message list could be split into multiple batches.
    The split is possible for a list itself along with internal resources.
    NrMc��tSr&)�MAX_MESSAGE_SIZEr(s r�_max_message_sizezSplittable._max_message_size�s���r
�messagesc#�JK�|jr�|jr}|D]x}|�|j��x}�|V��#t|��}|�||��D]/}|���}|||j<||��}|V��0�ydSt
|��Ed{V��dS)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_SIZEr�r�_size_bounded_batches�copy�iter)r"r-�messagerh�
message_class�batchr��new_messages        r�_split_itemszSplittable._split_itemss������?�	&�s�~�	&�#�	
*�	
*��$�[�[���9�9�9�E�B�!�M�M�M�M�$(��M�M�M�!$�!:�!:�5�'�!J�!J�*�*��&�|�|�~�~��05��S�_�-�&3�m�D�&9�&9��)�)�)�)�)�	*�	
*�	
*��H�~�~�%�%�%�%�%�%�%�%�%r
�is_dictc�D�t|r|d|din|��S)z�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.rr�r)r"�unitr9r4s    r�
_unit_sizezSplittable._unit_sizes)���7�D�d�1�g�t�A�w�/�/��E�E�Er
c#��
K�|���}t|t���
�
r!t|�����n|}�
fd�}g}d}|D]g}|�|�
|��}	|r2||	z|kst
|��|jkr||��V�gd}}|�|��||	z
}�h|r||��V�dSdS)z�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.c�D���rt|��nt|��Sr&)r�r�)�bufferr9s �r�buildz/Splittable._size_bounded_batches.<locals>.build's���#*�<�4��<�<�<��V���<r
rN)	r,r�r�r�rhr<r�r0r!)r"rhr4�budget�unitsr@r?�sizer;�	unit_sizer9s          @rr1z Splittable._size_bounded_batchess"�����
�&�&�(�(���U�D�)�)��'.�9��U�[�[�]�]�#�#�#�E��	=�	=�	=�	=�	=������	�	�D����t�W�g�>�>�I��
%��y� �6�)�)�S��[�[�C�N�-J�-J��e�F�m�m�#�#�#�!�1����M�M�$�����I��D�D��	 ��%��-�-������	 �	 r
c#�DK�|jpt|��}|���}g}d}|�|��D]Q}t	|��}|r$||z|kst|��|kr|V�gd}}|�|��||z
}�R|r|V�dSdS)Nr)�	LIST_SIZEr�r,r8r r!)r"r-�	list_sizerAr?rCr4�message_sizes        r�batchedzSplittable.batched8s������M�2�S��]�]�	��&�&�(�(�������'�'��1�1�	!�	!�G�(��1�1�L��
%��|�#�f�,�,��F���y�0H�0H�����!�1����M�M�'�"�"�"��L� �D�D��	��L�L�L�L�L�	�	r
)r	r
rrrFr0r/r*r"r,rr�r8rXr<r1rIrr
rr)r)�s���������
�I��J��K�� �#� � � ��[� ��&�D��$7�&�&�&��[�&�&�F�t�F��F�F�F��[�F�� � ��[� �2��t�M�2�����[���r
r)c��eZdZdZdZdS)�EnsureServiceStatez-Ensure the service has the appropriate status�ENSURE_SERVICE_STATENr�rr
rrKrKKs������7�7�+�N�N�Nr
rKc��eZdZdZdZdS)�SensorWordpressIncidentListzAggregated incident list�
INCIDENT_LISTNr�rr
rrNrNQs������"�"�$�N�N�Nr
rNc��eZdZdZdS)�WordpressPluginAction�WP_SECURITY_PLUGIN_ACTIONNr�rr
rrQrQWs������0�N�N�Nr
rQc�*�eZdZdZdZejZd�ZdS)�WordpressPluginTelemetryzX
    Information about telemetry event related to Imunify Security WordPress plugin
    �WP_SECURITY_PLUGIN_EVENTc�@�d�|jj��Sr�r�rs rryz!WordpressPluginTelemetry.__repr__cr�r
Nr�rr
rrTrT[s<��������0�N�
�
�F�:�:�:�:�:r
rTc��eZdZdZdZdS)�WPRuleDisabledz#WordPress protection rule disabled.�
RULE_DISABLEDNr�rr
rrXrXgs������-�-�$�N�N�Nr
rXc��eZdZdZdZdS)�
WPRuleEnabledz%WordPress protection rule re-enabled.�RULE_ENABLEDNr�rr
rr[r[ms������/�/�#�N�N�Nr
r[c��eZdZdZdS)�GeneralMetrics�GENERAL_METRICSNr�rr
rr^r^ss������&�N�N�Nr
r^)@rPr�os�enumr�typingr� defence360agent.contracts.configrr��	Exceptionrrrr.�MessageTyper3r9rDrLr�r0r�r�r�r�r�r�r�r�r�r�r�r�r�r�r�r�r�r�r�r�r�r�rBr"r�rtrrr
�environr�r+rr r)rKrNrQrTrXr[r^rr
r�<module>rgs#����������	�	�	�	�������������?�?�?�?�?�?�	�	�	�	�	�9�	�	�	�	�	�	�	�	�	�	�	�	&�	&�	&�	&�	&�	&�	&�	&���������$�l�n�n��#�#�#�#�#�4�#�#�#�
���������,
�
�
�
�
�x�
�
�
�* � � � � �x� � � �&4�4�4�4�4�d�H�4�4�4�n�����'����
�
�
�
�
�
�
�
�	�	�	�	�	�G�	�	�	�	�	�	�	�	�g�	�	�	�
	�	�	�	�	��	�	�	�-�-�-�-�-�7�J�-�-�-�1�1�1�1�1�'�:�1�1�1������7����	:�	:�	:�	:�	:�7�J�	:�	:�	:������G�Z����
	:�	:�	:�	:�	:��*�	:�	:�	:�
�
�
�
�
�7�
�
�
�2�����'�8����%�%�%�%�%�7�%�%�%�	�	�	�	�	�Y�	�	�	������W����&�&�&�&�&�G�Z�&�&�&�#�#�#�#�#��*�#�#�#�&�&�&�&�&�G�Z�&�&�&�3�3�3�3�3�G�3�3�3�"
�"
�"
�"
�"
�'�"
�"
�"
�J%�%�%�%�%�7�J�%�%�%��C����������3��3�����(#�#�#�#�#��*�#�#�#�%�%�%�%�%�(�'�:�%�%�%������
�����3��J�N�N�0�+�>�>����
'�C�'�'�'�'� �#� � � � �DU�U�U�U�U�U�U�U�p,�,�,�,�,��,�,�,�%�%�%�%�%�+�z�%�%�%�1�1�1�1�1�G�1�1�1�	:�	:�	:�	:�	:�w�
�	:�	:�	:�%�%�%�%�%�W�j�%�%�%�$�$�$�$�$�G�Z�$�$�$�'�'�'�'�'�[�*�'�'�'�'�'r
defence360agent/contracts/__pycache__/myimunify_id.cpython-311.opt-1.pyc0000644000000000000000000002363400000000000023131 0ustar  �

(M��(G�Q��l�ddlZddlZddlZddlZddlmZddlmZmZm	Z	ddl
mZddlm
Z
ddlmZmZddlmZddlmZd	Zd
ZdZed��ZGd
�de��Zdedede	efd�Zdeefd�Zdeeeffd�Z dedefd�Z!dededefd�Z"dedefd�Z#dedefd�Z$dedefd�Z%dS)�N)�Path)�Dict�List�Optional)�logger)�instance)�	MyImunify�update_users_protection)�HostingPanel)�safe_fileopsz
.myimunify_idzE# DO NOT EDIT
# This file contains MyImunify id unique to this user

� �0123456789abcdefc��eZdZdZdS)�MyImunifyIdErrorz5Exception representing issues related to MyImunify idN)�__name__�
__module__�__qualname__�__doc__���[/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/myimunify_id.pyrrs������?�?�?�?rr�user�
protection�returnc��K�tj|���\}}|���t||g|���d{V��t	jd||��	t
|���d{V��}n#t$rYdSwxYw|S)z5Save subscription type to the DB and generate id file�rNz(Applied setting MyImunify=%s for user %s)r	�
get_or_create�saver
r�info�_get_or_generate_idr)�sinkrr�	myimunify�_�myimunify_ids      r�add_myimunify_userr%s�����
�*��5�5�5�L�I�q�
�N�N����
!�$���
�
;�
;�;�;�;�;�;�;�;�
�K�:�J��M�M�M��0��6�6�6�6�6�6�6�6���������t�t������s�A4�4
B�Bc��NK�g}t������d{V��}t���d{V��}tj���5t
|�����D]�\}}tj	|���\}}|�
|�|i���dd��|||j|�|i���dd��d�����	ddd��n#1swxYwY|S)zP
    Get a list of MyImunify users, their subscription types and unique ids
    Nr�email��locale)r'�usernamer$rr))
r�get_user_details�_myimunify_user_to_idr�db�transaction�sorted�itemsr	r�append�getr)�users�user_details�myimunify_user_to_idr�
myimunify_uid�recordr#s       r�get_myimunify_usersr8.s~����

�E�%���8�8�:�:�:�:�:�:�:�:�L�!6�!8�!8�8�8�8�8�8�8��	��	 �	 �	"�	"���#)�*>�*D�*D�*F�*F�#G�#G�
	�
	��D�-�!�/�T�:�:�:�I�F�A��L�L�)�-�-�d�B�7�7�;�;�G�R�H�H� $�$1�"(�"3�*�.�.�t�R�8�8�<�<�X�r�J�J���
�
�
�
�
	��������������������Ls�B0D�D�!Dc	��&K�i}t������d{V��D]e}	t|���d{V��||<�#t$rY�(tj$r-}t
jd|t|����Yd}~�^d}~wwxYw|S)z+Get a list of users and their MyImunify idsNz+Unable to generate id for user=%s, error=%s)	r�	get_usersr rr�UnsafeFileOperationr�error�str)�
user_to_idr�es   rr,r,Es������J�"�n�n�.�.�0�0�0�0�0�0�0�0�
�
��		�%8��%>�%>�>�>�>�>�>�>�J�t�����	�	�	��H��/�	�	�	��L�=�t�S��V�V�
�
�
�
�H�H�H�H�����		����
�s�A�
B�B�!#B	�	Bc���K�t|���d{V��}	t|��S#ttf$r1t	j��j}t||���d{V��cYSwxYw)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_id�FileNotFoundErrorr�uuid�uuid1�hex�	_write_id)r�id_filer$s   rr r Ws�����
+�4�0�0�0�0�0�0�0�0�G�6��� � � ���/�0�6�6�6��z�|�|�'���|�W�5�5�5�5�5�5�5�5�5�5�5�6���s�(�?A*�)A*r$rHc���K�t|zdz}	tjt|��|���d{V��n/#t$r"}tjd|��t|�d}~wwxYw|S)zWrite MyImunify id to file�
Nz1Unable to write myimunify_id in user home dir: %s)�_BANNERr�
write_textr=�OSErrorr�warningr)r$rH�textr?s    rrGrGds������\�!�D�(�D�&��%�c�'�l�l�D�9�9�9�9�9�9�9�9�9�9���&�&�&���J�A�N�N�N��A�%�����&�����s�(:�
A&�A!�!A&c��	tjt|��tjtjz��}n#t
$r�t$rt�wxYw	tj	tj
|��j��st�tj|d��}|�
d��}n#t$rt�wxYw	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)�os�openr=�O_RDONLY�
O_NONBLOCKrCrMr�stat�S_ISREG�fstat�st_mode�read�decode�UnicodeDecodeError�close�	_parse_id)rH�fd�datarOs    rrBrBos���
�W�S��\�\�2�;���#>�
?�
?��������
�������������|�B�H�R�L�L�0�1�1�	#�"�"��w�r�4� � ���{�{�7�#�#��������������	
�	�������������������T�?�?�s(�9<�A�AB9�8C$�9C�C$�$C:rOc�*�d}|���D]r}|���}|s�|�d��r�/|�t�t	|��t
kst
d�|D����st�|}�s|�t�|S)z`Read line by line: skip comments (#). First non-comment line must be valid id; nothing after it.N�#c3�(K�|]
}|tvV��dS)N)�_HEX)�.0�cs  r�	<genexpr>z_parse_id.<locals>.<genexpr>�s&����'=�'=�a��T�	�'=�'=�'=�'=�'=�'=r)�
splitlines�strip�
startswithr�len�_ID_LEN�all)rO�id_line�line�ss    rr]r]�s����G����!�!�
�
���J�J�L�L���	���<�<����	����"�"��q�6�6�W���C�'=�'=�1�'=�'=�'=�$=�$=��"�"���������Nrc��K�	tj|��}t|j��tz}	tjt|����n�#t$r�|j	�
��stjd|��t�	tjt|�����d{V��n/#t$r"}tjd|��t|�d}~wwxYwYnXt$rtjd|��t�wxYw#t"$r"}tjd|��t|�d}~wwxYw|S)z<Get a file with MyImunify id and create it if does not existzNo such user homedir: %sNz/Unable to put myimunify_id in user home dir: %szCannot access identity file: %szNo such user: %s)�pwd�getpwnamr�pw_dir�MYIMUNIFY_ID_FILE_NAMEr�ensure_regular_filer=rC�parent�existsrr<r�touchrMrN�KeyError)r�user_pwdrHr?s    rrArA�s�����#��<��%�%��
�x��'�'�*@�@��	#��,�S��\�\�:�:�:�:�� �
	.�
	.�
	.��>�(�(�*�*�
'���7��>�>�>�&�&�
.�"�(��W���6�6�6�6�6�6�6�6�6�6���
.�
.�
.���E�q����'�A�-�����	
.����7�6��	#�	#�	#��L�:�G�D�D�D�"�"�	#�����%�&�&�&���'��.�.�.��A�%�����&����*�NsL�D�!A�?D�'B?�>D�?
C+�	C&�&C+�+D�0&D�
E�#E�E)&rQrqrUrD�pathlibr�typingrrr�%defence360agent.contracts.permissionsr�defence360agent.modelr�defence360agent.myimunify.modelr	r
�+defence360agent.subsys.panels.hosting_panelr�defence360agent.utilsrrtrKrk�	frozensetrc�	Exceptionrr=�boolr%r8r,r rGrBr]rArrr�<module>r�s@��	�	�	�	�
�
�
�
���������������'�'�'�'�'�'�'�'�'�'�8�8�8�8�8�8�*�*�*�*�*�*�N�N�N�N�N�N�N�N�D�D�D�D�D�D�.�.�.�.�.�.�(��O��
���y�#�$�$��@�@�@�@�@�y�@�@�@����!%��
�c�]�����&�4��:�����.�T�#�s�(�^�����$
6�C�
6�C�
6�
6�
6�
6��#����������d��s�����4�C��C�����&�s��t������rdefence360agent/contracts/__pycache__/myimunify_id.cpython-311.pyc0000644000000000000000000002363400000000000022172 0ustar  �

(M��(G�Q��l�ddlZddlZddlZddlZddlmZddlmZmZm	Z	ddl
mZddlm
Z
ddlmZmZddlmZddlmZd	Zd
ZdZed��ZGd
�de��Zdedede	efd�Zdeefd�Zdeeeffd�Z dedefd�Z!dededefd�Z"dedefd�Z#dedefd�Z$dedefd�Z%dS)�N)�Path)�Dict�List�Optional)�logger)�instance)�	MyImunify�update_users_protection)�HostingPanel)�safe_fileopsz
.myimunify_idzE# DO NOT EDIT
# This file contains MyImunify id unique to this user

� �0123456789abcdefc��eZdZdZdS)�MyImunifyIdErrorz5Exception representing issues related to MyImunify idN)�__name__�
__module__�__qualname__�__doc__���[/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/myimunify_id.pyrrs������?�?�?�?rr�user�
protection�returnc��K�tj|���\}}|���t||g|���d{V��t	jd||��	t
|���d{V��}n#t$rYdSwxYw|S)z5Save subscription type to the DB and generate id file�rNz(Applied setting MyImunify=%s for user %s)r	�
get_or_create�saver
r�info�_get_or_generate_idr)�sinkrr�	myimunify�_�myimunify_ids      r�add_myimunify_userr%s�����
�*��5�5�5�L�I�q�
�N�N����
!�$���
�
;�
;�;�;�;�;�;�;�;�
�K�:�J��M�M�M��0��6�6�6�6�6�6�6�6���������t�t������s�A4�4
B�Bc��NK�g}t������d{V��}t���d{V��}tj���5t
|�����D]�\}}tj	|���\}}|�
|�|i���dd��|||j|�|i���dd��d�����	ddd��n#1swxYwY|S)zP
    Get a list of MyImunify users, their subscription types and unique ids
    Nr�email��locale)r'�usernamer$rr))
r�get_user_details�_myimunify_user_to_idr�db�transaction�sorted�itemsr	r�append�getr)�users�user_details�myimunify_user_to_idr�
myimunify_uid�recordr#s       r�get_myimunify_usersr8.s~����

�E�%���8�8�:�:�:�:�:�:�:�:�L�!6�!8�!8�8�8�8�8�8�8��	��	 �	 �	"�	"���#)�*>�*D�*D�*F�*F�#G�#G�
	�
	��D�-�!�/�T�:�:�:�I�F�A��L�L�)�-�-�d�B�7�7�;�;�G�R�H�H� $�$1�"(�"3�*�.�.�t�R�8�8�<�<�X�r�J�J���
�
�
�
�
	��������������������Ls�B0D�D�!Dc	��&K�i}t������d{V��D]e}	t|���d{V��||<�#t$rY�(tj$r-}t
jd|t|����Yd}~�^d}~wwxYw|S)z+Get a list of users and their MyImunify idsNz+Unable to generate id for user=%s, error=%s)	r�	get_usersr rr�UnsafeFileOperationr�error�str)�
user_to_idr�es   rr,r,Es������J�"�n�n�.�.�0�0�0�0�0�0�0�0�
�
��		�%8��%>�%>�>�>�>�>�>�>�J�t�����	�	�	��H��/�	�	�	��L�=�t�S��V�V�
�
�
�
�H�H�H�H�����		����
�s�A�
B�B�!#B	�	Bc���K�t|���d{V��}	t|��S#ttf$r1t	j��j}t||���d{V��cYSwxYw)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_id�FileNotFoundErrorr�uuid�uuid1�hex�	_write_id)r�id_filer$s   rr r Ws�����
+�4�0�0�0�0�0�0�0�0�G�6��� � � ���/�0�6�6�6��z�|�|�'���|�W�5�5�5�5�5�5�5�5�5�5�5�6���s�(�?A*�)A*r$rHc���K�t|zdz}	tjt|��|���d{V��n/#t$r"}tjd|��t|�d}~wwxYw|S)zWrite MyImunify id to file�
Nz1Unable to write myimunify_id in user home dir: %s)�_BANNERr�
write_textr=�OSErrorr�warningr)r$rH�textr?s    rrGrGds������\�!�D�(�D�&��%�c�'�l�l�D�9�9�9�9�9�9�9�9�9�9���&�&�&���J�A�N�N�N��A�%�����&�����s�(:�
A&�A!�!A&c��	tjt|��tjtjz��}n#t
$r�t$rt�wxYw	tj	tj
|��j��st�tj|d��}|�
d��}n#t$rt�wxYw	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)�os�openr=�O_RDONLY�
O_NONBLOCKrCrMr�stat�S_ISREG�fstat�st_mode�read�decode�UnicodeDecodeError�close�	_parse_id)rH�fd�datarOs    rrBrBos���
�W�S��\�\�2�;���#>�
?�
?��������
�������������|�B�H�R�L�L�0�1�1�	#�"�"��w�r�4� � ���{�{�7�#�#��������������	
�	�������������������T�?�?�s(�9<�A�AB9�8C$�9C�C$�$C:rOc�*�d}|���D]r}|���}|s�|�d��r�/|�t�t	|��t
kst
d�|D����st�|}�s|�t�|S)z`Read line by line: skip comments (#). First non-comment line must be valid id; nothing after it.N�#c3�(K�|]
}|tvV��dS)N)�_HEX)�.0�cs  r�	<genexpr>z_parse_id.<locals>.<genexpr>�s&����'=�'=�a��T�	�'=�'=�'=�'=�'=�'=r)�
splitlines�strip�
startswithr�len�_ID_LEN�all)rO�id_line�line�ss    rr]r]�s����G����!�!�
�
���J�J�L�L���	���<�<����	����"�"��q�6�6�W���C�'=�'=�1�'=�'=�'=�$=�$=��"�"���������Nrc��K�	tj|��}t|j��tz}	tjt|����n�#t$r�|j	�
��stjd|��t�	tjt|�����d{V��n/#t$r"}tjd|��t|�d}~wwxYwYnXt$rtjd|��t�wxYw#t"$r"}tjd|��t|�d}~wwxYw|S)z<Get a file with MyImunify id and create it if does not existzNo such user homedir: %sNz/Unable to put myimunify_id in user home dir: %szCannot access identity file: %szNo such user: %s)�pwd�getpwnamr�pw_dir�MYIMUNIFY_ID_FILE_NAMEr�ensure_regular_filer=rC�parent�existsrr<r�touchrMrN�KeyError)r�user_pwdrHr?s    rrArA�s�����#��<��%�%��
�x��'�'�*@�@��	#��,�S��\�\�:�:�:�:�� �
	.�
	.�
	.��>�(�(�*�*�
'���7��>�>�>�&�&�
.�"�(��W���6�6�6�6�6�6�6�6�6�6���
.�
.�
.���E�q����'�A�-�����	
.����7�6��	#�	#�	#��L�:�G�D�D�D�"�"�	#�����%�&�&�&���'��.�.�.��A�%�����&����*�NsL�D�!A�?D�'B?�>D�?
C+�	C&�&C+�+D�0&D�
E�#E�E)&rQrqrUrD�pathlibr�typingrrr�%defence360agent.contracts.permissionsr�defence360agent.modelr�defence360agent.myimunify.modelr	r
�+defence360agent.subsys.panels.hosting_panelr�defence360agent.utilsrrtrKrk�	frozensetrc�	Exceptionrr=�boolr%r8r,r rGrBr]rArrr�<module>r�s@��	�	�	�	�
�
�
�
���������������'�'�'�'�'�'�'�'�'�'�8�8�8�8�8�8�*�*�*�*�*�*�N�N�N�N�N�N�N�N�D�D�D�D�D�D�.�.�.�.�.�.�(��O��
���y�#�$�$��@�@�@�@�@�y�@�@�@����!%��
�c�]�����&�4��:�����.�T�#�s�(�^�����$
6�C�
6�C�
6�
6�
6�
6��#����������d��s�����4�C��C�����&�s��t������rdefence360agent/contracts/__pycache__/permissions.cpython-311.opt-1.pyc0000644000000000000000000002307400000000000023000 0ustar  �

�j�`���J�ddlZddlmZddlmZddlmZddlmZm	Z	m
Z
ddlmZddl
mZmZddlmZdd	lmZdd
lmZddlmZddlmZ	dd
lmZn
#e$rdZYnwxYweje��ZdxZ \
Z!Z"Z#Z$Z%Z&Z'Z(Z)Z*Z+Z,Z-ed��Z.de/fd�Z0d*dee1de/fd�Z2d*dee1de/fd�Z3d*dee1de/fd�Z4d*dee1fd�Z5d*dee1de/fd�Z6	d*dee1de/fd�Z7d*dee1fd�Z8d*dee1fd�Z9ej:ddd����Z;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)�MyImunifyConfig�PermissionsConfig�	Wordpress)�
LicenseCLN)�	AV_REPORT�FULL)�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/imunify360�returnc�\�t��jtjkotjS�N)r�NAMErr�USE_PLESK_SERVICE_PLAN���Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/permissions.py�is_plesk_service_plan_enabledr=s#������u�z�)�	5��4�r�userc�*�tj|��Sr)r
�get_protection�rs r�myimunify_protection_enabledrDs���#�D�)�)�)rc�X�|�dStj|��jttfvS�NT)r�get_perm�avr
rrs r�ms_viewr$Hs2���|��t�!�*�4�0�0�3���8��rc��tj��stj��sdS|�dSt��rdSt	j|��jtkS)NFT)r	�is_free�is_validrrr"r#rrs r�ms_cleanr(Rs^�������:�#6�#8�#8���u��|��t�$�&�&���t�!�*�4�0�0�3�t�;�;rc�V�tjrt|��St|��Sr)r�ENABLEDrr(rs r�&ms_clean_requires_myimunify_protectionr+`s'����2�+�D�1�1�1��D�>�>�rc�^�|�dStjrdSt��rdStjSr!)rr*rr�ALLOW_MALWARE_SCANrs r�ms_on_demand_scanr.fs8���|��t�����t�$�&�&���t��/�/rc�P�tjrt|��StjSr)rr*rrr-rs r�$ms_on_demand_scan_without_rate_limitr0us&����2�+�D�1�1�1��/�/rc�>�|�dStjrdStjS�NTF)rr*r�USER_IGNORE_LISTrs r�ms_ignore_list_editr4~s%���|��t�����u��-�-rc�>�|�dStjrdStjSr2)rr*r�USER_OVERRIDE_MALWARE_ACTIONSrs r�ms_config_default_action_editr7�s'���|��t�����u��:�:rzimav.contracts.permissions�is_imunify_patch_enabledc��dS�NFr��_s r�<lambda>r=����e�r)�module�name�defaultz.imav.malwarelib.api.imunify_patch_subscription�has_imunify_patch_subscriptionsc��dSr:rr;s rr=r=�r>rc���K�t�"tj��pt|��Stj��p,t|��ptj���d{V��jSr)rr	�is_eligible_for_imunify_patchrB�get_purchase_eligibility�eligiblers r�%ms_imunify_patch_eligible_to_purchaserH�sz����#�*��4�6�6�
5�.�t�4�4�	
�
	�0�2�2�	�*�4�0�0�	�.�F�H�H�H�H�H�H�H�H�
�rc�N�|�dStj|��jtkSr!)rr"�	proactiverrs r�pd_viewrK�s%���|��t�!�*�4�0�0�:�d�B�Brc�>�|�dStjrdStjSr2)rr*r�USER_OVERRIDE_PROACTIVE_DEFENSErs r�pd_config_mode_editrN�s%���|��t�����u��<�<rc�|�|�dStjsdS	ttj��S#t$rYdSwxYwr2)r�SECURITY_PLUGIN_ENABLED�bool�WAF_ENABLED�KeyErrorrs r�wp_waf_editrT�sT���|��t��,���u���I�)�*�*�*�������t�t����s�-�
;�;c�`�|�dS	ttj��S#t$rYdSwxYwr!)rQr�ALLOW_WP_WAF_RULES_MANAGEMENTrSrs r�wp_waf_rules_editrW�sE���|��t���%�C�D�D�D�������t�t����s��
-�-c��K�t�|��}|�dSt|��r||���d{V��S||��Sr:)�HAS_PERMISSION�getr��
permissionr�funcs   r�has_permissionr^�sa�������j�)�)�D��|��u��4� � � ��T�$�Z�Z���������4��:�:�rc��K�t�|��}|�td���t|��r"||���d{V��std���dS||��std���dS)Nz$notifications.generalPermissionError)rYrZ�PermissionErrorrr[s   r�check_permissionra�s��������j�)�)�D��|��D�E�E�E��4� � �J��T�$�Z�Z�������	J�!�"H�I�I�I�	J�	J��t�D�z�z�	J�!�"H�I�I�I�	J�	Jrc��:�K��fd�tD���d{V��S)Nc��D�K�g|]}t|����d{V���|��Sr)r^)�.0r\rs  �r�
<listcomp>z$permissions_list.<locals>.<listcomp>sO����������
�D�1�1�1�1�1�1�1�1�����r)�PERMISSIONSrs`r�permissions_listrgsP���������%����������rr)rN)G�logging�asyncio.coroutinesr�pathlibr�typingr� 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.pleskr�defence360agent.utilsr�.imav.malwarelib.api.imunify_patch_subscriptionr�ImportError�	getLogger�__name__�loggerrf�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�GLOBAL_CONFDIRrQr�strrr$r(r+r.r0r4r7rZ�ms_imunify_patch_enabledrBrHrKrNrTrWrYr^ra�listrgrrr�<module>r�s�������2�2�2�2�2�2�����������������������
9�8�8�8�8�8�H�H�H�H�H�H�H�H�K�K�K�K�K�K�5�5�5�5�5�5�D�D�D�D�D�D�5�5�5�5�5�5�*�*�*�*�*�*�'����������'�'�'�"&����'����
��	�8�	$�	$�� ������*��(��!��)�����"��1�2�2���t�����*�*�x��}�*��*�*�*�*���(�3�-��4�����<�<�8�C�=�<�D�<�<�<�<����#������0�0�H�S�M�0�T�0�0�0�0� �0�0�
�3�-�0�	�0�0�0�0�.�.�h�s�m�.�.�.�.�	;�	;���
�	;�	;�	;�	;�(�8�<�'�	#��O�����#/�(�,�;�	*��O�#�#�#�����

��*��	�����"C�C�(�3�-�C�C�C�C�=�=�h�s�m�=�=�=�=���h�s�m�������H�S�M������W��h�*�.��(�(�*N��,�!�#@��6�)�+P��W��,����(���&�d�����	J�	J�	J�	J��D��I������s�A�A�Adefence360agent/contracts/__pycache__/permissions.cpython-311.pyc0000644000000000000000000002307400000000000022041 0ustar  �

�j�`���J�ddlZddlmZddlmZddlmZddlmZm	Z	m
Z
ddlmZddl
mZmZddlmZdd	lmZdd
lmZddlmZddlmZ	dd
lmZn
#e$rdZYnwxYweje��ZdxZ \
Z!Z"Z#Z$Z%Z&Z'Z(Z)Z*Z+Z,Z-ed��Z.de/fd�Z0d*dee1de/fd�Z2d*dee1de/fd�Z3d*dee1de/fd�Z4d*dee1fd�Z5d*dee1de/fd�Z6	d*dee1de/fd�Z7d*dee1fd�Z8d*dee1fd�Z9ej:ddd����Z;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)�MyImunifyConfig�PermissionsConfig�	Wordpress)�
LicenseCLN)�	AV_REPORT�FULL)�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/imunify360�returnc�\�t��jtjkotjS�N)r�NAMErr�USE_PLESK_SERVICE_PLAN���Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/permissions.py�is_plesk_service_plan_enabledr=s#������u�z�)�	5��4�r�userc�*�tj|��Sr)r
�get_protection�rs r�myimunify_protection_enabledrDs���#�D�)�)�)rc�X�|�dStj|��jttfvS�NT)r�get_perm�avr
rrs r�ms_viewr$Hs2���|��t�!�*�4�0�0�3���8��rc��tj��stj��sdS|�dSt��rdSt	j|��jtkS)NFT)r	�is_free�is_validrrr"r#rrs r�ms_cleanr(Rs^�������:�#6�#8�#8���u��|��t�$�&�&���t�!�*�4�0�0�3�t�;�;rc�V�tjrt|��St|��Sr)r�ENABLEDrr(rs r�&ms_clean_requires_myimunify_protectionr+`s'����2�+�D�1�1�1��D�>�>�rc�^�|�dStjrdSt��rdStjSr!)rr*rr�ALLOW_MALWARE_SCANrs r�ms_on_demand_scanr.fs8���|��t�����t�$�&�&���t��/�/rc�P�tjrt|��StjSr)rr*rrr-rs r�$ms_on_demand_scan_without_rate_limitr0us&����2�+�D�1�1�1��/�/rc�>�|�dStjrdStjS�NTF)rr*r�USER_IGNORE_LISTrs r�ms_ignore_list_editr4~s%���|��t�����u��-�-rc�>�|�dStjrdStjSr2)rr*r�USER_OVERRIDE_MALWARE_ACTIONSrs r�ms_config_default_action_editr7�s'���|��t�����u��:�:rzimav.contracts.permissions�is_imunify_patch_enabledc��dS�NFr��_s r�<lambda>r=����e�r)�module�name�defaultz.imav.malwarelib.api.imunify_patch_subscription�has_imunify_patch_subscriptionsc��dSr:rr;s rr=r=�r>rc���K�t�"tj��pt|��Stj��p,t|��ptj���d{V��jSr)rr	�is_eligible_for_imunify_patchrB�get_purchase_eligibility�eligiblers r�%ms_imunify_patch_eligible_to_purchaserH�sz����#�*��4�6�6�
5�.�t�4�4�	
�
	�0�2�2�	�*�4�0�0�	�.�F�H�H�H�H�H�H�H�H�
�rc�N�|�dStj|��jtkSr!)rr"�	proactiverrs r�pd_viewrK�s%���|��t�!�*�4�0�0�:�d�B�Brc�>�|�dStjrdStjSr2)rr*r�USER_OVERRIDE_PROACTIVE_DEFENSErs r�pd_config_mode_editrN�s%���|��t�����u��<�<rc�|�|�dStjsdS	ttj��S#t$rYdSwxYwr2)r�SECURITY_PLUGIN_ENABLED�bool�WAF_ENABLED�KeyErrorrs r�wp_waf_editrT�sT���|��t��,���u���I�)�*�*�*�������t�t����s�-�
;�;c�`�|�dS	ttj��S#t$rYdSwxYwr!)rQr�ALLOW_WP_WAF_RULES_MANAGEMENTrSrs r�wp_waf_rules_editrW�sE���|��t���%�C�D�D�D�������t�t����s��
-�-c��K�t�|��}|�dSt|��r||���d{V��S||��Sr:)�HAS_PERMISSION�getr��
permissionr�funcs   r�has_permissionr^�sa�������j�)�)�D��|��u��4� � � ��T�$�Z�Z���������4��:�:�rc��K�t�|��}|�td���t|��r"||���d{V��std���dS||��std���dS)Nz$notifications.generalPermissionError)rYrZ�PermissionErrorrr[s   r�check_permissionra�s��������j�)�)�D��|��D�E�E�E��4� � �J��T�$�Z�Z�������	J�!�"H�I�I�I�	J�	J��t�D�z�z�	J�!�"H�I�I�I�	J�	Jrc��:�K��fd�tD���d{V��S)Nc��D�K�g|]}t|����d{V���|��Sr)r^)�.0r\rs  �r�
<listcomp>z$permissions_list.<locals>.<listcomp>sO����������
�D�1�1�1�1�1�1�1�1�����r)�PERMISSIONSrs`r�permissions_listrgsP���������%����������rr)rN)G�logging�asyncio.coroutinesr�pathlibr�typingr� 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.pleskr�defence360agent.utilsr�.imav.malwarelib.api.imunify_patch_subscriptionr�ImportError�	getLogger�__name__�loggerrf�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�GLOBAL_CONFDIRrQr�strrr$r(r+r.r0r4r7rZ�ms_imunify_patch_enabledrBrHrKrNrTrWrYr^ra�listrgrrr�<module>r�s�������2�2�2�2�2�2�����������������������
9�8�8�8�8�8�H�H�H�H�H�H�H�H�K�K�K�K�K�K�5�5�5�5�5�5�D�D�D�D�D�D�5�5�5�5�5�5�*�*�*�*�*�*�'����������'�'�'�"&����'����
��	�8�	$�	$�� ������*��(��!��)�����"��1�2�2���t�����*�*�x��}�*��*�*�*�*���(�3�-��4�����<�<�8�C�=�<�D�<�<�<�<����#������0�0�H�S�M�0�T�0�0�0�0� �0�0�
�3�-�0�	�0�0�0�0�.�.�h�s�m�.�.�.�.�	;�	;���
�	;�	;�	;�	;�(�8�<�'�	#��O�����#/�(�,�;�	*��O�#�#�#�����

��*��	�����"C�C�(�3�-�C�C�C�C�=�=�h�s�m�=�=�=�=���h�s�m�������H�S�M������W��h�*�.��(�(�*N��,�!�#@��6�)�+P��W��,����(���&�d�����	J�	J�	J�	J��D��I������s�A�A�Adefence360agent/contracts/__pycache__/plugins.cpython-311.opt-1.pyc0000644000000000000000000003210300000000000022077 0ustar  �

Įð>����p�ddlZddlZddlZddlZddlmZmZmZddlm	Z	ddl
mZmZddl
mZmZddlmZeje��ZGd�de��ZGd	�d
ee��ZGd�dee��ZGd
�dee���ZGd�d��ZGd�deee��Zdd�d�Ze��Zd�Zd�Z dS)�N)�ABC�ABCMeta�abstractmethod)�suppress)�	lru_cache�wraps)�Message�MessageType)�Scopec�Z��eZdZejZdZdZgZ�fd�Z	e
d���Zd�Zd�Z
�xZS)�
BasePlugin�dTc�n��t��jdi|��|j�|��dS)N�)�super�__init_subclass__�_subclasses�append)�cls�kwargs�	__class__s  ��V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/plugins.pyrzBasePlugin.__init_subclass__s<���!����!�+�+�F�+�+�+�����s�#�#�#�#�#�c�$�d�|jD��S)Nc�:�g|]}tj|���|��Sr)�inspect�
isabstract)�.0�plugins  r�
<listcomp>z1BasePlugin.get_active_plugins.<locals>.<listcomp>s9��
�
�
���%�f�-�-�
��
�
�
r)r)rs r�get_active_pluginszBasePlugin.get_active_pluginss%��
�
��/�
�
�
�	
rc��
K�dS)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.

        Nr��selfs r�shutdownzBasePlugin.shutdown"s
����	
�rc�8�|jj�d|jj��S)N�.)r�
__module__�__name__r#s r�__repr__zBasePlugin.__repr__/s ���.�3�3�3�T�^�5L�5L�M�Mr)r)r(�__qualname__r�AV_IM360�SCOPE�SHUTDOWN_PRIORITY�AVAILABLE_ON_FREEMIUMrr�classmethodr!r%r*�
__classcell__)rs@rr
r
s���������N�E��� ���K�$�$�$�$�$��
�
��[�
�
�
�
�N�N�N�N�N�N�Nrr
c�$�eZdZed���ZdS)�
MessageSourcec��
K�dS�zThis method is a coroutine.Nr�r$�loop�sinks   r�
create_sourcezMessageSource.create_source4�
�����rN)r)r(r+rr9rrrr3r33s-�������*�*��^�*�*�*rr3c�.�eZdZdZd�Zed���ZdS)�Sensorz+
    Sensor is alias to MessageSource.
    c��>K�|�||���d{V��Sr5)�
create_sensorr6s   rr9zSensor.create_source>s.�����'�'��d�3�3�3�3�3�3�3�3�3rc��
K�dSr5rr6s   rr>zSensor.create_sensorBr:rN)r)r(r+�__doc__r9rr>rrrr<r<9sH��������4�4�4��*�*��^�*�*�*rr<c�<�eZdZdZdZdZd�Zd�Zed���Z	dS)�LogStreamReaderNic	��\K�||_||_d|_|jsdSdddd|jf|_t	j|jtjtjtjd|j	d���d{V��|_
|�|�|j
j
����dS)Nz
/usr/bin/tailz
--follow=namez-n0z--retryr)�stdin�stdout�stderr�bufsize�limit)�_loop�_sink�_cmd�source_file�asyncio�create_subprocess_exec�
subprocess�DEVNULL�PIPE�_LIMIT�_child_process�create_task�_infinite_read_and_proceedrEr6s   rr>zLogStreamReader.create_sensorOs�������
���
���	���	��F�
������
��	�%,�$B�
�Y��$��?��%���+�
%
�%
�%
�
�
�
�
�
�
���	
����+�+�D�,?�,F�G�G�	
�	
�	
�	
�	
rc��pK�|j��|jdc}|_t�d|��tt��5|j���ddd��n#1swxYwY|j����d{V��}t�d||��dSdS)NzTerminating child process [%s]z,Terminated child process [%s] with code [%d])rK�logger�debugr�ProcessLookupErrorrS�kill�wait)r$�cmd�rcs   rr%zLogStreamReader.shutdownns�����9� �!�Y��N�C����L�L�9�3�?�?�?��,�-�-�
+�
+��#�(�(�*�*�*�
+�
+�
+�
+�
+�
+�
+�
+�
+�
+�
+����
+�
+�
+�
+��*�/�/�1�1�1�1�1�1�1�1�B��L�L�>��R�
�
�
�
�
�!� s�A.�.A2�5A2c��K�t��N)�NotImplementedError)r$�
stream_readers  rrUz*LogStreamReader._infinite_read_and_proceed}s����!�!r)
r)r(r+rLrRrKr>r%rrUrrrrBrBGs^�������K��F��D�
�
�
�>
�
�
��"�"��^�"�"�"rrB)�	metaclassc�>�eZdZed���d���Zd�ZdS)�BaseMessageProcessor�)�maxsizec���g}t|��D]\}|�d��r�t||��}t|��r%t	|d��r|�|���]|S)N�_�_decorated_for_process_message)�dir�
startswith�getattr�callable�hasattrr)r$�rv�attr_str�funcs    r�_message_processorsz(BaseMessageProcessor._message_processors�s���
���D�	�	�	 �	 �H��"�"�3�'�'�
���4��*�*�D���~�~�
 �'��6�#�#�
 ��	�	�$������	rc���K�t�d||��|���D],}||���d{V��}t|t��r|cS�-dS)NzDispatching %r through %r...)rWrXrr�
isinstancer	)r$�message�coro�results    r�process_messagez$BaseMessageProcessor.process_message�s��������3�W�d�C�C�C��,�,�.�.�	�	�D��4��=�=�(�(�(�(�(�(�F��&�'�*�*�
��
�
�
�
�	�	rN)r)r(r+rrrrxrrrrdrd�sJ�������Y�q����
�
���
�����rrdc�L�eZdZGd�d��ZejZed���ZdS)�MessageSinkc�V�eZdZdZdZdZdZdZdZdZ	dZ
d	Zd
ZeZ
dZdZd
ZdZdZdZdZdS)�MessageSink.ProcessingOrder�
����(�2�7�<�F�P�Q�Zr�x���i�N)r)r(r+�PRE_PROCESS_MESSAGE�LFD�IGNORE_MESSAGE�UNBLOCK_FROM_SUBNET�CHECK_IP_IN_GRAYLIST�GRAYLIST_TIMEOUT�GRAYLIST_DB_FIXUP�IMPORT_EXPORT_WBLIST�
ML_PREDICTION�DEFAULT�IPSET_PROTECTOR�WEBSHIELD_PROTECTOR�WHITELIST_UNBLOCKED�SYNCLIST_UPDATE�POST_ACTION�
EVENT_HOOK�
ICONTACT_SENT�POST_PROCESS_MESSAGErrr�ProcessingOrderr|�ss������ ������ ��!������!���
���!�� �� �������
��
�"���rr�c��
K�dSr_r)r$r7s  r�create_sinkzMessageSink.create_sink�s�����rN)r)r(r+r�r��PROCESSING_ORDERrr�rrrrzrz�sa������"#�"#�"#�"#�"#�"#�"#�"#�J'�.���
�
��^�
�
�
rrz)�
async_lockc��������fd�}|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): ...
    c�����t�dd���d��r#td��������t	�������fd���}�|_|S)Nr)�rhz{coro} is not public�rvc��2����K��	��
fd�}d����fd��|��rىdur�����d{V��	����|����d{V��}�durBt�tj��r(����r����nV#t$rI}t�tj��r(����r����|�d}~wwxYw|S����r�|����d{V��SdS)Nc�~��t����o,t�fd�����D����S)Nc3�P�K�|] \}}��|��|kV��!dSr_)�get)r�k�vrus   �r�	<genexpr>zMexpect.<locals>.decorate.<locals>.decorated.<locals>.match.<locals>.<genexpr>�sO�����A�A�,0�A�q�G�K�K��N�N�a�'�A�A�A�A�A�Ar)rt�all�items)�
expect_fieldsru�message_types���r�matchz:expect.<locals>.decorate.<locals>.decorated.<locals>.match�s_���!�'�<�8�8��S�A�A�A�A�4A�4G�4G�4I�4I�A�A�A�>�>�rc�"�t|d��S)Nri)rnr�s r�
is_stackedz?expect.<locals>.decorate.<locals>.decorated.<locals>.is_stacked�s���t�%E�F�F�Frc�>���|��r�|j��S|Sr_)ri)rvr��terminals ��rr�z=expect.<locals>.decorate.<locals>.decorated.<locals>.terminal�s.����:�d�#�#�I�#�8�D�$G�H�H�H��rTF)�acquirertr
�Lockable�locked�release�	Exception)r$rur�rw�excr�r�r�rvr�r�s `   @@����r�	decoratedz+expect.<locals>.decorate.<locals>.decorated�s���������
�
�
�
�
�
�
�

G�
G�
G�
�
�
�
�
�
��u�w�w�
���%�%�!�/�/�+�+�+�+�+�+�+�+�+�*�#1�8�8�D�>�>�$��#@�#@�@�@�@�@�@�@�F�#�e�+�+�&�w��0D�E�E�,�#�N�N�,�,�,� ���)�)�)���!����"�7�K�,@�A�A�*�#�N�N�,�,�*� ���)�)�)��I�����
�����
��z�$���
1�!�T�$��0�0�0�0�0�0�0�0�0��4s�B"�"
C5�,AC0�0C5)rlrk�	TypeError�formatrri)rvr�r�r�r�s` ���r�decoratezexpect.<locals>.decorate�s������4��R�(�(�3�3�C�8�8�	F��2�9�9�t�9�D�D�E�E�E�	�t���&	�&	�&	�&	�&	�&	�&	�
��&	�P48�	�0��rr)r�r�r�r�s``` r�expectr��s0�����.�.�.�.�.�.�.�`�Orc�:�t�|��|S)zlRegister class as a plugin.

    >>> @thisguy
    >>> class ConcreteSink (MessageSink):
    >>>     ...
    )�_plugin_registry�add)�	pluginclss r�thisguyr�
s������#�#�#��rc��tS)z*Enumerate classobj for registered plugins.)r�rrr�	theseguysr�s���r)!rMr�loggingrO�abcrrr�
contextlibr�	functoolsrr�"defence360agent.contracts.messagesr	r
�defence360agent.utilsr�	getLoggerr)rW�objectr
r3r<rBrdrzr��setr�r�r�rrr�<module>r�s������������������,�,�,�,�,�,�,�,�,�,�������&�&�&�&�&�&�&�&�C�C�C�C�C�C�C�C�'�'�'�'�'�'�	��	�8�	$�	$��!N�!N�!N�!N�!N��!N�!N�!N�H*�*�*�*�*�J��*�*�*�*�*�*�*�*�]�C�*�*�*�8"�8"�8"�8"�8"�f��8"�8"�8"�8"�v��������,*
�*
�*
�*
�*
�*�2�C�*
�*
�*
�Z&*�?�?�?�?�?�D�3�5�5���������rdefence360agent/contracts/__pycache__/plugins.cpython-311.pyc0000644000000000000000000003210300000000000021140 0ustar  �

Įð>����p�ddlZddlZddlZddlZddlmZmZmZddlm	Z	ddl
mZmZddl
mZmZddlmZeje��ZGd�de��ZGd	�d
ee��ZGd�dee��ZGd
�dee���ZGd�d��ZGd�deee��Zdd�d�Ze��Zd�Zd�Z dS)�N)�ABC�ABCMeta�abstractmethod)�suppress)�	lru_cache�wraps)�Message�MessageType)�Scopec�Z��eZdZejZdZdZgZ�fd�Z	e
d���Zd�Zd�Z
�xZS)�
BasePlugin�dTc�n��t��jdi|��|j�|��dS)N�)�super�__init_subclass__�_subclasses�append)�cls�kwargs�	__class__s  ��V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/plugins.pyrzBasePlugin.__init_subclass__s<���!����!�+�+�F�+�+�+�����s�#�#�#�#�#�c�$�d�|jD��S)Nc�:�g|]}tj|���|��Sr)�inspect�
isabstract)�.0�plugins  r�
<listcomp>z1BasePlugin.get_active_plugins.<locals>.<listcomp>s9��
�
�
���%�f�-�-�
��
�
�
r)r)rs r�get_active_pluginszBasePlugin.get_active_pluginss%��
�
��/�
�
�
�	
rc��
K�dS)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.

        Nr��selfs r�shutdownzBasePlugin.shutdown"s
����	
�rc�8�|jj�d|jj��S)N�.)r�
__module__�__name__r#s r�__repr__zBasePlugin.__repr__/s ���.�3�3�3�T�^�5L�5L�M�Mr)r)r(�__qualname__r�AV_IM360�SCOPE�SHUTDOWN_PRIORITY�AVAILABLE_ON_FREEMIUMrr�classmethodr!r%r*�
__classcell__)rs@rr
r
s���������N�E��� ���K�$�$�$�$�$��
�
��[�
�
�
�
�N�N�N�N�N�N�Nrr
c�$�eZdZed���ZdS)�
MessageSourcec��
K�dS�zThis method is a coroutine.Nr�r$�loop�sinks   r�
create_sourcezMessageSource.create_source4�
�����rN)r)r(r+rr9rrrr3r33s-�������*�*��^�*�*�*rr3c�.�eZdZdZd�Zed���ZdS)�Sensorz+
    Sensor is alias to MessageSource.
    c��>K�|�||���d{V��Sr5)�
create_sensorr6s   rr9zSensor.create_source>s.�����'�'��d�3�3�3�3�3�3�3�3�3rc��
K�dSr5rr6s   rr>zSensor.create_sensorBr:rN)r)r(r+�__doc__r9rr>rrrr<r<9sH��������4�4�4��*�*��^�*�*�*rr<c�<�eZdZdZdZdZd�Zd�Zed���Z	dS)�LogStreamReaderNic	��\K�||_||_d|_|jsdSdddd|jf|_t	j|jtjtjtjd|j	d���d{V��|_
|�|�|j
j
����dS)Nz
/usr/bin/tailz
--follow=namez-n0z--retryr)�stdin�stdout�stderr�bufsize�limit)�_loop�_sink�_cmd�source_file�asyncio�create_subprocess_exec�
subprocess�DEVNULL�PIPE�_LIMIT�_child_process�create_task�_infinite_read_and_proceedrEr6s   rr>zLogStreamReader.create_sensorOs�������
���
���	���	��F�
������
��	�%,�$B�
�Y��$��?��%���+�
%
�%
�%
�
�
�
�
�
�
���	
����+�+�D�,?�,F�G�G�	
�	
�	
�	
�	
rc��pK�|j��|jdc}|_t�d|��tt��5|j���ddd��n#1swxYwY|j����d{V��}t�d||��dSdS)NzTerminating child process [%s]z,Terminated child process [%s] with code [%d])rK�logger�debugr�ProcessLookupErrorrS�kill�wait)r$�cmd�rcs   rr%zLogStreamReader.shutdownns�����9� �!�Y��N�C����L�L�9�3�?�?�?��,�-�-�
+�
+��#�(�(�*�*�*�
+�
+�
+�
+�
+�
+�
+�
+�
+�
+�
+����
+�
+�
+�
+��*�/�/�1�1�1�1�1�1�1�1�B��L�L�>��R�
�
�
�
�
�!� s�A.�.A2�5A2c��K�t��N)�NotImplementedError)r$�
stream_readers  rrUz*LogStreamReader._infinite_read_and_proceed}s����!�!r)
r)r(r+rLrRrKr>r%rrUrrrrBrBGs^�������K��F��D�
�
�
�>
�
�
��"�"��^�"�"�"rrB)�	metaclassc�>�eZdZed���d���Zd�ZdS)�BaseMessageProcessor�)�maxsizec���g}t|��D]\}|�d��r�t||��}t|��r%t	|d��r|�|���]|S)N�_�_decorated_for_process_message)�dir�
startswith�getattr�callable�hasattrr)r$�rv�attr_str�funcs    r�_message_processorsz(BaseMessageProcessor._message_processors�s���
���D�	�	�	 �	 �H��"�"�3�'�'�
���4��*�*�D���~�~�
 �'��6�#�#�
 ��	�	�$������	rc���K�t�d||��|���D],}||���d{V��}t|t��r|cS�-dS)NzDispatching %r through %r...)rWrXrr�
isinstancer	)r$�message�coro�results    r�process_messagez$BaseMessageProcessor.process_message�s��������3�W�d�C�C�C��,�,�.�.�	�	�D��4��=�=�(�(�(�(�(�(�F��&�'�*�*�
��
�
�
�
�	�	rN)r)r(r+rrrrxrrrrdrd�sJ�������Y�q����
�
���
�����rrdc�L�eZdZGd�d��ZejZed���ZdS)�MessageSinkc�V�eZdZdZdZdZdZdZdZdZ	dZ
d	Zd
ZeZ
dZdZd
ZdZdZdZdZdS)�MessageSink.ProcessingOrder�
����(�2�7�<�F�P�Q�Zr�x���i�N)r)r(r+�PRE_PROCESS_MESSAGE�LFD�IGNORE_MESSAGE�UNBLOCK_FROM_SUBNET�CHECK_IP_IN_GRAYLIST�GRAYLIST_TIMEOUT�GRAYLIST_DB_FIXUP�IMPORT_EXPORT_WBLIST�
ML_PREDICTION�DEFAULT�IPSET_PROTECTOR�WEBSHIELD_PROTECTOR�WHITELIST_UNBLOCKED�SYNCLIST_UPDATE�POST_ACTION�
EVENT_HOOK�
ICONTACT_SENT�POST_PROCESS_MESSAGErrr�ProcessingOrderr|�ss������ ������ ��!������!���
���!�� �� �������
��
�"���rr�c��
K�dSr_r)r$r7s  r�create_sinkzMessageSink.create_sink�s�����rN)r)r(r+r�r��PROCESSING_ORDERrr�rrrrzrz�sa������"#�"#�"#�"#�"#�"#�"#�"#�J'�.���
�
��^�
�
�
rrz)�
async_lockc��������fd�}|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): ...
    c�����t�dd���d��r#td��������t	�������fd���}�|_|S)Nr)�rhz{coro} is not public�rvc��2����K��	��
fd�}d����fd��|��rىdur�����d{V��	����|����d{V��}�durBt�tj��r(����r����nV#t$rI}t�tj��r(����r����|�d}~wwxYw|S����r�|����d{V��SdS)Nc�~��t����o,t�fd�����D����S)Nc3�P�K�|] \}}��|��|kV��!dSr_)�get)r�k�vrus   �r�	<genexpr>zMexpect.<locals>.decorate.<locals>.decorated.<locals>.match.<locals>.<genexpr>�sO�����A�A�,0�A�q�G�K�K��N�N�a�'�A�A�A�A�A�Ar)rt�all�items)�
expect_fieldsru�message_types���r�matchz:expect.<locals>.decorate.<locals>.decorated.<locals>.match�s_���!�'�<�8�8��S�A�A�A�A�4A�4G�4G�4I�4I�A�A�A�>�>�rc�"�t|d��S)Nri)rnr�s r�
is_stackedz?expect.<locals>.decorate.<locals>.decorated.<locals>.is_stacked�s���t�%E�F�F�Frc�>���|��r�|j��S|Sr_)ri)rvr��terminals ��rr�z=expect.<locals>.decorate.<locals>.decorated.<locals>.terminal�s.����:�d�#�#�I�#�8�D�$G�H�H�H��rTF)�acquirertr
�Lockable�locked�release�	Exception)r$rur�rw�excr�r�r�rvr�r�s `   @@����r�	decoratedz+expect.<locals>.decorate.<locals>.decorated�s���������
�
�
�
�
�
�
�

G�
G�
G�
�
�
�
�
�
��u�w�w�
���%�%�!�/�/�+�+�+�+�+�+�+�+�+�*�#1�8�8�D�>�>�$��#@�#@�@�@�@�@�@�@�F�#�e�+�+�&�w��0D�E�E�,�#�N�N�,�,�,� ���)�)�)���!����"�7�K�,@�A�A�*�#�N�N�,�,�*� ���)�)�)��I�����
�����
��z�$���
1�!�T�$��0�0�0�0�0�0�0�0�0��4s�B"�"
C5�,AC0�0C5)rlrk�	TypeError�formatrri)rvr�r�r�r�s` ���r�decoratezexpect.<locals>.decorate�s������4��R�(�(�3�3�C�8�8�	F��2�9�9�t�9�D�D�E�E�E�	�t���&	�&	�&	�&	�&	�&	�&	�
��&	�P48�	�0��rr)r�r�r�r�s``` r�expectr��s0�����.�.�.�.�.�.�.�`�Orc�:�t�|��|S)zlRegister class as a plugin.

    >>> @thisguy
    >>> class ConcreteSink (MessageSink):
    >>>     ...
    )�_plugin_registry�add)�	pluginclss r�thisguyr�
s������#�#�#��rc��tS)z*Enumerate classobj for registered plugins.)r�rrr�	theseguysr�s���r)!rMr�loggingrO�abcrrr�
contextlibr�	functoolsrr�"defence360agent.contracts.messagesr	r
�defence360agent.utilsr�	getLoggerr)rW�objectr
r3r<rBrdrzr��setr�r�r�rrr�<module>r�s������������������,�,�,�,�,�,�,�,�,�,�������&�&�&�&�&�&�&�&�C�C�C�C�C�C�C�C�'�'�'�'�'�'�	��	�8�	$�	$��!N�!N�!N�!N�!N��!N�!N�!N�H*�*�*�*�*�J��*�*�*�*�*�*�*�*�]�C�*�*�*�8"�8"�8"�8"�8"�f��8"�8"�8"�8"�v��������,*
�*
�*
�*
�*
�*�2�C�*
�*
�*
�Z&*�?�?�?�?�?�D�3�5�5���������rdefence360agent/contracts/__pycache__/sentry.cpython-311.opt-1.pyc0000644000000000000000000001366100000000000021752 0ustar  �

�<E�wY����J�ddlmZddlmZmZmZddlmZddlm	Z	d�Z
e	d���Ze	d���Zda
d	�Zd
eddfd�Zd
eddfd�Zdeddfd�Zdeddfd�Zdeddfd�Zdedzddfd�Zdedzddfd�Zdeddfd�Zdeddfd�Zdeddfd�Zdefd�Zdedefd�Zd!d �ZdS)"�)�Path)�DEVNULL�CalledProcessError�check_output)�Any)�stub_unexpected_errorc��	t|t���}n#ttf$rYdSwxYw|�dd������S)N)�stderrzutf-8�ignore)�errors)rr�FileNotFoundErrorr�decode�strip)�cmd�outs  �U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/sentry.py�_run_cmdr	sg����3�w�/�/�/�����1�2�����t�t������:�:�g�h�:�/�/�5�5�7�7�7s��.�.c��tdg��}|r|Stdg��}|r|Stgd���}|r|SdS)Nzsystemd-detect-virtz	virt-what)�	dmidecodez-szsystem-manufacturerzfail to detect)r)�systemd_virt�	virt_what�demicodes   r�_get_virtualization_typerse���2�3�4�4�L������+��'�'�I������B�B�B�C�C�H�������c�B�ddl}|���jdzS)Nri)�psutil�virtual_memory�total)rs r�_get_total_ramr#s%���M�M�M�� � �"�"�(�E�1�1rNc���t�jddlm}idd�dd�dd�dt|j�����dd�dd�d	t���d
d�dd�dt
���d
d�dd�dd�dd�dd�dd�atS)Nr��
OsReleaseInfo�
av_version�core_version�version�
os_details�ip�
hosting_panel�	total_ram�firewall�strategy�virtualization�	server_id�iaid�name�
test_build_id�test_build_job_id�test_parent_build_id)�_TAGS�defence360agent.utilsr"r�pretty_namerrr!s r�_tagsr6-s���}�7�7�7�7�7�7�
��$�
��D�
�
�t�
�
�J�/�
�0I�J�J�L�L�	
�

�$�
�
�T�

�
��)�)�
�
��
�
��
�
�6�8�8�
�
��
�
�D�
�
�D�
�
�T�
�
 ��
� 
#�D�!
��$�Lrr*�returnc�(�|t��d<dS)Nr*�r6)r*s r�set_firewall_typer:G���"�E�G�G�J���r�panelc�(�|t��d<dS)Nr(r9)r<s r�set_hosting_panelr>Ks��$�E�G�G�O���rr+c�(�|t��d<dS)Nr+r9)r+s r�set_strategyr@Or;rr'c�(�|t��d<dS)Nr'r9)r's r�set_iprBSs���E�G�G�D�M�M�Mr�productc�(�|t��d<dS)Nr/r9)rCs r�set_product_namerEWs���E�G�G�F�O�O�Or�idc�(�|t��d<dS)Nr-r9)rFs r�
set_server_idrH[s���E�G�G�K���rr.c�(�|t��d<dS)Nr.r9)r.s r�set_iaidrJ_s���E�G�G�F�O�O�Orr%c�(�|t��d<dS)Nr%r9�r%s r�set_versionrMcs�� �E�G�G�I���rc�(�|t��d<dS)Nr#r9rLs r�set_av_versionrOgs��#�E�G�G�L���rc�(�|t��d<dS)Nr$r9rLs r�set_core_versionrQks��%�E�G�G�N���rc�B�t�����S�N)r6�copy�rr�tagsrVos���7�7�<�<�>�>�rr/c�*�t��|SrSr9)r/s r�tagrXss���7�7�4�=�rc�,�td��dftd��dftd��dffD]`\}}|���rG	|������t	��|<�P#t
$rY�\wxYw�adS)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)r�exists�	read_textrr6�	Exception)�	file_namerXs  r�set_test_envr^ws���
�0�1�1��	
�

�4�5�5��	
�

�7�8�8�"�	
�
����	�3������	�
�(�2�2�4�4�:�:�<�<���������
�
�
���
����	��s�
5B�
B�B)r7N)�pathlibr�
subprocessrrr�typingrr4rrrrr3r6�strr:r>r@rBrErHrJrMrOrQ�dictrVrXr^rUrr�<module>rdso��������@�@�@�@�@�@�@�@�@�@�������8�7�7�7�7�7�8�8�8��
�
���
� �2�2���2�	
�����4#��#��#�#�#�#�%�S�%�T�%�%�%�%�#�3�#�4�#�#�#�#��s��t������c��d������c�D�j��T������3��:��$�����!��!��!�!�!�!�$�C�$�D�$�$�$�$�&�c�&�d�&�&�&�&��d������c��c����������rdefence360agent/contracts/__pycache__/sentry.cpython-311.pyc0000644000000000000000000001366100000000000021013 0ustar  �

�<E�wY����J�ddlmZddlmZmZmZddlmZddlm	Z	d�Z
e	d���Ze	d���Zda
d	�Zd
eddfd�Zd
eddfd�Zdeddfd�Zdeddfd�Zdeddfd�Zdedzddfd�Zdedzddfd�Zdeddfd�Zdeddfd�Zdeddfd�Zdefd�Zdedefd�Zd!d �ZdS)"�)�Path)�DEVNULL�CalledProcessError�check_output)�Any)�stub_unexpected_errorc��	t|t���}n#ttf$rYdSwxYw|�dd������S)N)�stderrzutf-8�ignore)�errors)rr�FileNotFoundErrorr�decode�strip)�cmd�outs  �U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/sentry.py�_run_cmdr	sg����3�w�/�/�/�����1�2�����t�t������:�:�g�h�:�/�/�5�5�7�7�7s��.�.c��tdg��}|r|Stdg��}|r|Stgd���}|r|SdS)Nzsystemd-detect-virtz	virt-what)�	dmidecodez-szsystem-manufacturerzfail to detect)r)�systemd_virt�	virt_what�demicodes   r�_get_virtualization_typerse���2�3�4�4�L������+��'�'�I������B�B�B�C�C�H�������c�B�ddl}|���jdzS)Nri)�psutil�virtual_memory�total)rs r�_get_total_ramr#s%���M�M�M�� � �"�"�(�E�1�1rNc���t�jddlm}idd�dd�dd�dt|j�����dd�dd�d	t���d
d�dd�dt
���d
d�dd�dd�dd�dd�dd�atS)Nr��
OsReleaseInfo�
av_version�core_version�version�
os_details�ip�
hosting_panel�	total_ram�firewall�strategy�virtualization�	server_id�iaid�name�
test_build_id�test_build_job_id�test_parent_build_id)�_TAGS�defence360agent.utilsr"r�pretty_namerrr!s r�_tagsr6-s���}�7�7�7�7�7�7�
��$�
��D�
�
�t�
�
�J�/�
�0I�J�J�L�L�	
�

�$�
�
�T�

�
��)�)�
�
��
�
��
�
�6�8�8�
�
��
�
�D�
�
�D�
�
�T�
�
 ��
� 
#�D�!
��$�Lrr*�returnc�(�|t��d<dS)Nr*�r6)r*s r�set_firewall_typer:G���"�E�G�G�J���r�panelc�(�|t��d<dS)Nr(r9)r<s r�set_hosting_panelr>Ks��$�E�G�G�O���rr+c�(�|t��d<dS)Nr+r9)r+s r�set_strategyr@Or;rr'c�(�|t��d<dS)Nr'r9)r's r�set_iprBSs���E�G�G�D�M�M�Mr�productc�(�|t��d<dS)Nr/r9)rCs r�set_product_namerEWs���E�G�G�F�O�O�Or�idc�(�|t��d<dS)Nr-r9)rFs r�
set_server_idrH[s���E�G�G�K���rr.c�(�|t��d<dS)Nr.r9)r.s r�set_iaidrJ_s���E�G�G�F�O�O�Orr%c�(�|t��d<dS)Nr%r9�r%s r�set_versionrMcs�� �E�G�G�I���rc�(�|t��d<dS)Nr#r9rLs r�set_av_versionrOgs��#�E�G�G�L���rc�(�|t��d<dS)Nr$r9rLs r�set_core_versionrQks��%�E�G�G�N���rc�B�t�����S�N)r6�copy�rr�tagsrVos���7�7�<�<�>�>�rr/c�*�t��|SrSr9)r/s r�tagrXss���7�7�4�=�rc�,�td��dftd��dftd��dffD]`\}}|���rG	|������t	��|<�P#t
$rY�\wxYw�adS)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)r�exists�	read_textrr6�	Exception)�	file_namerXs  r�set_test_envr^ws���
�0�1�1��	
�

�4�5�5��	
�

�7�8�8�"�	
�
����	�3������	�
�(�2�2�4�4�:�:�<�<���������
�
�
���
����	��s�
5B�
B�B)r7N)�pathlibr�
subprocessrrr�typingrr4rrrrr3r6�strr:r>r@rBrErHrJrMrOrQ�dictrVrXr^rUrr�<module>rdso��������@�@�@�@�@�@�@�@�@�@�������8�7�7�7�7�7�8�8�8��
�
���
� �2�2���2�	
�����4#��#��#�#�#�#�%�S�%�T�%�%�%�%�#�3�#�4�#�#�#�#��s��t������c��d������c�D�j��T������3��:��$�����!��!��!�!�!�!�$�C�$�D�$�$�$�$�&�c�&�d�&�&�&�&��d������c��c����������rdefence360agent/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 = ".el9"
    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 <init|disable> <backup-system>
#   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 ``<USER_CONFDIR>/<username>/`` and the
    config file inside it must end up owned by ``root:<user-gid>`` 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.py0000644000000000000000000004175000000000000014736 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
    <class 'defence360agent.contracts.messages.ConfigUpdate'>
    >>> _MessageType().NotExistMessage
    <class 'defence360agent.contracts.messages.UnknownMessage'>
    >>>
    """

    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"


class MDSReportList(ShortenReprListMixin, Message, Reportable):
    DEFAULT_METHOD = "MDS_SCAN_LIST"


class MDSReport(Accumulatable):
    LIST_CLASS = MDSReportList


# 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 _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 = estimate_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 EnsureServiceState(Message):
    """Ensure the service has the appropriate status"""

    DEFAULT_METHOD = "ENSURE_SERVICE_STATE"


class SensorWordpressIncidentList(MessageList, Reportable):
    """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.py0000644000000000000000000001340300000000000015623 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.error(
                "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.error("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.error("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.error("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  �

s�����s���dS)N�r��`/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/__init__.py�<module>rs���rdefence360agent/feature_management/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000031500000000000023045 0ustar  �

s�����s���dS)N�r��`/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/__init__.py�<module>rs���rdefence360agent/feature_management/__pycache__/checkers.cpython-311.opt-1.pyc0000644000000000000000000000655600000000000024051 0ustar  �

E%��FEn����ddlmZddlmZddlmZddlmZddlm	Z	de
dee
d	e
fd
�Zdde
d	e
de
fd
�Zdde
d	e
de
fd�Zde
d	e
fd�ZdS)�)�List)�deepcopy�)�FeatureDisabledError)�CONFIG_MAPPINGS)�FeatureManagementPerms�feature�permissions�userc��|�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)r�get_perm�get_featurer�format)r	r
r�perm�permission_values     �`/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/checkers.py�
check_featurer
su���|���!�*�4�0�0�D��'�'��0�0���{�*�*�"�<�C�C��4�
D�
�
�
�
�	
�+�*�F�sectionc��|�dS|tvrdS|t|vrdS	t|t|||��n#t$r|r�YdSwxYwdS)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	rr�raise_s    r�check_configr"s����|��t��o�%�%��t��o�g�.�.�.��t���g��w�7��@�$�G�G�G�G�������	���u�u�����
�4s�"A�
A�AN�data�returnc�j�t|��}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     r�config_cleanupr@sV����~�~�H�"�&�&���	&�	&�G����w�7�7�
&��W�%��	&��Orc�J�tD]}|D]}t|||d�����dS)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)rrr	rs    r�config_validationr RsK��#�>�>���	>�	>�G���$���=�=�=�=�=�	>�>�>r)F)N)�typingr�copyr�
exceptionsr�	constantsr�modelr�strrr�dictrr �rr�<module>r)s��������������,�,�,�,�,�,�&�&�&�&�&�&�)�)�)�)�)�)�
�3�
�T�#�Y�
�c�
�
�
�
�0��#��S��3�����<����S��D�����$>�D�>��>�>�>�>�>�>rdefence360agent/feature_management/__pycache__/checkers.cpython-311.pyc0000644000000000000000000000655600000000000023112 0ustar  �

E%��FEn����ddlmZddlmZddlmZddlmZddlm	Z	de
dee
d	e
fd
�Zdde
d	e
de
fd
�Zdde
d	e
de
fd�Zde
d	e
fd�ZdS)�)�List)�deepcopy�)�FeatureDisabledError)�CONFIG_MAPPINGS)�FeatureManagementPerms�feature�permissions�userc��|�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)r�get_perm�get_featurer�format)r	r
r�perm�permission_values     �`/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/checkers.py�
check_featurer
su���|���!�*�4�0�0�D��'�'��0�0���{�*�*�"�<�C�C��4�
D�
�
�
�
�	
�+�*�F�sectionc��|�dS|tvrdS|t|vrdS	t|t|||��n#t$r|r�YdSwxYwdS)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	rr�raise_s    r�check_configr"s����|��t��o�%�%��t��o�g�.�.�.��t���g��w�7��@�$�G�G�G�G�������	���u�u�����
�4s�"A�
A�AN�data�returnc�j�t|��}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     r�config_cleanupr@sV����~�~�H�"�&�&���	&�	&�G����w�7�7�
&��W�%��	&��Orc�J�tD]}|D]}t|||d�����dS)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)rrr	rs    r�config_validationr RsK��#�>�>���	>�	>�G���$���=�=�=�=�=�	>�>�>r)F)N)�typingr�copyr�
exceptionsr�	constantsr�modelr�strrr�dictrr �rr�<module>r)s��������������,�,�,�,�,�,�&�&�&�&�&�&�)�)�)�)�)�)�
�3�
�T�#�Y�
�c�
�
�
�
�0��#��S��3�����<����S��D�����$>�D�>��>�>�>�>�>�>rdefence360agent/feature_management/__pycache__/constants.cpython-311.opt-1.pyc0000644000000000000000000000134000000000000024260 0ustar  �

�� �u!L8��p�dZdZdZdZdZdZedeegiiZdZeded	iZeeeeeeiZ	eed
zfZ
dS)�	proactive�av�na�full�report�log�PROACTIVE_DEFENCE�
imunify360�
imunify360_av�imunify360_proactivez.tt2N)�	PROACTIVE�AV�NA�FULL�	AV_REPORT�LOG�CONFIG_MAPPINGS�NATIVE_EXTENSION_NAME�FEATURE_EXT_VARIABLES�EXTENSION_DEFAULTS�1NATIVE_FEATURE_MANAGEMENT_PACKAGE_EXTENSION_FILES���a/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/constants.py�<module>rs���
�	�	��
��
��
�	�����d�C�[����%����
�%����"��y��)�$�d�����F�"�5�1�1�1rdefence360agent/feature_management/__pycache__/constants.cpython-311.pyc0000644000000000000000000000134000000000000023321 0ustar  �

�� �u!L8��p�dZdZdZdZdZdZedeegiiZdZeded	iZeeeeeeiZ	eed
zfZ
dS)�	proactive�av�na�full�report�log�PROACTIVE_DEFENCE�
imunify360�
imunify360_av�imunify360_proactivez.tt2N)�	PROACTIVE�AV�NA�FULL�	AV_REPORT�LOG�CONFIG_MAPPINGS�NATIVE_EXTENSION_NAME�FEATURE_EXT_VARIABLES�EXTENSION_DEFAULTS�1NATIVE_FEATURE_MANAGEMENT_PACKAGE_EXTENSION_FILES���a/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/constants.py�<module>rs���
�	�	��
��
��
�	�����d�C�[����%����
�%����"��y��)�$�d�����F�"�5�1�1�1rdefence360agent/feature_management/__pycache__/control.cpython-311.opt-1.pyc0000644000000000000000000001072300000000000023731 0ustar  �

�\�Q��M`����ddlZddlZddlmZddlmZmZmZmZddl	m
Z
ddlmZddl
mZeje��Zd�Zd�Zed	���Zed
���Zed���ZdS)�N)�Version)�EXTENSION_DEFAULTS�FEATURE_EXT_VARIABLES�NATIVE_EXTENSION_NAME�1NATIVE_FEATURE_MANAGEMENT_PACKAGE_EXTENSION_FILES)�reset_features)�cPanel)�HostingPanelc�F��tj����fd���}|S)z-Do not run a function on an unsupported panelc���K�t���d{V��r�|i|���d{V��St�d��dS)Nz*Native feature management is not supported)�&is_native_feature_management_supported�logger�info)�args�kwargs�funcs  ��_/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/control.py�wrapperzsupported.<locals>.wrappersg�����7�9�9�9�9�9�9�9�9�	/���t�.�v�.�.�.�.�.�.�.�.�.����@�A�A�A�A�A�)�	functools�wraps)rrs` r�	supportedrs?����_�T���B�B�B�B���B�
�Nrc���K�t��}|jtjkrJ	t|����d{V����td��kS#t
$rYdSwxYwdS)z:Whether we support native feature management on the panel.Nz68.0F)r
�NAMEr	r�version�
ValueError��hps rr
r
s�����
���B�	�w�&�+���	�������-�-�-�-�-�-�.�.�'�&�/�/�A�A���	�	�	��5�5�	�����5s�7A�
A-�,A-c��K�t��}|�t���o|����d{V��S)z1Whether the native feature management is enabled.)�pkgsN)r
�is_extension_installedr�is_hook_installedrs r�$is_native_feature_management_enabledr#+sY����
���B�
�!�!�B�	"�	
�	
�	)��&�&�(�(�(�(�(�(�(�(�	rc��K�t��}tdid�tj��D�����d{V��|jt
tfit���d{V��t�	d��dS)z!Enable native feature management.c�0�i|]\}}|t|��S�)r)�.0�feature�pe_vars   r�
<dictcomp>z4enable_native_feature_management.<locals>.<dictcomp>@s4��
�
�
����
�'��/�
�
�
rNz-Imunify360 native feature management enabled.r&)
r
rr�items�install_extensionrrrrrrs r� enable_native_feature_managementr-8s�����
���B����
�
�#8�#>�#@�#@�
�
�
�����������"�
��9��������������K�K�?�@�@�@�@�@rc��K�t���d{V��st�d��dSt���t
t���d{V��t�d��dS)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_managementr0Os�����6�7�7�7�7�7�7�7�7�����B�C�C�C��t�
�.�.�
,�
,��9����������
�K�K�@�A�A�A��4r)r�logging�packaging.versionr�,defence360agent.feature_management.constantsrrrr�(defence360agent.feature_management.utilsr�$defence360agent.subsys.panels.cpanelr	�+defence360agent.subsys.panels.hosting_panelr
�	getLogger�__name__rrr
r#r-r0r&rr�<module>r9s/����������%�%�%�%�%�%�������������D�C�C�C�C�C�7�7�7�7�7�7�D�D�D�D�D�D�	��	�8�	$�	$��	�	�	�
�
�
��	�	���	��A�A���A�,�
�
���
�
�
rdefence360agent/feature_management/__pycache__/control.cpython-311.pyc0000644000000000000000000001072300000000000022772 0ustar  �

�\�Q��M`����ddlZddlZddlmZddlmZmZmZmZddl	m
Z
ddlmZddl
mZeje��Zd�Zd�Zed	���Zed
���Zed���ZdS)�N)�Version)�EXTENSION_DEFAULTS�FEATURE_EXT_VARIABLES�NATIVE_EXTENSION_NAME�1NATIVE_FEATURE_MANAGEMENT_PACKAGE_EXTENSION_FILES)�reset_features)�cPanel)�HostingPanelc�F��tj����fd���}|S)z-Do not run a function on an unsupported panelc���K�t���d{V��r�|i|���d{V��St�d��dS)Nz*Native feature management is not supported)�&is_native_feature_management_supported�logger�info)�args�kwargs�funcs  ��_/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/control.py�wrapperzsupported.<locals>.wrappersg�����7�9�9�9�9�9�9�9�9�	/���t�.�v�.�.�.�.�.�.�.�.�.����@�A�A�A�A�A�)�	functools�wraps)rrs` r�	supportedrs?����_�T���B�B�B�B���B�
�Nrc���K�t��}|jtjkrJ	t|����d{V����td��kS#t
$rYdSwxYwdS)z:Whether we support native feature management on the panel.Nz68.0F)r
�NAMEr	r�version�
ValueError��hps rr
r
s�����
���B�	�w�&�+���	�������-�-�-�-�-�-�.�.�'�&�/�/�A�A���	�	�	��5�5�	�����5s�7A�
A-�,A-c��K�t��}|�t���o|����d{V��S)z1Whether the native feature management is enabled.)�pkgsN)r
�is_extension_installedr�is_hook_installedrs r�$is_native_feature_management_enabledr#+sY����
���B�
�!�!�B�	"�	
�	
�	)��&�&�(�(�(�(�(�(�(�(�	rc��K�t��}tdid�tj��D�����d{V��|jt
tfit���d{V��t�	d��dS)z!Enable native feature management.c�0�i|]\}}|t|��S�)r)�.0�feature�pe_vars   r�
<dictcomp>z4enable_native_feature_management.<locals>.<dictcomp>@s4��
�
�
����
�'��/�
�
�
rNz-Imunify360 native feature management enabled.r&)
r
rr�items�install_extensionrrrrrrs r� enable_native_feature_managementr-8s�����
���B����
�
�#8�#>�#@�#@�
�
�
�����������"�
��9��������������K�K�?�@�@�@�@�@rc��K�t���d{V��st�d��dSt���t
t���d{V��t�d��dS)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_managementr0Os�����6�7�7�7�7�7�7�7�7�����B�C�C�C��t�
�.�.�
,�
,��9����������
�K�K�@�A�A�A��4r)r�logging�packaging.versionr�,defence360agent.feature_management.constantsrrrr�(defence360agent.feature_management.utilsr�$defence360agent.subsys.panels.cpanelr	�+defence360agent.subsys.panels.hosting_panelr
�	getLogger�__name__rrr
r#r-r0r&rr�<module>r9s/����������%�%�%�%�%�%�������������D�C�C�C�C�C�7�7�7�7�7�7�D�D�D�D�D�D�	��	�8�	$�	$��	�	�	�
�
�
��	�	���	��A�A���A�,�
�
���
�
�
rdefence360agent/feature_management/__pycache__/exceptions.cpython-311.opt-1.pyc0000644000000000000000000000202600000000000024427 0ustar  �

�VK�+<���Z�Gd�de��ZGd�de��ZGd�de��ZdS)c��eZdZdZdS)�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
rc��eZdZdZdS)�FeatureDisabledErrorzFeature is disabled for userNrr	r
rr
r
s������&�&�&�&r
r
c��eZdZdZdS)�UserArgumentNotFoundz;Method/function lack the parameter which contains user nameNrr	r
rrr	s������E�E�E�Er
rN)�	Exceptionrr
rr	r
r�<module>rs���0�0�0�0�0�Y�0�0�0�'�'�'�'�'�1�'�'�'�F�F�F�F�F�1�F�F�F�F�Fr
defence360agent/feature_management/__pycache__/exceptions.cpython-311.pyc0000644000000000000000000000202600000000000023470 0ustar  �

�VK�+<���Z�Gd�de��ZGd�de��ZGd�de��ZdS)c��eZdZdZdS)�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
rc��eZdZdZdS)�FeatureDisabledErrorzFeature is disabled for userNrr	r
rr
r
s������&�&�&�&r
r
c��eZdZdZdS)�UserArgumentNotFoundz;Method/function lack the parameter which contains user nameNrr	r
rrr	s������E�E�E�Er
rN)�	Exceptionrr
rr	r
r�<module>rs���0�0�0�0�0�Y�0�0�0�'�'�'�'�'�1�'�'�'�F�F�F�F�F�1�F�F�F�F�Fr
defence360agent/feature_management/__pycache__/hooks.cpython-311.opt-1.pyc0000644000000000000000000000763400000000000023403 0ustar  �

4|�Ś=W��&�dZddlZddlZddlmZmZmZddlmZddl	m
Z
mZmZm
Z
eje��Zd�Zd�Zedeed	ed
efd���ZedediZedeed	ed
efd
���Ze
ee
eiZded
eeeegeffd�ZdS)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)�Any�Callable�Optional)�
ConfigFile)�AV�FULL�LOG�	PROACTIVEc��dS)NT�)�_s �]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/hooks.py�
_hook_stubrs���4�c�F��tj����fd���}|S)Nc�h���||��}|p!t�d�j||��|S)Nz#Hook '%s(%s)' failed for user '%s'.)�logger�warning�__name__)�user�value�result�callbacks   �r�wrapz_result_warn.<locals>.wrapsC�����$��&�&���	
�&�.�.�1�����	
�
���
r)�	functools�wraps)rrs` r�_result_warnrs8����_�X�����������Krrr�returnc�p�|sdSt��}|�dd��}t|��}|�dd��}|tkrd}n0|r.|�d��r|r|�d��rd}	|�dd|��n#t
$rYdSwxYwdS)z#Called when 'av' feature is changedT�MALWARE_SCANNING�default_actionN�cleanup�notifyF)r�getr�
startswith�set�	Exception)rr�config�config_value�user_config�user_config_values      r�	antivirusr,)s�����t�
�\�\�F��:�:�0�2B�C�C�L��T�"�"�K�#���(:�<L�M�M����}�}� ����%��(�(��3�3�%�
�%�
�#�#�I�.�.�	%�%������� 0�2C�	
�	
�	
�	
�������u�u������4s�
B%�%
B3�2B3r	c��|sdSt�|d��}	t|���dd|��n#t$rYdSwxYwdS)z*Called when 'proactive' feature is changedT�DISABLED�PROACTIVE_DEFENCE�modeF)�_PROACTIVE_MODE_BY_PERMISSIONr$rr&r')rrr)s   r�	proactiver2Psx�����t�0�4�4�U�J�G�G�L���4�����0�&�,�G�G�G�G�������u�u������4s�$A�
A�A�featurec�B�t�|t��S)z�
    Get hook for specific feature. If no hook is implemented for this feature,
    return stub function
    :param feature: feature name
    :return: callable hook
    )�HOOKSr$r)r3s r�get_hookr6fs���9�9�W�j�)�)�)r)�__doc__r�logging�typingrrr� defence360agent.contracts.configr�,defence360agent.feature_management.constantsrrr	r
�	getLoggerrrrr�str�boolr,r1r2r5r6rrr�<module>r?s�������������*�*�*�*�*�*�*�*�*�*�7�7�7�7�7�7�������������
��	�8�	$�	$����������H�S�M��#��$�������@	�$���!����H�S�M��#��$������� �	�
�y�	��*�c�*�h���
�s�';�T�'A�B�*�*�*�*�*�*rdefence360agent/feature_management/__pycache__/hooks.cpython-311.pyc0000644000000000000000000000763400000000000022444 0ustar  �

4|�Ś=W��&�dZddlZddlZddlmZmZmZddlmZddl	m
Z
mZmZm
Z
eje��Zd�Zd�Zedeed	ed
efd���ZedediZedeed	ed
efd
���Ze
ee
eiZded
eeeegeffd�ZdS)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)�Any�Callable�Optional)�
ConfigFile)�AV�FULL�LOG�	PROACTIVEc��dS)NT�)�_s �]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/hooks.py�
_hook_stubrs���4�c�F��tj����fd���}|S)Nc�h���||��}|p!t�d�j||��|S)Nz#Hook '%s(%s)' failed for user '%s'.)�logger�warning�__name__)�user�value�result�callbacks   �r�wrapz_result_warn.<locals>.wrapsC�����$��&�&���	
�&�.�.�1�����	
�
���
r)�	functools�wraps)rrs` r�_result_warnrs8����_�X�����������Krrr�returnc�p�|sdSt��}|�dd��}t|��}|�dd��}|tkrd}n0|r.|�d��r|r|�d��rd}	|�dd|��n#t
$rYdSwxYwdS)z#Called when 'av' feature is changedT�MALWARE_SCANNING�default_actionN�cleanup�notifyF)r�getr�
startswith�set�	Exception)rr�config�config_value�user_config�user_config_values      r�	antivirusr,)s�����t�
�\�\�F��:�:�0�2B�C�C�L��T�"�"�K�#���(:�<L�M�M����}�}� ����%��(�(��3�3�%�
�%�
�#�#�I�.�.�	%�%������� 0�2C�	
�	
�	
�	
�������u�u������4s�
B%�%
B3�2B3r	c��|sdSt�|d��}	t|���dd|��n#t$rYdSwxYwdS)z*Called when 'proactive' feature is changedT�DISABLED�PROACTIVE_DEFENCE�modeF)�_PROACTIVE_MODE_BY_PERMISSIONr$rr&r')rrr)s   r�	proactiver2Psx�����t�0�4�4�U�J�G�G�L���4�����0�&�,�G�G�G�G�������u�u������4s�$A�
A�A�featurec�B�t�|t��S)z�
    Get hook for specific feature. If no hook is implemented for this feature,
    return stub function
    :param feature: feature name
    :return: callable hook
    )�HOOKSr$r)r3s r�get_hookr6fs���9�9�W�j�)�)�)r)�__doc__r�logging�typingrrr� defence360agent.contracts.configr�,defence360agent.feature_management.constantsrrr	r
�	getLoggerrrrr�str�boolr,r1r2r5r6rrr�<module>r?s�������������*�*�*�*�*�*�*�*�*�*�7�7�7�7�7�7�������������
��	�8�	$�	$����������H�S�M��#��$�������@	�$���!����H�S�M��#��$������� �	�
�y�	��*�c�*�h���
�s�';�T�'A�B�*�*�*�*�*�*rdefence360agent/feature_management/__pycache__/lookup.cpython-311.opt-1.pyc0000644000000000000000000001066300000000000023565 0ustar  �

/��Zrd2����ddlZddlmZmZmZddlmZddlmZddl	m
Z
ddlmZdd	l
mZe��Zd
edeeded
efdeded
eff
d�Z	dd
edeedeegeffd�ZdS)�N)�Any�Callable�List�)�MyImunifyConfig)�is_plesk_service_plan_enabled)�wraps�)�
check_feature)�UserArgumentNotFound�name�permissions�func.�user_key�returnc�R�������	�tj���}�|jvrtd�����|j��	�	j�	ju�������	fd��t
�����fd���}t
�����fd���}tj���r|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 %sc�����r�|vrtd�����tjr	dSt��r	dS|���j��}t
��|��dS)Nz3Argument '%s' for '%s' must be specified explicitly)rr�ENABLEDr�get�defaultr)�kwargs�userrr
rr�user_key_required�
user_params  �������^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/lookup.py�checkerz_wrapper.<locals>.checker!s�����	���!7�!7�&�E�����
��"�	�>��F�(�*�*�	�G��F��z�z�(�J�$6�7�7���d�K��.�.�.�.�.�c�$���di|���|i|��S�N�r ��argsrrrs  ��r�wrapperz_wrapper.<locals>.wrapper4s.�������&�����t�T�$�V�$�$�$rc��4�K��di|���|i|���d{V��Srr r!s  ��r�
async_wrapperz_wrapper.<locals>.async_wrapper9sD���������&�����T�4�*�6�*�*�*�*�*�*�*�*�*r)�inspect�	signature�
parametersrr�emptyr	�iscoroutinefunction)
r
rrrr'r#r%rrrs
````   @@@r�_wrapperr+
s����������!�$�'�'�I��y�+�+�+�"�,�h��
�
�	
��%�h�/�J�"�*�j�.>�>��/�/�/�/�/�/�/�/�/�/�&�4�[�[�%�%�%�%�%��[�%��4�[�[�+�+�+�+�+��[�+��"�4�(�(�����Nrrc��������fd�}|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
    c���tj|��rvt|di�����D]Q\}}|�d��s7tj|��r#t
��|���}t|||���Rn&tj|��rt
��|���}t�	���|S)N�__dict__�_)
r&�isclass�getattr�items�
startswith�
isfunctionr+�setattr�features�add)�obj�m_name�m_objr#r
rrs    ���r�	decoratorzfeature.<locals>.decoratorPs�����?�3���	=�!(��j�"�!=�!=�!C�!C�!E�!E�
2�
2�
����(�(��-�-�2�'�2D�U�2K�2K�2�&�t�[�%��J�J�G��C���1�1�1��
2��
��
$�
$�	=��4��c�8�<�<�C����T�����
rr )r
rrr;s``` r�featurer<Cs0�������������r)r)r&�typingrrr�contracts.configr�contracts.permissionsr�rpc_tools.lookupr	�checkersr�
exceptionsr�setr6�strr+r<r rr�<module>rEs3������&�&�&�&�&�&�&�&�&�&�.�.�.�.�.�.�A�A�A�A�A�A�$�$�$�$�$�$�#�#�#�#�#�#�,�,�,�,�,�,��3�5�5��3�

�3� ��I�3�-5�c�3�h�-?�3�KN�3�
�c�3�h��3�3�3�3�n17���

�� ��I��
�s�e�S�j�������rdefence360agent/feature_management/__pycache__/lookup.cpython-311.pyc0000644000000000000000000001066300000000000022626 0ustar  �

/��Zrd2����ddlZddlmZmZmZddlmZddlmZddl	m
Z
ddlmZdd	l
mZe��Zd
edeeded
efdeded
eff
d�Z	dd
edeedeegeffd�ZdS)�N)�Any�Callable�List�)�MyImunifyConfig)�is_plesk_service_plan_enabled)�wraps�)�
check_feature)�UserArgumentNotFound�name�permissions�func.�user_key�returnc�R�������	�tj���}�|jvrtd�����|j��	�	j�	ju�������	fd��t
�����fd���}t
�����fd���}tj���r|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 %sc�����r�|vrtd�����tjr	dSt��r	dS|���j��}t
��|��dS)Nz3Argument '%s' for '%s' must be specified explicitly)rr�ENABLEDr�get�defaultr)�kwargs�userrr
rr�user_key_required�
user_params  �������^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/lookup.py�checkerz_wrapper.<locals>.checker!s�����	���!7�!7�&�E�����
��"�	�>��F�(�*�*�	�G��F��z�z�(�J�$6�7�7���d�K��.�.�.�.�.�c�$���di|���|i|��S�N�r ��argsrrrs  ��r�wrapperz_wrapper.<locals>.wrapper4s.�������&�����t�T�$�V�$�$�$rc��4�K��di|���|i|���d{V��Srr r!s  ��r�
async_wrapperz_wrapper.<locals>.async_wrapper9sD���������&�����T�4�*�6�*�*�*�*�*�*�*�*�*r)�inspect�	signature�
parametersrr�emptyr	�iscoroutinefunction)
r
rrrr'r#r%rrrs
````   @@@r�_wrapperr+
s����������!�$�'�'�I��y�+�+�+�"�,�h��
�
�	
��%�h�/�J�"�*�j�.>�>��/�/�/�/�/�/�/�/�/�/�&�4�[�[�%�%�%�%�%��[�%��4�[�[�+�+�+�+�+��[�+��"�4�(�(�����Nrrc��������fd�}|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
    c���tj|��rvt|di�����D]Q\}}|�d��s7tj|��r#t
��|���}t|||���Rn&tj|��rt
��|���}t�	���|S)N�__dict__�_)
r&�isclass�getattr�items�
startswith�
isfunctionr+�setattr�features�add)�obj�m_name�m_objr#r
rrs    ���r�	decoratorzfeature.<locals>.decoratorPs�����?�3���	=�!(��j�"�!=�!=�!C�!C�!E�!E�
2�
2�
����(�(��-�-�2�'�2D�U�2K�2K�2�&�t�[�%��J�J�G��C���1�1�1��
2��
��
$�
$�	=��4��c�8�<�<�C����T�����
rr )r
rrr;s``` r�featurer<Cs0�������������r)r)r&�typingrrr�contracts.configr�contracts.permissionsr�rpc_tools.lookupr	�checkersr�
exceptionsr�setr6�strr+r<r rr�<module>rEs3������&�&�&�&�&�&�&�&�&�&�.�.�.�.�.�.�A�A�A�A�A�A�$�$�$�$�$�$�#�#�#�#�#�#�,�,�,�,�,�,��3�5�5��3�

�3� ��I�3�-5�c�3�h�-?�3�KN�3�
�c�3�h��3�3�3�3�n17���

�� ��I��
�s�e�S�j�������rdefence360agent/feature_management/__pycache__/model.cpython-311.opt-1.pyc0000644000000000000000000000755100000000000023356 0ustar  �

haN��_���f�ddlmZmZmZddlmZmZmZmZm	Z	m
Z
ddlmZm
Z
Gd�de��ZdS)�)�	CharField�Check�	TextField)�AV�	AV_REPORT�FULL�LOG�NA�	PROACTIVE)�Model�instancec
�^�eZdZdZdZGd�d��Zed���Zede	d�
eee
����ge
�	��Zede	d
�
eee
����ge�	��Zededdfd
���Zed���Zd�Zdedefd�Zdedefd�Zd�ZdS)�FeatureManagementPermszrPermissions state for Feature Management.

    Each record/instance is a set of permissions of a single user.
    �c� �eZdZejZdZdS)�FeatureManagementPerms.Meta�feature_management_permissionsN)�__name__�
__module__�__qualname__r
�db�database�db_table���]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/model.py�Metars�������;��3���rrT)�uniqueFzproactive in ('{}','{}','{}'))�null�constraints�defaultzav in ('{}','{}','{}')�user�returnc��|���}|�|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_defaultr�avr�	proactive�
get_or_create)�clsr"r!r%�perm�_s      r�get_permzFeatureManagementPerms.get_perm0sX���/�/�#�#���<��N�
��
��w�(�
��
�#�#���#�A�A���a��rc�8�|�|j���S)zGet default permissions)r")�get�DEFAULT)r*s rr&z"FeatureManagementPerms.get_defaultEs���w�w�C�K�w�(�(�(rc�"�|j|jkS)z&Check if current permission is default)r"r0��selfs r�
is_defaultz!FeatureManagementPerms.is_defaultJs���y�D�L�(�(r�keyc�"�t||��S)zGet permission by feature name)�getattr)r3r5s  r�get_featurez"FeatureManagementPerms.get_featureNs���t�S�!�!�!r�valuec�P�t|||��|���dS)zSet permissionN)�setattr�save)r3r5r9s   r�set_featurez"FeatureManagementPerms.set_featureRs%����c�5�!�!�!��	�	�����rc�6�t|jt|jiS)N)rr'rr(r2s r�as_dictzFeatureManagementPerms.as_dictWs������t�~�
�	
rN)rrr�__doc__r0rrr"rr�formatr
r	rr(rr'�classmethod�strr-r&r4r8r=r?rrrrrs���������
�G�4�4�4�4�4�4�4�4��9�D�!�!�!�D��	�
��E�1�8�8��S�$�G�G�H�H�
�����I�
��
��E�*�1�1�"�i��F�F�G�G�
��
�
�
�B���C��$<�����[��(�)�)��[�)�)�)�)�"�s�"�s�"�"�"�"��s��3�����

�
�
�
�
rrN)�peeweerrr�,defence360agent.feature_management.constantsrrrr	r
r�defence360agent.modelrr
rrrr�<module>rGs���.�.�.�.�.�.�.�.�.�.�����������������2�1�1�1�1�1�1�1�M
�M
�M
�M
�M
�U�M
�M
�M
�M
�M
rdefence360agent/feature_management/__pycache__/model.cpython-311.pyc0000644000000000000000000000755100000000000022417 0ustar  �

haN��_���f�ddlmZmZmZddlmZmZmZmZm	Z	m
Z
ddlmZm
Z
Gd�de��ZdS)�)�	CharField�Check�	TextField)�AV�	AV_REPORT�FULL�LOG�NA�	PROACTIVE)�Model�instancec
�^�eZdZdZdZGd�d��Zed���Zede	d�
eee
����ge
�	��Zede	d
�
eee
����ge�	��Zededdfd
���Zed���Zd�Zdedefd�Zdedefd�Zd�ZdS)�FeatureManagementPermszrPermissions state for Feature Management.

    Each record/instance is a set of permissions of a single user.
    �c� �eZdZejZdZdS)�FeatureManagementPerms.Meta�feature_management_permissionsN)�__name__�
__module__�__qualname__r
�db�database�db_table���]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/model.py�Metars�������;��3���rrT)�uniqueFzproactive in ('{}','{}','{}'))�null�constraints�defaultzav in ('{}','{}','{}')�user�returnc��|���}|�|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_defaultr�avr�	proactive�
get_or_create)�clsr"r!r%�perm�_s      r�get_permzFeatureManagementPerms.get_perm0sX���/�/�#�#���<��N�
��
��w�(�
��
�#�#���#�A�A���a��rc�8�|�|j���S)zGet default permissions)r")�get�DEFAULT)r*s rr&z"FeatureManagementPerms.get_defaultEs���w�w�C�K�w�(�(�(rc�"�|j|jkS)z&Check if current permission is default)r"r0��selfs r�
is_defaultz!FeatureManagementPerms.is_defaultJs���y�D�L�(�(r�keyc�"�t||��S)zGet permission by feature name)�getattr)r3r5s  r�get_featurez"FeatureManagementPerms.get_featureNs���t�S�!�!�!r�valuec�P�t|||��|���dS)zSet permissionN)�setattr�save)r3r5r9s   r�set_featurez"FeatureManagementPerms.set_featureRs%����c�5�!�!�!��	�	�����rc�6�t|jt|jiS)N)rr'rr(r2s r�as_dictzFeatureManagementPerms.as_dictWs������t�~�
�	
rN)rrr�__doc__r0rrr"rr�formatr
r	rr(rr'�classmethod�strr-r&r4r8r=r?rrrrrs���������
�G�4�4�4�4�4�4�4�4��9�D�!�!�!�D��	�
��E�1�8�8��S�$�G�G�H�H�
�����I�
��
��E�*�1�1�"�i��F�F�G�G�
��
�
�
�B���C��$<�����[��(�)�)��[�)�)�)�)�"�s�"�s�"�"�"�"��s��3�����

�
�
�
�
rrN)�peeweerrr�,defence360agent.feature_management.constantsrrrr	r
r�defence360agent.modelrr
rrrr�<module>rGs���.�.�.�.�.�.�.�.�.�.�����������������2�1�1�1�1�1�1�1�M
�M
�M
�M
�M
�U�M
�M
�M
�M
�M
rdefence360agent/feature_management/__pycache__/utils.cpython-311.opt-1.pyc0000644000000000000000000001520400000000000023410 0ustar  �

�z�Vc�	��ddlZddlZddlmZddlmZmZddlmZddl	m
Z
ddlmZddl
mZddlmZmZmZd	d
lmZeje��Zdeded
edefd�Zdedeed
edeefd�Zded
efd�Zdeedefd�Zd�ZdS)�N)�chain)�List�Any)�Core)�hooks)�FeatureManagementPerms)�instance)�execute_iterable_expression�is_safe_subdir_name�rmtree�)�features�user�feature�value�returnc��K�tj���5}tj|��}|�||��t
j|��}|||��}|rt�	d|||��n1t�
d|||��|���|cddd��S#1swxYwYdS)z�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.
    z!Applied setting %s=%s for user %sz)Failed to apply setting %s=%s for user %sN)r	�db�atomicr�get_perm�set_featurer�get_hook�logger�info�error�rollback)rrr�trx�perm�hook�oks       �]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/utils.pyrrs%����
��	�	�	�	���%�.�t�4�4������%�(�(�(��~�g�&�&��
�T�$��
�
��
�	��K�K�3�W�e�T�
�
�
�
�
�L�L�;����	
�
�
�
�L�L�N�N�N��#��������������������s�BC�C�C�users�existing_usersc��K�ggd�}|D]p}||vrt�d|���"t|||���d{V��r|d�|���U|d�|���q|S)N)�	succeeded�failedzNo such user: %sr%r&)r�warningr�append)rr"rr#�resultrs      r!�update_usersr*.s�������
,�
,�F��*�*���~�%�%��N�N�-�t�4�4�4���T�7�E�2�2�2�2�2�2�2�2�	*��;��&�&�t�,�,�,�,��8��#�#�D�)�)�)�)��M�c��K�tj��}|�||��tj|��}|d|��S�N)r�get_defaultrrr)rrrrs    r!�update_defaultr/@sK����!�-�/�/�D����W�e�$�$�$��>�'�"�"�D��4��e���r+c��K�t|��}tjtj��}tt	|������}||z
}|�tj��|r�t�	d|��d�}t|t|����|D]�}t|��s�tj�t j|��}	t%|���M#t&$rY�Yt($r&}t�d||��Yd}~��d}~wwxYw||z
}|rt�	d|��|D]U}tj|��}	t.D]7}
|	�|
��}t3j|
��}|||���8�Vt7|��pt7|��S)z1Synchronize existing permissions with panel userszRemove permissions of users %sc��tj���tj�|����Sr-)r�delete�wherer�in_)�perms_to_removes r!�
expressionzsync_users.<locals>.expressionTs7��)�0�2�2�8�8�&�+�/�/��@�@���
r+z'Failed to remove user_config dir %s: %sNzAdd permissions to users %s)�setr�selectrr�tuples�remove�DEFAULTrrr
�listr�os�path�joinr�USER_CONFDIRr�FileNotFoundError�OSErrorr'rr�get_featurerr�bool)
r"�panel_users�
perm_usersr5r6r�target�e�perms_to_addrrr�callbacks
             r!�
sync_usersrKGs�����e�*�*�K�'�.�/E�/J�K�K�J��U�J�-�-�/�/�0�1�1�J� �;�.�O����1�9�:�:�:������4�o�F�F�F�	�	�	�
	$�J��_�0E�0E�F�F�F�#�	�	�D�&�t�,�,�
���W�\�\�$�"3�T�:�:�F�
��v������$�
�
�
����
�
�
����=�v�q�������������
����
��+�L��A����1�<�@�@�@��"�"��%�.�t�4�4���	"�	"�G��$�$�W�-�-�E��~�g�.�.�H��H�T�5�!�!�!�!�	"�����6��o�!6�!6�6s�8D�
E�	E�D>�>Ec��JK�tttjtj���tjtjk��������}|D].}|j��D]\}}t|||���d{V����/dS)zlSets feature values for all existing users in feature management
    database to given values in `features`.N)
r<rrr8rr3r;r9�itemsr)rr"rrrs     r!�reset_featuresrNws�����
�
�
#�
*�+A�+F�
G�
G�
�U�&�+�/E�/M�M����V�X�X�	
�
�
�E��4�4��,�h�n�.�.�	4�	4�N�G�U��d�G�U�3�3�3�3�3�3�3�3�3�3�	4�4�4r+)�loggingr=�	itertoolsr�typingrr� defence360agent.contracts.configr�"defence360agent.feature_managementr�(defence360agent.feature_management.modelr�defence360agent.modelr	�defence360agent.utilsr
rr�lookupr�	getLogger�__name__r�strrDrr*r/rKrN�r+r!�<module>r\s�������	�	�	�	���������������1�1�1�1�1�1�4�4�4�4�4�4�K�K�K�K�K�K�*�*�*�*�*�*�����������
������	��	�8�	$�	$���C��#��c��d�����4�
���c���+.��@D�S�	�����$�#��c�����-7�D��I�-7�$�-7�-7�-7�-7�`4�4�4�4�4r+defence360agent/feature_management/__pycache__/utils.cpython-311.pyc0000644000000000000000000001520400000000000022451 0ustar  �

�z�Vc�	��ddlZddlZddlmZddlmZmZddlmZddl	m
Z
ddlmZddl
mZddlmZmZmZd	d
lmZeje��Zdeded
edefd�Zdedeed
edeefd�Zded
efd�Zdeedefd�Zd�ZdS)�N)�chain)�List�Any)�Core)�hooks)�FeatureManagementPerms)�instance)�execute_iterable_expression�is_safe_subdir_name�rmtree�)�features�user�feature�value�returnc��K�tj���5}tj|��}|�||��t
j|��}|||��}|rt�	d|||��n1t�
d|||��|���|cddd��S#1swxYwYdS)z�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.
    z!Applied setting %s=%s for user %sz)Failed to apply setting %s=%s for user %sN)r	�db�atomicr�get_perm�set_featurer�get_hook�logger�info�error�rollback)rrr�trx�perm�hook�oks       �]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/utils.pyrrs%����
��	�	�	�	���%�.�t�4�4������%�(�(�(��~�g�&�&��
�T�$��
�
��
�	��K�K�3�W�e�T�
�
�
�
�
�L�L�;����	
�
�
�
�L�L�N�N�N��#��������������������s�BC�C�C�users�existing_usersc��K�ggd�}|D]p}||vrt�d|���"t|||���d{V��r|d�|���U|d�|���q|S)N)�	succeeded�failedzNo such user: %sr%r&)r�warningr�append)rr"rr#�resultrs      r!�update_usersr*.s�������
,�
,�F��*�*���~�%�%��N�N�-�t�4�4�4���T�7�E�2�2�2�2�2�2�2�2�	*��;��&�&�t�,�,�,�,��8��#�#�D�)�)�)�)��M�c��K�tj��}|�||��tj|��}|d|��S�N)r�get_defaultrrr)rrrrs    r!�update_defaultr/@sK����!�-�/�/�D����W�e�$�$�$��>�'�"�"�D��4��e���r+c��K�t|��}tjtj��}tt	|������}||z
}|�tj��|r�t�	d|��d�}t|t|����|D]�}t|��s�tj�t j|��}	t%|���M#t&$rY�Yt($r&}t�d||��Yd}~��d}~wwxYw||z
}|rt�	d|��|D]U}tj|��}	t.D]7}
|	�|
��}t3j|
��}|||���8�Vt7|��pt7|��S)z1Synchronize existing permissions with panel userszRemove permissions of users %sc��tj���tj�|����Sr-)r�delete�wherer�in_)�perms_to_removes r!�
expressionzsync_users.<locals>.expressionTs7��)�0�2�2�8�8�&�+�/�/��@�@���
r+z'Failed to remove user_config dir %s: %sNzAdd permissions to users %s)�setr�selectrr�tuples�remove�DEFAULTrrr
�listr�os�path�joinr�USER_CONFDIRr�FileNotFoundError�OSErrorr'rr�get_featurerr�bool)
r"�panel_users�
perm_usersr5r6r�target�e�perms_to_addrrr�callbacks
             r!�
sync_usersrKGs�����e�*�*�K�'�.�/E�/J�K�K�J��U�J�-�-�/�/�0�1�1�J� �;�.�O����1�9�:�:�:������4�o�F�F�F�	�	�	�
	$�J��_�0E�0E�F�F�F�#�	�	�D�&�t�,�,�
���W�\�\�$�"3�T�:�:�F�
��v������$�
�
�
����
�
�
����=�v�q�������������
����
��+�L��A����1�<�@�@�@��"�"��%�.�t�4�4���	"�	"�G��$�$�W�-�-�E��~�g�.�.�H��H�T�5�!�!�!�!�	"�����6��o�!6�!6�6s�8D�
E�	E�D>�>Ec��JK�tttjtj���tjtjk��������}|D].}|j��D]\}}t|||���d{V����/dS)zlSets feature values for all existing users in feature management
    database to given values in `features`.N)
r<rrr8rr3r;r9�itemsr)rr"rrrs     r!�reset_featuresrNws�����
�
�
#�
*�+A�+F�
G�
G�
�U�&�+�/E�/M�M����V�X�X�	
�
�
�E��4�4��,�h�n�.�.�	4�	4�N�G�U��d�G�U�3�3�3�3�3�3�3�3�3�3�	4�4�4r+)�loggingr=�	itertoolsr�typingrr� defence360agent.contracts.configr�"defence360agent.feature_managementr�(defence360agent.feature_management.modelr�defence360agent.modelr	�defence360agent.utilsr
rr�lookupr�	getLogger�__name__r�strrDrr*r/rKrN�r+r!�<module>r\s�������	�	�	�	���������������1�1�1�1�1�1�4�4�4�4�4�4�K�K�K�K�K�K�*�*�*�*�*�*�����������
������	��	�8�	$�	$���C��#��c��d�����4�
���c���+.��@D�S�	�����$�#��c�����-7�D��I�-7�$�-7�-7�-7�-7�`4�4�4�4�4r+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  �

s�����s���dS)N�r��h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/plugins/__init__.py�<module>rs���rdefence360agent/feature_management/plugins/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000032500000000000024527 0ustar  �

s�����s���dS)N�r��h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/plugins/__init__.py�<module>rs���rdefence360agent/feature_management/plugins/__pycache__/native.cpython-311.opt-1.pyc0000644000000000000000000001474600000000000025231 0ustar  �

����`���ddlmZddlmZddlmZmZmZmZm	Z	ddl
mZddlm
Z
ddlmZddlmZddlmZee��ZGd	�d
e��ZdS)�)�	getLogger)�Dict)�AV�EXTENSION_DEFAULTS�FEATURE_EXT_VARIABLES�NATIVE_EXTENSION_NAME�	PROACTIVE��$is_native_feature_management_enabled)�FeatureManagementPerms��set_feature)�SettingsChangeBase)�packagesc	���eZdZeegZ�fd�Z�fd�Zd�Ze	de
dedee
e
ffd���Z
edee
e
ffd���Zd	�Zd
�Zd�Z�xZS)�%NativeFeatureManagementSettingsChangec��x�K�t���|���d{V��|�d��p|�d��}|rct�d|��tj���t
j|k���	��dSdS)N�user�usernamezResetting FM settings for %s)
�super�_process_account_removed�get�logger�infor�delete�wherer�execute)�self�messager�	__class__s   ��f/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/plugins/native.pyrz>NativeFeatureManagementSettingsChange._process_account_removeds�������g�g�.�.�w�7�7�7�7�7�7�7�7�7��{�{�6�"�"�=�g�k�k�*�&=�&=���	��K�K�6��=�=�=�"�)�+�+�1�1�&�+�t�3�
�
��g�i�i�i�i�i�		�	�c��$�K�t���|���d{V��d|jvr\|jd}tj|j����tj|k�����dSdS)N�old_username)r)	r�_process_modify�datar�updaterrrr)rrr$r s   �r!r%z5NativeFeatureManagementSettingsChange._process_modify$s�������g�g�%�%�g�.�.�.�.�.�.�.�.�.��W�\�)�)�"�<��7�L�"�)�w�/?�@�@�@�F�F�&�+�|�;�
�
��g�i�i�i�i�i�	*�)r"c��K�i}|jD]3}	|jt|}n#t$rd}YnwxYw|||<�4|S�N)�
FEATURES_LISTr&r�KeyError)rr�settings�feature�values     r!�_get_settings_from_messagez@NativeFeatureManagementSettingsChange._get_settings_from_message,sl�������)�	&�	&�G�
���%:�7�%C�D�����
�
�
�����
���� %�H�W����s�(�7�7�package_name�add_to_package�returnc��lK�t�d|��	tj|���d{V��}nD#tj$r2t�d|��|���cYSwxYwi}	tj��D]\}}||||<�|S#t$rCt�d|��|r#tj
t|fit���d{V��Yn/tj$rt�d|��YnwxYw|���S)NzGetting package settings %szPackage %s doesn't existz,No extension's fields in package settings %s)
rrr�get_package_info�PackageNotExistError�warning�_default_settingsr�itemsr+�
add_extensionrr)�clsr0r1�pkg_info�package_settingsr-�pe_vars       r!�_get_package_settingsz;NativeFeatureManagementSettingsChange._get_package_settings6s�����	���1�<�@�@�@�	+�%�6�|�D�D�D�D�D�D�D�D�H�H���,�	+�	+�	+��N�N�5�|�D�D�D��(�(�*�*�*�*�*�	+������
	E�#8�#>�#@�#@�
=�
=����,4�V�,<� ��)�)�#�#���	�	�	��K�K�>��
�
�
��
��,�)�8���7I�������������,�	E�	E�	E��N�N�5�|�D�D�D�D�D�	E�����$�$�&�&�&s(�:�>A;�:A;�%B'�'A
D�3)D�Dc�<�d�tj��D��S)Nc�0�i|]\}}|t|��S�)r)�.0r-r=s   r!�
<dictcomp>zKNativeFeatureManagementSettingsChange._default_settings.<locals>.<dictcomp>Ts4��
�
�
����
�'��/�
�
�
r")rr8rAr"r!r7z7NativeFeatureManagementSettingsChange._default_settingsRs,��
�
�#8�#>�#@�#@�
�
�
�	
r"c��8K�t|||���d{V��dSr)r
)rrr-r.s    r!�on_settings_changez8NativeFeatureManagementSettingsChange.on_settings_changeYs0�����$���/�/�/�/�/�/�/�/�/�/�/r"c��dS)NTrA)rrs  r!�_message_is_relatablez;NativeFeatureManagementSettingsChange._message_is_relatable\s���tr"c��.K�t���d{V��Sr)r
)rs r!�
is_enabledz0NativeFeatureManagementSettingsChange.is_enabled_s$����9�;�;�;�;�;�;�;�;�;r")�__name__�
__module__�__qualname__r	rr*rr%r/�classmethod�str�boolrr>�staticmethodr7rErGrI�
__classcell__)r s@r!rrs���������O�M���������������'��'�04�'�	
�c�3�h��'�'�'��[�'�6�
�t�C��H�~�
�
�
��\�
�0�0�0����<�<�<�<�<�<�<r"rN)�loggingr�typingr�,defence360agent.feature_management.constantsrrrrr	�*defence360agent.feature_management.controlr�(defence360agent.feature_management.modelr�(defence360agent.feature_management.utilsr�7defence360agent.plugins.event_monitor_message_processorr�$defence360agent.subsys.panels.cpanelrrJrrrAr"r!�<module>rZs4����������������������������������L�K�K�K�K�K�@�@�@�@�@�@�������:�9�9�9�9�9�	��8�	�	��H<�H<�H<�H<�H<�,>�H<�H<�H<�H<�H<r"defence360agent/feature_management/plugins/__pycache__/native.cpython-311.pyc0000644000000000000000000001474600000000000024272 0ustar  �

����`���ddlmZddlmZddlmZmZmZmZm	Z	ddl
mZddlm
Z
ddlmZddlmZddlmZee��ZGd	�d
e��ZdS)�)�	getLogger)�Dict)�AV�EXTENSION_DEFAULTS�FEATURE_EXT_VARIABLES�NATIVE_EXTENSION_NAME�	PROACTIVE��$is_native_feature_management_enabled)�FeatureManagementPerms��set_feature)�SettingsChangeBase)�packagesc	���eZdZeegZ�fd�Z�fd�Zd�Ze	de
dedee
e
ffd���Z
edee
e
ffd���Zd	�Zd
�Zd�Z�xZS)�%NativeFeatureManagementSettingsChangec��x�K�t���|���d{V��|�d��p|�d��}|rct�d|��tj���t
j|k���	��dSdS)N�user�usernamezResetting FM settings for %s)
�super�_process_account_removed�get�logger�infor�delete�wherer�execute)�self�messager�	__class__s   ��f/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/plugins/native.pyrz>NativeFeatureManagementSettingsChange._process_account_removeds�������g�g�.�.�w�7�7�7�7�7�7�7�7�7��{�{�6�"�"�=�g�k�k�*�&=�&=���	��K�K�6��=�=�=�"�)�+�+�1�1�&�+�t�3�
�
��g�i�i�i�i�i�		�	�c��$�K�t���|���d{V��d|jvr\|jd}tj|j����tj|k�����dSdS)N�old_username)r)	r�_process_modify�datar�updaterrrr)rrr$r s   �r!r%z5NativeFeatureManagementSettingsChange._process_modify$s�������g�g�%�%�g�.�.�.�.�.�.�.�.�.��W�\�)�)�"�<��7�L�"�)�w�/?�@�@�@�F�F�&�+�|�;�
�
��g�i�i�i�i�i�	*�)r"c��K�i}|jD]3}	|jt|}n#t$rd}YnwxYw|||<�4|S�N)�
FEATURES_LISTr&r�KeyError)rr�settings�feature�values     r!�_get_settings_from_messagez@NativeFeatureManagementSettingsChange._get_settings_from_message,sl�������)�	&�	&�G�
���%:�7�%C�D�����
�
�
�����
���� %�H�W����s�(�7�7�package_name�add_to_package�returnc��lK�t�d|��	tj|���d{V��}nD#tj$r2t�d|��|���cYSwxYwi}	tj��D]\}}||||<�|S#t$rCt�d|��|r#tj
t|fit���d{V��Yn/tj$rt�d|��YnwxYw|���S)NzGetting package settings %szPackage %s doesn't existz,No extension's fields in package settings %s)
rrr�get_package_info�PackageNotExistError�warning�_default_settingsr�itemsr+�
add_extensionrr)�clsr0r1�pkg_info�package_settingsr-�pe_vars       r!�_get_package_settingsz;NativeFeatureManagementSettingsChange._get_package_settings6s�����	���1�<�@�@�@�	+�%�6�|�D�D�D�D�D�D�D�D�H�H���,�	+�	+�	+��N�N�5�|�D�D�D��(�(�*�*�*�*�*�	+������
	E�#8�#>�#@�#@�
=�
=����,4�V�,<� ��)�)�#�#���	�	�	��K�K�>��
�
�
��
��,�)�8���7I�������������,�	E�	E�	E��N�N�5�|�D�D�D�D�D�	E�����$�$�&�&�&s(�:�>A;�:A;�%B'�'A
D�3)D�Dc�<�d�tj��D��S)Nc�0�i|]\}}|t|��S�)r)�.0r-r=s   r!�
<dictcomp>zKNativeFeatureManagementSettingsChange._default_settings.<locals>.<dictcomp>Ts4��
�
�
����
�'��/�
�
�
r")rr8rAr"r!r7z7NativeFeatureManagementSettingsChange._default_settingsRs,��
�
�#8�#>�#@�#@�
�
�
�	
r"c��8K�t|||���d{V��dSr)r
)rrr-r.s    r!�on_settings_changez8NativeFeatureManagementSettingsChange.on_settings_changeYs0�����$���/�/�/�/�/�/�/�/�/�/�/r"c��dS)NTrA)rrs  r!�_message_is_relatablez;NativeFeatureManagementSettingsChange._message_is_relatable\s���tr"c��.K�t���d{V��Sr)r
)rs r!�
is_enabledz0NativeFeatureManagementSettingsChange.is_enabled_s$����9�;�;�;�;�;�;�;�;�;r")�__name__�
__module__�__qualname__r	rr*rr%r/�classmethod�str�boolrr>�staticmethodr7rErGrI�
__classcell__)r s@r!rrs���������O�M���������������'��'�04�'�	
�c�3�h��'�'�'��[�'�6�
�t�C��H�~�
�
�
��\�
�0�0�0����<�<�<�<�<�<�<r"rN)�loggingr�typingr�,defence360agent.feature_management.constantsrrrrr	�*defence360agent.feature_management.controlr�(defence360agent.feature_management.modelr�(defence360agent.feature_management.utilsr�7defence360agent.plugins.event_monitor_message_processorr�$defence360agent.subsys.panels.cpanelrrJrrrAr"r!�<module>rZs4����������������������������������L�K�K�K�K�K�@�@�@�@�@�@�������:�9�9�9�9�9�	��8�	�	��H<�H<�H<�H<�H<�,>�H<�H<�H<�H<�H<r"defence360agent/feature_management/plugins/__pycache__/proactive_log_migration.cpython-311.opt-1.pyc0000644000000000000000000001063700000000000030644 0ustar  �

3�J���3���dZddlZddlmZddlmZddlmZmZddl	m
Z
mZmZddl
mZddlmZdd	lmZmZee��ZeGd
�de����ZdS)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)�MessageSink�thisguy)�LOG�NA�	PROACTIVE)�FeatureManagementPerms)�set_feature)�Scope�create_task_and_log_exceptionsc�N�eZdZejZdejfd�Zd�Z	e
d���ZdS)�ProactiveLogMigration�loopc��>K�t||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'���� >��$�-� 
� 
�����c���K�t|dd��}|�|���rdS|���	|�d{V��dS#tj$rYdSwxYw)Nr)�getattr�done�cancel�asyncio�CancelledError)r�tasks  r�shutdownzProactiveLogMigration.shutdown,sz�����t�.��5�5���<�4�9�9�;�;�<��F����
�
�
�	��J�J�J�J�J�J�J�J�J���%�	�	�	��D�D�	���s�A�A�Ac��vK�t���dd��}|dkrdSd�tj���tjtktjtjkz��D��}|sdSt�
dt|����d}|D]T}	t|tt���d{V��r|dz
}�*#t$rt�d|��Y�QwxYwt�
d	|t|����dS)
N�FEATURE_MANAGEMENT�proactive_disable_target�logc��g|]	}|j��
S�)�user)�.0�rows  r�
<listcomp>z2ProactiveLogMigration._migrate.<locals>.<listcomp>>s*��	
�	
�	
��
�H�	
�	
�	
rz2Promoting %d proactive=na users to log (DEF-42523)r�z-Failed to promote proactive=na user %s to logz%Promoted %d/%d users to proactive=log)r�getr
�select�where�	proactiverr&�DEFAULT�logger�info�lenrr	r�	Exception�	exception)�target�users�promotedr&s    rrzProactiveLogMigration._migrate6s|�������!�!� �"<�
�
���U�?�?��F�	
�	
�-�4�6�6�<�<�'�1�R�7�*�/�-�5�6����	
�	
�	
���	��F����@���J�J�	
�	
�	
����	�	�D�
�$�T�9�c�:�:�:�:�:�:�:�:�"���M�H����
�
�
�� � �C�T������
����	���3����J�J�	
�	
�	
�	
�	
s�>&C%�%%D
�D
N)�__name__�
__module__�__qualname__r�IM360�SCOPEr�AbstractEventLooprr�staticmethodrr%rrrrse������

�K�E�	
�g�&?�	
�	
�	
�	
�����%
�%
��\�%
�%
�%
rr)�__doc__r�loggingr� defence360agent.contracts.configr�!defence360agent.contracts.pluginsrr�,defence360agent.feature_management.constantsrrr	�(defence360agent.feature_management.modelr
�(defence360agent.feature_management.utilsr�defence360agent.utilsrr
r8r0rr%rr�<module>rGs	��������������7�7�7�7�7�7�B�B�B�B�B�B�B�B�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�@�@�@�@�@�@�G�G�G�G�G�G�G�G�	��8�	�	��	�A
�A
�A
�A
�A
�K�A
�A
�	��A
�A
�A
rdefence360agent/feature_management/plugins/__pycache__/proactive_log_migration.cpython-311.pyc0000644000000000000000000001063700000000000027705 0ustar  �

3�J���3���dZddlZddlmZddlmZddlmZmZddl	m
Z
mZmZddl
mZddlmZdd	lmZmZee��ZeGd
�de����ZdS)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)�MessageSink�thisguy)�LOG�NA�	PROACTIVE)�FeatureManagementPerms)�set_feature)�Scope�create_task_and_log_exceptionsc�N�eZdZejZdejfd�Zd�Z	e
d���ZdS)�ProactiveLogMigration�loopc��>K�t||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'���� >��$�-� 
� 
�����c���K�t|dd��}|�|���rdS|���	|�d{V��dS#tj$rYdSwxYw)Nr)�getattr�done�cancel�asyncio�CancelledError)r�tasks  r�shutdownzProactiveLogMigration.shutdown,sz�����t�.��5�5���<�4�9�9�;�;�<��F����
�
�
�	��J�J�J�J�J�J�J�J�J���%�	�	�	��D�D�	���s�A�A�Ac��vK�t���dd��}|dkrdSd�tj���tjtktjtjkz��D��}|sdSt�
dt|����d}|D]T}	t|tt���d{V��r|dz
}�*#t$rt�d|��Y�QwxYwt�
d	|t|����dS)
N�FEATURE_MANAGEMENT�proactive_disable_target�logc��g|]	}|j��
S�)�user)�.0�rows  r�
<listcomp>z2ProactiveLogMigration._migrate.<locals>.<listcomp>>s*��	
�	
�	
��
�H�	
�	
�	
rz2Promoting %d proactive=na users to log (DEF-42523)r�z-Failed to promote proactive=na user %s to logz%Promoted %d/%d users to proactive=log)r�getr
�select�where�	proactiverr&�DEFAULT�logger�info�lenrr	r�	Exception�	exception)�target�users�promotedr&s    rrzProactiveLogMigration._migrate6s|�������!�!� �"<�
�
���U�?�?��F�	
�	
�-�4�6�6�<�<�'�1�R�7�*�/�-�5�6����	
�	
�	
���	��F����@���J�J�	
�	
�	
����	�	�D�
�$�T�9�c�:�:�:�:�:�:�:�:�"���M�H����
�
�
�� � �C�T������
����	���3����J�J�	
�	
�	
�	
�	
s�>&C%�%%D
�D
N)�__name__�
__module__�__qualname__r�IM360�SCOPEr�AbstractEventLooprr�staticmethodrr%rrrrse������

�K�E�	
�g�&?�	
�	
�	
�	
�����%
�%
��\�%
�%
�%
rr)�__doc__r�loggingr� defence360agent.contracts.configr�!defence360agent.contracts.pluginsrr�,defence360agent.feature_management.constantsrrr	�(defence360agent.feature_management.modelr
�(defence360agent.feature_management.utilsr�defence360agent.utilsrr
r8r0rr%rr�<module>rGs	��������������7�7�7�7�7�7�B�B�B�B�B�B�B�B�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�@�@�@�@�@�@�G�G�G�G�G�G�G�G�	��8�	�	��	�A
�A
�A
�A
�A
�K�A
�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  �

s�����s���dS)N�r��d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/rpc/__init__.py�<module>rs���rdefence360agent/feature_management/rpc/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000032100000000000023626 0ustar  �

s�����s���dS)N�r��d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/rpc/__init__.py�<module>rs���rdefence360agent/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  �

c�O��s6��"�ddlmZmZmZgd�ZdS)�)�native�show�updateN)�rrr�__all__���n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/rpc/endpoints/__init__.py�<module>rs2��"�"�"�"�"�"�"�"�"�"�
&�
&�
&���r	defence360agent/feature_management/rpc/endpoints/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000051600000000000025637 0ustar  �

c�O��s6��"�ddlmZmZmZgd�ZdS)�)�native�show�updateN)�rrr�__all__���n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/rpc/endpoints/__init__.py�<module>rs2��"�"�"�"�"�"�"�"�"�"�
&�
&�
&���r	defence360agent/feature_management/rpc/endpoints/__pycache__/native.cpython-311.opt-1.pyc0000644000000000000000000000556300000000000026334 0ustar  �

^�`���|�ddlmZddlmZddlmZmZddlmZm	Z	m
Z
mZee��Z
gZGd�de��ZdS)	�)�	getLogger)�MyImunifyConfig)�
RootEndpoints�bind�)�!disable_native_feature_management� enable_native_feature_management�$is_native_feature_management_enabled�&is_native_feature_management_supportedc���eZdZ�fd�Zeddd��d���Zeddd��d���Zeddd��d	���Z�xZS)
� FeatureManagementNativeEndpointsc�J��t���|��dS�N)�super�__init__)�self�sink�	__class__s  ��l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/rpc/endpoints/native.pyrz)FeatureManagementNativeEndpoints.__init__s!���
�����������zfeature-management�native�statusc��K�tjsCtt���d{V����}tt	���d{V����}nd}d}d||d�iS)NF�items)�	supported�enabled)r�ENABLED�boolrr
)rrrs   r� feature_management_native_statuszAFeatureManagementNativeEndpoints.feature_management_native_statuss������&�	��#I�#K�#K�K�K�K�K�K�K�L�L�I��!E�!G�!G�G�G�G�G�G�G�H�H�G�G��I��G�
�&�"���
�	
r�enablec��2K�t���d{V��dSr)r	)rs r� feature_management_native_enablezAFeatureManagementNativeEndpoints.feature_management_native_enable&s*����.�0�0�0�0�0�0�0�0�0�0�0r�disablec��>K�t���d{V��}|rddiSdS)NrzAImunify360 package extensions have been removed from all packages)r)r�disableds  r�!feature_management_native_disablezBFeatureManagementNativeEndpoints.feature_management_native_disable*sE����:�<�<�<�<�<�<�<�<���	��\��
�	�	r)	�__name__�
__module__�__qualname__rrrr"r&�
__classcell__)rs@rr
r
s�������������
�T�
��(�3�3�

�

�4�3�

�
�T�
��(�3�3�1�1�4�3�1�
�T�
��)�4�4���5�4�����rr
N)�loggingr� defence360agent.contracts.configr� defence360agent.rpc_tools.lookuprr�controlrr	r
rr'�logger�__all__r
�rr�<module>r2s���������<�<�<�<�<�<�@�@�@�@�@�@�@�@�������������
��8�	�	��
�������}�����rdefence360agent/feature_management/rpc/endpoints/__pycache__/native.cpython-311.pyc0000644000000000000000000000556300000000000025375 0ustar  �

^�`���|�ddlmZddlmZddlmZmZddlmZm	Z	m
Z
mZee��Z
gZGd�de��ZdS)	�)�	getLogger)�MyImunifyConfig)�
RootEndpoints�bind�)�!disable_native_feature_management� enable_native_feature_management�$is_native_feature_management_enabled�&is_native_feature_management_supportedc���eZdZ�fd�Zeddd��d���Zeddd��d���Zeddd��d	���Z�xZS)
� FeatureManagementNativeEndpointsc�J��t���|��dS�N)�super�__init__)�self�sink�	__class__s  ��l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/rpc/endpoints/native.pyrz)FeatureManagementNativeEndpoints.__init__s!���
�����������zfeature-management�native�statusc��K�tjsCtt���d{V����}tt	���d{V����}nd}d}d||d�iS)NF�items)�	supported�enabled)r�ENABLED�boolrr
)rrrs   r� feature_management_native_statuszAFeatureManagementNativeEndpoints.feature_management_native_statuss������&�	��#I�#K�#K�K�K�K�K�K�K�L�L�I��!E�!G�!G�G�G�G�G�G�G�H�H�G�G��I��G�
�&�"���
�	
r�enablec��2K�t���d{V��dSr)r	)rs r� feature_management_native_enablezAFeatureManagementNativeEndpoints.feature_management_native_enable&s*����.�0�0�0�0�0�0�0�0�0�0�0r�disablec��>K�t���d{V��}|rddiSdS)NrzAImunify360 package extensions have been removed from all packages)r)r�disableds  r�!feature_management_native_disablezBFeatureManagementNativeEndpoints.feature_management_native_disable*sE����:�<�<�<�<�<�<�<�<���	��\��
�	�	r)	�__name__�
__module__�__qualname__rrrr"r&�
__classcell__)rs@rr
r
s�������������
�T�
��(�3�3�

�

�4�3�

�
�T�
��(�3�3�1�1�4�3�1�
�T�
��)�4�4���5�4�����rr
N)�loggingr� defence360agent.contracts.configr� defence360agent.rpc_tools.lookuprr�controlrr	r
rr'�logger�__all__r
�rr�<module>r2s���������<�<�<�<�<�<�@�@�@�@�@�@�@�@�������������
��8�	�	��
�������}�����rdefence360agent/feature_management/rpc/endpoints/__pycache__/show.cpython-311.opt-1.pyc0000644000000000000000000001315400000000000026021 0ustar  �

[�U�a�=�����ddlmZddlmcmcmZddlmZm	Z	m
Z
ddlmZddl
mZddlmZddlmZddlmZmZmZee��ZeGd	�d
e����ZGd�de��ZdS)
�)�	getLoggerN)�AV�FULL�	PROACTIVE)�features)�FeatureManagementPerms)�builtin_feature_management_only)�apply_order_by)�CommonEndpoints�
RootEndpoints�bindc��eZdZd�Zedd��d���Zedd��d���Zedd��	d
d	���ZdS)�"FeatureManagementShowRootEndpointsc�P�ttfD]}||tk||<�|S�N)rrr)�self�item�features   �j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/rpc/endpoints/show.py�_adapt_valuez/FeatureManagementShowRootEndpoints._adapt_values.���9�}�	2�	2�G� ��M�T�1�D��M�M����feature-management�listc��2K�dtt��iS)zGet list of features�items)rr)rs r�feature_management_listz:FeatureManagementShowRootEndpoints.feature_management_lists������h���(�(r�defaultsc��~K�tj��}d|�|�����iS)zGet default feature permissionsr)r�get_defaultr�as_dict)r�perms  r�feature_management_defaultsz>FeatureManagementShowRootEndpoints.feature_management_defaults s7����&�1�3�3����*�*�4�<�<�>�>�:�:�;�;r�showNc�����	K�tj������d{V���	�r �fd��	���D���	t	j��}|rt
|t|��}�	fd�|D��}t|��}|r
||d�}|r
|d|�}��	fd�|D��}||fS)zShow permissionsNc�d��i|],\}}�|vs tt�fd�|�����)||��-S)c����|vSr�)�x�searchs �r�<lambda>zWFeatureManagementShowRootEndpoints.feature_management_show.<locals>.<dictcomp>.<lambda>0s���v��{�r)�any�map)�.0�user�domainsr)s   �r�
<dictcomp>zNFeatureManagementShowRootEndpoints.feature_management_show.<locals>.<dictcomp>-sP������!�D�'��T�>�>�S��-B�-B�-B�-B�G�)L�)L�%M�%M�>��g�!�>�>rc�&��g|]
}|j�v�|��Sr')r.)r-r!�userss  �r�
<listcomp>zNFeatureManagementShowRootEndpoints.feature_management_show.<locals>.<listcomp>6s%���:�:�:�$�t�y�E�'9�'9��'9�'9�'9rc���g|]=}|j�|j��|�����d���>S))�namer/r)r.rr )r-r!rr2s  ��rr3zNFeatureManagementShowRootEndpoints.feature_management_show.<locals>.<listcomp><sZ���
�
�
��	�	� ���+� �-�-�d�l�l�n�n�=�=�
�
�
�
�
r)�hp�HostingPanel�get_domains_per_userrr�selectr
�len)
rr)�limit�offset�order_by�q�perms�	perms_len�resultr2s
``       @r�feature_management_showz:FeatureManagementShowRootEndpoints.feature_management_show&s#�������
�o�'�'�<�<�>�>�>�>�>�>�>�>���	�����%*�[�[�]�]����E�
#�)�+�+���	D��x�)?��C�C�A�:�:�:�:�!�:�:�:����J�J�	��	#��&�'�'�N�E��	"��&�5�&�M�E�
�
�
�
�
��

�
�
���&� � r)NNNN)�__name__�
__module__�__qualname__rr
rr"rBr'rrrrs����������

�T�
��'�'�)�)�(�'�)�
�T�
�
�+�+�<�<�,�+�<�

�T�
��'�'�=A�!�!�!�(�'�!�!�!rrc�J�eZdZeedd��dd�����ZdS)�!FeatureManagementShowAnyEndpointsr�getNc��ZK�tj|��}d|���iS)zGet user feature permissionsr)r�get_permr )r.r!s  r�feature_management_getz8FeatureManagementShowAnyEndpoints.feature_management_getIs+����&�.�t�4�4��������(�(rr)rCrDrE�staticmethodr
rKr'rrrGrGHsJ�������	�T�
��&�&�)�)�)�'�&��\�)�)�)rrG)�loggingr�+defence360agent.subsys.panels.hosting_panel�subsys�panels�
hosting_panelr6�,defence360agent.feature_management.constantsrrr�)defence360agent.feature_management.lookupr�(defence360agent.feature_management.modelr�6defence360agent.feature_management.rpc.endpoints.utilsr	�$defence360agent.model.simplificationr
� defence360agent.rpc_tools.lookuprrr
rC�loggerrrGr'rr�<module>rYs_��������8�8�8�8�8�8�8�8�8�8�8�8�L�L�L�L�L�L�L�L�L�L�>�>�>�>�>�>�K�K�K�K�K�K�������@�?�?�?�?�?�����������
��8�	�	��!�0!�0!�0!�0!�0!��0!�0!�!� �0!�f)�)�)�)�)��)�)�)�)�)rdefence360agent/feature_management/rpc/endpoints/__pycache__/show.cpython-311.pyc0000644000000000000000000001315400000000000025062 0ustar  �

[�U�a�=�����ddlmZddlmcmcmZddlmZm	Z	m
Z
ddlmZddl
mZddlmZddlmZddlmZmZmZee��ZeGd	�d
e����ZGd�de��ZdS)
�)�	getLoggerN)�AV�FULL�	PROACTIVE)�features)�FeatureManagementPerms)�builtin_feature_management_only)�apply_order_by)�CommonEndpoints�
RootEndpoints�bindc��eZdZd�Zedd��d���Zedd��d���Zedd��	d
d	���ZdS)�"FeatureManagementShowRootEndpointsc�P�ttfD]}||tk||<�|S�N)rrr)�self�item�features   �j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/rpc/endpoints/show.py�_adapt_valuez/FeatureManagementShowRootEndpoints._adapt_values.���9�}�	2�	2�G� ��M�T�1�D��M�M����feature-management�listc��2K�dtt��iS)zGet list of features�items)rr)rs r�feature_management_listz:FeatureManagementShowRootEndpoints.feature_management_lists������h���(�(r�defaultsc��~K�tj��}d|�|�����iS)zGet default feature permissionsr)r�get_defaultr�as_dict)r�perms  r�feature_management_defaultsz>FeatureManagementShowRootEndpoints.feature_management_defaults s7����&�1�3�3����*�*�4�<�<�>�>�:�:�;�;r�showNc�����	K�tj������d{V���	�r �fd��	���D���	t	j��}|rt
|t|��}�	fd�|D��}t|��}|r
||d�}|r
|d|�}��	fd�|D��}||fS)zShow permissionsNc�d��i|],\}}�|vs tt�fd�|�����)||��-S)c����|vSr�)�x�searchs �r�<lambda>zWFeatureManagementShowRootEndpoints.feature_management_show.<locals>.<dictcomp>.<lambda>0s���v��{�r)�any�map)�.0�user�domainsr)s   �r�
<dictcomp>zNFeatureManagementShowRootEndpoints.feature_management_show.<locals>.<dictcomp>-sP������!�D�'��T�>�>�S��-B�-B�-B�-B�G�)L�)L�%M�%M�>��g�!�>�>rc�&��g|]
}|j�v�|��Sr')r.)r-r!�userss  �r�
<listcomp>zNFeatureManagementShowRootEndpoints.feature_management_show.<locals>.<listcomp>6s%���:�:�:�$�t�y�E�'9�'9��'9�'9�'9rc���g|]=}|j�|j��|�����d���>S))�namer/r)r.rr )r-r!rr2s  ��rr3zNFeatureManagementShowRootEndpoints.feature_management_show.<locals>.<listcomp><sZ���
�
�
��	�	� ���+� �-�-�d�l�l�n�n�=�=�
�
�
�
�
r)�hp�HostingPanel�get_domains_per_userrr�selectr
�len)
rr)�limit�offset�order_by�q�perms�	perms_len�resultr2s
``       @r�feature_management_showz:FeatureManagementShowRootEndpoints.feature_management_show&s#�������
�o�'�'�<�<�>�>�>�>�>�>�>�>���	�����%*�[�[�]�]����E�
#�)�+�+���	D��x�)?��C�C�A�:�:�:�:�!�:�:�:����J�J�	��	#��&�'�'�N�E��	"��&�5�&�M�E�
�
�
�
�
��

�
�
���&� � r)NNNN)�__name__�
__module__�__qualname__rr
rr"rBr'rrrrs����������

�T�
��'�'�)�)�(�'�)�
�T�
�
�+�+�<�<�,�+�<�

�T�
��'�'�=A�!�!�!�(�'�!�!�!rrc�J�eZdZeedd��dd�����ZdS)�!FeatureManagementShowAnyEndpointsr�getNc��ZK�tj|��}d|���iS)zGet user feature permissionsr)r�get_permr )r.r!s  r�feature_management_getz8FeatureManagementShowAnyEndpoints.feature_management_getIs+����&�.�t�4�4��������(�(rr)rCrDrE�staticmethodr
rKr'rrrGrGHsJ�������	�T�
��&�&�)�)�)�'�&��\�)�)�)rrG)�loggingr�+defence360agent.subsys.panels.hosting_panel�subsys�panels�
hosting_panelr6�,defence360agent.feature_management.constantsrrr�)defence360agent.feature_management.lookupr�(defence360agent.feature_management.modelr�6defence360agent.feature_management.rpc.endpoints.utilsr	�$defence360agent.model.simplificationr
� defence360agent.rpc_tools.lookuprrr
rC�loggerrrGr'rr�<module>rYs_��������8�8�8�8�8�8�8�8�8�8�8�8�L�L�L�L�L�L�L�L�L�L�>�>�>�>�>�>�K�K�K�K�K�K�������@�?�?�?�?�?�����������
��8�	�	��!�0!�0!�0!�0!�0!��0!�0!�!� �0!�f)�)�)�)�)��)�)�)�)�)rdefence360agent/feature_management/rpc/endpoints/__pycache__/update.cpython-311.opt-1.pyc0000644000000000000000000001026200000000000026320 0ustar  �

3	�r'�z*����ddlmZddlmZmZddlmcmcmZ	ddl
mZddlm
Z
mZmZmZmZmZddlmZddlmZmZddlmZmZee��Zd	efd
�ZeGd�de����ZdS)
�)�	getLogger)�Any�ListN)�
ConfigFile)�AV�	AV_REPORT�FULL�LOG�NA�	PROACTIVE)�builtin_feature_management_only)�update_default�update_users)�
RootEndpoints�bind�returnc�n�t���dd��}|dkrtntS)z�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.
    �FEATURE_MANAGEMENT�proactive_disable_target�log)r�getr
r)�values �l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/rpc/endpoints/update.py�_proactive_disable_targetrs1��
�L�L���1�3M�N�N�E��5�.�.�3�3�b�(�c��eZdZededeedefd���Zd�Ze	dd��ddefd	���Z
e	dd
��ddefd���ZdS)
� FeatureManagementUpdateEndpoints�feature�usersrc���K�|sdt||���d{V��rdndiSdt|||tj������d{V�����d{V��iS)N�items�succeed�failed)rr�hp�HostingPanel�	get_users)rrrs   r�_updatez(FeatureManagementUpdateEndpoints._update's������	��'���7�7�7�7�7�7�7�7������
�
�<����R�_�->�->�-H�-H�-J�-J�'J�'J�'J�'J�'J�'J���������
�	
rc�x�|rtS|tkrtS|tkrt	��St
S�N)r	rrrrr)�selfrrs   r�_adapt_valuez-FeatureManagementUpdateEndpoints._adapt_value5s:���	��K��b�=�=����i���,�.�.�.��	rzfeature-management�enableNc��hK�|�|||�|d�����d{V��S)zEnable specified featureTN�r'r+�r*rrs   r�feature_management_enablez:FeatureManagementUpdateEndpoints.feature_management_enable>sS�����\�\��U�D�-�-�g�t�<�<�
�
�
�
�
�
�
�
�	
r�disablec��hK�|�|||�|d�����d{V��S)zDisable specified featureFNr.r/s   r�feature_management_disablez;FeatureManagementUpdateEndpoints.feature_management_disableEsS�����\�\��U�D�-�-�g�u�=�=�
�
�
�
�
�
�
�
�	
rr))�__name__�
__module__�__qualname__�staticmethod�strrrr'r+rr0r3�rrrr%s��������
�s�
�4��9�
�S�
�
�
��\�
����
�T�
��)�)�
�
�s�
�
�
�*�)�
�
�T�
�	�*�*�
�
��
�
�
�+�*�
�
�
rr) �loggingr�typingrr�+defence360agent.subsys.panels.hosting_panel�subsys�panels�
hosting_panelr$� defence360agent.contracts.configr�,defence360agent.feature_management.constantsrrr	r
rr�6defence360agent.feature_management.rpc.endpoints.utilsr
�(defence360agent.feature_management.utilsrr� defence360agent.rpc_tools.lookuprrr4�loggerr8rrr9rr�<module>rFs�����������������8�8�8�8�8�8�8�8�8�8�8�8�7�7�7�7�7�7�������������������������������A�@�@�@�@�@�@�@�	��8�	�	��)�3�)�)�)�)�!�$
�$
�$
�$
�$
�}�$
�$
�!� �$
�$
�$
rdefence360agent/feature_management/rpc/endpoints/__pycache__/update.cpython-311.pyc0000644000000000000000000001026200000000000025361 0ustar  �

3	�r'�z*����ddlmZddlmZmZddlmcmcmZ	ddl
mZddlm
Z
mZmZmZmZmZddlmZddlmZmZddlmZmZee��Zd	efd
�ZeGd�de����ZdS)
�)�	getLogger)�Any�ListN)�
ConfigFile)�AV�	AV_REPORT�FULL�LOG�NA�	PROACTIVE)�builtin_feature_management_only)�update_default�update_users)�
RootEndpoints�bind�returnc�n�t���dd��}|dkrtntS)z�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.
    �FEATURE_MANAGEMENT�proactive_disable_target�log)r�getr
r)�values �l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/rpc/endpoints/update.py�_proactive_disable_targetrs1��
�L�L���1�3M�N�N�E��5�.�.�3�3�b�(�c��eZdZededeedefd���Zd�Ze	dd��ddefd	���Z
e	dd
��ddefd���ZdS)
� FeatureManagementUpdateEndpoints�feature�usersrc���K�|sdt||���d{V��rdndiSdt|||tj������d{V�����d{V��iS)N�items�succeed�failed)rr�hp�HostingPanel�	get_users)rrrs   r�_updatez(FeatureManagementUpdateEndpoints._update's������	��'���7�7�7�7�7�7�7�7������
�
�<����R�_�->�->�-H�-H�-J�-J�'J�'J�'J�'J�'J�'J���������
�	
rc�x�|rtS|tkrtS|tkrt	��St
S�N)r	rrrrr)�selfrrs   r�_adapt_valuez-FeatureManagementUpdateEndpoints._adapt_value5s:���	��K��b�=�=����i���,�.�.�.��	rzfeature-management�enableNc��hK�|�|||�|d�����d{V��S)zEnable specified featureTN�r'r+�r*rrs   r�feature_management_enablez:FeatureManagementUpdateEndpoints.feature_management_enable>sS�����\�\��U�D�-�-�g�t�<�<�
�
�
�
�
�
�
�
�	
r�disablec��hK�|�|||�|d�����d{V��S)zDisable specified featureFNr.r/s   r�feature_management_disablez;FeatureManagementUpdateEndpoints.feature_management_disableEsS�����\�\��U�D�-�-�g�u�=�=�
�
�
�
�
�
�
�
�	
rr))�__name__�
__module__�__qualname__�staticmethod�strrrr'r+rr0r3�rrrr%s��������
�s�
�4��9�
�S�
�
�
��\�
����
�T�
��)�)�
�
�s�
�
�
�*�)�
�
�T�
�	�*�*�
�
��
�
�
�+�*�
�
�
rr) �loggingr�typingrr�+defence360agent.subsys.panels.hosting_panel�subsys�panels�
hosting_panelr$� defence360agent.contracts.configr�,defence360agent.feature_management.constantsrrr	r
rr�6defence360agent.feature_management.rpc.endpoints.utilsr
�(defence360agent.feature_management.utilsrr� defence360agent.rpc_tools.lookuprrr4�loggerr8rrr9rr�<module>rFs�����������������8�8�8�8�8�8�8�8�8�8�8�8�7�7�7�7�7�7�������������������������������A�@�@�@�@�@�@�@�	��8�	�	��)�3�)�)�)�)�!�$
�$
�$
�$
�$
�}�$
�$
�!� �$
�$
�$
rdefence360agent/feature_management/rpc/endpoints/__pycache__/utils.cpython-311.opt-1.pyc0000644000000000000000000000427200000000000026202 0ustar  �

_�,����8�ddlZddlmZddlmZddlmZd�ZdS)�N)�$is_native_feature_management_enabled��wraps)�ValidationErrorc�$�d�}tj|��s
Jd���t|di�����D]J\}}|�d��s0tj|��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�<��t����fd���}|S)Nc��j�K�t���d{V��rtd����|i|���d{V��S)Nz|Command is disabled because native feature management is enabled. Please use your hosting panel interface to manage features)rr)�args�kwargs�coros  ��k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/rpc/endpoints/utils.py�wrapperzBbuiltin_feature_management_only.<locals>._wrapper.<locals>.wrappersg�����9�;�;�;�;�;�;�;�;�
�%�*������t�.�v�.�.�.�.�.�.�.�.�.�r)rrs` r
�_wrapperz1builtin_feature_management_only.<locals>._wrappers3���	�t���	/�	/�	/�	/�
��	/��rz+This decorator can only be used for classes�__dict__�_)�inspect�isclass�getattr�items�
startswith�iscoroutinefunction�setattr)�clsr�m_name�m_obj�wrappeds     r
�builtin_feature_management_onlyr
s�������?�3���N�N�!N�N�N�� ��j�"�5�5�;�;�=�=�*�*�
���� � ��%�%�	*�'�*E�e�*L�*L�	*��h�u�o�o�G��C���)�)�)���Jr)r�*defence360agent.feature_management.controlr� defence360agent.rpc_tools.lookupr�"defence360agent.rpc_tools.validaterr�rr
�<module>r#sk������������3�2�2�2�2�2�>�>�>�>�>�>�����rdefence360agent/feature_management/rpc/endpoints/__pycache__/utils.cpython-311.pyc0000644000000000000000000000427200000000000025243 0ustar  �

_�,����8�ddlZddlmZddlmZddlmZd�ZdS)�N)�$is_native_feature_management_enabled��wraps)�ValidationErrorc�$�d�}tj|��s
Jd���t|di�����D]J\}}|�d��s0tj|��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�<��t����fd���}|S)Nc��j�K�t���d{V��rtd����|i|���d{V��S)Nz|Command is disabled because native feature management is enabled. Please use your hosting panel interface to manage features)rr)�args�kwargs�coros  ��k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/rpc/endpoints/utils.py�wrapperzBbuiltin_feature_management_only.<locals>._wrapper.<locals>.wrappersg�����9�;�;�;�;�;�;�;�;�
�%�*������t�.�v�.�.�.�.�.�.�.�.�.�r)rrs` r
�_wrapperz1builtin_feature_management_only.<locals>._wrappers3���	�t���	/�	/�	/�	/�
��	/��rz+This decorator can only be used for classes�__dict__�_)�inspect�isclass�getattr�items�
startswith�iscoroutinefunction�setattr)�clsr�m_name�m_obj�wrappeds     r
�builtin_feature_management_onlyr
s�������?�3���N�N�!N�N�N�� ��j�"�5�5�;�;�=�=�*�*�
���� � ��%�%�	*�'�*E�e�*L�*L�	*��h�u�o�o�G��C���)�)�)���Jr)r�*defence360agent.feature_management.controlr� defence360agent.rpc_tools.lookupr�"defence360agent.rpc_tools.validaterr�rr
�<module>r#sk������������3�2�2�2�2�2�>�>�>�>�>�>�����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_type��NullAgentResponse��help��
(internal)��cli�}�(�users�]��root�a�require_rpc��any�uu�!feature-management native disable�}�(�help��
(internal)��cli�}�(�users�]��root�a�require_rpc��any�uu� feature-management native status�}�(�return_type��+FeaturesManagementNativeStatusAgentResponse��help��
(internal)��cli�}�(�users�]��root�a�require_rpc��any�uuu.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��#FeaturesManagementListAgentResponse��help��List all available features��type��dict��cli�}��users�]��root�asu�feature-management defaults�}�(�return_type��'FeaturesManagementDefaultsAgentResponse��help��3Get the default state of all features for new users��type��dict��cli�}��users�]��root�asu�feature-management show�}�(�return_type��#FeaturesManagementShowAgentResponse��help��,List the state of all features for all users��type��dict��cli�}��users�]��root�as�schema�}�(�search�}�(�help��Search specific users by name.��type��string��nullable��u�limit�}�(�help��5Limits the output with specified number of incidents.��type��integer��coerce��int��default�Kdu�offset�}�(�help��Offset for pagination.��type��integer��coerce��int��default�Ku�order_by�}�(�help��&List of fields to sort the results by.��type��list��schema�}�(�type��order_by��coerce��order_by�u�nullable��uuu�feature-management get�}�(�return_type��"FeaturesManagementGetAgentResponse��help��1Get the state of all features for a specific user��type��dict��cli�}��users�]��root�as�schema�}��user�}�(�help��:Specifies a user name to obtain the status of features for��type��string�usuu.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��#FeaturesManagementEditAgentResponse��help��CEnable a feature for specified users or all new ones (set defaults)��type��dict��cli�}��users�]��root�as�schema�}�(�feature�}�(�help��KAllowed values: `av` for Malware Cleanup, `proactive` for Proactive Defense��type��string��allowed�]�(�	proactive��av�e�required��u�users�}�(�help��tList 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�s�nullable��uuu�feature-management disable�}�(�return_type��#FeaturesManagementEditAgentResponse��help��DDisable a feature for specified users or all new ones (set defaults)��type��dict��cli�}��users�]��root�as�schema�}�(�feature�}�(�help��KAllowed values: `av` for Malware Cleanup, `proactive` for Proactive Defense��type��string��allowed�]�(�	proactive��av�e�required��u�users�}�(�help��vList 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�s�nullable��uuuu.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.py0000644000000000000000000001000100000000000016077 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()
        )
    )
    for user in users:
        for feature, value in features.items():
            await set_feature(user, feature, value)
defence360agent/files/0000755000000000000000000000000000000000000011650 5ustar  defence360agent/files/__init__.py0000644000000000000000000014501000000000000013762 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.error(
                    "%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.error(
                    "%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.error(
            "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  �

?��U��6�
�P�UdZddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlZddl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/ddl0m1Z1ddl2m3Z3dd
l4m5Z5ddl6m7Z7ddl8m9Z9m:Z:m;Z;ddl<m=Z=m>Z>ddl?m@Z@ddlAmBZBmCZCddlDmEZFddlGmHZHe#eI��ZJejKd��ZLdZMdZNdeOfd�ZPdZQdZRdZSdZTdZUejKd ��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,eh��ZiGd-�d.eh��Zjd/�Zkd0e
jld1e^de(fd2�Zmde`fd3�Znd4e]d5eoddfd6�Zpe:ejekeY�7��d4e]de`fd8���Zqid9�d4e]d:e_fd;�Zre:ejekeY�7��d4e]d<e_d:e_deOfd=���Zsed>d?�d4e]d:e_fd@���ZtdAe(fdB�Zud5eodCe^deOfdD�Zve:ejekeYev�E��d>ddF�d4e]dGe
jldHe^de]fdI���ZwedJd4dKg��ZxdLe'de-exfdM�ZydN�ZzeVfdOejKdPejKddfdQ�Z{GdR�dS��Z|dYdT�Z}e|j~Ze|j�Z�	dZdVe,e]ddfdW�Z�dX�Z�dS)[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)�	ExitStack�suppress�contextmanager)�
formatdate�parsedate_to_datetime)�GzipFile)�chain)�	getLogger)�Version)	�Any�BinaryIO�Dict�Iterable�List�Optional�Set�Tuple�Union)�urlparse)�config)�
LicenseCLN)�PanelException)�	file_hash�retry_on�run_with_umask)�
rate_limit�HOUR)�	to_thread)�UrlTransport�RandomIpChooserWithIPv6Toggle)�safe_extractall�)�default_hookz/var/imunify360/.ipv6_disabledz(/proc/sys/net/ipv6/conf/all/disable_ipv6z(/sys/module/{mod}/parameters/{parameter}�returnc��t�dd���}	t|��5}|������dkr	ddd��dS	ddd��n#1swxYwYn#t
$rYdSwxYw	tt��5}|������dkr	ddd��dS	ddd��n#1swxYwYn#t
$rYnwxYwdS)	z�Check whether IPv6 is disabled at the kernel level.

    Reads the module parameter and the runtime sysctl without
    depending on the im360 package.
    �ipv6�disable)�mod�	parameter�0NT�1F)�
_MOD_PAR_PATH�format�open�read�strip�OSError�_SYSCTL_DISABLE_IPV6)�
param_file�fs  �S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/files/__init__.py�_is_kernel_ipv6_disabledr8Ds����%�%�&�I�%�F�F�J��
�*�
�
�	���v�v�x�x�~�~���3�&�&��	�	�	�	�	�	�	�	�&�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	��������t�t�����
�
�&�
'�
'�	�1��v�v�x�x�~�~���3�&�&��	�	�	�	�	�	�	�	�&�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	����
�
�
���
�����5sv�A?�,A3�A?�'A?�3A7�7A?�:A7�;A?�?
B
�B
�C7�%,C+�C7�C7�+C/�/C7�2C/�3C7�7
D�D�eula�sigszrealtime-av-confzwp-rules�geoz/var/imunify360/filesz$https://files.imunify360.com/static/i��
g�������?�_IP_CHOOSER�
_TRANSPORTc�P�t��pt���S)z�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.
    )r8�_IPV6_DISABLED_STATE�exists��r7�_should_disable_ipv6rD~s"��$�%�%�F�)=�)D�)D�)F�)F�FrCc��	t���dS#t$r t�dd���YdSwxYw)z:Persist IPv6 disabled state so it survives agent restarts.z%Could not persist IPv6 disabled stateT)�exc_infoN)r@�touchr3�logger�debugrBrCr7�_persist_ipv6_disabledrJ�s_��M��"�"�$�$�$�$�$���M�M�M����<�t��L�L�L�L�L�L�M���s��&A�Ac��t�;t��}t|���a|st�d��tS)N��ipv6_enabledz9IPv6 disabled at startup (kernel flag or persisted state))r=rDr"rH�inforLs r7�_get_ip_chooserrO�sO����/�1�1�1��3��N�N�N���	��K�K�K�
�
�
��rCc�V�t�tt�����atS)N)�
ip_chooser)r>r!rOrBrCr7�_get_transportrR�s$����!�_�->�->�?�?�?�
��rCc��eZdZdZdS)�IntegrityErrorzERaised when on disk content does not match hashes in description.jsonN��__name__�
__module__�__qualname__�__doc__rBrCr7rTrT�s������O�O�O�OrCrTc��eZdZdZdS)�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[c���K�t�d�||�����tjtjd|z��tz���d{V��dS)Nz2Files update failed with error: {err}, try: {try_})�err�try_r$)rH�warningr/�asyncio�sleep�random�	randrange�_TIMEOUT_MULTIPLICATOR��exc�is  r7�_log_failed_updaterh�sy����
�N�N�<�C�C��!�	D�	
�	
�����-��(��a��0�0�3I�I�
J�
J�J�J�J�J�J�J�J�J�JrC�path�modec���td��5tj|tjtjztjz|��}ddd��n#1swxYwYtj|d��S)zbOpen file at `path` using permission `mode` for writing in binary mode
    and return file object.rN�wb)r�osr0�O_WRONLY�O_CREAT�O_TRUNC�fdopen)rirj�fds   r7�_open_with_moders�s���
��	�	�H�H�
�W�T�2�;���3�b�j�@�$�
G�
G��H�H�H�H�H�H�H�H�H�H�H����H�H�H�H�
�9�R����s�;A�A�Ac	���t||���5}tjtj|d|d�d�������cddd��S#1swxYwYdS)N��timeout�file�headerszutf-8)�encoding)�
_fetch_url�json�load�io�
TextIOWrapper�get_content_charset)�urlrv�responses   r7�_fetch_json_syncr��s���	�C��	)�	)�	)�
�X��y����� �!�)�,�@�@��I�I�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
����
�
�
�
�
�
s�AA&�&A*�-A*r�rfc��t��}|���rg|���rUt�d||���|��|���t��dSdSdS)Nz>Network error for %s via IPv6 IP %r, disabling IPv6. Error: %r)rO�is_ipv6_enabled�last_ip_was_ipv6rHr_�last_ip�disable_ipv6rJ)r�rf�choosers   r7�_disable_ipv6_on_network_errorr��s������G���� � �!�W�%=�%=�%?�%?�!����L���O�O����		
�	
�	
�	������� � � � � �!�!�!�!rC)�on_error�	max_triesc��ZK�tj��}	|�dt||���d{V��S#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)
z�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.
    Nz!json decode error [{}] for url {}�request to {} timed out�request to {} reset�%eof error while updating files, url: �, err: �8urllib/http error while updating files, url: {}, err: {}�Can't fetch �
, reason: )r`�get_event_loop�run_in_executorr��UnicodeDecodeErrorr{�JSONDecodeErrorr[r/�socketrvr��ConnectionResetError�EOFError�http�client�
HTTPException�urllib�error�URLErrorr3)r�rv�loop�es    r7�_fetch_jsonr��s������!�#�#�D�=��)�)�$�0@�#�w�O�O�O�O�O�O�O�O�O���� 4�5�N�N�N��=�D�D�Q��L�L�M�M�M������>�A�A�A�&�s�A�.�.�.��3�:�:�3�?�?�@�@�@������=�=�=�&�s�A�.�.�.��/�6�6�s�;�;�<�<�<������
�
�
��C�C�C�C��C�C�
�
�	
�����
�K�%�v�|�'<�=�
�
�
�&�s�A�.�.�.��F�M�M��Q�
�
�
�
�	
�����
�=�=�=�&�s�A�.�.�.��;��;�;��;�;�<�<�<�����=���sQ�":�F*�#A3�3F*�2B7�7
F*�2C6�6
F*�D�(F*�3E3�3
F*�%F%�%F*�rxrvc�
�tj�|dtj��pdi|�d���}t���||���5}|j|jfcddd��S#1swxYwYdS)z>Perform HEAD http request to *url* with *timeout* & *headers*.�Imunify-Server-Id��HEAD)rx�methodruN)	r��request�Requestr�
get_server_idrRr0�coderx)r�rvrx�req�rs     r7�_perform_http_head_syncr�s����.�
 �
 ����!9�!;�!;�!A�r�
��
��
!���C�
�	�	�	�	�s�G�	�	4�	4�!���v�q�y� �!�!�!�!�!�!�!�!�!�!�!�!����!�!�!�!�!�!s�A8�8A<�?A<�
current_mtimec���K�|turdSt|d���}	tt||d|i����d{V��\}}|dkrt	d|�d|�����tt��5t|d	�����}||kr$t�
d
||d	||��ddd��n#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)z�Check 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-Sincer�N��zUnexpected 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'r�r�r�i0Fr�)�_NEVERrr r�r[r�	Exceptionr	�	timestamprHr_r�rvr�r/r�r�r�r�r�r�r��hasattrr�)r�r�rv�formatted_mtimer�rx�
last_mtimer�s        r7�_need_to_downloadr�s����������t� ��t�<�<�<�O�-�'�#���(�/�:�	
�
�
�
�
�
�
�
�
�
��g�,�3�;�;��:��:�:�S�:�:���
��i�
 �
 �	�	�.���(����i�k�k�
��]�*�*����4���O�,�#�����	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�$�t��M�>�A�A�A�&�s�A�.�.�.��3�:�:�3�?�?�@�@�@������=�=�=�&�s�A�.�.�.��/�6�6�s�;�;�<�<�<������K�%�v�|�'<�=�
�
�
��1�f���	�!�&�C�-�-��5�5�5�5�5�&�s�A�.�.�.��F�M�M��Q�
�
�
�
�	
�����	
���sH�#C�2AC�C�C�G�,2D�
G�+2E�(G�G�&3G�GT)�compressc#��K�i}|�||d<dtj��pdi}|r|�ddi��tj�||���}t
��j|fi|��5}t��5}|j	�
d��dk}|rl|sj|j	�
d	��d
krLt�d|j	�
d��|j	�
��|��|r#|�t|�����n||j	d
�V�ddd��n#1swxYwYddd��dS#1swxYwYdS)zs
    Fetch *url* as binary file.
    If *compress* is true, ungzipping is done automatically
    if necessary.
    Nrvr�r�zAccept-Encoding�gzipr��Content-EncodingzContent-Typezapplication/zipz�Requested 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)rr��updater�r�r�rRr0rrx�getrHrN�items�
enter_contextr
)	r�rvr��
parameters�req_headersr�r��stack�gzippeds	         r7rzrzQs7�����J��� '�
�9��&�
�(@�(B�(B�(H�b�I�K��8����-�v�6�7�7�7�
�.�
 �
 ��k�
 �
:�
:�C�	��	�	�	��
�
��
�
�
�	�9�;�;�
�"'��"�&�&�'9�:�:�f�D���	��	�� �$�$�^�4�4�8I�I�I��K�K�J�� �$�$�%7�8�8�� �&�&�(�(��

�
�
����#�#�H�X�$>�$>�$>�?�?�?���'�

�
�	
�	
�	
�%
�
�
�
�
�
�
�
�
�
�
����
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
����
�
�
�
�
�
s7�7E(�B>E�E(�E	�E(�E	�E(�(E,�/E,�	dest_filec�0�tj��}|���}t|||���5}|d�t
��x}rL|�|��|�|��|d�t
��x}�Lddd��n#1swxYwY|d�d��dks�|���|z
}	|d�dd��}
|
�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.
    )rvr�rwNrxr�r�zContent-Lengthz&{got} bytes read, {diff} more expected)�got�diff)�message�contentzcontent fetched from z does not match hash: expected=z, got=)�hashlib�md5�tellrzr1�_BUFSIZEr��writer��intr�r��ContentTooShortErrorr/�	hexdigestr[)
r�r�rvr��md5sumr��initial_file_offsetr��chunk�file_length�content_length_header�expected_file_length�
got_md5sums
             r7�_fetch_n_md5sum_urlr�~s���+�-�-�C�#�.�.�*�*��	�C��8�	<�	<�	<�#����'�,�,�X�6�6�6�e�	#��J�J�u�����O�O�E�"�"�"� ��'�,�,�X�6�6�6�e�	#�#�#�#�#�#�#�#�#�#�#�#����#�#�#�#��I��"�"�#5�6�6�&�@�@� �n�n�&�&�)<�<��!)�� 3� 7� 7�8H�$� O� O�� �,�#&�'<�#=�#=� �#�{�2�2��l�7�7�D�K�K�'�1�K�?�L���!�8���������J�
��j�F�2�2��
4�C�
4�
4��
4�
4�'1�
4�
4�
�
�	
��s�A/B5�5B9�<B9rgc��(K�dt|��vS)NzHTTP Error 404)�strres  r7�$_fetch_and_save_should_retry_handlerr��s�����3�s�8�8�+�+rC)r�r��should_retry�r�r��	dest_path�	dest_modec	��0K�	t||��5}tt|||||����d{V��cddd��S#1swxYwYdS#tj$r7}t||��t
d�|�����d}~wt$r7}t||��t
d�|�����d}~wt$r*}t||��t
d|�d|�����d}~wtjjtjjf$r8}t||��t
d�||�����d}~wt $r-}t||��t
d|�d	|�d
|�����d}~wwxYw)z�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.
    r�Nr�r�r�r�r�r�z to r�)rsr r�r�rvr�r[r/r�r�r�r�r�r�r�r�r3)r�r�rvr�r�r�r�r�s        r7�_fetch_and_saver��sT����0L�
�Y�	�
2�
2�	�i�"�#����!��
���������	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	�	���>�A�A�A�&�s�A�.�.�.��3�:�:�3�?�?�@�@�@������=�=�=�&�s�A�.�.�.��/�6�6�s�;�;�<�<�<������
�
�
�&�s�A�.�.�.��C�C�C�C��C�C�
�
�	
�����
�K�%�v�|�'<�=�
�
�
�&�s�A�.�.�.��F�M�M��Q�
�
�
�
�	
�����
�L�L�L�&�s�A�.�.�.��J��J�J�)�J�J�q�J�J�K�K�K�����L���si�A� A�A�A�A�A�	A�F�2B�
F�2C�
F�%D�(F�(3E�
F�((F�F�_Itemr��datac�&�d�|dD��S)z,Return a set of _Item for easy manipulation.c�F�h|]}t|d|d����S)r�r�)r�)�.0�items  r7�	<setcomp>z_items.<locals>.<setcomp>�s*��I�I�I�4�E�$�u�+�t�H�~�.�.�I�I�IrCr�rB)r�s r7�_itemsr��s��I�I�4��=�I�I�I�IrCc��d�}|||��tj|��D]d\}}}|D],}|tj�||��|���-|D],}|tj�||��|���-�edS)z�Check 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)Ni�zGFixing wrong permission to file/dir %s [%s] expected [%s] (not symlink)z&Failed to change permission to file %s)rm�lstat�st_moderi�islinkrHr_�oct�chmod�PermissionErrorr�)�
file_dir_path�
permission�current_modes   r7�	_os_chmodz"check_mode_dirs.<locals>._os_chmod�s���	��8�M�2�2�:�U�B�L��z�)�)�"�'�.�.��3�3�)����;�!���%�%��
�O�O�������
�3�3�3�3�3�*�)�)�)���	�	�	��L�L�8�-�
�
�
�
�
�
�	���s�B
B�%B>�=B>N)rm�walkri�join)	�dirname�dir_perm�	file_permr�ri�dirs�files�	directory�names	         r7�check_mode_dirsr�s������&�I�g�x� � � ��W�W�-�-�;�;���d�E��	?�	?�I��I�b�g�l�l�4��3�3�X�>�>�>�>��	;�	;�D��I�b�g�l�l�4��.�.�	�:�:�:�:�	;�;�;rC�description_path�
files_pathc���||jvsJ�|j}|}||krR|���r/|�d���|�dd���dS|j}||k�PdSdS)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)�parents�exist_okN)r
�parent�is_file�unlink�mkdir)rr�_dir�topmost_dirs    r7�_fix_directory_structurers����)�1�1�1�1�1��"�D��K�
�*�
�
��<�<�>�>�	��K�K�4�K�(�(�(����d�T��:�:�:��E��{���*�
�
�
�
�
�
rCc��eZdZeej��Zee��ZiZ	iZ
e��Ze��ZiZ
dZedez���ej��ZdDd�Zd�Zd�Zdejd	d
fd�Zdejfd�Zd
�Zeddd�dedededededed	d
fd���Z ed	efd���Z!ed	e"efd���Z#eded	efd���Z$dEd	efd�Z%d	efd�Z&d	e"efd�Z'ed���Z(d	e)efd�Z*d�Z+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-���Z<d	efd.�Z=dEd/ed0ed	d
fd1�Z>d	e?e@e?eeezfffd2�ZAd	eBefd3�ZCdFd4�ZDd5e2j3d,e2j3d	eEe2j3fd6�ZFd	efd7�ZGe:d8ejd9ejd:e"ed	d
fd;���ZHd<ed	efd=�ZIdFd>�ZJdFd?�ZKdEdFd@�ZLe	dGdAeEed	d
fdB���ZMeded	d
fdC���ZNd
S)H�Indexz/static�)�periodTc�X�||jvrtd|�d|j�����||_d|_dgi|_|���}	t
|��5}tj|��|_ddd��n#1swxYwYn�#t$rEt�d|��ttj|��t��YnTt 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 Fr�NzPath %s has a file in parentszcannot read description file {}Tz'some files are missing or corrupted: {}z, )�_TYPES�
ValueError�type�	_is_blank�_json�_descriptionfile_pathr0r{r|�NotADirectoryErrorr�_throttled_log_errorr�pathlib�Path�	FILES_DIR�FileNotFoundErrorr�r�rTr/�_corrupted_files�lenr�r)�self�type_�integrity_checkrir6r��	bad_filess       r7�__init__zIndex.__init__1s)�����#�#��(�U�(�(��+�(�(���
���	�����r�]��
��)�)�+�+��	"��d���
*�q�!�Y�q�\�\��
�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*����
*�
*�
*�
*���!�	D�	D�	D��&�&�'F��M�M�M�$�W�\�$�%7�%7��C�C�C�C�C���� �
�		"�		"�		"�
�
�$�5�<�<�T�B�B�����"�D�N�N�N�N�N�N�����		"�����	��-�-�/�/�I��9�~�~�
�$�=�D�D��	�	�)�,�,������
�~�	#�� � �"�"�"�"�"�	#�	#sC�B�B�9B�B	�	B�B	�
B�AD0�D0�:,D+�+D0c��|j|jko/|j|jko|j|jko|j|jkS�N)�	__class__rrr)r&�others  r7�__eq__zIndex.__eq__\sH���N�e�o�-�
*��	�U�Z�'�
*���%�/�1�
*��
�e�k�)�		
rCc��d|jj�d|j�d|j�dt	|������d�	S)N�<z(type_=z) is_blank=z	, json={<z item(s)>}>)r-rVrrr%r��r&s r7�__repr__zIndex.__repr__ds\��
���'�
�
��	�
�
���
�
��4�:�:�<�<�(�(�
�
�
�	
rCrr&Nc��t�d|j|��|�|��}||jd���dS)zjWhether *files_path* dir may be used for this type's file group.

        :raises: IntegrityError
        zValidating [%s]: %sT�r(N)rHrNr�_make_file_group�r&r�	FileGroups   r7�validatezIndex.validatelsT��
	���)�4�9�j�A�A�A��)�)��
�
�	�	�	�$�)�T�2�2�2�2�2�2rCc�6���G��fd�d�j��}|S)zV
        Return FileGroup class: Index class with local path == *files_path*.
        c�6��eZdZededef��fd���ZdS)�)Index._make_file_group.<locals>.FileGroupr'r&c�F��|�jksJ�tj���S�z+Return local base path for given file type.)rrm�fspath)�clsr'rr&s  ��r7rz4Index._make_file_group.<locals>.FileGroup.files_path~s(�����	�)�)�)�)��y��,�,�,rCN)rVrWrX�classmethodr�r)rr&s��r7r8r<}sP�������
�
-�s�
-�s�
-�
-�
-�
-�
-�
-��[�
-�
-�
-rCr8)r-r7s`` r7r6zIndex._make_file_groupxsG����
	-�	-�	-�	-�	-�	-�	-�	-���	-�	-�	-��rCc	�.�tj|j}ttj�tj�ttj	|jtj
����|d|d��dS)N�dirrw)r�_PERMSrrrmri�normpathr�r"�_PATHS�pardir)r&�permss  r7rzIndex.check_mode_dirs�sq����T�Y�'����G�������Y���T�Y�(?���K�K�
�
�
�%�L��&�M�	
�	
�	
�	
�	
rCF��all_zip�	essentialr'�
relative_pathr�rrJrKc��|j�|��|r|j�|��||j|<||d�|j|<||j|<dS)a�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.
        )rCrwN)r�add�_ESSENTIAL_TYPESrFrD�_ALL_ZIP_SUPPORT)r@r'rLr�rrJrKs       r7�add_typezIndex.add_type�sj��,	�
���u�����	,�� �$�$�U�+�+�+�)��
�5��$,�i�@�@��
�5��&-���U�#�#�#rCc��BK�td�|jD����S)zuWhether essential files exist.

        Note: the files may be corrupted (integrity check is not performed).
        c3�DK�|]}t|d���jV��dS)Fr5N)rr)r�r's  r7�	<genexpr>z.Index.essential_files_exist.<locals>.<genexpr>�sI����
�
���e�U�3�3�3�=�=�
�
�
�
�
�
rC)�allrO�r@s r7�essential_files_existzIndex.essential_files_exist�s9�����
�
��-�
�
�
�
�
�	
rCc�4�|j���S)z&Return a set of all known files types.)r�copyrVs r7�typeszIndex.types�s���z��� � � rCc�b�tj�t|j|��Sr>)rmrir�r"rF�r@r's  r7rzIndex.files_path�s!���w�|�|�I�s�z�%�'8�9�9�9rCc�
�|rJ|jtjjvr2tj�|���d��Stj�|�|j��d��S)z9Return local path for description.json for current index.�description.json)	rr�FilesUpdate�DISABLEDrmrir��_descriptionfile_path_latestr)r&�latests  r7rzIndex._descriptionfile_path�sm���	�d�i�6�#5�#>�>�>��7�<�<��1�1�3�3�5G���
��w�|�|�D�O�O�D�I�6�6�8J�K�K�KrCc�r�d�|������D��d}|S)Nc�.�g|]}|d�
|d��S)rbrCrB)r��xs  r7�
<listcomp>z6Index._descriptionfile_path_latest.<locals>.<listcomp>�s%��J�J�J�A�a��k�J�q��x�J�J�JrCr)�	_get_list�values)r&�ltss  r7raz"Index._descriptionfile_path_latest�s5��J�J����!1�!1�!8�!8�!:�!:�J�J�J�1�M���
rCc�R�t��}t|j��D]�}|�|j��}	t|tjt��}n%#t$r|�
|��Y�_wxYw||jkr|�
|����|S)z9Return a set of file paths that are missing or corrupted.)�setr�r�
localfilepathr�rr�r�r�r#rNr�)r&r)r�ri�actuals     r7r$zIndex._corrupted_files�s����E�E�	��4�:�&�&�	$�	$�D��%�%�d�h�/�/�D�
�"�4���h�?�?����$�
�
�
��
�
�d�#�#�#���
�������$�$��
�
�d�#�#�#���s� A!�!B�Bc��|j|S)z`
        usage example:
        >> async with Index.locked(WHITELISTS):
            ...
        )�_lockr\s  r7�lockedzIndex.locked�s���y���rCc�D���fd�t�j��D��S)z(Return iterable over all files in index.c3�L�K�|]}��|j��V��dSr,�rlr��r�r�r&s  �r7rTzIndex.files.<locals>.<genexpr>�s3�����L�L���"�"�4�8�,�,�L�L�L�L�L�LrC)r�rr2s`r7rzIndex.files�s'���L�L�L�L���
�9K�9K�L�L�L�LrCc��|jdS)z+Return 'items' field from JSON description.r�)rr2s r7r�zIndex.items�s���z�'�"�"rCc��	tj|�d�����jS#t$r|cYSwxYw)zBReturn mtime of description file if it exists, otherwise -math.infT�rb)rm�statr�st_mtimer3)r&�defaults  r7�_descriptionfile_mtimezIndex._descriptionfile_mtime�sO��	��7�4�5�5�T�5�B�B�C�C�L�L���	�	�	��N�N�N�	���s�,/�>�>c��|���}|sdS|tjjzt	j��kS)z4Return True if last update was too late in the past.T)r{rr_�PERIOD�time)r&�_desc_mtimes  r7�_is_outdatedzIndex._is_outdated�s<���1�1�3�3���	��4��V�/�6�6�����D�DrCrvc��K�|jpyt|�����dkpT|���o@t	|�|j��|���|���d{V��S)z>Return True if update from server is needed for current index.rN)rr%r$r�r��_descriptionfile_urlrr{)r&rvs  r7�is_update_neededzIndex.is_update_needed�s�����
�N�	
��4�(�(�*�*�+�+�a�/�	
��!�!�#�#��+��-�-�d�i�8�8��/�/�1�1����������	
rCc���	td��5tj|||���ddd��dS#1swxYwYdS#t$r"}t	t|����|�d}~wwxYw)z)Create local directory for current index.r)rjrN)rrm�makedirsr3r[r�)r&r��dir_moderr�s     r7�	_makedirszIndex._makedirss���	-���"�"�
G�
G���G�(�X�F�F�F�F�
G�
G�
G�
G�
G�
G�
G�
G�
G�
G�
G�
G����
G�
G�
G�
G�
G�
G���	-�	-�	-��c�!�f�f�%�%�1�,�����	-���s2�A�6�A�:�A�:�A�
A/�
A*�*A/�	to_updatec���K�|�|��}||jd���}|j|jd}|j|jd}|D]�}|�|j��}	t
j�|	��}
t
j�|
��s|�	|
|d���t|j|	|||j����d{V����dS)zX
        Fetch files from *to_update* set, verify hashes, save to *files_path*.
        Fr5rCrw�r)r�r�N)r6rrDrlr�rmrir��isdirr�r�r�)r&rr�rvr8�fgr��	file_moder��filenamer�s           r7�
_update_fileszIndex._update_filess�����)�)��
�
�	��Y�t�y�%�
8�
8�
8���9�R�W�%�e�,���I�b�g�&�v�.�	��	�	�D��'�'���1�1�H��g�o�o�h�/�/�G��7�=�=��)�)�
B����w��5��A�A�A�!�����#��{����
�
�
�
�
�
�
�
�	�	rC�remote_itemsc�����t�j��}�fd�|D��}�fd�|D��}||z
}�������fd�|D��}||z
}||fS)z�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.c�D��h|]}��|j����SrBrsrts  �r7r�z+Index._calculate_changes.<locals>.<setcomp>7s)���L�L�L��t�)�)�$�(�3�3�L�L�LrCc�D��h|]}��|j����SrBrsrts  �r7r�z+Index._calculate_changes.<locals>.<setcomp>8s)���N�N�N���*�*�4�8�4�4�N�N�NrCc�L��h|] }��|j���v�|��!SrBrs)r�r�r)r&s  ��r7r�z+Index._calculate_changes.<locals>.<setcomp><s>���
�
�
���!�!�$�(�+�+�9�<�<�
�<�<�<rC)r�rr$)	r&r��local_items�local_files�remote_files�	to_remove�	local_setr�r)s	`       @r7�_calculate_changeszIndex._calculate_changes-s������T�Z�(�(��L�L�L�L��L�L�L��N�N�N�N��N�N�N���,�.�	��)�)�+�+�	�
�
�
�
�
�#�
�
�
�	�
!�9�,�	��)�#�#rCc�N�d�t|j|��S)z'Return remote path for description.jsonz{}{}/description.json�r/�BASE_URLrFr\s  r7r�zIndex._descriptionfile_urlDs!��'�-�-�h��
�5�8I�J�J�JrCc�N�d�t|j|��S)zReturn remote path for all.zipz{}{}/all.zipr�r\s  r7�_all_zip_urlzIndex._all_zip_urlIs!���$�$�X�s�z�%�/@�A�A�ArCc��	tj|��n@#t$r3}t�d|t|����Yd}~nd}~wwxYw|r3t�d|��tj|d���dSdS)Nzfailed to remove %s: %sz'Removing old path on all.zip update: %sT��
ignore_errors)	rmrr3rHr_r�rN�shutil�rmtree)r�all_zip_localpath�remove_filesr�s    r7�_all_zip_cleanupzIndex._all_zip_cleanupNs���	��I�'�(�(�(�(���	�	�	��N�N�)�+<�c�!�f�f�
�
�
�
�
�
�
�
�����	�����	:��K�K�A�:�N�N�N��M�*�D�9�9�9�9�9�9�	:�	:s��
A�)A�A�	live_pathc��tj����d��}|�|j|z��S)z�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.

        z_%Y-%m-%dT%H%M%S.%fZ)�DT�datetime�utcnow�strftime�	with_namer)r��
new_suffixs  r7�_generate_new_pathzIndex._generate_new_pathZsA���[�'�'�)�)�2�2�3I�J�J�
��"�"�9�>�J�#>�?�?�?rCc	��K�tj|�|j����}t�|��}|�|jdz��}|j|jd}|j|jd}|�	|j��}t��5}|�||d���|�tj
||d���t||||d����d	{V��}		tj|d
��5}
t#|
|��d	d	d	��n#1swxYwYt%j|��D]v\}}}
|D]5}t%jt$j�||��|���6|
D]5}t%jt$j�||��|���6�w|�|��|�||��}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.ziprwrCFr�T)r�)r�r�Nr�)&r r!rrrr�r�rrDr�rr��callbackr�r��zipfile�ZipFile�_safe_extractallrmr�r�rir�r9�_replace_live_with_new_dirr�rTr3r�
BadZipfile�LargeZipFiler[r��pop_all�boolrr_r`)r&rvr��new_path�archive_pathr�r��all_zip_url�rollback_stack�_�archive�root�directories�	filenamesrr��old_pathr�s                  r7�_run_update_all_zipzIndex._run_update_all_zipesT�����L������!;�!;�<�<�	��+�+�I�6�6���)�)�(�-�)�*C�D�D���K��	�*�6�2�	��;�t�y�)�%�0���'�'��	�2�2��
�[�[�1	%�N��N�N�8�X��N�>�>�>��#�#��&���!�	
$�
�
�
�&����#�����������A�
1��_�\�3�7�7�8�7�$�W�h�7�7�7�8�8�8�8�8�8�8�8�8�8�8����8�8�8�8�
57�G�H�4E�4E�J�J�0�D�+�y�%0�J�J�	�������d�I�!>�!>��I�I�I�I�$-�J�J��������d�H�!=�!=�y�I�I�I�I�J��
�
�h�'�'�'� �:�:�8�Y�O�O���������"��$�
�
1�
1�
1�"�#�a�&�&�)�)�q�0�����
1����
�"�"�$�$�$�c1	%�1	%�1	%�1	%�1	%�1	%�1	%�1	%�1	%�1	%�1	%����1	%�1	%�1	%�1	%�h	����l��h���	�	
�	
�	
��y�� 2� ;�;�;s\�>AJ�H	�*E�;H	�E	�H	�E	�B9H	�J�	3I�<I�I�J�J�J�version�forcec���K�tj|�|j����}|���std|j�d|�����|dkrY|���}d�|���D��d}t�	d|j|��|�
d���}	t|d	z����
����t|��kr|std
|�d|j�����n(#t$rtd|j�d|�����wxYw|j���D]�}|���s�|���r�t|d	z����
����t|��krh|�|jd
z��}|�|d���|�|��|�d����d{V��dS��td
|�d|j�����)aUpdate 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)r��ver�props   r7rfz#Index.update_to.<locals>.<listcomp>�s5�����!��T��h������rCrz%Try to update to latest version %s %sF��strict�VERSIONzVersion z is already set for z5, because current version doesn't have VERSION file: �liveT��target_is_directory��
is_updatedNz not found in )r r!rr�
is_symlinkr[rgr�rHrN�resolver
�	read_textr2r#r�iterdir�is_dirr�r�
symlink_to�rename�
_run_hooks)r&r�r�r��versions�current_pathri�
new_live_paths        r7�	update_tozIndex.update_to�s������L������!;�!;�<�<�	��#�#�%�%�	��+��9�9�9�i�i�)���
��h����~�~�'�'�H���%-�^�^�%5�%5������G�
�K�K�7���G�
�
�
�!�(�(��(�6�6��
	���	�1�<�<�>�>�D�D�F�F�G�G��7�#�#�$�$��$�"�G�w�G�G�D�I�G�G������!�	�	�	��+�'+�y�y�y�,�,�@���
�	�����$�,�,�.�.�		�		�D��?�?�$�$�
������
��D�9�,�7�7�9�9�?�?�A�A�B�B�g��G�G���%)�N�N�4�9�v�3E�$F�$F�M�!�,�,�T�t�,�L�L�L�!�(�(��3�3�3��/�/�T�/�:�:�:�:�:�:�:�:�:��F�F���k�'�'�'�4�9�9�M�N�N�Ns
�A#D7�7%Ec���tj|�|j����}|���siS|�d���}i}t
d��}|j���D]�}|���r�|���|krd}nd}|dzx}�	��rqd}	|�
��}t
|��}	|dt|��d�||	<|	|kr|	}��#t$rt�d||��Y��wxYw��|t
d��krd||d<|S)	NFr�r,Tr�)�currentrbrCz Version file %s is not valid: %srb)r r!rrr�r�r
rr�rAr�r�rrHr�)
r&r�r��result�max_versionrir��version_filer��_vers
          r7rgzIndex._get_list�s����L������!;�!;�<�<�	��#�#�%�%�	��I� �(�(��(�6�6�����c�l�l���$�,�,�.�.�	�	�D���� � �
���|�|�~�~��-�-������ $�y� 0�0��8�8�:�:�
����*�4�4�6�6�G�"�7�+�+�D�#*�"'�"�4�y�y�$�$�F�4�L�
�k�)�)�&*����!�����L�L�:�$�����
�H�
����
�&�����%�%�,0�F�;���)��
s�AD�&D>�=D>c���g}t|������d����D]4\}}|drdn|drdnd}|�|�|�����5|S)z/Return list of versions available in the index.c��|dS)NrrB)res r7�<lambda>z Index.get_list.<locals>.<lambda>s
��A�a�D�rC)�keyr�z
 (current)rbz	 (latest)r�)�sortedrgr��append)r&r�r�r��markers     r7�get_listzIndex.get_lists�����#��N�N���"�"�$�$�.�.�
�
�
�
	0�
	0�M�G�T�
�	�?������>��[�[��
�
�M�M�W�.�f�.�.�/�/�/�/��
rCc���tj|�|j����}|���sdS|�d���}|j���D]�}tj	�
tjj��tj	�
|���jtjj��z
j}|���rf|���sR||krL|t$jjkr7t*�d|j|��t/j|d�����dS)z~Remove old versions of files.

        This is done by removing old directories in the path, that older than 30 days.
        NFr�zRemoving old version of %s: %sTr�)r r!rrr�r�rr�r�r��now�timezone�utc�
fromtimestamprxry�daysr�rr_�DAYS_TO_KEEPrHrNr�r�)r&r�r�ri�days_olds     r7�_clean_old_versionszIndex._clean_old_versions s6���L������!;�!;�<�<�	��#�#�%�%�	��F� �(�(��(�6�6���$�,�,�.�.�	8�	8�D��������0�0��+�+�+��I�I�K�K�(�"�+�/�����
����
�
�
8����)�)�
8��L�(�(��� 2� ?�?�?����<�d�i��N�N�N��
�d�$�7�7�7�7��	8�	8rCr�c	��|jtjjvr7|���t
�d|j|��dS|�|jdz��}d}t��5}|�
|d���|�|j��|�
��r|�d���nd}td��D]�}	|�|��n�#t"$r�|r�|�
��sj|�|jd	z��}t
�d
|||��|�|��|�|j|��Y��wxYw|�t'j|d���|���ddd��n#1swxYwY|S)zbReplace *live_path* with *new_path*.

        Return *old_path*

        :raises: OSError
        zFSkipping update for %s, because it is disabled. New files stored in %sNr�Tr�Fr��z.live-movedz1Moving %s [live] to %s, to rename %s to it [live]r�)rrr_r`r�rHrNr�rrr�r�rr�r��ranger��IsADirectoryError�replacer�r�r�)r&r�r�r��
moved_pathr�r��lasts        r7r�z Index._replace_live_with_new_dir:s^���9��*�3�3�3��$�$�&�&�&��K�K� ��	��	
�
�
��4� �*�*�8�=�6�+A�B�B�
��
�
�[�[�-	%�N��$�$�X�4�$�H�H�H��#�#�M�$8�9�9�9��'�'�)�)��	�!�!��!�/�/�/��
��a���
O�
O��O�!�(�(��3�3�3��E��(�O�O�O����
%�/�/�1�1�O�%2�%<�%<�)�.��>�&�&�
����9�%�&�)����"�)�)�*�5�5�5�'�/�/�
�0B�I�N�N�N���-O����0�%��
�j��=�=�=�=�
�"�"�$�$�$�[-	%�-	%�-	%�-	%�-	%�-	%�-	%�-	%�-	%�-	%�-	%����-	%�-	%�-	%�-	%�^�s8�=A/G
�-D�G
�BF�G
�F�/G
�
G�Gc	���K����j��}t||����d{V��}��t	|����\}}|p|}|s6t
�d�j������dStj	��
�j����}|���r|�d���nd}t�|��}	t��5}
��|	�j�jdd���|
�t&j|	d�	��|r|���r|n|}|���rAt�||	|��fd
�|D�������d{V����|	||����d{V��	t3|	dz�j�jd��5}|�t7j|�������ddd��n#1swxYwY��|	����|	|��}n6#t@tBf$r"}
tEtG|
����|
�d}
~
wwxYw|
�$��ddd��n#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.Fr�rCr�Tr�c3�L�K�|]}��|j��V��dSr,rsrts  �r7rTz$Index._run_update.<locals>.<genexpr>�sD�����$�$�9=��*�*�4�8�4�4�$�$�$�$�$�$rCr^rwz,Removing old path on file by file update: %s)(r�rr�r�r�rHrN�_touchr r!rr�r�rr�rr�rDr�r�r�r��	_copytree�unionr�rsr�r{�dumps�encoder9r�rTr3r[r�r�rr_r`)r&rvr��as_jsonr�r��need_updater�r�r�r��	from_pathrwr�s`             r7�_run_updatezIndex._run_updates������'�'��	�2�2��#�C��9�9�9�9�9�9�9�9�9��#�6�6�v�g���G�G��	�9��,�9���	��K�K�9�4�9�E�E�E��K�K�M�M�M��5��L������!;�!;�<�<�	�09�/C�/C�/E�/E�O�I���U��+�+�+�4�	��+�+�I�6�6���[�[�*	%�N��N�N��$�+�d�i�0��7�%�
�
�
�
�
�#�#��
�x�t�
$�
�
�
�%�I����):�):�I���	�
����!�!�
��o�o����O�O�$�$�$�$�AJ�$�$�$�������������$�$�X�y�'�$�J�J�J�J�J�J�J�J�J�
1�$��1�1��K��	�*�6�2���=���J�J�t�z�'�2�2�9�9�;�;�<�<�<�	=�=�=�=�=�=�=�=�=�=�=����=�=�=�=��
�
�h�'�'�'� �:�:�8�Y�O�O����"�G�,�
1�
1�
1�!�#�a�&�&�)�)�q�0�����
1����
�"�"�$�$�$�U*	%�*	%�*	%�*	%�*	%�*	%�*	%�*	%�*	%�*	%�*	%����*	%�*	%�*	%�*	%�Z�	8����)�)�	8��K�K�>��
�
�
�
�M�(�$�7�7�7�7��y�� 2� ;�;�;s[�CK1�5)J�:I$�J�$I(	�(J�+I(	�,.J�K1�K�,K	�	K�K1�1K5�8K5�from_dir�to_dir�
ignored_pathsc��`�K��fd�}ttj||d|d����d{V��dS)z7Copy *from_dir* to *to_dir* except for *ignored_paths*.c����ttj���t��sJ�t	��fd�|D����S)z(Return  names that should not be copied.c3�`�K�|](}tj��|���v�$|V��)dSr,)rmrir�)r�rrris  ��r7rTz8Index._copytree.<locals>.ignore_names.<locals>.<genexpr>�sJ���������7�<�<��d�+�+�}�<�<��<�<�<�<��rC)�
isinstancermr?r��	frozenset)ri�namesrs` �r7�ignore_namesz%Index._copytree.<locals>.ignore_names�s_�����b�i��o�o�s�3�3�3�3�3�������!������
rCT)�symlinks�ignore�
dirs_exist_okN)r r��copytree)r
rrrs  ` r7rzIndex._copytree�su�����	�	�	�	�	���O������

�
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
rCr�c���tj�t|��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 ({}))
rmri�relpathr�_URL_PATH_PREFIXrFrr r!r
r/r�r)r&r��url_relpath�	type_pathrLs     r7rlzIndex.localfilepath�s����g�o�o��S�M�M��� 5�
�
���K��	�*�	��L��#�#�w�|�K�'@�'@�'H�H�H�H�1�8�8��i�H�H�
I�H�H������Y�?�?�
��w�|�|�D�O�O�D�I�6�6�
�F�F�FrCc��	|�d���}tj�|��rtj|��dSdS#t
$r2}t�t|����Yd}~dSd}~wwxYw)z5Update mtime of description.json file so it is fresh.TrwN)	rrmri�isfile�utimer3rHr_r�)r&rir�s   r7rzIndex._touch�s���	#��-�-�T�-�:�:�D��w�~�~�d�#�#�
���������
�
���	#�	#�	#��N�N�3�q�6�6�"�"�"�"�"�"�"�"�"�����	#���s�A	A�
B�'B�Bc��K�t|j|jtg��D]}}	|||���d{V���#tt
f$r&}t�d||��Yd}~�Hd}~wt$r&}t�	d||��Yd}~�vd}~wwxYwt�
d|jd|z��dS)Nzhook %s error: %sz%s files update finished%sz (not updated))r�_HOOKSrr%rTrrHr�r��	exceptionrN)r&r��hookr�s    r7r�zIndex._run_hooks�s�����$�+�d�i�0�<�.�A�A�	?�	?�D�
?��d�4��,�,�,�,�,�,�,�,�,�,��"�N�3�
;�
;�
;����0�$��:�:�:�:�:�:�:�:������
?�
?�
?�� � �!4�d�A�>�>�>�>�>�>�>�>�����
?�������(��I��J��/�	
�	
�	
�	
�	
s!�?�B$�A1�1
B$�>B�B$c��$K�tjj}|sy|�|���d{V��s^t�d|jttjjdz����|�	d����d{V��dS|j
o|j|j}|}|r�d}t�d|j|��	tj
|�tjj��|���d{V��}|r!t�d|j|��nG#tjt"f$r.}t�d	|j||��d
}Yd}~nd}~wwxYw|r�d}t�d|j|��	tj
|�tjj��|���d{V��}|r!t�d|j|��nr#tjt"f$rY}t�d	|j||��|�	d����d{V��|j|jvr|�Yd}~dSd}~wwxYw|�	|p|����d{V��dS)a�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.
        Nz(%s was updated less than %s minutes ago.�<Fr�r�zUpdating %s files via %szUpdated %s using %sz%s update error via %s: %sTzfile by file download)rr_�TIMEOUTr�rHrNrr�r}r�rrPr`�wait_forr��SOCKET_TIMEOUT�TimeoutErrorr[r�r	rO)r&r�rvrJ�file_by_file�log_str�updatedr�s        r7r�zIndex.updates�����$�,���	�4�#8�#8��#A�#A�A�A�A�A�A�A�	��K�K�:��	��F�&�-��3�4�4�
�
�
�
�/�/�U�/�3�3�3�3�3�3�3�3�3��F��.�E�T�%:�4�9�%E��"�{���	$��G��K�K�2�D�I�w�G�G�G�


$� '� 0��,�,��*�9����	!�!���������K��K�K� 5�t�y�'�J�J�J����(�+�6�
$�
$�
$����0�$�)�W�a���� $�����������	
$����
�	�-�G��K�K�2�D�I�w�G�G�G�
� '� 0��$�$�V�%7�%F�G�G��!�!���������K��K�K� 5�t�y�'�J�J�J����(�+�6�	
�	
�	
����0�$�)�W�a�����o�o��o�7�7�7�7�7�7�7�7�7��9�� 5�5�5��G��F�F�F�F�F�����	
�����o�o��)9�E�o�:�:�:�:�:�:�:�:�:�:�:s3�A D2�2E6�$E1�1E6�A H�I/�AI*�*I/�	only_typec��K�|rj||d���}|�|��4�d{V��|�|���d{V��ddd���d{V��dS#1�d{V��swxYwYdS|r�t�d��|jD]i}||d���}|�|��4�d{V��|�|���d{V��ddd���d{V��n#1�d{V��swxYwY�jdSt�d��|jD]i}||d���}|�|��4�d{V��|�|���d{V��ddd���d{V��n#1�d{V��swxYwY�jdS)zkRun update for all registered `types` of files.

        Raises asyncio.TimeoutError, UpdateError.
        Fr5NzUpdating essential fileszUpdating all files)rpr�rHrNrOr)r@r-r��only_essential�indexr's      r7�
update_allzIndex.update_allOss�����	.��C�	�5�9�9�9�E��z�z�)�,�,�
*�
*�
*�
*�
*�
*�
*�
*��l�l�5�)�)�)�)�)�)�)�)�)�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*����
*�
*�
*�
*�
*�
*�
�	.��K�K�2�3�3�3��-�
.�
.����E�5�9�9�9���:�:�e�,�,�.�.�.�.�.�.�.�.��,�,�u�-�-�-�-�-�-�-�-�-�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.����.�.�.�.��
.�
.�

�K�K�,�-�-�-���
.�
.����E�5�9�9�9���:�:�e�,�,�.�.�.�.�.�.�.�.��,�,�u�-�-�-�-�-�-�-�-�-�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.����.�.�.�.��
.�
.s5�A�
A&�)A&�<C*�*
C4	�7C4	�	E7�7
F	�F	c�F�|j|�|��dS)z:Add a hook for type_ to be called after successful update.N)r!rN)r@r'r#s   r7�add_hookzIndex.add_hookhs%��	�
�5����d�#�#�#�#�#rC)T)F�r&N)NFF)OrVrWrXrr`�Lockrorkr!rFrDrrOrPrrrrHr�rr*r/r3rm�PathLiker9r6rrAr�r�r�rQrWrrZrrrar$rprrr�r��floatr{r�r�r�r r!r�r�rr�r�r��staticmethodr�r�r�r�rr
rgrr�r�rr�r	rrlrr�r�r1r3rBrCr7rr$s�������
�K���%�%�E�
�[��
�
�F�
�F�
�F�
�S�U�U�F��s�u�u���� ��6�:�:�Q��X�6�6�6�v�|�D�D��)#�)#�)#�)#�V
�
�
�
�
�
�
3�2�;�
3�4�
3�
3�
3�
3��2�;�����
�
�
����.�.�.��.��.��	.�
�.��.��.�
�.�.�.��[�.�8�	
�D�	
�	
�	
��[�	
��!�c�#�h�!�!�!��[�!��:�s�:�s�:�:�:��[�:�L�L�S�L�L�L�L��c������#�c�(������ � ��[� �M�x��}�M�M�M�M�#�#�#�.4��������E�d�E�E�E�E�

�e�

��

�

�

�

�-�-�-�-��!�,��36�u�:��	
�����2$���J�$�	�s�5�z�3�s�8�#�	$�$�$�$�$�.�K��K��K�K�K��[�K��B��B��B�B�B��[�B��	:�	:�	:��\�	:��@�g�l�@�w�|�@�@�@��\�@�O<�D�O<�O<�O<�O<�b1O�1O�s�1O�4�1O�D�1O�1O�1O�1O�f%�4���c�4�#�:�o�)>� >�?�%�%�%�%�N�$�s�)����� 8�8�8�8�4C���C�18��C�	�'�,�	�C�C�C�C�JN<�D�N<�N<�N<�N<�`�
��+�
�')�{�
�CF�s�8�
�	
�
�
�
��\�
�.G��G��G�G�G�G�#�#�#�#�
�
�
�
�B;�B;�B;�B;�B;�H�JO�.�.� ��
�.�	
�.�.�.��[�.�0�$�S�$�4�$�$�$��[�$�$�$rCrc�r�t�tdddd���t�tdddd	���t�td
ddd���t�t
dddd	d���t�td
ddd	d���dS)zRegister required file types.zeula/v1i�i�F)rJzsigs/v1i�i�Tzrealtime-av-conf/v1zwp-rules/v1rIzgeo/v1N)rrQ�EULA�SIGS�REALTIME_AV_CONF�WP_RULES�GEOrBrCr7�	configurer?ns���	�N�N�4��E�5�%�N�@�@�@�	�N�N�4��E�5�$�N�?�?�?�	�N�N���
�
������
�N�N���
�
���
����
�N�N�3��%����N�N�N�N�N�NrCFr-c���K�	t�||���d{V��S#tjtf$r'}t
�d||��Yd}~dSd}~wwxYw)z.Run files.update and log Update/TimeoutErrors.Nz*Failed to update files [%s] with error: %s)rr1r`r)r[rHr�)r-r�r]s   r7�update_and_log_errorrA�s�����
��%�%�i��7�7�7�7�7�7�7�7�7��� �+�.�
�
�
����8�)�S�	
�	
�	
�	
�	
�	
�	
�	
�	
�����
���s� %�A"�A�A"c��NK�	t�d����d{V��S#tjtf$ri}t����d{V��rt�d|��n#t|tj��rt|��Yd}~dSd}~wwxYw)z6Update all files. Don't fail if essential files exist.T)r/Nz2Failed to update files [essential files exist]: %s)	rr1r`r)r[rWrHr�r)r]s r7�!update_all_no_fail_if_files_existrC�s�������%�%�T�%�:�:�:�:�:�:�:�:�:��� �+�.�	�	�	��,�,�.�.�.�.�.�.�.�.�	��L�L�D�c�
�
�
�
��#�w�3�4�4�
�!�s�*��
�
�
�
�
�
�����	���s� %�B$�AB�B$r4)NF)�rYr`r�r�r��http.clientr�r}r{�mathrmr rbr�r�r~r��urllib.errorr��urllib.request�collectionsrr�
contextlibrrr�email.utilsrr	r�r
�	itertoolsr�loggingr�packaging.versionr
�typingrrrrrrrrr�urllib.parser�defence360agent.contractsr�!defence360agent.contracts.licenser�"defence360agent.subsys.panels.baser�defence360agent.utilsrrr�defence360agent.utils.commonrr�defence360agent.utils.threadsr �#defence360agent.utils.net_transportr!r"�defence360agent.utils.zipsafer#r��hooksr%rVrHr!r@r4r.r�r8r:r;r<r=r>r"r�r��_MAX_TRIES_FOR_DOWNLOADrd�infr�r�r�r7�JSONTyper=�__annotations__r>rDrJrOrR�RuntimeErrorrTr[rhr6rsr�r�r�r�r�r�rzr�r�r�r�r�rrrr?r1r�rWrArCrBrCr7�<module>r^s����������������������	�	�	�	���������	�	�	�	�����
�
�
�
�
�
�
�
�
�
�
�
�����������������/�/�/�/�/�/�/�/�:�:�:�:�:�:�:�:�:�:�9�9�9�9�9�9�9�9�������������������%�%�%�%�%�%�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�"�!�!�!�!�!�-�,�,�,�,�,�8�8�8�8�8�8�=�=�=�=�=�=�E�E�E�E�E�E�E�E�E�E�9�9�9�9�9�9�9�9�3�3�3�3�3�3���������N�M�M�M�M�M�������	��8�	�	��#�w�|�$D�E�E��A��:�
��$�����4��
��%�������G�L�0�1�1�	�1����������(�����c�5�$��d�3��8�n�d�3�i�G�H��8<��X�3�
4�;�;�;�%)�
�H�\�"�)�)�)�G�d�G�G�G�G�M�M�M�M�	�6�	�	�	�	�������P�P�P�P�P�\�P�P�P�
�
�
�
�
�,�
�
�
�K�K�K��"�+��S��X�����
�h�
�
�
�
�
!��
!�)�
!��
!�
!�
!�
!�
���,�8O����"=�3�"=�H�"=�"=�"=���"=�L*,�
!�
!�
!�	�
!��
!�
!�
!�
!� 
���,�8O����7�	�7�"�7�-2�7�	�7�7�7���7�t�59�)
�)
�)
�C�)
�U�)
�)
�)
���)
�X&��&�&�&�&�R,�I�,�#�,�$�,�,�,�,�
���
�%�5�	�����0L�0L�0L�	�0L��{�0L�
�0L�	�0L�0L�0L�
��0L�f	�
�7�U�H�-�.�.��J��J��U��J�J�J�J�
;�;�;�F@I����l��07���	�����,G
$�G
$�G
$�G
$�G
$�G
$�G
$�G
$�TO�O�O�O�,
�	���3��,1�	
�	
���}�	
�	�	
�	
�	
�	
�
�
�
�
�
rCdefence360agent/files/__pycache__/__init__.cpython-311.pyc0000644000000000000000000022225400000000000020330 0ustar  �

?��U��6�
�P�UdZddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlZddl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/ddl0m1Z1ddl2m3Z3dd
l4m5Z5ddl6m7Z7ddl8m9Z9m:Z:m;Z;ddl<m=Z=m>Z>ddl?m@Z@ddlAmBZBmCZCddlDmEZFddlGmHZHe#eI��ZJejKd��ZLdZMdZNdeOfd�ZPdZQdZRdZSdZTdZUejKd ��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,eh��ZiGd-�d.eh��Zjd/�Zkd0e
jld1e^de(fd2�Zmde`fd3�Znd4e]d5eoddfd6�Zpe:ejekeY�7��d4e]de`fd8���Zqid9�d4e]d:e_fd;�Zre:ejekeY�7��d4e]d<e_d:e_deOfd=���Zsed>d?�d4e]d:e_fd@���ZtdAe(fdB�Zud5eodCe^deOfdD�Zve:ejekeYev�E��d>ddF�d4e]dGe
jldHe^de]fdI���ZwedJd4dKg��ZxdLe'de-exfdM�ZydN�ZzeVfdOejKdPejKddfdQ�Z{GdR�dS��Z|dYdT�Z}e|j~Ze|j�Z�	dZdVe,e]ddfdW�Z�dX�Z�dS)[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)�	ExitStack�suppress�contextmanager)�
formatdate�parsedate_to_datetime)�GzipFile)�chain)�	getLogger)�Version)	�Any�BinaryIO�Dict�Iterable�List�Optional�Set�Tuple�Union)�urlparse)�config)�
LicenseCLN)�PanelException)�	file_hash�retry_on�run_with_umask)�
rate_limit�HOUR)�	to_thread)�UrlTransport�RandomIpChooserWithIPv6Toggle)�safe_extractall�)�default_hookz/var/imunify360/.ipv6_disabledz(/proc/sys/net/ipv6/conf/all/disable_ipv6z(/sys/module/{mod}/parameters/{parameter}�returnc��t�dd���}	t|��5}|������dkr	ddd��dS	ddd��n#1swxYwYn#t
$rYdSwxYw	tt��5}|������dkr	ddd��dS	ddd��n#1swxYwYn#t
$rYnwxYwdS)	z�Check whether IPv6 is disabled at the kernel level.

    Reads the module parameter and the runtime sysctl without
    depending on the im360 package.
    �ipv6�disable)�mod�	parameter�0NT�1F)�
_MOD_PAR_PATH�format�open�read�strip�OSError�_SYSCTL_DISABLE_IPV6)�
param_file�fs  �S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/files/__init__.py�_is_kernel_ipv6_disabledr8Ds����%�%�&�I�%�F�F�J��
�*�
�
�	���v�v�x�x�~�~���3�&�&��	�	�	�	�	�	�	�	�&�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	��������t�t�����
�
�&�
'�
'�	�1��v�v�x�x�~�~���3�&�&��	�	�	�	�	�	�	�	�&�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	����
�
�
���
�����5sv�A?�,A3�A?�'A?�3A7�7A?�:A7�;A?�?
B
�B
�C7�%,C+�C7�C7�+C/�/C7�2C/�3C7�7
D�D�eula�sigszrealtime-av-confzwp-rules�geoz/var/imunify360/filesz$https://files.imunify360.com/static/i��
g�������?�_IP_CHOOSER�
_TRANSPORTc�P�t��pt���S)z�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.
    )r8�_IPV6_DISABLED_STATE�exists��r7�_should_disable_ipv6rD~s"��$�%�%�F�)=�)D�)D�)F�)F�FrCc��	t���dS#t$r t�dd���YdSwxYw)z:Persist IPv6 disabled state so it survives agent restarts.z%Could not persist IPv6 disabled stateT)�exc_infoN)r@�touchr3�logger�debugrBrCr7�_persist_ipv6_disabledrJ�s_��M��"�"�$�$�$�$�$���M�M�M����<�t��L�L�L�L�L�L�M���s��&A�Ac��t�;t��}t|���a|st�d��tS)N��ipv6_enabledz9IPv6 disabled at startup (kernel flag or persisted state))r=rDr"rH�inforLs r7�_get_ip_chooserrO�sO����/�1�1�1��3��N�N�N���	��K�K�K�
�
�
��rCc�V�t�tt�����atS)N)�
ip_chooser)r>r!rOrBrCr7�_get_transportrR�s$����!�_�->�->�?�?�?�
��rCc��eZdZdZdS)�IntegrityErrorzERaised when on disk content does not match hashes in description.jsonN��__name__�
__module__�__qualname__�__doc__rBrCr7rTrT�s������O�O�O�OrCrTc��eZdZdZdS)�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[c���K�t�d�||�����tjtjd|z��tz���d{V��dS)Nz2Files update failed with error: {err}, try: {try_})�err�try_r$)rH�warningr/�asyncio�sleep�random�	randrange�_TIMEOUT_MULTIPLICATOR��exc�is  r7�_log_failed_updaterh�sy����
�N�N�<�C�C��!�	D�	
�	
�����-��(��a��0�0�3I�I�
J�
J�J�J�J�J�J�J�J�J�JrC�path�modec���td��5tj|tjtjztjz|��}ddd��n#1swxYwYtj|d��S)zbOpen file at `path` using permission `mode` for writing in binary mode
    and return file object.rN�wb)r�osr0�O_WRONLY�O_CREAT�O_TRUNC�fdopen)rirj�fds   r7�_open_with_moders�s���
��	�	�H�H�
�W�T�2�;���3�b�j�@�$�
G�
G��H�H�H�H�H�H�H�H�H�H�H����H�H�H�H�
�9�R����s�;A�A�Ac	���t||���5}tjtj|d|d�d�������cddd��S#1swxYwYdS)N��timeout�file�headerszutf-8)�encoding)�
_fetch_url�json�load�io�
TextIOWrapper�get_content_charset)�urlrv�responses   r7�_fetch_json_syncr��s���	�C��	)�	)�	)�
�X��y����� �!�)�,�@�@��I�I�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
����
�
�
�
�
�
s�AA&�&A*�-A*r�rfc��t��}|���rg|���rUt�d||���|��|���t��dSdSdS)Nz>Network error for %s via IPv6 IP %r, disabling IPv6. Error: %r)rO�is_ipv6_enabled�last_ip_was_ipv6rHr_�last_ip�disable_ipv6rJ)r�rf�choosers   r7�_disable_ipv6_on_network_errorr��s������G���� � �!�W�%=�%=�%?�%?�!����L���O�O����		
�	
�	
�	������� � � � � �!�!�!�!rC)�on_error�	max_triesc��ZK�tj��}	|�dt||���d{V��S#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)
z�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.
    Nz!json decode error [{}] for url {}�request to {} timed out�request to {} reset�%eof error while updating files, url: �, err: �8urllib/http error while updating files, url: {}, err: {}�Can't fetch �
, reason: )r`�get_event_loop�run_in_executorr��UnicodeDecodeErrorr{�JSONDecodeErrorr[r/�socketrvr��ConnectionResetError�EOFError�http�client�
HTTPException�urllib�error�URLErrorr3)r�rv�loop�es    r7�_fetch_jsonr��s������!�#�#�D�=��)�)�$�0@�#�w�O�O�O�O�O�O�O�O�O���� 4�5�N�N�N��=�D�D�Q��L�L�M�M�M������>�A�A�A�&�s�A�.�.�.��3�:�:�3�?�?�@�@�@������=�=�=�&�s�A�.�.�.��/�6�6�s�;�;�<�<�<������
�
�
��C�C�C�C��C�C�
�
�	
�����
�K�%�v�|�'<�=�
�
�
�&�s�A�.�.�.��F�M�M��Q�
�
�
�
�	
�����
�=�=�=�&�s�A�.�.�.��;��;�;��;�;�<�<�<�����=���sQ�":�F*�#A3�3F*�2B7�7
F*�2C6�6
F*�D�(F*�3E3�3
F*�%F%�%F*�rxrvc�
�tj�|dtj��pdi|�d���}t���||���5}|j|jfcddd��S#1swxYwYdS)z>Perform HEAD http request to *url* with *timeout* & *headers*.�Imunify-Server-Id��HEAD)rx�methodruN)	r��request�Requestr�
get_server_idrRr0�coderx)r�rvrx�req�rs     r7�_perform_http_head_syncr�s����.�
 �
 ����!9�!;�!;�!A�r�
��
��
!���C�
�	�	�	�	�s�G�	�	4�	4�!���v�q�y� �!�!�!�!�!�!�!�!�!�!�!�!����!�!�!�!�!�!s�A8�8A<�?A<�
current_mtimec���K�|turdSt|d���}	tt||d|i����d{V��\}}|dkrt	d|�d|�����tt��5t|d	�����}||kr$t�
d
||d	||��ddd��n#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)z�Check 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-Sincer�N��zUnexpected 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'r�r�r�i0Fr�)�_NEVERrr r�r[r�	Exceptionr	�	timestamprHr_r�rvr�r/r�r�r�r�r�r�r��hasattrr�)r�r�rv�formatted_mtimer�rx�
last_mtimer�s        r7�_need_to_downloadr�s����������t� ��t�<�<�<�O�-�'�#���(�/�:�	
�
�
�
�
�
�
�
�
�
��g�,�3�;�;��:��:�:�S�:�:���
��i�
 �
 �	�	�.���(����i�k�k�
��]�*�*����4���O�,�#�����	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�$�t��M�>�A�A�A�&�s�A�.�.�.��3�:�:�3�?�?�@�@�@������=�=�=�&�s�A�.�.�.��/�6�6�s�;�;�<�<�<������K�%�v�|�'<�=�
�
�
��1�f���	�!�&�C�-�-��5�5�5�5�5�&�s�A�.�.�.��F�M�M��Q�
�
�
�
�	
�����	
���sH�#C�2AC�C�C�G�,2D�
G�+2E�(G�G�&3G�GT)�compressc#��K�i}|�||d<dtj��pdi}|r|�ddi��tj�||���}t
��j|fi|��5}t��5}|j	�
d��dk}|rl|sj|j	�
d	��d
krLt�d|j	�
d��|j	�
��|��|r#|�t|�����n||j	d
�V�ddd��n#1swxYwYddd��dS#1swxYwYdS)zs
    Fetch *url* as binary file.
    If *compress* is true, ungzipping is done automatically
    if necessary.
    Nrvr�r�zAccept-Encoding�gzipr��Content-EncodingzContent-Typezapplication/zipz�Requested 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)rr��updater�r�r�rRr0rrx�getrHrN�items�
enter_contextr
)	r�rvr��
parameters�req_headersr�r��stack�gzippeds	         r7rzrzQs7�����J��� '�
�9��&�
�(@�(B�(B�(H�b�I�K��8����-�v�6�7�7�7�
�.�
 �
 ��k�
 �
:�
:�C�	��	�	�	��
�
��
�
�
�	�9�;�;�
�"'��"�&�&�'9�:�:�f�D���	��	�� �$�$�^�4�4�8I�I�I��K�K�J�� �$�$�%7�8�8�� �&�&�(�(��

�
�
����#�#�H�X�$>�$>�$>�?�?�?���'�

�
�	
�	
�	
�%
�
�
�
�
�
�
�
�
�
�
����
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
����
�
�
�
�
�
s7�7E(�B>E�E(�E	�E(�E	�E(�(E,�/E,�	dest_filec�0�tj��}|���}t|||���5}|d�t
��x}rL|�|��|�|��|d�t
��x}�Lddd��n#1swxYwY|d�d��dks�|���|z
}	|d�dd��}
|
�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.
    )rvr�rwNrxr�r�zContent-Lengthz&{got} bytes read, {diff} more expected)�got�diff)�message�contentzcontent fetched from z does not match hash: expected=z, got=)�hashlib�md5�tellrzr1�_BUFSIZEr��writer��intr�r��ContentTooShortErrorr/�	hexdigestr[)
r�r�rvr��md5sumr��initial_file_offsetr��chunk�file_length�content_length_header�expected_file_length�
got_md5sums
             r7�_fetch_n_md5sum_urlr�~s���+�-�-�C�#�.�.�*�*��	�C��8�	<�	<�	<�#����'�,�,�X�6�6�6�e�	#��J�J�u�����O�O�E�"�"�"� ��'�,�,�X�6�6�6�e�	#�#�#�#�#�#�#�#�#�#�#�#����#�#�#�#��I��"�"�#5�6�6�&�@�@� �n�n�&�&�)<�<��!)�� 3� 7� 7�8H�$� O� O�� �,�#&�'<�#=�#=� �#�{�2�2��l�7�7�D�K�K�'�1�K�?�L���!�8���������J�
��j�F�2�2��
4�C�
4�
4��
4�
4�'1�
4�
4�
�
�	
��s�A/B5�5B9�<B9rgc��(K�dt|��vS)NzHTTP Error 404)�strres  r7�$_fetch_and_save_should_retry_handlerr��s�����3�s�8�8�+�+rC)r�r��should_retry�r�r��	dest_path�	dest_modec	��0K�	t||��5}tt|||||����d{V��cddd��S#1swxYwYdS#tj$r7}t||��t
d�|�����d}~wt$r7}t||��t
d�|�����d}~wt$r*}t||��t
d|�d|�����d}~wtjjtjjf$r8}t||��t
d�||�����d}~wt $r-}t||��t
d|�d	|�d
|�����d}~wwxYw)z�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.
    r�Nr�r�r�r�r�r�z to r�)rsr r�r�rvr�r[r/r�r�r�r�r�r�r�r�r3)r�r�rvr�r�r�r�r�s        r7�_fetch_and_saver��sT����0L�
�Y�	�
2�
2�	�i�"�#����!��
���������	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	�	���>�A�A�A�&�s�A�.�.�.��3�:�:�3�?�?�@�@�@������=�=�=�&�s�A�.�.�.��/�6�6�s�;�;�<�<�<������
�
�
�&�s�A�.�.�.��C�C�C�C��C�C�
�
�	
�����
�K�%�v�|�'<�=�
�
�
�&�s�A�.�.�.��F�M�M��Q�
�
�
�
�	
�����
�L�L�L�&�s�A�.�.�.��J��J�J�)�J�J�q�J�J�K�K�K�����L���si�A� A�A�A�A�A�	A�F�2B�
F�2C�
F�%D�(F�(3E�
F�((F�F�_Itemr��datac�&�d�|dD��S)z,Return a set of _Item for easy manipulation.c�F�h|]}t|d|d����S)r�r�)r�)�.0�items  r7�	<setcomp>z_items.<locals>.<setcomp>�s*��I�I�I�4�E�$�u�+�t�H�~�.�.�I�I�IrCr�rB)r�s r7�_itemsr��s��I�I�4��=�I�I�I�IrCc��d�}|||��tj|��D]d\}}}|D],}|tj�||��|���-|D],}|tj�||��|���-�edS)z�Check 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)Ni�zGFixing wrong permission to file/dir %s [%s] expected [%s] (not symlink)z&Failed to change permission to file %s)rm�lstat�st_moderi�islinkrHr_�oct�chmod�PermissionErrorr�)�
file_dir_path�
permission�current_modes   r7�	_os_chmodz"check_mode_dirs.<locals>._os_chmod�s���	��8�M�2�2�:�U�B�L��z�)�)�"�'�.�.��3�3�)����;�!���%�%��
�O�O�������
�3�3�3�3�3�*�)�)�)���	�	�	��L�L�8�-�
�
�
�
�
�
�	���s�B
B�%B>�=B>N)rm�walkri�join)	�dirname�dir_perm�	file_permr�ri�dirs�files�	directory�names	         r7�check_mode_dirsr�s������&�I�g�x� � � ��W�W�-�-�;�;���d�E��	?�	?�I��I�b�g�l�l�4��3�3�X�>�>�>�>��	;�	;�D��I�b�g�l�l�4��.�.�	�:�:�:�:�	;�;�;rC�description_path�
files_pathc���||jvsJ�|j}|}||krR|���r/|�d���|�dd���dS|j}||k�PdSdS)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)�parents�exist_okN)r
�parent�is_file�unlink�mkdir)rr�_dir�topmost_dirs    r7�_fix_directory_structurers����)�1�1�1�1�1��"�D��K�
�*�
�
��<�<�>�>�	��K�K�4�K�(�(�(����d�T��:�:�:��E��{���*�
�
�
�
�
�
rCc��eZdZeej��Zee��ZiZ	iZ
e��Ze��ZiZ
dZedez���ej��ZdDd�Zd�Zd�Zdejd	d
fd�Zdejfd�Zd
�Zeddd�dedededededed	d
fd���Z ed	efd���Z!ed	e"efd���Z#eded	efd���Z$dEd	efd�Z%d	efd�Z&d	e"efd�Z'ed���Z(d	e)efd�Z*d�Z+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-���Z<d	efd.�Z=dEd/ed0ed	d
fd1�Z>d	e?e@e?eeezfffd2�ZAd	eBefd3�ZCdFd4�ZDd5e2j3d,e2j3d	eEe2j3fd6�ZFd	efd7�ZGe:d8ejd9ejd:e"ed	d
fd;���ZHd<ed	efd=�ZIdFd>�ZJdFd?�ZKdEdFd@�ZLe	dGdAeEed	d
fdB���ZMeded	d
fdC���ZNd
S)H�Indexz/static�)�periodTc�X�||jvrtd|�d|j�����||_d|_dgi|_|���}	t
|��5}tj|��|_ddd��n#1swxYwYn�#t$rEt�d|��ttj|��t��YnTt 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 Fr�NzPath %s has a file in parentszcannot read description file {}Tz'some files are missing or corrupted: {}z, )�_TYPES�
ValueError�type�	_is_blank�_json�_descriptionfile_pathr0r{r|�NotADirectoryErrorr�_throttled_log_errorr�pathlib�Path�	FILES_DIR�FileNotFoundErrorr�r�rTr/�_corrupted_files�lenr�r)�self�type_�integrity_checkrir6r��	bad_filess       r7�__init__zIndex.__init__1s)�����#�#��(�U�(�(��+�(�(���
���	�����r�]��
��)�)�+�+��	"��d���
*�q�!�Y�q�\�\��
�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*����
*�
*�
*�
*���!�	D�	D�	D��&�&�'F��M�M�M�$�W�\�$�%7�%7��C�C�C�C�C���� �
�		"�		"�		"�
�
�$�5�<�<�T�B�B�����"�D�N�N�N�N�N�N�����		"�����	��-�-�/�/�I��9�~�~�
�$�=�D�D��	�	�)�,�,������
�~�	#�� � �"�"�"�"�"�	#�	#sC�B�B�9B�B	�	B�B	�
B�AD0�D0�:,D+�+D0c��|j|jko/|j|jko|j|jko|j|jkS�N)�	__class__rrr)r&�others  r7�__eq__zIndex.__eq__\sH���N�e�o�-�
*��	�U�Z�'�
*���%�/�1�
*��
�e�k�)�		
rCc��d|jj�d|j�d|j�dt	|������d�	S)N�<z(type_=z) is_blank=z	, json={<z item(s)>}>)r-rVrrr%r��r&s r7�__repr__zIndex.__repr__ds\��
���'�
�
��	�
�
���
�
��4�:�:�<�<�(�(�
�
�
�	
rCrr&Nc��t�d|j|��|�|��}||jd���dS)zjWhether *files_path* dir may be used for this type's file group.

        :raises: IntegrityError
        zValidating [%s]: %sT�r(N)rHrNr�_make_file_group�r&r�	FileGroups   r7�validatezIndex.validatelsT��
	���)�4�9�j�A�A�A��)�)��
�
�	�	�	�$�)�T�2�2�2�2�2�2rCc�6���G��fd�d�j��}|S)zV
        Return FileGroup class: Index class with local path == *files_path*.
        c�6��eZdZededef��fd���ZdS)�)Index._make_file_group.<locals>.FileGroupr'r&c�F��|�jksJ�tj���S�z+Return local base path for given file type.)rrm�fspath)�clsr'rr&s  ��r7rz4Index._make_file_group.<locals>.FileGroup.files_path~s(�����	�)�)�)�)��y��,�,�,rCN)rVrWrX�classmethodr�r)rr&s��r7r8r<}sP�������
�
-�s�
-�s�
-�
-�
-�
-�
-�
-��[�
-�
-�
-rCr8)r-r7s`` r7r6zIndex._make_file_groupxsG����
	-�	-�	-�	-�	-�	-�	-�	-���	-�	-�	-��rCc	�.�tj|j}ttj�tj�ttj	|jtj
����|d|d��dS)N�dirrw)r�_PERMSrrrmri�normpathr�r"�_PATHS�pardir)r&�permss  r7rzIndex.check_mode_dirs�sq����T�Y�'����G�������Y���T�Y�(?���K�K�
�
�
�%�L��&�M�	
�	
�	
�	
�	
rCF��all_zip�	essentialr'�
relative_pathr�rrJrKc��|j�|��|r|j�|��||j|<||d�|j|<||j|<dS)a�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.
        )rCrwN)r�add�_ESSENTIAL_TYPESrFrD�_ALL_ZIP_SUPPORT)r@r'rLr�rrJrKs       r7�add_typezIndex.add_type�sj��,	�
���u�����	,�� �$�$�U�+�+�+�)��
�5��$,�i�@�@��
�5��&-���U�#�#�#rCc��BK�td�|jD����S)zuWhether essential files exist.

        Note: the files may be corrupted (integrity check is not performed).
        c3�DK�|]}t|d���jV��dS)Fr5N)rr)r�r's  r7�	<genexpr>z.Index.essential_files_exist.<locals>.<genexpr>�sI����
�
���e�U�3�3�3�=�=�
�
�
�
�
�
rC)�allrO�r@s r7�essential_files_existzIndex.essential_files_exist�s9�����
�
��-�
�
�
�
�
�	
rCc�4�|j���S)z&Return a set of all known files types.)r�copyrVs r7�typeszIndex.types�s���z��� � � rCc�b�tj�t|j|��Sr>)rmrir�r"rF�r@r's  r7rzIndex.files_path�s!���w�|�|�I�s�z�%�'8�9�9�9rCc�
�|rJ|jtjjvr2tj�|���d��Stj�|�|j��d��S)z9Return local path for description.json for current index.�description.json)	rr�FilesUpdate�DISABLEDrmrir��_descriptionfile_path_latestr)r&�latests  r7rzIndex._descriptionfile_path�sm���	�d�i�6�#5�#>�>�>��7�<�<��1�1�3�3�5G���
��w�|�|�D�O�O�D�I�6�6�8J�K�K�KrCc�r�d�|������D��d}|S)Nc�.�g|]}|d�
|d��S)rbrCrB)r��xs  r7�
<listcomp>z6Index._descriptionfile_path_latest.<locals>.<listcomp>�s%��J�J�J�A�a��k�J�q��x�J�J�JrCr)�	_get_list�values)r&�ltss  r7raz"Index._descriptionfile_path_latest�s5��J�J����!1�!1�!8�!8�!:�!:�J�J�J�1�M���
rCc�R�t��}t|j��D]�}|�|j��}	t|tjt��}n%#t$r|�
|��Y�_wxYw||jkr|�
|����|S)z9Return a set of file paths that are missing or corrupted.)�setr�r�
localfilepathr�rr�r�r�r#rNr�)r&r)r�ri�actuals     r7r$zIndex._corrupted_files�s����E�E�	��4�:�&�&�	$�	$�D��%�%�d�h�/�/�D�
�"�4���h�?�?����$�
�
�
��
�
�d�#�#�#���
�������$�$��
�
�d�#�#�#���s� A!�!B�Bc��|j|S)z`
        usage example:
        >> async with Index.locked(WHITELISTS):
            ...
        )�_lockr\s  r7�lockedzIndex.locked�s���y���rCc�D���fd�t�j��D��S)z(Return iterable over all files in index.c3�L�K�|]}��|j��V��dSr,�rlr��r�r�r&s  �r7rTzIndex.files.<locals>.<genexpr>�s3�����L�L���"�"�4�8�,�,�L�L�L�L�L�LrC)r�rr2s`r7rzIndex.files�s'���L�L�L�L���
�9K�9K�L�L�L�LrCc��|jdS)z+Return 'items' field from JSON description.r�)rr2s r7r�zIndex.items�s���z�'�"�"rCc��	tj|�d�����jS#t$r|cYSwxYw)zBReturn mtime of description file if it exists, otherwise -math.infT�rb)rm�statr�st_mtimer3)r&�defaults  r7�_descriptionfile_mtimezIndex._descriptionfile_mtime�sO��	��7�4�5�5�T�5�B�B�C�C�L�L���	�	�	��N�N�N�	���s�,/�>�>c��|���}|sdS|tjjzt	j��kS)z4Return True if last update was too late in the past.T)r{rr_�PERIOD�time)r&�_desc_mtimes  r7�_is_outdatedzIndex._is_outdated�s<���1�1�3�3���	��4��V�/�6�6�����D�DrCrvc��K�|jpyt|�����dkpT|���o@t	|�|j��|���|���d{V��S)z>Return True if update from server is needed for current index.rN)rr%r$r�r��_descriptionfile_urlrr{)r&rvs  r7�is_update_neededzIndex.is_update_needed�s�����
�N�	
��4�(�(�*�*�+�+�a�/�	
��!�!�#�#��+��-�-�d�i�8�8��/�/�1�1����������	
rCc���	td��5tj|||���ddd��dS#1swxYwYdS#t$r"}t	t|����|�d}~wwxYw)z)Create local directory for current index.r)rjrN)rrm�makedirsr3r[r�)r&r��dir_moderr�s     r7�	_makedirszIndex._makedirss���	-���"�"�
G�
G���G�(�X�F�F�F�F�
G�
G�
G�
G�
G�
G�
G�
G�
G�
G�
G�
G����
G�
G�
G�
G�
G�
G���	-�	-�	-��c�!�f�f�%�%�1�,�����	-���s2�A�6�A�:�A�:�A�
A/�
A*�*A/�	to_updatec���K�|�|��}||jd���}|j|jd}|j|jd}|D]�}|�|j��}	t
j�|	��}
t
j�|
��s|�	|
|d���t|j|	|||j����d{V����dS)zX
        Fetch files from *to_update* set, verify hashes, save to *files_path*.
        Fr5rCrw�r)r�r�N)r6rrDrlr�rmrir��isdirr�r�r�)r&rr�rvr8�fgr��	file_moder��filenamer�s           r7�
_update_fileszIndex._update_filess�����)�)��
�
�	��Y�t�y�%�
8�
8�
8���9�R�W�%�e�,���I�b�g�&�v�.�	��	�	�D��'�'���1�1�H��g�o�o�h�/�/�G��7�=�=��)�)�
B����w��5��A�A�A�!�����#��{����
�
�
�
�
�
�
�
�	�	rC�remote_itemsc�����t�j��}�fd�|D��}�fd�|D��}||z
}�������fd�|D��}||z
}||fS)z�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.c�D��h|]}��|j����SrBrsrts  �r7r�z+Index._calculate_changes.<locals>.<setcomp>7s)���L�L�L��t�)�)�$�(�3�3�L�L�LrCc�D��h|]}��|j����SrBrsrts  �r7r�z+Index._calculate_changes.<locals>.<setcomp>8s)���N�N�N���*�*�4�8�4�4�N�N�NrCc�L��h|] }��|j���v�|��!SrBrs)r�r�r)r&s  ��r7r�z+Index._calculate_changes.<locals>.<setcomp><s>���
�
�
���!�!�$�(�+�+�9�<�<�
�<�<�<rC)r�rr$)	r&r��local_items�local_files�remote_files�	to_remove�	local_setr�r)s	`       @r7�_calculate_changeszIndex._calculate_changes-s������T�Z�(�(��L�L�L�L��L�L�L��N�N�N�N��N�N�N���,�.�	��)�)�+�+�	�
�
�
�
�
�#�
�
�
�	�
!�9�,�	��)�#�#rCc�N�d�t|j|��S)z'Return remote path for description.jsonz{}{}/description.json�r/�BASE_URLrFr\s  r7r�zIndex._descriptionfile_urlDs!��'�-�-�h��
�5�8I�J�J�JrCc�N�d�t|j|��S)zReturn remote path for all.zipz{}{}/all.zipr�r\s  r7�_all_zip_urlzIndex._all_zip_urlIs!���$�$�X�s�z�%�/@�A�A�ArCc��	tj|��n@#t$r3}t�d|t|����Yd}~nd}~wwxYw|r3t�d|��tj|d���dSdS)Nzfailed to remove %s: %sz'Removing old path on all.zip update: %sT��
ignore_errors)	rmrr3rHr_r�rN�shutil�rmtree)r�all_zip_localpath�remove_filesr�s    r7�_all_zip_cleanupzIndex._all_zip_cleanupNs���	��I�'�(�(�(�(���	�	�	��N�N�)�+<�c�!�f�f�
�
�
�
�
�
�
�
�����	�����	:��K�K�A�:�N�N�N��M�*�D�9�9�9�9�9�9�	:�	:s��
A�)A�A�	live_pathc��tj����d��}|�|j|z��S)z�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.

        z_%Y-%m-%dT%H%M%S.%fZ)�DT�datetime�utcnow�strftime�	with_namer)r��
new_suffixs  r7�_generate_new_pathzIndex._generate_new_pathZsA���[�'�'�)�)�2�2�3I�J�J�
��"�"�9�>�J�#>�?�?�?rCc	��K�tj|�|j����}t�|��}|�|jdz��}|j|jd}|j|jd}|�	|j��}t��5}|�||d���|�tj
||d���t||||d����d	{V��}		tj|d
��5}
t#|
|��d	d	d	��n#1swxYwYt%j|��D]v\}}}
|D]5}t%jt$j�||��|���6|
D]5}t%jt$j�||��|���6�w|�|��|�||��}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.ziprwrCFr�T)r�)r�r�Nr�)&r r!rrrr�r�rrDr�rr��callbackr�r��zipfile�ZipFile�_safe_extractallrmr�r�rir�r9�_replace_live_with_new_dirr�rTr3r�
BadZipfile�LargeZipFiler[r��pop_all�boolrr_r`)r&rvr��new_path�archive_pathr�r��all_zip_url�rollback_stack�_�archive�root�directories�	filenamesrr��old_pathr�s                  r7�_run_update_all_zipzIndex._run_update_all_zipesT�����L������!;�!;�<�<�	��+�+�I�6�6���)�)�(�-�)�*C�D�D���K��	�*�6�2�	��;�t�y�)�%�0���'�'��	�2�2��
�[�[�1	%�N��N�N�8�X��N�>�>�>��#�#��&���!�	
$�
�
�
�&����#�����������A�
1��_�\�3�7�7�8�7�$�W�h�7�7�7�8�8�8�8�8�8�8�8�8�8�8����8�8�8�8�
57�G�H�4E�4E�J�J�0�D�+�y�%0�J�J�	�������d�I�!>�!>��I�I�I�I�$-�J�J��������d�H�!=�!=�y�I�I�I�I�J��
�
�h�'�'�'� �:�:�8�Y�O�O���������"��$�
�
1�
1�
1�"�#�a�&�&�)�)�q�0�����
1����
�"�"�$�$�$�c1	%�1	%�1	%�1	%�1	%�1	%�1	%�1	%�1	%�1	%�1	%����1	%�1	%�1	%�1	%�h	����l��h���	�	
�	
�	
��y�� 2� ;�;�;s\�>AJ�H	�*E�;H	�E	�H	�E	�B9H	�J�	3I�<I�I�J�J�J�version�forcec���K�tj|�|j����}|���std|j�d|�����|dkrY|���}d�|���D��d}t�	d|j|��|�
d���}	t|d	z����
����t|��kr|std
|�d|j�����n(#t$rtd|j�d|�����wxYw|j���D]�}|���s�|���r�t|d	z����
����t|��krh|�|jd
z��}|�|d���|�|��|�d����d{V��dS��td
|�d|j�����)aUpdate 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)r��ver�props   r7rfz#Index.update_to.<locals>.<listcomp>�s5�����!��T��h������rCrz%Try to update to latest version %s %sF��strict�VERSIONzVersion z is already set for z5, because current version doesn't have VERSION file: �liveT��target_is_directory��
is_updatedNz not found in )r r!rr�
is_symlinkr[rgr�rHrN�resolver
�	read_textr2r#r�iterdir�is_dirr�r�
symlink_to�rename�
_run_hooks)r&r�r�r��versions�current_pathri�
new_live_paths        r7�	update_tozIndex.update_to�s������L������!;�!;�<�<�	��#�#�%�%�	��+��9�9�9�i�i�)���
��h����~�~�'�'�H���%-�^�^�%5�%5������G�
�K�K�7���G�
�
�
�!�(�(��(�6�6��
	���	�1�<�<�>�>�D�D�F�F�G�G��7�#�#�$�$��$�"�G�w�G�G�D�I�G�G������!�	�	�	��+�'+�y�y�y�,�,�@���
�	�����$�,�,�.�.�		�		�D��?�?�$�$�
������
��D�9�,�7�7�9�9�?�?�A�A�B�B�g��G�G���%)�N�N�4�9�v�3E�$F�$F�M�!�,�,�T�t�,�L�L�L�!�(�(��3�3�3��/�/�T�/�:�:�:�:�:�:�:�:�:��F�F���k�'�'�'�4�9�9�M�N�N�Ns
�A#D7�7%Ec���tj|�|j����}|���siS|�d���}i}t
d��}|j���D]�}|���r�|���|krd}nd}|dzx}�	��rqd}	|�
��}t
|��}	|dt|��d�||	<|	|kr|	}��#t$rt�d||��Y��wxYw��|t
d��krd||d<|S)	NFr�r,Tr�)�currentrbrCz Version file %s is not valid: %srb)r r!rrr�r�r
rr�rAr�r�rrHr�)
r&r�r��result�max_versionrir��version_filer��_vers
          r7rgzIndex._get_list�s����L������!;�!;�<�<�	��#�#�%�%�	��I� �(�(��(�6�6�����c�l�l���$�,�,�.�.�	�	�D���� � �
���|�|�~�~��-�-������ $�y� 0�0��8�8�:�:�
����*�4�4�6�6�G�"�7�+�+�D�#*�"'�"�4�y�y�$�$�F�4�L�
�k�)�)�&*����!�����L�L�:�$�����
�H�
����
�&�����%�%�,0�F�;���)��
s�AD�&D>�=D>c���g}t|������d����D]4\}}|drdn|drdnd}|�|�|�����5|S)z/Return list of versions available in the index.c��|dS)NrrB)res r7�<lambda>z Index.get_list.<locals>.<lambda>s
��A�a�D�rC)�keyr�z
 (current)rbz	 (latest)r�)�sortedrgr��append)r&r�r�r��markers     r7�get_listzIndex.get_lists�����#��N�N���"�"�$�$�.�.�
�
�
�
	0�
	0�M�G�T�
�	�?������>��[�[��
�
�M�M�W�.�f�.�.�/�/�/�/��
rCc���tj|�|j����}|���sdS|�d���}|j���D]�}tj	�
tjj��tj	�
|���jtjj��z
j}|���rf|���sR||krL|t$jjkr7t*�d|j|��t/j|d�����dS)z~Remove old versions of files.

        This is done by removing old directories in the path, that older than 30 days.
        NFr�zRemoving old version of %s: %sTr�)r r!rrr�r�rr�r�r��now�timezone�utc�
fromtimestamprxry�daysr�rr_�DAYS_TO_KEEPrHrNr�r�)r&r�r�ri�days_olds     r7�_clean_old_versionszIndex._clean_old_versions s6���L������!;�!;�<�<�	��#�#�%�%�	��F� �(�(��(�6�6���$�,�,�.�.�	8�	8�D��������0�0��+�+�+��I�I�K�K�(�"�+�/�����
����
�
�
8����)�)�
8��L�(�(��� 2� ?�?�?����<�d�i��N�N�N��
�d�$�7�7�7�7��	8�	8rCr�c	��|jtjjvr7|���t
�d|j|��dS|�|jdz��}d}t��5}|�
|d���|�|j��|�
��r|�d���nd}td��D]�}	|�|��n�#t"$r�|r�|�
��sj|�|jd	z��}t
�d
|||��|�|��|�|j|��Y��wxYw|�t'j|d���|���ddd��n#1swxYwY|S)zbReplace *live_path* with *new_path*.

        Return *old_path*

        :raises: OSError
        zFSkipping update for %s, because it is disabled. New files stored in %sNr�Tr�Fr��z.live-movedz1Moving %s [live] to %s, to rename %s to it [live]r�)rrr_r`r�rHrNr�rrr�r�rr�r��ranger��IsADirectoryError�replacer�r�r�)r&r�r�r��
moved_pathr�r��lasts        r7r�z Index._replace_live_with_new_dir:s^���9��*�3�3�3��$�$�&�&�&��K�K� ��	��	
�
�
��4� �*�*�8�=�6�+A�B�B�
��
�
�[�[�-	%�N��$�$�X�4�$�H�H�H��#�#�M�$8�9�9�9��'�'�)�)��	�!�!��!�/�/�/��
��a���
O�
O��O�!�(�(��3�3�3��E��(�O�O�O����
%�/�/�1�1�O�%2�%<�%<�)�.��>�&�&�
����9�%�&�)����"�)�)�*�5�5�5�'�/�/�
�0B�I�N�N�N���-O����0�%��
�j��=�=�=�=�
�"�"�$�$�$�[-	%�-	%�-	%�-	%�-	%�-	%�-	%�-	%�-	%�-	%�-	%����-	%�-	%�-	%�-	%�^�s8�=A/G
�-D�G
�BF�G
�F�/G
�
G�Gc	���K����j��}t||����d{V��}��t	|����\}}|p|}|s6t
�d�j������dStj	��
�j����}|���r|�d���nd}t�|��}	t��5}
��|	�j�jdd���|
�t&j|	d�	��|r|���r|n|}|���rAt�||	|��fd
�|D�������d{V����|	||����d{V��	t3|	dz�j�jd��5}|�t7j|�������ddd��n#1swxYwY��|	����|	|��}n6#t@tBf$r"}
tEtG|
����|
�d}
~
wwxYw|
�$��ddd��n#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.Fr�rCr�Tr�c3�L�K�|]}��|j��V��dSr,rsrts  �r7rTz$Index._run_update.<locals>.<genexpr>�sD�����$�$�9=��*�*�4�8�4�4�$�$�$�$�$�$rCr^rwz,Removing old path on file by file update: %s)(r�rr�r�r�rHrN�_touchr r!rr�r�rr�rr�rDr�r�r�r��	_copytree�unionr�rsr�r{�dumps�encoder9r�rTr3r[r�r�rr_r`)r&rvr��as_jsonr�r��need_updater�r�r�r��	from_pathrwr�s`             r7�_run_updatezIndex._run_updates������'�'��	�2�2��#�C��9�9�9�9�9�9�9�9�9��#�6�6�v�g���G�G��	�9��,�9���	��K�K�9�4�9�E�E�E��K�K�M�M�M��5��L������!;�!;�<�<�	�09�/C�/C�/E�/E�O�I���U��+�+�+�4�	��+�+�I�6�6���[�[�*	%�N��N�N��$�+�d�i�0��7�%�
�
�
�
�
�#�#��
�x�t�
$�
�
�
�%�I����):�):�I���	�
����!�!�
��o�o����O�O�$�$�$�$�AJ�$�$�$�������������$�$�X�y�'�$�J�J�J�J�J�J�J�J�J�
1�$��1�1��K��	�*�6�2���=���J�J�t�z�'�2�2�9�9�;�;�<�<�<�	=�=�=�=�=�=�=�=�=�=�=����=�=�=�=��
�
�h�'�'�'� �:�:�8�Y�O�O����"�G�,�
1�
1�
1�!�#�a�&�&�)�)�q�0�����
1����
�"�"�$�$�$�U*	%�*	%�*	%�*	%�*	%�*	%�*	%�*	%�*	%�*	%�*	%����*	%�*	%�*	%�*	%�Z�	8����)�)�	8��K�K�>��
�
�
�
�M�(�$�7�7�7�7��y�� 2� ;�;�;s[�CK1�5)J�:I$�J�$I(	�(J�+I(	�,.J�K1�K�,K	�	K�K1�1K5�8K5�from_dir�to_dir�
ignored_pathsc��`�K��fd�}ttj||d|d����d{V��dS)z7Copy *from_dir* to *to_dir* except for *ignored_paths*.c����ttj���t��sJ�t	��fd�|D����S)z(Return  names that should not be copied.c3�`�K�|](}tj��|���v�$|V��)dSr,)rmrir�)r�rrris  ��r7rTz8Index._copytree.<locals>.ignore_names.<locals>.<genexpr>�sJ���������7�<�<��d�+�+�}�<�<��<�<�<�<��rC)�
isinstancermr?r��	frozenset)ri�namesrs` �r7�ignore_namesz%Index._copytree.<locals>.ignore_names�s_�����b�i��o�o�s�3�3�3�3�3�������!������
rCT)�symlinks�ignore�
dirs_exist_okN)r r��copytree)r
rrrs  ` r7rzIndex._copytree�su�����	�	�	�	�	���O������

�
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
rCr�c���tj�t|��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 ({}))
rmri�relpathr�_URL_PATH_PREFIXrFrr r!r
r/r�r)r&r��url_relpath�	type_pathrLs     r7rlzIndex.localfilepath�s����g�o�o��S�M�M��� 5�
�
���K��	�*�	��L��#�#�w�|�K�'@�'@�'H�H�H�H�1�8�8��i�H�H�
I�H�H������Y�?�?�
��w�|�|�D�O�O�D�I�6�6�
�F�F�FrCc��	|�d���}tj�|��rtj|��dSdS#t
$r2}t�t|����Yd}~dSd}~wwxYw)z5Update mtime of description.json file so it is fresh.TrwN)	rrmri�isfile�utimer3rHr_r�)r&rir�s   r7rzIndex._touch�s���	#��-�-�T�-�:�:�D��w�~�~�d�#�#�
���������
�
���	#�	#�	#��N�N�3�q�6�6�"�"�"�"�"�"�"�"�"�����	#���s�A	A�
B�'B�Bc��K�t|j|jtg��D]}}	|||���d{V���#tt
f$r&}t�d||��Yd}~�Hd}~wt$r&}t�	d||��Yd}~�vd}~wwxYwt�
d|jd|z��dS)Nzhook %s error: %sz%s files update finished%sz (not updated))r�_HOOKSrr%rTrrHr�r��	exceptionrN)r&r��hookr�s    r7r�zIndex._run_hooks�s�����$�+�d�i�0�<�.�A�A�	?�	?�D�
?��d�4��,�,�,�,�,�,�,�,�,�,��"�N�3�
;�
;�
;����0�$��:�:�:�:�:�:�:�:������
?�
?�
?�� � �!4�d�A�>�>�>�>�>�>�>�>�����
?�������(��I��J��/�	
�	
�	
�	
�	
s!�?�B$�A1�1
B$�>B�B$c��$K�tjj}|sy|�|���d{V��s^t�d|jttjjdz����|�	d����d{V��dS|j
o|j|j}|}|r�d}t�d|j|��	tj
|�tjj��|���d{V��}|r!t�d|j|��nG#tjt"f$r.}t�d	|j||��d
}Yd}~nd}~wwxYw|r�d}t�d|j|��	tj
|�tjj��|���d{V��}|r!t�d|j|��nr#tjt"f$rY}t�d	|j||��|�	d����d{V��|j|jvr|�Yd}~dSd}~wwxYw|�	|p|����d{V��dS)a�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.
        Nz(%s was updated less than %s minutes ago.�<Fr�r�zUpdating %s files via %szUpdated %s using %sz%s update error via %s: %sTzfile by file download)rr_�TIMEOUTr�rHrNrr�r}r�rrPr`�wait_forr��SOCKET_TIMEOUT�TimeoutErrorr[r�r	rO)r&r�rvrJ�file_by_file�log_str�updatedr�s        r7r�zIndex.updates�����$�,���	�4�#8�#8��#A�#A�A�A�A�A�A�A�	��K�K�:��	��F�&�-��3�4�4�
�
�
�
�/�/�U�/�3�3�3�3�3�3�3�3�3��F��.�E�T�%:�4�9�%E��"�{���	$��G��K�K�2�D�I�w�G�G�G�


$� '� 0��,�,��*�9����	!�!���������K��K�K� 5�t�y�'�J�J�J����(�+�6�
$�
$�
$����0�$�)�W�a���� $�����������	
$����
�	�-�G��K�K�2�D�I�w�G�G�G�
� '� 0��$�$�V�%7�%F�G�G��!�!���������K��K�K� 5�t�y�'�J�J�J����(�+�6�	
�	
�	
����0�$�)�W�a�����o�o��o�7�7�7�7�7�7�7�7�7��9�� 5�5�5��G��F�F�F�F�F�����	
�����o�o��)9�E�o�:�:�:�:�:�:�:�:�:�:�:s3�A D2�2E6�$E1�1E6�A H�I/�AI*�*I/�	only_typec��K�|rj||d���}|�|��4�d{V��|�|���d{V��ddd���d{V��dS#1�d{V��swxYwYdS|r�t�d��|jD]i}||d���}|�|��4�d{V��|�|���d{V��ddd���d{V��n#1�d{V��swxYwY�jdSt�d��|jD]i}||d���}|�|��4�d{V��|�|���d{V��ddd���d{V��n#1�d{V��swxYwY�jdS)zkRun update for all registered `types` of files.

        Raises asyncio.TimeoutError, UpdateError.
        Fr5NzUpdating essential fileszUpdating all files)rpr�rHrNrOr)r@r-r��only_essential�indexr's      r7�
update_allzIndex.update_allOss�����	.��C�	�5�9�9�9�E��z�z�)�,�,�
*�
*�
*�
*�
*�
*�
*�
*��l�l�5�)�)�)�)�)�)�)�)�)�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*�
*����
*�
*�
*�
*�
*�
*�
�	.��K�K�2�3�3�3��-�
.�
.����E�5�9�9�9���:�:�e�,�,�.�.�.�.�.�.�.�.��,�,�u�-�-�-�-�-�-�-�-�-�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.����.�.�.�.��
.�
.�

�K�K�,�-�-�-���
.�
.����E�5�9�9�9���:�:�e�,�,�.�.�.�.�.�.�.�.��,�,�u�-�-�-�-�-�-�-�-�-�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.�.����.�.�.�.��
.�
.s5�A�
A&�)A&�<C*�*
C4	�7C4	�	E7�7
F	�F	c�F�|j|�|��dS)z:Add a hook for type_ to be called after successful update.N)r!rN)r@r'r#s   r7�add_hookzIndex.add_hookhs%��	�
�5����d�#�#�#�#�#rC)T)F�r&N)NFF)OrVrWrXrr`�Lockrorkr!rFrDrrOrPrrrrHr�rr*r/r3rm�PathLiker9r6rrAr�r�r�rQrWrrZrrrar$rprrr�r��floatr{r�r�r�r r!r�r�rr�r�r��staticmethodr�r�r�r�rr
rgrr�r�rr�r	rrlrr�r�r1r3rBrCr7rr$s�������
�K���%�%�E�
�[��
�
�F�
�F�
�F�
�S�U�U�F��s�u�u���� ��6�:�:�Q��X�6�6�6�v�|�D�D��)#�)#�)#�)#�V
�
�
�
�
�
�
3�2�;�
3�4�
3�
3�
3�
3��2�;�����
�
�
����.�.�.��.��.��	.�
�.��.��.�
�.�.�.��[�.�8�	
�D�	
�	
�	
��[�	
��!�c�#�h�!�!�!��[�!��:�s�:�s�:�:�:��[�:�L�L�S�L�L�L�L��c������#�c�(������ � ��[� �M�x��}�M�M�M�M�#�#�#�.4��������E�d�E�E�E�E�

�e�

��

�

�

�

�-�-�-�-��!�,��36�u�:��	
�����2$���J�$�	�s�5�z�3�s�8�#�	$�$�$�$�$�.�K��K��K�K�K��[�K��B��B��B�B�B��[�B��	:�	:�	:��\�	:��@�g�l�@�w�|�@�@�@��\�@�O<�D�O<�O<�O<�O<�b1O�1O�s�1O�4�1O�D�1O�1O�1O�1O�f%�4���c�4�#�:�o�)>� >�?�%�%�%�%�N�$�s�)����� 8�8�8�8�4C���C�18��C�	�'�,�	�C�C�C�C�JN<�D�N<�N<�N<�N<�`�
��+�
�')�{�
�CF�s�8�
�	
�
�
�
��\�
�.G��G��G�G�G�G�#�#�#�#�
�
�
�
�B;�B;�B;�B;�B;�H�JO�.�.� ��
�.�	
�.�.�.��[�.�0�$�S�$�4�$�$�$��[�$�$�$rCrc�r�t�tdddd���t�tdddd	���t�td
ddd���t�t
dddd	d���t�td
ddd	d���dS)zRegister required file types.zeula/v1i�i�F)rJzsigs/v1i�i�Tzrealtime-av-conf/v1zwp-rules/v1rIzgeo/v1N)rrQ�EULA�SIGS�REALTIME_AV_CONF�WP_RULES�GEOrBrCr7�	configurer?ns���	�N�N�4��E�5�%�N�@�@�@�	�N�N�4��E�5�$�N�?�?�?�	�N�N���
�
������
�N�N���
�
���
����
�N�N�3��%����N�N�N�N�N�NrCFr-c���K�	t�||���d{V��S#tjtf$r'}t
�d||��Yd}~dSd}~wwxYw)z.Run files.update and log Update/TimeoutErrors.Nz*Failed to update files [%s] with error: %s)rr1r`r)r[rHr�)r-r�r]s   r7�update_and_log_errorrA�s�����
��%�%�i��7�7�7�7�7�7�7�7�7��� �+�.�
�
�
����8�)�S�	
�	
�	
�	
�	
�	
�	
�	
�	
�����
���s� %�A"�A�A"c��NK�	t�d����d{V��S#tjtf$ri}t����d{V��rt�d|��n#t|tj��rt|��Yd}~dSd}~wwxYw)z6Update all files. Don't fail if essential files exist.T)r/Nz2Failed to update files [essential files exist]: %s)	rr1r`r)r[rWrHr�r)r]s r7�!update_all_no_fail_if_files_existrC�s�������%�%�T�%�:�:�:�:�:�:�:�:�:��� �+�.�	�	�	��,�,�.�.�.�.�.�.�.�.�	��L�L�D�c�
�
�
�
��#�w�3�4�4�
�!�s�*��
�
�
�
�
�
�����	���s� %�B$�AB�B$r4)NF)�rYr`r�r�r��http.clientr�r}r{�mathrmr rbr�r�r~r��urllib.errorr��urllib.request�collectionsrr�
contextlibrrr�email.utilsrr	r�r
�	itertoolsr�loggingr�packaging.versionr
�typingrrrrrrrrr�urllib.parser�defence360agent.contractsr�!defence360agent.contracts.licenser�"defence360agent.subsys.panels.baser�defence360agent.utilsrrr�defence360agent.utils.commonrr�defence360agent.utils.threadsr �#defence360agent.utils.net_transportr!r"�defence360agent.utils.zipsafer#r��hooksr%rVrHr!r@r4r.r�r8r:r;r<r=r>r"r�r��_MAX_TRIES_FOR_DOWNLOADrd�infr�r�r�r7�JSONTyper=�__annotations__r>rDrJrOrR�RuntimeErrorrTr[rhr6rsr�r�r�r�r�r�rzr�r�r�r�r�rrrr?r1r�rWrArCrBrCr7�<module>r^s����������������������	�	�	�	���������	�	�	�	�����
�
�
�
�
�
�
�
�
�
�
�
�����������������/�/�/�/�/�/�/�/�:�:�:�:�:�:�:�:�:�:�9�9�9�9�9�9�9�9�������������������%�%�%�%�%�%�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�"�!�!�!�!�!�-�,�,�,�,�,�8�8�8�8�8�8�=�=�=�=�=�=�E�E�E�E�E�E�E�E�E�E�9�9�9�9�9�9�9�9�3�3�3�3�3�3���������N�M�M�M�M�M�������	��8�	�	��#�w�|�$D�E�E��A��:�
��$�����4��
��%�������G�L�0�1�1�	�1����������(�����c�5�$��d�3��8�n�d�3�i�G�H��8<��X�3�
4�;�;�;�%)�
�H�\�"�)�)�)�G�d�G�G�G�G�M�M�M�M�	�6�	�	�	�	�������P�P�P�P�P�\�P�P�P�
�
�
�
�
�,�
�
�
�K�K�K��"�+��S��X�����
�h�
�
�
�
�
!��
!�)�
!��
!�
!�
!�
!�
���,�8O����"=�3�"=�H�"=�"=�"=���"=�L*,�
!�
!�
!�	�
!��
!�
!�
!�
!� 
���,�8O����7�	�7�"�7�-2�7�	�7�7�7���7�t�59�)
�)
�)
�C�)
�U�)
�)
�)
���)
�X&��&�&�&�&�R,�I�,�#�,�$�,�,�,�,�
���
�%�5�	�����0L�0L�0L�	�0L��{�0L�
�0L�	�0L�0L�0L�
��0L�f	�
�7�U�H�-�.�.��J��J��U��J�J�J�J�
;�;�;�F@I����l��07���	�����,G
$�G
$�G
$�G
$�G
$�G
$�G
$�G
$�TO�O�O�O�,
�	���3��,1�	
�	
���}�	
�	�	
�	
�	
�	
�
�
�
�
�
rCdefence360agent/files/__pycache__/hooks.cpython-311.opt-1.pyc0000644000000000000000000000251100000000000020643 0ustar  �

��\��A��H�dZddlmZddlmZmZee��ZiZdd�Z	dS)z9Run default hooks for files update and log errors if any.�)�	getLogger)�	check_run�
CheckRunError�returnNc��K�|r�t�|j��}|rb|���rP	t	|g���d{V��dS#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_HOOKS�get�type�existsrr�logger�error)�files_index_object�
is_updated�hook�es    �P/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/files/hooks.py�default_hookr
s������C�� � �!3�!8�9�9���	C�D�K�K�M�M�	C�
C����'�'�'�'�'�'�'�'�'�'�'�� �
C�
C�
C����>��B�B�B�B�B�B�B�B�B�����
C����C�C�	C�	C�	C�	Cs�A�
B�A>�>B)rN)
�__doc__�loggingr�defence360agent.utilsrr�__name__rrr��r�<module>rsm��?�?�������:�:�:�:�:�:�:�:�	��8�	�	���
�C�C�C�C�C�Crdefence360agent/files/__pycache__/hooks.cpython-311.pyc0000644000000000000000000000251100000000000017704 0ustar  �

��\��A��H�dZddlmZddlmZmZee��ZiZdd�Z	dS)z9Run default hooks for files update and log errors if any.�)�	getLogger)�	check_run�
CheckRunError�returnNc��K�|r�t�|j��}|rb|���rP	t	|g���d{V��dS#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_HOOKS�get�type�existsrr�logger�error)�files_index_object�
is_updated�hook�es    �P/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/files/hooks.py�default_hookr
s������C�� � �!3�!8�9�9���	C�D�K�K�M�M�	C�
C����'�'�'�'�'�'�'�'�'�'�'�� �
C�
C�
C����>��B�B�B�B�B�B�B�B�B�����
C����C�C�	C�	C�	C�	Cs�A�
B�A>�>B)rN)
�__doc__�loggingr�defence360agent.utilsrr�__name__rrr��r�<module>rsm��?�?�������:�:�:�:�:�:�:�:�	��8�	�	���
�C�C�C�C�C�Crdefence360agent/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  �

s�����s���dS)N�r��S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/hooks/__init__.py�<module>rs���rdefence360agent/hooks/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000030000000000000020333 0ustar  �

s�����s���dS)N�r��S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/hooks/__init__.py�<module>rs���rdefence360agent/hooks/__pycache__/execute.cpython-311.opt-1.pyc0000644000000000000000000001753700000000000021221 0ustar  �

�k��������ddlZddlZddlZddlZddlZddlmZddlmZ	ddl
mZddlm
Z
ddlmZddlmZmZe��Zd�Zd
d
�Zd
d�Zejfd�ZdS)�N)�Core��native)�EventHookLogger)�	EventHook)�db)�run�
snake_casec��tjrgStj���tj|k��}t
|��S)N)r�deferredr�select�where�event�list)r�hookss  �R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/hooks/execute.py�	get_hooksrsB��
�{���	�����$�$�Y�_��%=�>�>�E���;�;��Fc�X�tj�|��s"td�|�����|rBtj|tj��s"td�|�����nAtj|tj��s"td�|�����tj�|��}	tj	|��}n5#t$r(}td�||�����d}~wwxYw|jtjzr"td�|�����tj�
|��}|r�|dkr�	tj	|��}n5#t$r(}td�||�����d}~wwxYw|jtjzr;|jtjzs)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-<user>/... 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 {}: {})�os�path�isfile�
ValueError�format�access�R_OK�X_OK�realpath�stat�OSError�st_mode�S_IWOTH�dirname�S_ISVTX)rr�real�st�exc�parent�psts       r�_validate_hook_pathr+s;��(�7�>�>�$���
��;�B�B�4�H�H�
�
�	
��M��y��r�w�'�'�	K��<�C�C�D�I�I�J�J�J�	K��y��r�w�'�'�	M��>�E�E�d�K�K�L�L�L�
�7���D�!�!�D�L�
�W�T�]�]�����L�L�L��8�?�?��c�J�J�K�K�K�����L����
�z�D�L� �I��:�A�A�$�G�G�H�H�H�
�W�_�_�T�
"�
"�F�
��&�C�-�-�	��'�&�/�/�C�C���	�	�	��1�8�8���E�E���
�����	����
�K�$�,�&�	���t�|�1K�	��� �&���.�.���
���-�-�	�	�	�	s0�'C<�<
D.�#D)�)D.�F$�$
G�.#G�Gc��^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)�shell�input�cwd��~)�asyncio�get_event_loop�run_in_executorr+�native_hooks�execute_hook�json�dumps�encoderrr$r	�FileNotFoundError�repr�PermissionError�	Exception)
r�datar�loopr/�	exit_code�_�errr(�es
          rr6r6Ts�������%�'�'���"�"�4�)<�d�F�K�K�K�K�K�K�K�K�K��	��%�d�D�1�1�1��7��z�$���&�&�(�(���g�o�o�d�#�#��		"�&)���e�4�S�'�'�'�!�!�!�!�!�!��I�q�#�#��!�	"�	"�	"���S�	�	�>�!�!�!�!�!�!������	"�	"�	"���S�	�	�>�!�!�!�!�!�!�����	"�����#�~��������T�!�W�W�}��������������sm�A
D�AD�B8�7D�8
D�C�D�D�
D�%C;�5D�6D�;D�D�
D,�D'�!D,�'D,c��.K�|�d��}t|��}t|j��}|sdSt	|j|j��5}|r�t
|jj��dz}tj
d|d|���}tj||��|�
��tj|�����|j|d<|j|j|d�}|D]�}	||	j|	j���5}
|
���t+|	j||	j����d{V��\}}|
�||��ddd��n#1swxYwY��	ddd��dS#1swxYwYdS)	N�DUMPrAzw+z.json)�mode�prefix�suffix�dir�tmp_filename)r�subtype�paramsr)�get�dictrr�event_hook_loggerrKr
�	__class__�__name__�tempfile�NamedTemporaryFiler7�dump�flushr�fsync�fileno�namerr�beginr6�finish)
r�tempdirrTrLr�event_loggerrG�tmpr>�hook�hook_loggerr@rBs
             r�
execute_hooksr`rs?�����9�9�V���D�
�%�[�[�F��e�k�"�"�E�����	�5�;��
�	6�	6�3�,��	.���� 8�9�9�C�?�F��-��&��g����C�
�I�d�C� � � ��I�I�K�K�K��H�S�Z�Z�\�\�"�"�"�%(�X�F�>�"��[��}��
�
���	3�	3�D���d�i���<�<�<�
3���!�!�#�#�#�'3��I�t�D�K�(�(�(�"�"�"�"�"�"��	�3��"�"�9�c�2�2�2�
3�
3�
3�
3�
3�
3�
3�
3�
3�
3�
3����
3�
3�
3�
3��	3�#3�3�3�3�3�3�3�3�3�3�3�3����3�3�3�3�3�3s8�B;F
�AE0�$F
�0E4�4F
�7E4�8F
�
F�F)F)r2r7rr rR� defence360agent.contracts.configr�defence360agent.hooksrr5� defence360agent.internals.loggerr� defence360agent.model.event_hookr�defence360agent.model.instancer�defence360agent.utilsr	r
rOrr+r6�TMPDIRr`�rr�<module>ris����������	�	�	�	���������1�1�1�1�1�1�8�8�8�8�8�8�<�<�<�<�<�<�6�6�6�6�6�6�-�-�-�-�-�-�1�1�1�1�1�1�1�1�#�O�%�%�����7�7�7�7�t����<(,�{�3�3�3�3�3�3rdefence360agent/hooks/__pycache__/execute.cpython-311.pyc0000644000000000000000000001753700000000000020262 0ustar  �

�k��������ddlZddlZddlZddlZddlZddlmZddlmZ	ddl
mZddlm
Z
ddlmZddlmZmZe��Zd�Zd
d
�Zd
d�Zejfd�ZdS)�N)�Core��native)�EventHookLogger)�	EventHook)�db)�run�
snake_casec��tjrgStj���tj|k��}t
|��S)N)r�deferredr�select�where�event�list)r�hookss  �R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/hooks/execute.py�	get_hooksrsB��
�{���	�����$�$�Y�_��%=�>�>�E���;�;��Fc�X�tj�|��s"td�|�����|rBtj|tj��s"td�|�����nAtj|tj��s"td�|�����tj�|��}	tj	|��}n5#t$r(}td�||�����d}~wwxYw|jtjzr"td�|�����tj�
|��}|r�|dkr�	tj	|��}n5#t$r(}td�||�����d}~wwxYw|jtjzr;|jtjzs)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-<user>/... 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 {}: {})�os�path�isfile�
ValueError�format�access�R_OK�X_OK�realpath�stat�OSError�st_mode�S_IWOTH�dirname�S_ISVTX)rr�real�st�exc�parent�psts       r�_validate_hook_pathr+s;��(�7�>�>�$���
��;�B�B�4�H�H�
�
�	
��M��y��r�w�'�'�	K��<�C�C�D�I�I�J�J�J�	K��y��r�w�'�'�	M��>�E�E�d�K�K�L�L�L�
�7���D�!�!�D�L�
�W�T�]�]�����L�L�L��8�?�?��c�J�J�K�K�K�����L����
�z�D�L� �I��:�A�A�$�G�G�H�H�H�
�W�_�_�T�
"�
"�F�
��&�C�-�-�	��'�&�/�/�C�C���	�	�	��1�8�8���E�E���
�����	����
�K�$�,�&�	���t�|�1K�	��� �&���.�.���
���-�-�	�	�	�	s0�'C<�<
D.�#D)�)D.�F$�$
G�.#G�Gc��^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)�shell�input�cwd��~)�asyncio�get_event_loop�run_in_executorr+�native_hooks�execute_hook�json�dumps�encoderrr$r	�FileNotFoundError�repr�PermissionError�	Exception)
r�datar�loopr/�	exit_code�_�errr(�es
          rr6r6Ts�������%�'�'���"�"�4�)<�d�F�K�K�K�K�K�K�K�K�K��	��%�d�D�1�1�1��7��z�$���&�&�(�(���g�o�o�d�#�#��		"�&)���e�4�S�'�'�'�!�!�!�!�!�!��I�q�#�#��!�	"�	"�	"���S�	�	�>�!�!�!�!�!�!������	"�	"�	"���S�	�	�>�!�!�!�!�!�!�����	"�����#�~��������T�!�W�W�}��������������sm�A
D�AD�B8�7D�8
D�C�D�D�
D�%C;�5D�6D�;D�D�
D,�D'�!D,�'D,c��.K�|�d��}t|��}t|j��}|sdSt	|j|j��5}|r�t
|jj��dz}tj
d|d|���}tj||��|�
��tj|�����|j|d<|j|j|d�}|D]�}	||	j|	j���5}
|
���t+|	j||	j����d{V��\}}|
�||��ddd��n#1swxYwY��	ddd��dS#1swxYwYdS)	N�DUMPrAzw+z.json)�mode�prefix�suffix�dir�tmp_filename)r�subtype�paramsr)�get�dictrr�event_hook_loggerrKr
�	__class__�__name__�tempfile�NamedTemporaryFiler7�dump�flushr�fsync�fileno�namerr�beginr6�finish)
r�tempdirrTrLr�event_loggerrG�tmpr>�hook�hook_loggerr@rBs
             r�
execute_hooksr`rs?�����9�9�V���D�
�%�[�[�F��e�k�"�"�E�����	�5�;��
�	6�	6�3�,��	.���� 8�9�9�C�?�F��-��&��g����C�
�I�d�C� � � ��I�I�K�K�K��H�S�Z�Z�\�\�"�"�"�%(�X�F�>�"��[��}��
�
���	3�	3�D���d�i���<�<�<�
3���!�!�#�#�#�'3��I�t�D�K�(�(�(�"�"�"�"�"�"��	�3��"�"�9�c�2�2�2�
3�
3�
3�
3�
3�
3�
3�
3�
3�
3�
3����
3�
3�
3�
3��	3�#3�3�3�3�3�3�3�3�3�3�3�3����3�3�3�3�3�3s8�B;F
�AE0�$F
�0E4�4F
�7E4�8F
�
F�F)F)r2r7rr rR� defence360agent.contracts.configr�defence360agent.hooksrr5� defence360agent.internals.loggerr� defence360agent.model.event_hookr�defence360agent.model.instancer�defence360agent.utilsr	r
rOrr+r6�TMPDIRr`�rr�<module>ris����������	�	�	�	���������1�1�1�1�1�1�8�8�8�8�8�8�<�<�<�<�<�<�6�6�6�6�6�6�-�-�-�-�-�-�1�1�1�1�1�1�1�1�#�O�%�%�����7�7�7�7�t����<(,�{�3�3�3�3�3�3rdefence360agent/hooks/__pycache__/native.cpython-311.opt-1.pyc0000644000000000000000000000372600000000000021040 0ustar  �

��睞r���p�UddlZddlZddlmZdZedd��ZiZee	efe
d<d�Zd�Zd	�Z
dS)
�N)�
namedtuple�im_hook�
ModuleInfo��object�mtime�ctime�modulesc���tj|��}|tvrHt|j|jkr-t|j|jkrt|jStj	�
||��}tj	�|��}|j�
|��t||j|j���t|<|S)Nr)�os�statr
r�st_mtimer	�st_ctimer�	importlib�util�spec_from_file_location�module_from_spec�loader�exec_moduler)�path�	file_stat�spec�hook_modules    �Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/hooks/native.py�import_hookrs������
�
�I������D�M��9�#5�5�5��D�M��9�#5�5�5��t�}�#�#��>�1�1�$��=�=�D��.�1�1�$�7�7�K��K���K�(�(�(���)�"4�I�<N����G�D�M���c�P�|tvrt�|��dSdS�N)r
�pop)rs r�remove_hookr s*���w������D�������rc�`�t|��}t|t��}||��Sr)r�getattr�
ENTRYPOINT)r�
dict_paramr�
entrypoints    r�execute_hookr&"s.���d�#�#�K���j�1�1�J��:�j�!�!�!r)�importlib.utilrr�collectionsrr#rr
�dict�str�__annotations__rr r&�rr�<module>r-s��������	�	�	�	�"�"�"�"�"�"��
�
�Z��&B�
C�
C�
�!#���c�:�o�	�#�#�#����$���
"�"�"�"�"rdefence360agent/hooks/__pycache__/native.cpython-311.pyc0000644000000000000000000000372600000000000020101 0ustar  �

��睞r���p�UddlZddlZddlmZdZedd��ZiZee	efe
d<d�Zd�Zd	�Z
dS)
�N)�
namedtuple�im_hook�
ModuleInfo��object�mtime�ctime�modulesc���tj|��}|tvrHt|j|jkr-t|j|jkrt|jStj	�
||��}tj	�|��}|j�
|��t||j|j���t|<|S)Nr)�os�statr
r�st_mtimer	�st_ctimer�	importlib�util�spec_from_file_location�module_from_spec�loader�exec_moduler)�path�	file_stat�spec�hook_modules    �Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/hooks/native.py�import_hookrs������
�
�I������D�M��9�#5�5�5��D�M��9�#5�5�5��t�}�#�#��>�1�1�$��=�=�D��.�1�1�$�7�7�K��K���K�(�(�(���)�"4�I�<N����G�D�M���c�P�|tvrt�|��dSdS�N)r
�pop)rs r�remove_hookr s*���w������D�������rc�`�t|��}t|t��}||��Sr)r�getattr�
ENTRYPOINT)r�
dict_paramr�
entrypoints    r�execute_hookr&"s.���d�#�#�K���j�1�1�J��:�j�!�!�!r)�importlib.utilrr�collectionsrr#rr
�dict�str�__annotations__rr r&�rr�<module>r-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-<user>/... 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  �

s�����s���dS)N�r��W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/__init__.py�<module>rs���rdefence360agent/internals/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000030400000000000021213 0ustar  �

s�����s���dS)N�r��W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/__init__.py�<module>rs���rdefence360agent/internals/__pycache__/auth_protocol.cpython-311.opt-1.pyc0000644000000000000000000000342000000000000023277 0ustar  �

WX߭�i��l�ddlZddlZddlZddlZeje��ZGd�dej��ZdS)�Nc��eZdZdZdZd�ZdS)�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�~�||_|j�d��}|�tjtjt
j|j����}t
j	|j|��\|_
|_|_t�d|j
|j|j��dS)N�socketz1New socket connection from pid=%s, uid=%s, gid=%s)�
_transport�get_extra_info�
getsockoptr�
SOL_SOCKET�SO_PEERCRED�struct�calcsize�
STRUCT_FORMAT�unpack�_pid�_uid�_gid�logger�debug)�self�	transport�conn�credss    �\/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/auth_protocol.py�connection_madez&UnixSocketAuthProtocol.connection_mades���#�����-�-�h�7�7����������O�D�.�/�/�
�
��
+1�-����+
�+
�'��	�4�9�d�i�	���?��I��I��I�		
�	
�	
�	
�	
�N)�__name__�
__module__�__qualname__�__doc__rr�rrrr	s4���������M�
�
�
�
�
rr)	�asyncior�loggingr
�	getLoggerrr�Protocolrr!rr�<module>r&sr������
�
�
�
�����
�
�
�
�	��	�8�	$�	$��"
�"
�"
�"
�"
�W�-�"
�"
�"
�"
�"
rdefence360agent/internals/__pycache__/auth_protocol.cpython-311.pyc0000644000000000000000000000342000000000000022340 0ustar  �

WX߭�i��l�ddlZddlZddlZddlZeje��ZGd�dej��ZdS)�Nc��eZdZdZdZd�ZdS)�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�~�||_|j�d��}|�tjtjt
j|j����}t
j	|j|��\|_
|_|_t�d|j
|j|j��dS)N�socketz1New socket connection from pid=%s, uid=%s, gid=%s)�
_transport�get_extra_info�
getsockoptr�
SOL_SOCKET�SO_PEERCRED�struct�calcsize�
STRUCT_FORMAT�unpack�_pid�_uid�_gid�logger�debug)�self�	transport�conn�credss    �\/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/auth_protocol.py�connection_madez&UnixSocketAuthProtocol.connection_mades���#�����-�-�h�7�7����������O�D�.�/�/�
�
��
+1�-����+
�+
�'��	�4�9�d�i�	���?��I��I��I�		
�	
�	
�	
�	
�N)�__name__�
__module__�__qualname__�__doc__rr�rrrr	s4���������M�
�
�
�
�
rr)	�asyncior�loggingr
�	getLoggerrr�Protocolrr!rr�<module>r&sr������
�
�
�
�����
�
�
�
�	��	�8�	$�	$��"
�"
�"
�"
�"
�W�-�"
�"
�"
�"
�"
rdefence360agent/internals/__pycache__/cln.cpython-311.opt-1.pyc0000644000000000000000000005177400000000000021210 0ustar  �

��{��������ddlZddlZddlZddlZddlZddlZddlZddlZddl	m
Z
ddlmZddlm
Z
mZmZmZmZddlZddlmZddlmZddlmZddlmZmZmZdd	lmZd
Zed��Z ej!e"��Z#dZ$ed
���de%fd���Z&d�Z'Gd�de(��Z)Gd�de(��Z*Gd�de)��Z+dd�Z,Gd�d��Z-Gd�d��Z.d�Z/dS)�N)�defaultdict)�Path)�	parse_qsl�	urlencode�urljoin�urlparse�
urlunparse)�ANTIVIRUS_MODE)�
LicenseCLN)�HostingPanel)�
CheckRunError�async_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'�)�maxsize�returnc���K�	ttd����d{V��nM#t$r@}|jdkr*t�dt
|������Yd}~dSd}~wwxYwdS)NT)�shell�dzimunify-email check failed F)r�IE_SUPPORTED_CMDr
�
returncode�logger�error�str)�es �R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/cln.py�is_imunify_email_supportedrs�������(��5�5�5�5�5�5�5�5�5�5�5�������<�3����L�L�?�s�1�v�v�?�?�@�@�@��u�u�u�u�u����������4s�!�
A+�5A&�&A+c���K�trdSt���sdS	tt	t��dg���d{V��}n#t
$rYdSwxYwd|���vS)zTry to get imunify-email statusF�statusNz&spamfilter exim configuration: enabled)r
� _IMUNIFY_EMAIL_CONFIG_EXECUTABLE�existsrrr
�decode)�outputs r�get_imunify_email_statusr%*s��������u�+�2�2�4�4���u�� �
�1�
2�
2�H�=�
�
�
�
�
�
�
�
���������u�u�����3�v�}�}���F�Fs�)A�
A �A c��eZdZdd�Zd�ZdS)�CLNErrorNc�"�||_||_dS�N��messager )�selfr r+s   r�__init__zCLNError.__init__:s����������c�R�|jr|jSd�|j��S)Nz#Unexpected status code from CLN: {})r+�formatr �r,s r�__str__zCLNError.__str__>s*���<�	 ��<��4�;�;�D�K�H�H�Hr.)NN)�__name__�
__module__�__qualname__r-r2�r.rr'r'9s<����������I�I�I�I�Ir.r'c��eZdZdS)�InvalidLicenseErrorN)r3r4r5r6r.rr8r8Es�������Dr.r8c�*�eZdZdZd�Zd�Zd�Zd�ZdS)�BackupNotFoundi@c��||_dSr)��url)r,r=s  rr-zBackupNotFound.__init__Ls
������r.c��dS)NzBackup not found in CLNr6r1s rr2zBackupNotFound.__str__Os��(�(r.c	�"�|j�dSt|j��}tt|j����}|���|d<t
|j|j|j	|j
t|��|jf��S)N�
used_space)
r=r�dictr�query�_disk_usager	�scheme�netloc�path�paramsr�fragment)r,�purBs   r�add_used_spacezBackupNotFound.add_used_spaceRs����8���F�
�d�h�
�
���Y�r�x�(�(�)�)��"�.�.�0�0��l����	��	����	��%� � ���

�	
�	
�		
r.c�N�d}tj��}t��}|D]i}|j|vr^d|jvrU|j�d��s;|tj|j��jz
}|�	|j���jt||jz��S)Nr�noautoz	/dev/loop)�psutil�disk_partitions�set�device�opts�
startswith�
disk_usage�
mountpoint�used�add�round�GB)r,�
total_used�
partitions�	processed�ps     rrCzBackupNotFound._disk_usagees����
��+�-�-�
��E�E�	��	(�	(�A����*�*��Q�V�+�+���,�,�[�9�9�,��f�/���=�=�B�B�
��
�
�a�h�'�'�'���Z�$�'�)�*�*�*r.N)r3r4r5rXr-r2rJrCr6r.rr:r:IsU������	�B����)�)�)�
�
�
�&+�+�+�+�+r.r:c	��i}|�||d<|��t|t��r|�dddi��n�t|t��r.|�d��}|�dddi��nJt
j�|���d��}|�dddi��||d	<	t
j�	tjj
|fi|��|�
��}|5|jdkr|jdfcddd��S|jdvr�	|���}	|jtj|�����fcddd��S#tj$r'}t#d
|�d|j��|j���|�d}~wwxYw#t$j$rt)d���wxYwt#|j���#1swxYwYdS#t
jj$r�}|jdkrt#|j��|�d}|j�wt0�d||||j|j��	|���}	n"#t$j$rt)d���wxYw|	�d���}t#||j���|�d}~wt
jj$r#}t#t|�����|�d}~wt$j$rt)d���t8$r%}t0�d|||||���d}~wwxYw)z!To be used by RestCLN._request().N�headers�Content-typezapplication/octet-streamzutf-8ztext/plain; charset=utf-8�asciiz!application/x-www-form-urlencoded�data)�timeout��)����zNon-json data from CLN: z
 for code=r*zTimed out reading responsei�z,CLN.post(url=%r, data=%r, headers=%r): %d %szTimed out reading error message�replace)�errors)r+zTimed out receiving responsez5CLN.post(url=%r, data=%r, headers=%r, timeout=%r): %s)�
isinstance�bytes�
setdefaultr�encode�urllib�parser�request�urlopen�Request�code�read�json�loadsr#�JSONDecodeErrorr'�socketrb�TimeoutErrorr�	HTTPError�fpr�warning�reason�URLError�OSError)
r=rar^rb�kwargs�resp�contentrr+�	resp_datas
          r�
_post_requestr�tsX��
�F���#��y�����d�E�"�"�	�����N�,F�G�
�
�
�
���c�
"�
"�
	��;�;�w�'�'�D�����N�,G�H�
�
�
�
��<�)�)�$�/�/�6�6�w�?�?�D������!D�E�
�
�
���v��?*��~�%�%��N�"�3�1�1�&�1�1�7�&�
�
��R�	*�	*��y�C����y�$��	*�	*�	*�	*�	*�	*�	*�	*���j�(�(�!�"�i�i�k�k�G�	!�#�y�$�*�W�^�^�5E�5E�*F�*F�F�	*�	*�	*�	*�	*�	*�	*�	*�� �/�!�!�!�&�!5�7�!5�!5�)-��!5�!5�$(�9���� !�
!�����!������~�E�E�E�&�'C�D�D�D�E�����t�y�)�)�)�+	*�	*�	*�	*����	*�	*�	*�	*�	*�	*��M�<�!�>�>�>��6�C�<�<��1�6�"�"��)����4���N�N�>��������

�
�
�
F��F�F�H�H�	�	���>�
F�
F�
F�"�#D�E�E�E�
F���� �&�&�i�&�8�8�G��w�q�v�6�6�6�A�=������<� �.�.�.��s�1�v�v�&�&�&�A�-������>�;�;�;��9�:�:�:��	�	�	����C�����
�
	
�	
�	
�	�����	���s��
6G:�G-�&	G-�0F7�,E>�>F4�
"F/�/F4�4G-�7G�G-�-G1�4G1�:M	�AK�!I6�5K�6J�0K�M	�K:�:*M	�$ M�M	c�j�eZdZdZdZej�dd��Zej�dd��Z	e�
ej�de�����Zeed��Z
eed��Zeed	��Zeed
��Zeed��Zeed��Zd
ZdZedded�d���Zed���Zededefd���Ze	ddededefd���Zededefd���Zedefd���Zededefd���Z edd���Z!dS)�RestCLNzhttps://{domain}/api/im/zcln.cloudlinux.com�IM360_CLN_API_BASE_URLzipv6.cln.cloudlinux.comzipv4.cln.cloudlinux.com��domain�register�
unregister�checkinzab/credentialsz	ab/removezab/check�okzok-trialN)rar^rbc��rK�tj���dt||||���d{V��Sr))�asyncio�get_event_loop�run_in_executorr�)�clsr=rar^rbs     r�_requestzRestCLN._request�sR�����+�-�-�=�=��-��d�G�W�
�
�
�
�
�
�
�
�	
r.c��K�t|j�|j���d��}dt	��d�}	|�||����d{V��\}}ns#t$rf}|jdkrOt|j�|j���d��}|�||����d{V��\}}n|�Yd}~nd}~wwxYw|S)Nr�r��IPL��key�hostname�rai�)	r�_URL_PATH_TEMPLATEr0�_IPV4_DOMAIN_NAMErr�r'r �_IPV6_DOMAIN_NAME)r��v4_license_urlra�_�token�	cln_error�v6_license_urls       r�process_ipl_licencezRestCLN.process_ipl_licence�s!���� ��"�)�)��1F�)�G�G��
�
���,�.�.�9�9��
	 � �\�\�.�t�\�D�D�D�D�D�D�D�D�H�A�u�u���	 �	 �	 ���3�&�&�!(��*�1�1�"�4�2����	"�"��"%���n�4��!H�!H�H�H�H�H�H�H���5�5����5�5�5�5�����	 �����s� A$�$
C�.AC�Cr�rc��K�|dkr|����d{V��S|�|j|t��d�����d{V��\}}|S)z�
        Register server with key
        :param key: registration key
        :return: license token in case of success
        r�Nr�r�)r�r��
_REGISTER_URLr)r�r�r�r�s    rr�zRestCLN.register�s������%�<�<��0�0�2�2�2�2�2�2�2�2�2�������,�.�.�9�9�&�
�
�
�
�
�
�
�
���5��r.�	server_id�users_countr�c��"K�|p
t��}t���d{V��}t��}	|����d{V��}nH#t$r;}t
�dt|��d���|j}Yd}~nd}~wwxYw|||||dt���d{V��id�d�}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_info�IM_EMAIL)�users�panel�imunifyEmail�supported_features)�idr��imzCLN checkin: %sr_zapplication/json)rar^)rr%r�name�	Exceptionrrr�NAMErrs�dumps�infor��_CHECKIN_URL)r�r�r�r��imunify_email_statusr��
panel_namer�reqrar�r�s            rr�zRestCLN.checkins~�����-�|�~�~��%=�%?�%?�?�?�?�?�?�?������	$�$�z�z�|�|�+�+�+�+�+�+�J�J���	$�	$�	$��L�L�1�3�q�6�6�D�
�
�
�
���J�J�J�J�J�J�����		$����� �$�#� 4��&@�&B�&B� B� B� B� B� B� B�'�	��
�
���z�#�������%�t�,�,�,�������#�%7�8�&�
�
�
�
�
�
�
�
���5�
�s�A�
B�1B�Bc��XK�|�|jd|i����d{V��\}}|S)zl
        Creates Acronis Backup account and get user & password
        :param server_id: server id
        r�r�N)r��_ACRONIS_CREDENTIALS_URL)r�r�r��credss    r�acronis_credentialszRestCLN.acronis_credentials<sV��������(��i�/@�&�
�
�
�
�
�
�
�
���5��r.c��RK�|�|jd|i����d{V��dS)zT
        Removes Acronis Backup account
        :param server_id: server id
        r�r�N)r��_ACRONIS_REMOVE_URL�r�r�s  r�acronis_removezRestCLN.acronis_removeGs>�����l�l�3�2�$�	�9J�l�K�K�K�K�K�K�K�K�K�K�Kr.c��K�|�|jd|i����d{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
        r�r�Nrer<)r��_ACRONIS_CHECK_URLr:)r�r�r �responses    r�
acronis_checkzRestCLN.acronis_checkOsq����"%����"�$�	�):�".�"
�"
�
�
�
�
�
�
�����S�=�=� �T�*�*�*�*��r.c��|K�|ptj��}|�|jd|i����d{V��dS)z<
        Unregister server id
        :return: None
        r�r�N)r�
get_server_idr��_UNREGISTER_URLr�s  rr�zRestCLN.unregister]sQ�����;��!9�!;�!;�	��l�l�3�.�d�I�5F�l�G�G�G�G�G�G�G�G�G�G�Gr.r))"r3r4r5r��_BASE_DOMAIN_NAME�os�environ�getr�r�r0�	_BASE_URLrr�r�r�r�r�r��STATUS_OK_PAID_LICENSE�STATUS_OK_TRIAL_LICENSE�classmethod�_TIMEOUTr�r�rrAr��intr�r�r�r�r�r6r.rr�r��sk������3��,���
��� �";�����
��� �";����#�)�)��z�~�~�6�8I�J�J�*���I��G�I�z�2�2�M��g�i��6�6�O��7�9�i�0�0�L�&�w�y�2B�C�C��!�'�)�[�9�9�� ���J�7�7��!��(���)-�t�X�
�
�
�
��[�
�
����[��,���������[���
�	+�+��+��+��	+�+�+��[�+�Z��#��$�����[���L�S�L�L�L��[�L���C��D�����[���H�H�H��[�H�H�Hr.r�c��eZdZee��Zed���Zed���Zed���Z	ed���Z
ed���Zed���ZdS)�CLNc�F�|j|�|��dSr))�
_CALLBACKSrV)r��method_name�
coro_callbacks   r�add_callback_forzCLN.add_callback_forjs#����{�#�'�'�
�6�6�6�6�6r.c	��K�|j|D]h}	|���d{V���#tj$r�t$r9}t�d�|||����Yd}~�ad}~wwxYwdS)Nz;Error '{!r}' happened when run callback {} forCLN {} method)r�r��CancelledErrorr�r�	exceptionr0)r�r��callbackrs    r�run_callbacks_forzCLN.run_callbacks_forns�������{�3�		�		�H�
��h�j�j� � � � � � � � ���)�
�
�
���
�
�
�� � �$�$*�F�1�h��$D�$D�������������
����		�		s�$�A6�/A1�1A6c�,�|�d��S)N�IMAVP)rR)r�r�s  r�
is_avp_keyzCLN.is_avp_key{s���~�~�g�&�&�&r.c��K�|�|��rtstd���t�|���d{V��}tj|��s5t�|d���d{V��td���tj|��|�	d���d{V��dS)Nz4Imunify360 can not be registered with ImunifyAV+ keyr�z"License is invalid for this serverr�)
r�r
r8r�r�r�is_validr��updater�)r�r��licenses   rr�zCLN.registers������>�>�#���	�~�	�%�F���
� �(�(��-�-�-�-�-�-�-�-���"�7�+�+�	L��$�$�W�T�]�3�3�3�3�3�3�3�3�3�%�&J�K�K�K���'�"�"�"��#�#�J�/�/�/�/�/�/�/�/�/�/�/r.c��K�t����d{V��tj��|�d���d{V��dS)Nr�)r�r�r�deleter�)r�s rr�zCLN.unregister�se����� � �"�"�"�"�"�"�"�"�"�������#�#�L�1�1�1�1�1�1�1�1�1�1�1r.c��$K�tj��rtj��Stj���d��rtj��St�|dtj���d{V��}t�d|��|� t�
���d{V��ntj|��|�d���d{V��tj��S)z>Refreshes token and returns new one on success, None otherwise�is_alternativer�NzGot new token from CLN: %s�
refresh_token)
r�is_free�	get_tokenr�r�r�r�rr�r�r�r�r�)r�r��	new_tokens   rr�zCLN.refresh_token�s	��������	*��'�)�)�)���!�!�%�%�&6�7�7�	*��'�)�)�)�!�/�/�%��+�z�7M�N�N�N�N�N�N�N�N�	����0�)�<�<�<����.�.�"�"�"�"�"�"�"�"�"�"���i�(�(�(��#�#�O�4�4�4�4�4�4�4�4�4��#�%�%�%r.N)
r3r4r5rrOr�r�r�r�r�r�r�r�r6r.rr�r�gs���������S�!�!�J��7�7��[�7��
�
��[�
��'�'��[�'��
0�
0��[�
0��2�2��[�2�
�&�&��[�&�&�&r.r�c�H�dD]}t�||����dS)N)r�r�r�)r�)r�r�)�coror�s  r�subscribe_to_license_changesr��s7��B�>�>�����[���=�=�=�=�>�>r.)NNN)0r�rs�loggingr�rv�urllib.errorrl�urllib.parse�urllib.request�collectionsr�pathlibrrrrrr	rM� defence360agent.contracts.configr
�!defence360agent.contracts.licenser�+defence360agent.subsys.panels.hosting_panelr�defence360agent.utilsr
rr�defence360agent.utils.commonrr�r!�	getLoggerr3rr�boolrr%r�r'r8r:r�r�r�r�r6r.r�<module>r�s���������������	�	�	�	�
�
�
�
�������������#�#�#�#�#�#�������L�L�L�L�L�L�L�L�L�L�L�L�L�L�
�
�
�
�;�;�;�;�;�;�8�8�8�8�8�8�D�D�D�D�D�D�K�K�K�K�K�K�K�K�K�K�5�5�5�5�5�5���#'�4�(=�#>�#>� �	��	�8�	$�	$�� ���������$�������G�G�G�	I�	I�	I�	I�	I�y�	I�	I�	I�	�	�	�	�	�)�	�	�	�(+�(+�(+�(+�(+�X�(+�(+�(+�VU*�U*�U*�U*�pXH�XH�XH�XH�XH�XH�XH�XH�v@&�@&�@&�@&�@&�@&�@&�@&�F>�>�>�>�>r.defence360agent/internals/__pycache__/cln.cpython-311.pyc0000644000000000000000000005177400000000000020251 0ustar  �

��{��������ddlZddlZddlZddlZddlZddlZddlZddlZddl	m
Z
ddlmZddlm
Z
mZmZmZmZddlZddlmZddlmZddlmZddlmZmZmZdd	lmZd
Zed��Z ej!e"��Z#dZ$ed
���de%fd���Z&d�Z'Gd�de(��Z)Gd�de(��Z*Gd�de)��Z+dd�Z,Gd�d��Z-Gd�d��Z.d�Z/dS)�N)�defaultdict)�Path)�	parse_qsl�	urlencode�urljoin�urlparse�
urlunparse)�ANTIVIRUS_MODE)�
LicenseCLN)�HostingPanel)�
CheckRunError�async_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'�)�maxsize�returnc���K�	ttd����d{V��nM#t$r@}|jdkr*t�dt
|������Yd}~dSd}~wwxYwdS)NT)�shell�dzimunify-email check failed F)r�IE_SUPPORTED_CMDr
�
returncode�logger�error�str)�es �R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/cln.py�is_imunify_email_supportedrs�������(��5�5�5�5�5�5�5�5�5�5�5�������<�3����L�L�?�s�1�v�v�?�?�@�@�@��u�u�u�u�u����������4s�!�
A+�5A&�&A+c���K�trdSt���sdS	tt	t��dg���d{V��}n#t
$rYdSwxYwd|���vS)zTry to get imunify-email statusF�statusNz&spamfilter exim configuration: enabled)r
� _IMUNIFY_EMAIL_CONFIG_EXECUTABLE�existsrrr
�decode)�outputs r�get_imunify_email_statusr%*s��������u�+�2�2�4�4���u�� �
�1�
2�
2�H�=�
�
�
�
�
�
�
�
���������u�u�����3�v�}�}���F�Fs�)A�
A �A c��eZdZdd�Zd�ZdS)�CLNErrorNc�"�||_||_dS�N��messager )�selfr r+s   r�__init__zCLNError.__init__:s����������c�R�|jr|jSd�|j��S)Nz#Unexpected status code from CLN: {})r+�formatr �r,s r�__str__zCLNError.__str__>s*���<�	 ��<��4�;�;�D�K�H�H�Hr.)NN)�__name__�
__module__�__qualname__r-r2�r.rr'r'9s<����������I�I�I�I�Ir.r'c��eZdZdS)�InvalidLicenseErrorN)r3r4r5r6r.rr8r8Es�������Dr.r8c�*�eZdZdZd�Zd�Zd�Zd�ZdS)�BackupNotFoundi@c��||_dSr)��url)r,r=s  rr-zBackupNotFound.__init__Ls
������r.c��dS)NzBackup not found in CLNr6r1s rr2zBackupNotFound.__str__Os��(�(r.c	�"�|j�dSt|j��}tt|j����}|���|d<t
|j|j|j	|j
t|��|jf��S)N�
used_space)
r=r�dictr�query�_disk_usager	�scheme�netloc�path�paramsr�fragment)r,�purBs   r�add_used_spacezBackupNotFound.add_used_spaceRs����8���F�
�d�h�
�
���Y�r�x�(�(�)�)��"�.�.�0�0��l����	��	����	��%� � ���

�	
�	
�		
r.c�N�d}tj��}t��}|D]i}|j|vr^d|jvrU|j�d��s;|tj|j��jz
}|�	|j���jt||jz��S)Nr�noautoz	/dev/loop)�psutil�disk_partitions�set�device�opts�
startswith�
disk_usage�
mountpoint�used�add�round�GB)r,�
total_used�
partitions�	processed�ps     rrCzBackupNotFound._disk_usagees����
��+�-�-�
��E�E�	��	(�	(�A����*�*��Q�V�+�+���,�,�[�9�9�,��f�/���=�=�B�B�
��
�
�a�h�'�'�'���Z�$�'�)�*�*�*r.N)r3r4r5rXr-r2rJrCr6r.rr:r:IsU������	�B����)�)�)�
�
�
�&+�+�+�+�+r.r:c	��i}|�||d<|��t|t��r|�dddi��n�t|t��r.|�d��}|�dddi��nJt
j�|���d��}|�dddi��||d	<	t
j�	tjj
|fi|��|�
��}|5|jdkr|jdfcddd��S|jdvr�	|���}	|jtj|�����fcddd��S#tj$r'}t#d
|�d|j��|j���|�d}~wwxYw#t$j$rt)d���wxYwt#|j���#1swxYwYdS#t
jj$r�}|jdkrt#|j��|�d}|j�wt0�d||||j|j��	|���}	n"#t$j$rt)d���wxYw|	�d���}t#||j���|�d}~wt
jj$r#}t#t|�����|�d}~wt$j$rt)d���t8$r%}t0�d|||||���d}~wwxYw)z!To be used by RestCLN._request().N�headers�Content-typezapplication/octet-streamzutf-8ztext/plain; charset=utf-8�asciiz!application/x-www-form-urlencoded�data)�timeout��)����zNon-json data from CLN: z
 for code=r*zTimed out reading responsei�z,CLN.post(url=%r, data=%r, headers=%r): %d %szTimed out reading error message�replace)�errors)r+zTimed out receiving responsez5CLN.post(url=%r, data=%r, headers=%r, timeout=%r): %s)�
isinstance�bytes�
setdefaultr�encode�urllib�parser�request�urlopen�Request�code�read�json�loadsr#�JSONDecodeErrorr'�socketrb�TimeoutErrorr�	HTTPError�fpr�warning�reason�URLError�OSError)
r=rar^rb�kwargs�resp�contentrr+�	resp_datas
          r�
_post_requestr�tsX��
�F���#��y�����d�E�"�"�	�����N�,F�G�
�
�
�
���c�
"�
"�
	��;�;�w�'�'�D�����N�,G�H�
�
�
�
��<�)�)�$�/�/�6�6�w�?�?�D������!D�E�
�
�
���v��?*��~�%�%��N�"�3�1�1�&�1�1�7�&�
�
��R�	*�	*��y�C����y�$��	*�	*�	*�	*�	*�	*�	*�	*���j�(�(�!�"�i�i�k�k�G�	!�#�y�$�*�W�^�^�5E�5E�*F�*F�F�	*�	*�	*�	*�	*�	*�	*�	*�� �/�!�!�!�&�!5�7�!5�!5�)-��!5�!5�$(�9���� !�
!�����!������~�E�E�E�&�'C�D�D�D�E�����t�y�)�)�)�+	*�	*�	*�	*����	*�	*�	*�	*�	*�	*��M�<�!�>�>�>��6�C�<�<��1�6�"�"��)����4���N�N�>��������

�
�
�
F��F�F�H�H�	�	���>�
F�
F�
F�"�#D�E�E�E�
F���� �&�&�i�&�8�8�G��w�q�v�6�6�6�A�=������<� �.�.�.��s�1�v�v�&�&�&�A�-������>�;�;�;��9�:�:�:��	�	�	����C�����
�
	
�	
�	
�	�����	���s��
6G:�G-�&	G-�0F7�,E>�>F4�
"F/�/F4�4G-�7G�G-�-G1�4G1�:M	�AK�!I6�5K�6J�0K�M	�K:�:*M	�$ M�M	c�j�eZdZdZdZej�dd��Zej�dd��Z	e�
ej�de�����Zeed��Z
eed��Zeed	��Zeed
��Zeed��Zeed��Zd
ZdZedded�d���Zed���Zededefd���Ze	ddededefd���Zededefd���Zedefd���Zededefd���Z edd���Z!dS)�RestCLNzhttps://{domain}/api/im/zcln.cloudlinux.com�IM360_CLN_API_BASE_URLzipv6.cln.cloudlinux.comzipv4.cln.cloudlinux.com��domain�register�
unregister�checkinzab/credentialsz	ab/removezab/check�okzok-trialN)rar^rbc��rK�tj���dt||||���d{V��Sr))�asyncio�get_event_loop�run_in_executorr�)�clsr=rar^rbs     r�_requestzRestCLN._request�sR�����+�-�-�=�=��-��d�G�W�
�
�
�
�
�
�
�
�	
r.c��K�t|j�|j���d��}dt	��d�}	|�||����d{V��\}}ns#t$rf}|jdkrOt|j�|j���d��}|�||����d{V��\}}n|�Yd}~nd}~wwxYw|S)Nr�r��IPL��key�hostname�rai�)	r�_URL_PATH_TEMPLATEr0�_IPV4_DOMAIN_NAMErr�r'r �_IPV6_DOMAIN_NAME)r��v4_license_urlra�_�token�	cln_error�v6_license_urls       r�process_ipl_licencezRestCLN.process_ipl_licence�s!���� ��"�)�)��1F�)�G�G��
�
���,�.�.�9�9��
	 � �\�\�.�t�\�D�D�D�D�D�D�D�D�H�A�u�u���	 �	 �	 ���3�&�&�!(��*�1�1�"�4�2����	"�"��"%���n�4��!H�!H�H�H�H�H�H�H���5�5����5�5�5�5�����	 �����s� A$�$
C�.AC�Cr�rc��K�|dkr|����d{V��S|�|j|t��d�����d{V��\}}|S)z�
        Register server with key
        :param key: registration key
        :return: license token in case of success
        r�Nr�r�)r�r��
_REGISTER_URLr)r�r�r�r�s    rr�zRestCLN.register�s������%�<�<��0�0�2�2�2�2�2�2�2�2�2�������,�.�.�9�9�&�
�
�
�
�
�
�
�
���5��r.�	server_id�users_countr�c��"K�|p
t��}t���d{V��}t��}	|����d{V��}nH#t$r;}t
�dt|��d���|j}Yd}~nd}~wwxYw|||||dt���d{V��id�d�}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_info�IM_EMAIL)�users�panel�imunifyEmail�supported_features)�idr��imzCLN checkin: %sr_zapplication/json)rar^)rr%r�name�	Exceptionrrr�NAMErrs�dumps�infor��_CHECKIN_URL)r�r�r�r��imunify_email_statusr��
panel_namer�reqrar�r�s            rr�zRestCLN.checkins~�����-�|�~�~��%=�%?�%?�?�?�?�?�?�?������	$�$�z�z�|�|�+�+�+�+�+�+�J�J���	$�	$�	$��L�L�1�3�q�6�6�D�
�
�
�
���J�J�J�J�J�J�����		$����� �$�#� 4��&@�&B�&B� B� B� B� B� B� B�'�	��
�
���z�#�������%�t�,�,�,�������#�%7�8�&�
�
�
�
�
�
�
�
���5�
�s�A�
B�1B�Bc��XK�|�|jd|i����d{V��\}}|S)zl
        Creates Acronis Backup account and get user & password
        :param server_id: server id
        r�r�N)r��_ACRONIS_CREDENTIALS_URL)r�r�r��credss    r�acronis_credentialszRestCLN.acronis_credentials<sV��������(��i�/@�&�
�
�
�
�
�
�
�
���5��r.c��RK�|�|jd|i����d{V��dS)zT
        Removes Acronis Backup account
        :param server_id: server id
        r�r�N)r��_ACRONIS_REMOVE_URL�r�r�s  r�acronis_removezRestCLN.acronis_removeGs>�����l�l�3�2�$�	�9J�l�K�K�K�K�K�K�K�K�K�K�Kr.c��K�|�|jd|i����d{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
        r�r�Nrer<)r��_ACRONIS_CHECK_URLr:)r�r�r �responses    r�
acronis_checkzRestCLN.acronis_checkOsq����"%����"�$�	�):�".�"
�"
�
�
�
�
�
�
�����S�=�=� �T�*�*�*�*��r.c��|K�|ptj��}|�|jd|i����d{V��dS)z<
        Unregister server id
        :return: None
        r�r�N)r�
get_server_idr��_UNREGISTER_URLr�s  rr�zRestCLN.unregister]sQ�����;��!9�!;�!;�	��l�l�3�.�d�I�5F�l�G�G�G�G�G�G�G�G�G�G�Gr.r))"r3r4r5r��_BASE_DOMAIN_NAME�os�environ�getr�r�r0�	_BASE_URLrr�r�r�r�r�r��STATUS_OK_PAID_LICENSE�STATUS_OK_TRIAL_LICENSE�classmethod�_TIMEOUTr�r�rrAr��intr�r�r�r�r�r6r.rr�r��sk������3��,���
��� �";�����
��� �";����#�)�)��z�~�~�6�8I�J�J�*���I��G�I�z�2�2�M��g�i��6�6�O��7�9�i�0�0�L�&�w�y�2B�C�C��!�'�)�[�9�9�� ���J�7�7��!��(���)-�t�X�
�
�
�
��[�
�
����[��,���������[���
�	+�+��+��+��	+�+�+��[�+�Z��#��$�����[���L�S�L�L�L��[�L���C��D�����[���H�H�H��[�H�H�Hr.r�c��eZdZee��Zed���Zed���Zed���Z	ed���Z
ed���Zed���ZdS)�CLNc�F�|j|�|��dSr))�
_CALLBACKSrV)r��method_name�
coro_callbacks   r�add_callback_forzCLN.add_callback_forjs#����{�#�'�'�
�6�6�6�6�6r.c	��K�|j|D]h}	|���d{V���#tj$r�t$r9}t�d�|||����Yd}~�ad}~wwxYwdS)Nz;Error '{!r}' happened when run callback {} forCLN {} method)r�r��CancelledErrorr�r�	exceptionr0)r�r��callbackrs    r�run_callbacks_forzCLN.run_callbacks_forns�������{�3�		�		�H�
��h�j�j� � � � � � � � ���)�
�
�
���
�
�
�� � �$�$*�F�1�h��$D�$D�������������
����		�		s�$�A6�/A1�1A6c�,�|�d��S)N�IMAVP)rR)r�r�s  r�
is_avp_keyzCLN.is_avp_key{s���~�~�g�&�&�&r.c��K�|�|��rtstd���t�|���d{V��}tj|��s5t�|d���d{V��td���tj|��|�	d���d{V��dS)Nz4Imunify360 can not be registered with ImunifyAV+ keyr�z"License is invalid for this serverr�)
r�r
r8r�r�r�is_validr��updater�)r�r��licenses   rr�zCLN.registers������>�>�#���	�~�	�%�F���
� �(�(��-�-�-�-�-�-�-�-���"�7�+�+�	L��$�$�W�T�]�3�3�3�3�3�3�3�3�3�%�&J�K�K�K���'�"�"�"��#�#�J�/�/�/�/�/�/�/�/�/�/�/r.c��K�t����d{V��tj��|�d���d{V��dS)Nr�)r�r�r�deleter�)r�s rr�zCLN.unregister�se����� � �"�"�"�"�"�"�"�"�"�������#�#�L�1�1�1�1�1�1�1�1�1�1�1r.c��$K�tj��rtj��Stj���d��rtj��St�|dtj���d{V��}t�d|��|� t�
���d{V��ntj|��|�d���d{V��tj��S)z>Refreshes token and returns new one on success, None otherwise�is_alternativer�NzGot new token from CLN: %s�
refresh_token)
r�is_free�	get_tokenr�r�r�r�rr�r�r�r�r�)r�r��	new_tokens   rr�zCLN.refresh_token�s	��������	*��'�)�)�)���!�!�%�%�&6�7�7�	*��'�)�)�)�!�/�/�%��+�z�7M�N�N�N�N�N�N�N�N�	����0�)�<�<�<����.�.�"�"�"�"�"�"�"�"�"�"���i�(�(�(��#�#�O�4�4�4�4�4�4�4�4�4��#�%�%�%r.N)
r3r4r5rrOr�r�r�r�r�r�r�r�r6r.rr�r�gs���������S�!�!�J��7�7��[�7��
�
��[�
��'�'��[�'��
0�
0��[�
0��2�2��[�2�
�&�&��[�&�&�&r.r�c�H�dD]}t�||����dS)N)r�r�r�)r�)r�r�)�coror�s  r�subscribe_to_license_changesr��s7��B�>�>�����[���=�=�=�=�>�>r.)NNN)0r�rs�loggingr�rv�urllib.errorrl�urllib.parse�urllib.request�collectionsr�pathlibrrrrrr	rM� defence360agent.contracts.configr
�!defence360agent.contracts.licenser�+defence360agent.subsys.panels.hosting_panelr�defence360agent.utilsr
rr�defence360agent.utils.commonrr�r!�	getLoggerr3rr�boolrr%r�r'r8r:r�r�r�r�r6r.r�<module>r�s���������������	�	�	�	�
�
�
�
�������������#�#�#�#�#�#�������L�L�L�L�L�L�L�L�L�L�L�L�L�L�
�
�
�
�;�;�;�;�;�;�8�8�8�8�8�8�D�D�D�D�D�D�K�K�K�K�K�K�K�K�K�K�5�5�5�5�5�5���#'�4�(=�#>�#>� �	��	�8�	$�	$�� ���������$�������G�G�G�	I�	I�	I�	I�	I�y�	I�	I�	I�	�	�	�	�	�)�	�	�	�(+�(+�(+�(+�(+�X�(+�(+�(+�VU*�U*�U*�U*�pXH�XH�XH�XH�XH�XH�XH�XH�v@&�@&�@&�@&�@&�@&�@&�@&�F>�>�>�>�>r.defence360agent/internals/__pycache__/deadlock_detecting_lock.cpython-311.opt-1.pyc0000644000000000000000000000411600000000000025224 0ustar  �

�N�����D�ddlZGd�de��ZGd�d��ZdS)�Nc��eZdZdZdS)�
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�*�eZdZdZd�Zd�Zd�Zd�ZdS)�DeadlockDetectingLockzp
    Lock that detects deadlock when it is about to be
    acquired by the same task that already holds it.
    c�D�tj��|_d|_dS�N)�asyncio�Lock�_lock�_owner��selfs r�__init__zDeadlockDetectingLock.__init__s���\�^�^��
�����r
c�4�|j���Sr)r�lockedrs rrzDeadlockDetectingLock.lockeds���z� � �"�"�"r
c��K�tj��}|j|krt���|j����d{V��||_|Sr)r�current_taskrrr�acquire)r�	curr_tasks  r�
__aenter__z DeadlockDetectingLock.__aenter__s]�����(�*�*�	��;�)�#�#��/�/�!��j� � �"�"�"�"�"�"�"�"�"�����r
c��JK�d|_|j���dSr)rr�release)r�exc_type�exc�tbs    r�	__aexit__zDeadlockDetectingLock.__aexit__s'��������
�������r
N)rrrrrrrr#r	r
rr
r
sZ��������
���#�#�#��������r
r
)r�	Exceptionrr
r	r
r�<module>r%sr������A�A�A�A�A�I�A�A�A����������r
defence360agent/internals/__pycache__/deadlock_detecting_lock.cpython-311.pyc0000644000000000000000000000411600000000000024265 0ustar  �

�N�����D�ddlZGd�de��ZGd�d��ZdS)�Nc��eZdZdZdS)�
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�*�eZdZdZd�Zd�Zd�Zd�ZdS)�DeadlockDetectingLockzp
    Lock that detects deadlock when it is about to be
    acquired by the same task that already holds it.
    c�D�tj��|_d|_dS�N)�asyncio�Lock�_lock�_owner��selfs r�__init__zDeadlockDetectingLock.__init__s���\�^�^��
�����r
c�4�|j���Sr)r�lockedrs rrzDeadlockDetectingLock.lockeds���z� � �"�"�"r
c��K�tj��}|j|krt���|j����d{V��||_|Sr)r�current_taskrrr�acquire)r�	curr_tasks  r�
__aenter__z DeadlockDetectingLock.__aenter__s]�����(�*�*�	��;�)�#�#��/�/�!��j� � �"�"�"�"�"�"�"�"�"�����r
c��JK�d|_|j���dSr)rr�release)r�exc_type�exc�tbs    r�	__aexit__zDeadlockDetectingLock.__aexit__s'��������
�������r
N)rrrrrrrr#r	r
rr
r
sZ��������
���#�#�#��������r
r
)r�	Exceptionrr
r	r
r�<module>r%sr������A�A�A�A�A�I�A�A�A����������r
defence360agent/internals/__pycache__/feature_flags.cpython-311.opt-1.pyc0000644000000000000000000003436400000000000023237 0ustar  �

���pἰ����UdZddlmZddlZddlZddlZddlmZdZdZ	dZ
iaded	<ia
d
ed<e��aded
<daded<d0d�Zd1d�Zd2d�Zd3d�Zd4d�Zd5d�Zd6d�Zd7d!�Zd8d$�Zd6d%�Zd7d&�Zd9d'�Zd9d(�Zd:d;d-�Zd<d.�Zd=d/�Z 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_flags�mqtt_tracked_methods�dict[str, Any]�
_cached_flags�dict[str, list[str]]�_cached_params�frozenset[str]�_cached_mqtt_methods��float�
_cached_mtime�rawr�returnc��|�iSt|t��r#i}|D]}t|t��rd||<�|St|t��r;|�d��}t|t��rt|��S|SiS)z@Map file JSON to a flat name->value dict for :func:`is_enabled`.NT�flags)�
isinstance�list�str�dict�get�_normalize_flags_from_file)r�out�item�inners    �\/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/feature_flags.pyrr4s���
�{��	��#�t���� ���	!�	!�D��$��$�$�
!� ��D�	���
��#�t�������� � ���e�T�"�"�	5�-�e�4�4�4��
�
�I�c�@�t|t��siS|�d��}t|t��siSi}|���D]C\}}t|t��rt|t
��s�0d�|D��}|r|||<�D|S)z�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.
    �paramsc�<�g|]}t|t���|��S��rr)�.0�vs  r�
<listcomp>z%_params_from_file.<locals>.<listcomp>Us'��;�;�;��
�1�c�(:�(:�;�1�;�;�;r)rrr�itemsrr)r�
raw_paramsr�name�values�cleaneds      r�_params_from_filer+Fs����c�4� � ���	�����"�"�J��j�$�'�'���	� "�C�"�(�(�*�*� � ���f��$��$�$�	�J�v�t�,D�,D�	��;�;�f�;�;�;���	 ��C��I���Jr�+tuple[dict[str, Any], dict[str, list[str]]]c�^�	tj�t��}n2#t$r%iaiat��ada	t
tfcYSwxYw|tkrt
tfS	tt��5}tj|��}ddd��n#1swxYwYt|��at|��an #ttjf$riaiaYnwxYwtt�t"d����a|a	t
tfS)Nrr!)�os�path�getmtime�
FLAGS_PATH�OSErrorrr	�	frozensetrr�open�json�loadrr+�JSONDecodeErrorr�MQTT_TRACKED_METHODS_FLAG)�mtime�frs   r�_read_stater;[sr��-��� � ��,�,�����-�-�-��
���(�{�{���
��n�,�,�,�,�-����
�
����n�,�,��
�*�
�
�	���)�A�,�,�C�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�2�3�7�7�
�*�3�/�/�����T�)�*�����
���������%����4�b�9�9�����M��.�(�(sE�$'�,A�A�3C�B(�C�(B,�,C�/B,�0!C�C/�.C/c�(�t��\}}|S�N�r;)r�_s  r�_read_flagsr@xs���}�}�H�E�1��Lrc�(�t��\}}|Sr=r>)r?rs  r�_read_paramsrB}s���
�
�I�A�v��Mrr�	list[str]c��t|ttf��s$tdt	|��j�����t
|��}td�|���D����S)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�	<genexpr>z,enabled_flag_names_sorted.<locals>.<genexpr>�s+����8�8���1�a�8�!�8�8�8�8�8�8r)	rrr�	TypeError�type�__name__r�sortedr&)r�
normalizeds  r�enabled_flag_names_sortedrN�sx���e�d�D�\�*�*�
��E�t�E�{�{�/C�E�E�
�
�	
�,�E�2�2�J��8�8�
� 0� 0� 2� 2�8�8�8�8�8�8r�names�bytesc�r�t|��}tj|dd������S)z|JSON array bytes used for sync MD5 when no params are present
    (matches correlation_api ``checksum_for_sync_flag_list``).T���	sort_keys�indent�rLr5�dumps�encode)rO�ordereds  r�canonical_sync_flag_list_bytesrZ�s2���U�m�m�G��:�g��a�8�8�8�?�?�A�A�Arrc���|st|��St|��d�t|�����D��d�}tj|dd������S)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.
    c�4�i|]\}}|t|����Sr!�rLrFs   r�
<dictcomp>z1canonical_sync_response_bytes.<locals>.<dictcomp>��$��C�C�C�D�A�q�1�f�Q�i�i�C�C�Cr�rrTrRrS)rZrLr&r5rWrX�rOr�	canonicals   r�canonical_sync_response_bytesrc�su���5�-�e�4�4�4�����C�C�F�6�<�<�>�>�,B�,B�C�C�C���I��:�i�4��:�:�:�A�A�C�C�Crr/rc�r�	t|d���5}tj|��}ddd��n#1swxYwYn##tttjf$rYdSwxYwt
|��}t|��}t||��}tj
|d������S)z�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.
    zutf-8)�encodingN�F)�usedforsecurity)r4r5r6r2�UnicodeDecodeErrorr7rNr+rc�hashlib�md5�	hexdigest)r/r:rrOr�payloads      r�!sync_checksum_hex_from_flags_filerm�s����
�$��
)�
)�
)�	�Q��)�A�,�,�C�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	����'��)=�>�����r�r�����%�c�*�*�E�
�s�
#�
#�F�+�E�6�:�:�G��;�w��6�6�6�@�@�B�B�Bs,�A�4�A�8�A�8�A�A �A c��d�td�|D����D��}tj|dd������S)z<On-disk legacy JSON: ``{flag: true, ...}`` with sorted keys.c��i|]}|d��S)Tr!)r#�ns  rr^z2legacy_feature_flags_map_bytes.<locals>.<dictcomp>�s��K�K�K�Q��D�K�K�Krc�<�h|]}t|t���|��Sr!r")r#�xs  r�	<setcomp>z1legacy_feature_flags_map_bytes.<locals>.<setcomp>�s'��!I�!I�!I��j��C�6H�6H�!I�!�!I�!I�!IrTrRrSrV)rO�ds  r�legacy_feature_flags_map_bytesru�sO��K�K�&�!I�!I�U�!I�!I�!I�J�J�K�K�K�A��:�a�4��2�2�2�9�9�;�;�;rc���t|��d�t|�����D��d�}tj|dd������S)z�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``.
    c�4�i|]\}}|t|����Sr!r]rFs   rr^z,sync_response_file_bytes.<locals>.<dictcomp>�r_rr`TrRrS)rLr&r5rWrXras   r�sync_response_file_bytesrx�s_������C�C�F�6�<�<�>�>�,B�,B�C�C�C���I��:�i�4��:�:�:�A�A�C�C�Crc�|�t|��}|sdSd�|��dz���S)zPBody for ``FLAGS_PLAIN_PATH``: one name per line, trailing newline if non-empty.r�
)rN�joinrX)rrOs  r�$plain_text_payload_for_enabled_flagsr|�s?��%�e�,�,�E����s��I�I�e���t�#�+�+�-�-�-rc���t|t��r)tj|dd������Stdt
|��j�����)zBSerialize dict flags for writing ``FLAGS_PATH`` (legacy map only).TrRrSzflags must be dict, not )rrr5rWrXrIrJrK)rs r�$serialize_feature_flags_file_payloadr~�sX���%����D��z�%�4��:�:�:�A�A�C�C�C�
�E�t�E�{�{�/C�E�E�
F�
F�FrF�	flag_name�default�boolc�n�t��}|�|��}|�|St|��S)z�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.
    )r@rr�)rr�r�values    r�
is_enabledr��s4��
�M�M�E��I�I�i� � �E��}�����;�;�rc�`�tt���|d����S)z�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).
    r!)rrBr)rs r�
get_paramsr��s&������"�"�9�b�1�1�2�2�2rc�,�t��tS)u�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.
    )r;rr!rrrr�s���M�M�M��r)rrrr)rrrr)rr,)rr)rr)rrrrC)rOrCrrP)rOrCrrrrP)r/rrr)rrrrP)F)rrr�r�rr�)rrrrC)rr
)!�__doc__�
__future__rrir5r.�typingrr1�FLAGS_PLAIN_PATHr8r�__annotations__r	r3rrrr+r;r@rBrNrZrcrmrurxr|r~r�r�rr!rr�<module>r�s�����4#�"�"�"�"�"���������	�	�	�	�������
1�
�2��3�� "�
�"�"�"�"�')��)�)�)�)�(1�y�{�{��2�2�2�2��
���������$����*)�)�)�)�:����
����
9�9�9�9�B�B�B�B�D�D�D�D�(C�C�C�C�$<�<�<�<�D�D�D�D�.�.�.�.�G�G�G�G������3�3�3�3� � � � � � rdefence360agent/internals/__pycache__/feature_flags.cpython-311.pyc0000644000000000000000000003436400000000000022300 0ustar  �

���pἰ����UdZddlmZddlZddlZddlZddlmZdZdZ	dZ
iaded	<ia
d
ed<e��aded
<daded<d0d�Zd1d�Zd2d�Zd3d�Zd4d�Zd5d�Zd6d�Zd7d!�Zd8d$�Zd6d%�Zd7d&�Zd9d'�Zd9d(�Zd:d;d-�Zd<d.�Zd=d/�Z 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_flags�mqtt_tracked_methods�dict[str, Any]�
_cached_flags�dict[str, list[str]]�_cached_params�frozenset[str]�_cached_mqtt_methods��float�
_cached_mtime�rawr�returnc��|�iSt|t��r#i}|D]}t|t��rd||<�|St|t��r;|�d��}t|t��rt|��S|SiS)z@Map file JSON to a flat name->value dict for :func:`is_enabled`.NT�flags)�
isinstance�list�str�dict�get�_normalize_flags_from_file)r�out�item�inners    �\/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/feature_flags.pyrr4s���
�{��	��#�t���� ���	!�	!�D��$��$�$�
!� ��D�	���
��#�t�������� � ���e�T�"�"�	5�-�e�4�4�4��
�
�I�c�@�t|t��siS|�d��}t|t��siSi}|���D]C\}}t|t��rt|t
��s�0d�|D��}|r|||<�D|S)z�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.
    �paramsc�<�g|]}t|t���|��S��rr)�.0�vs  r�
<listcomp>z%_params_from_file.<locals>.<listcomp>Us'��;�;�;��
�1�c�(:�(:�;�1�;�;�;r)rrr�itemsrr)r�
raw_paramsr�name�values�cleaneds      r�_params_from_filer+Fs����c�4� � ���	�����"�"�J��j�$�'�'���	� "�C�"�(�(�*�*� � ���f��$��$�$�	�J�v�t�,D�,D�	��;�;�f�;�;�;���	 ��C��I���Jr�+tuple[dict[str, Any], dict[str, list[str]]]c�^�	tj�t��}n2#t$r%iaiat��ada	t
tfcYSwxYw|tkrt
tfS	tt��5}tj|��}ddd��n#1swxYwYt|��at|��an #ttjf$riaiaYnwxYwtt�t"d����a|a	t
tfS)Nrr!)�os�path�getmtime�
FLAGS_PATH�OSErrorrr	�	frozensetrr�open�json�loadrr+�JSONDecodeErrorr�MQTT_TRACKED_METHODS_FLAG)�mtime�frs   r�_read_stater;[sr��-��� � ��,�,�����-�-�-��
���(�{�{���
��n�,�,�,�,�-����
�
����n�,�,��
�*�
�
�	���)�A�,�,�C�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�2�3�7�7�
�*�3�/�/�����T�)�*�����
���������%����4�b�9�9�����M��.�(�(sE�$'�,A�A�3C�B(�C�(B,�,C�/B,�0!C�C/�.C/c�(�t��\}}|S�N�r;)r�_s  r�_read_flagsr@xs���}�}�H�E�1��Lrc�(�t��\}}|Sr=r>)r?rs  r�_read_paramsrB}s���
�
�I�A�v��Mrr�	list[str]c��t|ttf��s$tdt	|��j�����t
|��}td�|���D����S)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�	<genexpr>z,enabled_flag_names_sorted.<locals>.<genexpr>�s+����8�8���1�a�8�!�8�8�8�8�8�8r)	rrr�	TypeError�type�__name__r�sortedr&)r�
normalizeds  r�enabled_flag_names_sortedrN�sx���e�d�D�\�*�*�
��E�t�E�{�{�/C�E�E�
�
�	
�,�E�2�2�J��8�8�
� 0� 0� 2� 2�8�8�8�8�8�8r�names�bytesc�r�t|��}tj|dd������S)z|JSON array bytes used for sync MD5 when no params are present
    (matches correlation_api ``checksum_for_sync_flag_list``).T���	sort_keys�indent�rLr5�dumps�encode)rO�ordereds  r�canonical_sync_flag_list_bytesrZ�s2���U�m�m�G��:�g��a�8�8�8�?�?�A�A�Arrc���|st|��St|��d�t|�����D��d�}tj|dd������S)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.
    c�4�i|]\}}|t|����Sr!�rLrFs   r�
<dictcomp>z1canonical_sync_response_bytes.<locals>.<dictcomp>��$��C�C�C�D�A�q�1�f�Q�i�i�C�C�Cr�rrTrRrS)rZrLr&r5rWrX�rOr�	canonicals   r�canonical_sync_response_bytesrc�su���5�-�e�4�4�4�����C�C�F�6�<�<�>�>�,B�,B�C�C�C���I��:�i�4��:�:�:�A�A�C�C�Crr/rc�r�	t|d���5}tj|��}ddd��n#1swxYwYn##tttjf$rYdSwxYwt
|��}t|��}t||��}tj
|d������S)z�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.
    zutf-8)�encodingN�F)�usedforsecurity)r4r5r6r2�UnicodeDecodeErrorr7rNr+rc�hashlib�md5�	hexdigest)r/r:rrOr�payloads      r�!sync_checksum_hex_from_flags_filerm�s����
�$��
)�
)�
)�	�Q��)�A�,�,�C�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	����'��)=�>�����r�r�����%�c�*�*�E�
�s�
#�
#�F�+�E�6�:�:�G��;�w��6�6�6�@�@�B�B�Bs,�A�4�A�8�A�8�A�A �A c��d�td�|D����D��}tj|dd������S)z<On-disk legacy JSON: ``{flag: true, ...}`` with sorted keys.c��i|]}|d��S)Tr!)r#�ns  rr^z2legacy_feature_flags_map_bytes.<locals>.<dictcomp>�s��K�K�K�Q��D�K�K�Krc�<�h|]}t|t���|��Sr!r")r#�xs  r�	<setcomp>z1legacy_feature_flags_map_bytes.<locals>.<setcomp>�s'��!I�!I�!I��j��C�6H�6H�!I�!�!I�!I�!IrTrRrSrV)rO�ds  r�legacy_feature_flags_map_bytesru�sO��K�K�&�!I�!I�U�!I�!I�!I�J�J�K�K�K�A��:�a�4��2�2�2�9�9�;�;�;rc���t|��d�t|�����D��d�}tj|dd������S)z�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``.
    c�4�i|]\}}|t|����Sr!r]rFs   rr^z,sync_response_file_bytes.<locals>.<dictcomp>�r_rr`TrRrS)rLr&r5rWrXras   r�sync_response_file_bytesrx�s_������C�C�F�6�<�<�>�>�,B�,B�C�C�C���I��:�i�4��:�:�:�A�A�C�C�Crc�|�t|��}|sdSd�|��dz���S)zPBody for ``FLAGS_PLAIN_PATH``: one name per line, trailing newline if non-empty.r�
)rN�joinrX)rrOs  r�$plain_text_payload_for_enabled_flagsr|�s?��%�e�,�,�E����s��I�I�e���t�#�+�+�-�-�-rc���t|t��r)tj|dd������Stdt
|��j�����)zBSerialize dict flags for writing ``FLAGS_PATH`` (legacy map only).TrRrSzflags must be dict, not )rrr5rWrXrIrJrK)rs r�$serialize_feature_flags_file_payloadr~�sX���%����D��z�%�4��:�:�:�A�A�C�C�C�
�E�t�E�{�{�/C�E�E�
F�
F�FrF�	flag_name�default�boolc�n�t��}|�|��}|�|St|��S)z�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.
    )r@rr�)rr�r�values    r�
is_enabledr��s4��
�M�M�E��I�I�i� � �E��}�����;�;�rc�`�tt���|d����S)z�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).
    r!)rrBr)rs r�
get_paramsr��s&������"�"�9�b�1�1�2�2�2rc�,�t��tS)u�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.
    )r;rr!rrrr�s���M�M�M��r)rrrr)rrrr)rr,)rr)rr)rrrrC)rOrCrrP)rOrCrrrrP)r/rrr)rrrrP)F)rrr�r�rr�)rrrrC)rr
)!�__doc__�
__future__rrir5r.�typingrr1�FLAGS_PLAIN_PATHr8r�__annotations__r	r3rrrr+r;r@rBrNrZrcrmrurxr|r~r�r�rr!rr�<module>r�s�����4#�"�"�"�"�"���������	�	�	�	�������
1�
�2��3�� "�
�"�"�"�"�')��)�)�)�)�(1�y�{�{��2�2�2�2��
���������$����*)�)�)�)�:����
����
9�9�9�9�B�B�B�B�D�D�D�D�(C�C�C�C�$<�<�<�<�D�D�D�D�.�.�.�.�G�G�G�G������3�3�3�3� � � � � � rdefence360agent/internals/__pycache__/geo.cpython-311.opt-1.pyc0000644000000000000000000000657300000000000021203 0ustar  �

1o�+h�"��~�ddlmZddlmZmZmZmZddlmZddl	m
Z
ddlmZGd�d��Z
ed���Zd	S)
�)�contextmanager)�IPv4Address�IPv4Network�IPv6Address�IPv6Network)�Union)�CountryInfo)�IPc�z�eZdZd�Zdeeeeee	ffd�Z
deeeeee	ffd�Zdeeeeee	ffd�Z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��+������addressc���ddlm}	tj|��}n#t$rYdSwxYw	|j�t|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�
ValueErrorr�country�str�network_address)rrr�ip�objs     r�getz
Reader.get
s���	7�6�6�6�6�6�	��)�'�2�2�B�B���	�	�	��4�4�	����	��%�-�-�c�"�2D�.E�.E�F�F�C�C��#�	�	�	��4�4�	����"�+�s�{�{�t�+s��
+�+�,A�A%�$A%c�B�|�|��}|r|jSdS)za
        :param address: valid ipv4 address
        :return: maxmind's id of the country
        N)r�
geoname_id�rr�country_infos   r�get_idz
Reader.get_id,s+���x�x��(�(���	+��*�*��trc�B�|�|��}|r|jSdS)ze
        :param address: valid ipv4 address
        :return: country code in ISO-3166 format
        N)r�iso_coder"s   r�get_codezReader.get_code;s+���x�x��(�(���	)��(�(��trN)
�__name__�
__module__�__qualname__rrrrrrrrr$r'�rrrr	s�������,�,�,�,����k�;��C�
�,�,�,�,�>
����k�;��C�
�
�
�
�
�
����k�;��C�
�
�
�
�
�
�
rrc#�K�ddl}|j�tj��5}t|��V�ddd��dS#1swxYwYdS)zH
    :return Reader obj: instance to be reused to it's method calls
    rN)�geoip2.database�databaserr	�DB)�geoip2rs  r�readerr1Ks�����
����	��	�	���	/�	/�$�=��]�#�#�#�#�#�$�$�$�$�$�$�$�$�$�$�$�$����$�$�$�$�$�$s�A
�
A�AN)�
contextlibr�	ipaddressrrrr�typingr� defence360agent.contracts.configr	�defence360agent.utils.validater
rr1r+rr�<module>r7s���%�%�%�%�%�%�H�H�H�H�H�H�H�H�H�H�H�H�������8�8�8�8�8�8�-�-�-�-�-�-�?�?�?�?�?�?�?�?�D�$�$���$�$�$rdefence360agent/internals/__pycache__/geo.cpython-311.pyc0000644000000000000000000000657300000000000020244 0ustar  �

1o�+h�"��~�ddlmZddlmZmZmZmZddlmZddl	m
Z
ddlmZGd�d��Z
ed���Zd	S)
�)�contextmanager)�IPv4Address�IPv4Network�IPv6Address�IPv6Network)�Union)�CountryInfo)�IPc�z�eZdZd�Zdeeeeee	ffd�Z
deeeeee	ffd�Zdeeeeee	ffd�Z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��+������addressc���ddlm}	tj|��}n#t$rYdSwxYw	|j�t|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�
ValueErrorr�country�str�network_address)rrr�ip�objs     r�getz
Reader.get
s���	7�6�6�6�6�6�	��)�'�2�2�B�B���	�	�	��4�4�	����	��%�-�-�c�"�2D�.E�.E�F�F�C�C��#�	�	�	��4�4�	����"�+�s�{�{�t�+s��
+�+�,A�A%�$A%c�B�|�|��}|r|jSdS)za
        :param address: valid ipv4 address
        :return: maxmind's id of the country
        N)r�
geoname_id�rr�country_infos   r�get_idz
Reader.get_id,s+���x�x��(�(���	+��*�*��trc�B�|�|��}|r|jSdS)ze
        :param address: valid ipv4 address
        :return: country code in ISO-3166 format
        N)r�iso_coder"s   r�get_codezReader.get_code;s+���x�x��(�(���	)��(�(��trN)
�__name__�
__module__�__qualname__rrrrrrrrr$r'�rrrr	s�������,�,�,�,����k�;��C�
�,�,�,�,�>
����k�;��C�
�
�
�
�
�
����k�;��C�
�
�
�
�
�
�
rrc#�K�ddl}|j�tj��5}t|��V�ddd��dS#1swxYwYdS)zH
    :return Reader obj: instance to be reused to it's method calls
    rN)�geoip2.database�databaserr	�DB)�geoip2rs  r�readerr1Ks�����
����	��	�	���	/�	/�$�=��]�#�#�#�#�#�$�$�$�$�$�$�$�$�$�$�$�$����$�$�$�$�$�$s�A
�
A�AN)�
contextlibr�	ipaddressrrrr�typingr� defence360agent.contracts.configr	�defence360agent.utils.validater
rr1r+rr�<module>r7s���%�%�%�%�%�%�H�H�H�H�H�H�H�H�H�H�H�H�������8�8�8�8�8�8�-�-�-�-�-�-�?�?�?�?�?�?�?�?�D�$�$���$�$�$rdefence360agent/internals/__pycache__/global_scope.cpython-311.opt-1.pyc0000644000000000000000000000237600000000000023057 0ustar  �

�~��\��$��^�ddlZeje��ZGd�de��Ze��ZdS)�Nc��eZdZd�Zd�ZdS)�GlobalScopec�^�	||S#t$r}t|�d���|�d}~wwxYw)Nz is not in global scope)�KeyError�AttributeError)�self�item�errs   �[/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/global_scope.py�__getattr__zGlobalScope.__getattr__sN��	L���:����	L�	L�	L� �D�!A�!A�!A�B�B��K�����	L���s�
�
,�'�,c�R�||vrt�d|��dS|||<dS)Nz"Name %s is already in global scope)�logger�warning)r�key�values   r�__setattr__zGlobalScope.__setattr__
s3���$�;�;��N�N�?��E�E�E�E�E��D��I�I�I�N)�__name__�
__module__�__qualname__rr�rrrrs5������L�L�L�����rr)�logging�	getLoggerrr�dictr�grrr�<module>rs[������	��	�8�	$�	$�������$�����K�M�M���rdefence360agent/internals/__pycache__/global_scope.cpython-311.pyc0000644000000000000000000000237600000000000022120 0ustar  �

�~��\��$��^�ddlZeje��ZGd�de��Ze��ZdS)�Nc��eZdZd�Zd�ZdS)�GlobalScopec�^�	||S#t$r}t|�d���|�d}~wwxYw)Nz is not in global scope)�KeyError�AttributeError)�self�item�errs   �[/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/global_scope.py�__getattr__zGlobalScope.__getattr__sN��	L���:����	L�	L�	L� �D�!A�!A�!A�B�B��K�����	L���s�
�
,�'�,c�R�||vrt�d|��dS|||<dS)Nz"Name %s is already in global scope)�logger�warning)r�key�values   r�__setattr__zGlobalScope.__setattr__
s3���$�;�;��N�N�?��E�E�E�E�E��D��I�I�I�N)�__name__�
__module__�__qualname__rr�rrrrs5������L�L�L�����rr)�logging�	getLoggerrr�dictr�grrr�<module>rs[������	��	�8�	$�	$�������$�����K�M�M���rdefence360agent/internals/__pycache__/iaid.cpython-311.opt-1.pyc0000644000000000000000000005225300000000000021333 0ustar  �

�5&��˷��.�ddlZddlZddlZddlZddlZddlZddlmZddlm	Z	ddl
mZddlm
Z
ddlmZddlmZddlmZmZdd	lmZdd
lmZmZddlmZddlmZdd
lmZm Z e	e!��Z"dZ#dZ$	dZ%Gd�de&��Z'Gd�de��Z(dS)�N)�	dataclass)�	getLogger)�Path)�Callable)�urljoin)�Request)�API�APIError)�
LicenseCLN)�atomic_rewrite�safe_cancel_task)�DAY)�g)�DeadlockDetectingLock�
DeadlockError�
��<c��eZdZdZdS)�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������.�.�.�.rrc��eZdZdZeeje�d����Zeeje�d����Z	eeje�d����Z
eeje�d����Zed��Z
e
dzZe
dzZe
d	zZe
d
zZgggd�Ze��Zej��Zed�
��Gd�d����Zedd�defd���Zedd�defd���Zed���Zed���Z ed���Z!ed���Z"ed)d���Z#ed���Z$ed���Z%edefd���Z&ed ���Z'ed*d#���Z(ed$���Z)ed%���Z*ed+d&���Z+ed'���Z,ed+d(���Z-dS),�IndependentAgentIDAPIz/api/auth/agent/{}�register�activate�loginz
token-infoz/var/imunify360�iaidz
iaid-passwordz
iaid-tokenziaid-activated)r r!r"T)�frozenc�J�eZdZUgd�Zeed<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)�timeout�coroc��K�tj|tjd|z��tzz���d{V��||��d{V��dS)N�)�asyncio�sleep�random�	randrange�_TIMEOUT_MULTIPLICATOR)r1�attemptr0�argss    r�_retry_on_errorz%IndependentAgentIDAPI._retry_on_errorMs}�����m��f�&�q�G�|�4�4�7M�M�M�
�
�	
�	
�	
�	
�	
�	
�	
��d�D�k���������rc
�f�d�|j|D��|j|<t|j|��dkrXtj��}|j|�|�|j|g|�R||d������dSt�d|��dS)Nc�:�g|]}|����|��Sr)�done)�.0�tasks  r�
<listcomp>z3IndependentAgentIDAPI._add_task.<locals>.<listcomp>Ws5��
�
�
��T�Y�Y�[�[�
��
�
�
rr3�r9r0zTask %s already in retry queue)	�_tasks�lenr4�get_event_loop�append�create_taskr;�logger�info)�cls�typer1r9r0r:�loops       r�	_add_taskzIndependentAgentIDAPI._add_taskUs���
�
� �Z��-�
�
�
��
�4���s�z�$�� � �A�%�%��)�+�+�D��J�t��#�#�� � �'�C�'���#���-4�g������
�
�
�
�
�
�K�K�8�$�?�?�?�?�?rc�@�|�d|jd���dS)Nr!r�r9)rMr!�rJs r�add_initial_taskz&IndependentAgentIDAPI.add_initial_taskfs"���
�
�j�#�,��
�:�:�:�:�:rc���K�|j���D]N\}}|D]F}|���s0t|���d{V��t�d|���G�OdS)NzRetry task %s was canceled.)rC�itemsr>r
rHrI)rJrK�tasksr@s    r�shutdownzIndependentAgentIDAPI.shutdownjs������:�+�+�-�-�	E�	E�K�D�%��
E�
E���y�y�{�{�E�*�4�0�0�0�0�0�0�0�0�0��K�K� =�t�D�D�D��
E�	E�	Erc�4�tjd��jS)N�_imunify)�grp�getgrnam�gr_gidrrr�_gidzIndependentAgentIDAPI._gidrs���|�J�'�'�.�.rc�j�|j���r|j���SdS�N)�	IAID_FILE�exists�	read_textrPs r�get_iaidzIndependentAgentIDAPI.get_iaidvs1���=���!�!�	-��=�*�*�,�,�,��trN�POSTc��ddi}|�|�|��t||||r&tj|�����nd���S)NzContent-Typezapplication/json)�method�headers�data)�updater�json�dumps�encode)�urlrerd�kwargs�_headerss     r�_requestzIndependentAgentIDAPI._request|sh��"�$6�7�����O�O�G�$�$�$�����06�@���F�#�#�*�*�,�,�,�D�	
�
�
�	
rc�L�td�|j|jfD����S)Nc3�>K�|]}|���V��dSr])r_)r?�	iaid_files  r�	<genexpr>z6IndependentAgentIDAPI.is_registered.<locals>.<genexpr>�sB����
�
��
�����
�
�
�
�
�
r)�allr^�IAID_PASSWORD_FILErPs r�
is_registeredz#IndependentAgentIDAPI.is_registered�s:���
�
�!�m�S�-C�D�
�
�
�
�
�	
rc��K�t���rGt����d{V��t���rtd���	|j�d������}|std���|S#t$r}td|����|�d}~wwxYw)zWEnsure that iaid token is up to date
        Return iaid token or raise IAIDTokenError.NzIAID token is expired�ascii)�encodingzIAID_TOKEN_FILE is emptyzCan't get iaid token, reason: )r�is_token_expiredr"r�IAID_TOKEN_FILEr`�strip�	Exception)rJ�token�es   r�	get_tokenzIndependentAgentIDAPI.get_token�s�����!�1�1�3�3�	>�'�-�-�/�/�/�/�/�/�/�/�/�$�5�5�7�7�
>�$�%<�=�=�=�	N��'�1�1�7�1�C�C�I�I�K�K�E��
A�$�%?�@�@�@��L���	N�	N�	N� �!E�!�!E�!E�F�F�A�M�����	N���s�$?B$�$
C�.C�C�returnc��lK�|����d{V��}d|i}|�|j|d���}|�|���d{V��}|�d��}|�td|���	|jdi|��S#t$r}td|�d|����|�d}~wwxYw)	NzX-Auth�GET)rerd�
token_infozwrong response %rzincomplete token_info z: r)rrn�
TOKEN_INFO�
async_request�getr
r/�	TypeError)rJ�
iaid_tokenre�request�resultr}r~s       r�_get_token_infoz%IndependentAgentIDAPI._get_token_info�s������=�=�?�?�*�*�*�*�*�*�
��Z�(���,�,�s�~�w�u�,�M�M���(�(��1�1�1�1�1�1�1�1���
�
�<�(�(���=��.��7�7�7�	O� �3�=�)�)�5�)�)�)���	O�	O�	O��(�U�U�U�A�A�F�G�G�Q�N�����	O���s�B�
B3�B.�.B3c��	tj|j��}|j}n#t$rd}YnwxYwtj��|z
tkS)Ng)�os�statrz�st_mtime�FileNotFoundError�timer)rJr�r�s   rryz&IndependentAgentIDAPI.is_token_expired�sb��	%��7�3�.�/�/�D��}�H�H��!�	�	�	��H�H�H�	�����y�{�{�X�%��+�+s�#�2�2Fr3c
��`K�|j���}	|j4�d{V��|���rF|r|r	ddd���d{V��dS|�,||���kr	ddd���d{V��dSt	��}tj��}|r||d<|j|jfi|��}	|�	|���d{V��}|j
�d���tt|j��|d|j���d|���d���tt|j��|d|j���d	�
��|����d{V��n�#t&$r�}	t(�d|	|��|	j�|	jdks|	jd
kr/|t.kr$|�d|j|||dz|���n"t(�d|j||	��Yd}	~	ddd���d{V��dSd}	~	wwxYw	ddd���d{V��dS#1�d{V��swxYwYdS#t8$rt(�d|��YdSwxYw)Nr)T��
missing_okr#������backup�uid�gid�permissions�passwordi�)r�r�z0Something went wrong on register %r - attempt %s���r r3rOz-Failed to register (%s) after %s attempts: %rz<Received incorrect credentials on register after %s attempts)�_register_lock�lockedru�_get_credentials_ts�dictr�
get_server_idrn�REGISTER_URLr��IAID_ACTIVATED_FILE�unlinkrr.r^r_r[rtr!r
rH�warning�status_code�
_MAX_TRIESrMr �error�full_urlr)
rJ�force�tried_credentials_tsr9�was_waiting�payloadr)r�r�r~s
          rr zIndependentAgentIDAPI.register�s������(�/�/�1�1��I	��)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)��$�$�&�&�� ��K���C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�-�8�0�3�3J�3J�3L�3L�L�L��C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)��&�&��&�4�6�6�	��5�+4�G�K�(�&�#�,�s�'7�C�C�7�C�C��.)�#&�#4�#4�W�#=�#=�=�=�=�=�=�=�F��+�2�2�d�2�C�C�C�<#��C�M�*�*��v��"�}�3�3�5�5���H�H�J�J�$)�
����#��C�2�3�3��z�*�"�5�<�<�>�>�$)�	�����,�,�.�.�(�(�(�(�(�(�(�(��W �����N�N�J�������
�-��=�C�/�/��=�C�/�/�!�J�.�.��
�
�&��L�!�0�#�a�K�$+�
&��������K�#�,�#��	����F�F�F�gC
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�����0����V)�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�	�	�	��L�L�N��
�
�
�
�
�
�	���s~�
J�I1�J�I1�2J�;I1�6F*�7B3I1�*
I�4B
I�>I1�J�I�I1�J�1
I;�;J�>I;�?J�%J-�,J-c��PK�|j���s|����d{V��dStj��}|����d{V��}|j|�d��ks|j|�d��kr7t�
d|��|����d{V��dS|j�
��}|jr|j|ks|jr|����d{V��dSdS)z!Check whether the agent activatedN�status�idzGot a corrupted token: %r)r�r_r!rrr�r(r�r)rHr��
reactivater^r`r'r#r*r")rJ�licr}r#s    r�ensure_is_activated_and_validz3IndependentAgentIDAPI.ensure_is_activated_and_valid
sJ�����&�-�-�/�/�	��,�,�.�.� � � � � � � ��F��"�$�$���)�)�+�+�+�+�+�+�+�+����3�7�7��$
�$
�
�
�
�_�����
�
�
-�
-��L�L�4�e�<�<�<��.�.�"�"�"�"�"�"�"�"�"��F��}�&�&�(�(���{�	�e�j�D�0�0�E�4D�0��)�)�+�+����������1�0rc�>�|j���jSr])rtr�r�rPs rr�z)IndependentAgentIDAPI._get_credentials_tss���%�*�*�,�,�5�5rc	��K�|j���r|���td<dS|���s6t
�d��|����d{V��dStj	��rL|���td<|�
��r|����d{V��dStj��}|st
�d��dS|j
4�d{V��|j���r0|���td<	ddd���d{V��dS|j���}|j���}|���}|�|j|||���}|td<d}	|�|���d{V��|j���|j�d���|����d{V��n�#t0$r�}t
�d||��|jr|jd	krd}nr|jrI|jd
ks|jdkr3|t4kr(|�d|j|d
z|t:���n"t
�d|j||��Yd}~nd}~wwxYwddd���d{V��n#1�d{V��swxYwY|r|�d|����d{V��dSdS)Nr#z&need to register first before activatez9Can't continue iaid activation: no valid license is found)r#r��licenseFTr�z.Something went wrong on activate %r attempt %s�r�r�r!r3rBz-Failed to activate (%s) after %s attempts: %r�r�r�) r�r_rarrurHr�r r�is_freeryr"r�_activate_lockr^r`rtr�rn�ACTIVATE_URLr��touchrzr�r
r�r�rMr!�_ACTIVATE_MINIMUM_TIMEOUTr�r�)	rJr9r�r#r��credentials_tsr��need_to_registerr~s	         rr!zIndependentAgentIDAPI.activate!sc�����"�)�)�+�+�	������A�f�I��F�� � �"�"�	��N�N�C�D�D�D��,�,�.�.� � � � � � � ��F�����	������A�f�I��#�#�%�%�
"��i�i�k�k�!�!�!�!�!�!�!��F��"�$�$���	��N�N�K�
�
�
�
�F��%�2	"�2	"�2	"�2	"�2	"�2	"�2	"�2	"��&�-�-�/�/�
��L�L�N�N��&�	��2	"�2	"�2	"�2	"�2	"�2	"�2	"�2	"�2	"�2	"�2	"�2	"�2	"�2	"��=�*�*�,�,�D��-�7�7�9�9�H� �4�4�6�6�N��l�l�� �t�h��#���G��A�f�I�$��$
"��'�'��0�0�0�0�0�0�0�0�0��'�-�-�/�/�/�B�#�*�*�d�*�;�;�;��i�i�k�k�!�!�!�!�!�!�!�!��C�
�
�
����D������
�=��Q�]�c�%9�%9�'+�$�$��M����#�-�-���#�1E�1E��*�,�,�
�M�M�"����!�� '� 9�"������L�L�G��(���	������������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��,�O�O�O�O�O�O�O�O�O�O�O�	P�	PsD�!7L�+A0L�4I�6L�
K=�B#K8�3L�8K=�=L�
L�Lc��tK�|j�d���|����d{V��dS)NTr�)r�r�r!rPs rr�z IndependentAgentIDAPI.reactivateksF������&�&�$�&�7�7�7��l�l�n�n���������rc��K�|���st�d��dS|j���}|j���}|���}|�|j||���}	|�	|���d{V��}tt|j��|d|j�
��d|���d���dS#t$r�}t�d||��|t"kr]|j�|jdkr"|�d	|j|d
z|���nW|jdkr|�d
|����d{V��n(t�d|j||��Yd}~dSYd}~dSYd}~dSd}~wwxYw)Nz#need to register first before login)r#r�r}r�r�r�z/Something wrong happened on login %r attempt %sr�r"r3rOr�Tr�z*Failed to login (%s) after %s attempts: %r)rurHr�r^r`rtr�rn�	LOGIN_URLr�rr.rzr_r[r
r�r�r�rMr"r r�)rJr9r#r�r�r�r�r~s        rr"zIndependentAgentIDAPI.loginpsS����� � �"�"�	��L�L�>�?�?�?��F��}�&�&�(�(���)�3�3�5�5���0�0�2�2���,�,�s�}�4�(�,�K�K��	��,�,�W�5�5�5�5�5�5�5�5�F�.
��C�'�(�(��w���*�1�1�3�3���H�H�J�J�!�

�
�
�
�
�
��-�	�	�	��N�N�A�1�g�
�
�
���#�#��=�(�A�M�S�,@�,@��M�M����G�a�K��"������]�c�)�)��,�,�"��'��������������@��$���	�������������������������	���s�D
�

G�B&G�G)Nrb)FNr3)r3).rrr�API_PATHrr	�	_BASE_URL�formatr�r�r�r�r�IAID_DIRr^rtrzr�rCrr�r4�Lockr�rr/�staticmethodrr;�classmethodrMrQrUr[rarnrurr�ryr r�r�r!r�r"rrrrr*s������#�H��7�3�=�(�/�/�*�*E�*E�F�F�L��7�3�=�(�/�/�*�*E�*E�F�F�L����
�x���w�'?�'?�@�@�I���������(E�(E�F�F�J��t�%�&�&�H��6�!�I�!�O�3����-�O�"�%5�5�������F�
+�*�,�,�N�!�W�\�^�^�N��Y�d���������������FG����H�����\���EF�@�@�@�8�@�@�@��[�@� �;�;��[�;��E�E��[�E��/�/��\�/�����[��
�	
�	
�	
��\�	
��
�
��[�
��
N�
N��[�
N��O�i�O�O�O��[�O��,�,��[�,��P�P�P��[�P�d����[��$�6�6��[�6��GP�GP�GP��[�GP�R����[���(�(�(��[�(�(�(rr))r4rXrhr�r6r��dataclassesr�loggingr�pathlibr�typingr�urllib.parser�urllib.requestr�defence360agent.api.serverr	r
�!defence360agent.contracts.licenser�defence360agent.utilsrr
�defence360agent.utils.commonr�&defence360agent.internals.global_scoper�1defence360agent.internals.deadlock_detecting_lockrrrrHr�r8r��RuntimeErrorrrrrr�<module>r�s�������
�
�
�
�����	�	�	�	�
�
�
�
�����!�!�!�!�!�!������������������� � � � � � �"�"�"�"�"�"�4�4�4�4�4�4�4�4�8�8�8�8�8�8�B�B�B�B�B�B�B�B�,�,�,�,�,�,�4�4�4�4�4�4���������

��8�	�	���
������/�/�/�/�/�\�/�/�/�o�o�o�o�o�C�o�o�o�o�ordefence360agent/internals/__pycache__/iaid.cpython-311.pyc0000644000000000000000000005225300000000000020374 0ustar  �

�5&��˷��.�ddlZddlZddlZddlZddlZddlZddlmZddlm	Z	ddl
mZddlm
Z
ddlmZddlmZddlmZmZdd	lmZdd
lmZmZddlmZddlmZdd
lmZm Z e	e!��Z"dZ#dZ$	dZ%Gd�de&��Z'Gd�de��Z(dS)�N)�	dataclass)�	getLogger)�Path)�Callable)�urljoin)�Request)�API�APIError)�
LicenseCLN)�atomic_rewrite�safe_cancel_task)�DAY)�g)�DeadlockDetectingLock�
DeadlockError�
��<c��eZdZdZdS)�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������.�.�.�.rrc��eZdZdZeeje�d����Zeeje�d����Z	eeje�d����Z
eeje�d����Zed��Z
e
dzZe
dzZe
d	zZe
d
zZgggd�Ze��Zej��Zed�
��Gd�d����Zedd�defd���Zedd�defd���Zed���Zed���Z ed���Z!ed���Z"ed)d���Z#ed���Z$ed���Z%edefd���Z&ed ���Z'ed*d#���Z(ed$���Z)ed%���Z*ed+d&���Z+ed'���Z,ed+d(���Z-dS),�IndependentAgentIDAPIz/api/auth/agent/{}�register�activate�loginz
token-infoz/var/imunify360�iaidz
iaid-passwordz
iaid-tokenziaid-activated)r r!r"T)�frozenc�J�eZdZUgd�Zeed<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)�timeout�coroc��K�tj|tjd|z��tzz���d{V��||��d{V��dS)N�)�asyncio�sleep�random�	randrange�_TIMEOUT_MULTIPLICATOR)r1�attemptr0�argss    r�_retry_on_errorz%IndependentAgentIDAPI._retry_on_errorMs}�����m��f�&�q�G�|�4�4�7M�M�M�
�
�	
�	
�	
�	
�	
�	
�	
��d�D�k���������rc
�f�d�|j|D��|j|<t|j|��dkrXtj��}|j|�|�|j|g|�R||d������dSt�d|��dS)Nc�:�g|]}|����|��Sr)�done)�.0�tasks  r�
<listcomp>z3IndependentAgentIDAPI._add_task.<locals>.<listcomp>Ws5��
�
�
��T�Y�Y�[�[�
��
�
�
rr3�r9r0zTask %s already in retry queue)	�_tasks�lenr4�get_event_loop�append�create_taskr;�logger�info)�cls�typer1r9r0r:�loops       r�	_add_taskzIndependentAgentIDAPI._add_taskUs���
�
� �Z��-�
�
�
��
�4���s�z�$�� � �A�%�%��)�+�+�D��J�t��#�#�� � �'�C�'���#���-4�g������
�
�
�
�
�
�K�K�8�$�?�?�?�?�?rc�@�|�d|jd���dS)Nr!r�r9)rMr!�rJs r�add_initial_taskz&IndependentAgentIDAPI.add_initial_taskfs"���
�
�j�#�,��
�:�:�:�:�:rc���K�|j���D]N\}}|D]F}|���s0t|���d{V��t�d|���G�OdS)NzRetry task %s was canceled.)rC�itemsr>r
rHrI)rJrK�tasksr@s    r�shutdownzIndependentAgentIDAPI.shutdownjs������:�+�+�-�-�	E�	E�K�D�%��
E�
E���y�y�{�{�E�*�4�0�0�0�0�0�0�0�0�0��K�K� =�t�D�D�D��
E�	E�	Erc�4�tjd��jS)N�_imunify)�grp�getgrnam�gr_gidrrr�_gidzIndependentAgentIDAPI._gidrs���|�J�'�'�.�.rc�j�|j���r|j���SdS�N)�	IAID_FILE�exists�	read_textrPs r�get_iaidzIndependentAgentIDAPI.get_iaidvs1���=���!�!�	-��=�*�*�,�,�,��trN�POSTc��ddi}|�|�|��t||||r&tj|�����nd���S)NzContent-Typezapplication/json)�method�headers�data)�updater�json�dumps�encode)�urlrerd�kwargs�_headerss     r�_requestzIndependentAgentIDAPI._request|sh��"�$6�7�����O�O�G�$�$�$�����06�@���F�#�#�*�*�,�,�,�D�	
�
�
�	
rc�L�td�|j|jfD����S)Nc3�>K�|]}|���V��dSr])r_)r?�	iaid_files  r�	<genexpr>z6IndependentAgentIDAPI.is_registered.<locals>.<genexpr>�sB����
�
��
�����
�
�
�
�
�
r)�allr^�IAID_PASSWORD_FILErPs r�
is_registeredz#IndependentAgentIDAPI.is_registered�s:���
�
�!�m�S�-C�D�
�
�
�
�
�	
rc��K�t���rGt����d{V��t���rtd���	|j�d������}|std���|S#t$r}td|����|�d}~wwxYw)zWEnsure that iaid token is up to date
        Return iaid token or raise IAIDTokenError.NzIAID token is expired�ascii)�encodingzIAID_TOKEN_FILE is emptyzCan't get iaid token, reason: )r�is_token_expiredr"r�IAID_TOKEN_FILEr`�strip�	Exception)rJ�token�es   r�	get_tokenzIndependentAgentIDAPI.get_token�s�����!�1�1�3�3�	>�'�-�-�/�/�/�/�/�/�/�/�/�$�5�5�7�7�
>�$�%<�=�=�=�	N��'�1�1�7�1�C�C�I�I�K�K�E��
A�$�%?�@�@�@��L���	N�	N�	N� �!E�!�!E�!E�F�F�A�M�����	N���s�$?B$�$
C�.C�C�returnc��lK�|����d{V��}d|i}|�|j|d���}|�|���d{V��}|�d��}|�td|���	|jdi|��S#t$r}td|�d|����|�d}~wwxYw)	NzX-Auth�GET)rerd�
token_infozwrong response %rzincomplete token_info z: r)rrn�
TOKEN_INFO�
async_request�getr
r/�	TypeError)rJ�
iaid_tokenre�request�resultr}r~s       r�_get_token_infoz%IndependentAgentIDAPI._get_token_info�s������=�=�?�?�*�*�*�*�*�*�
��Z�(���,�,�s�~�w�u�,�M�M���(�(��1�1�1�1�1�1�1�1���
�
�<�(�(���=��.��7�7�7�	O� �3�=�)�)�5�)�)�)���	O�	O�	O��(�U�U�U�A�A�F�G�G�Q�N�����	O���s�B�
B3�B.�.B3c��	tj|j��}|j}n#t$rd}YnwxYwtj��|z
tkS)Ng)�os�statrz�st_mtime�FileNotFoundError�timer)rJr�r�s   rryz&IndependentAgentIDAPI.is_token_expired�sb��	%��7�3�.�/�/�D��}�H�H��!�	�	�	��H�H�H�	�����y�{�{�X�%��+�+s�#�2�2Fr3c
��`K�|j���}	|j4�d{V��|���rF|r|r	ddd���d{V��dS|�,||���kr	ddd���d{V��dSt	��}tj��}|r||d<|j|jfi|��}	|�	|���d{V��}|j
�d���tt|j��|d|j���d|���d���tt|j��|d|j���d	�
��|����d{V��n�#t&$r�}	t(�d|	|��|	j�|	jdks|	jd
kr/|t.kr$|�d|j|||dz|���n"t(�d|j||	��Yd}	~	ddd���d{V��dSd}	~	wwxYw	ddd���d{V��dS#1�d{V��swxYwYdS#t8$rt(�d|��YdSwxYw)Nr)T��
missing_okr#������backup�uid�gid�permissions�passwordi�)r�r�z0Something went wrong on register %r - attempt %s���r r3rOz-Failed to register (%s) after %s attempts: %rz<Received incorrect credentials on register after %s attempts)�_register_lock�lockedru�_get_credentials_ts�dictr�
get_server_idrn�REGISTER_URLr��IAID_ACTIVATED_FILE�unlinkrr.r^r_r[rtr!r
rH�warning�status_code�
_MAX_TRIESrMr �error�full_urlr)
rJ�force�tried_credentials_tsr9�was_waiting�payloadr)r�r�r~s
          rr zIndependentAgentIDAPI.register�s������(�/�/�1�1��I	��)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)��$�$�&�&�� ��K���C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�-�8�0�3�3J�3J�3L�3L�L�L��C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)��&�&��&�4�6�6�	��5�+4�G�K�(�&�#�,�s�'7�C�C�7�C�C��.)�#&�#4�#4�W�#=�#=�=�=�=�=�=�=�F��+�2�2�d�2�C�C�C�<#��C�M�*�*��v��"�}�3�3�5�5���H�H�J�J�$)�
����#��C�2�3�3��z�*�"�5�<�<�>�>�$)�	�����,�,�.�.�(�(�(�(�(�(�(�(��W �����N�N�J�������
�-��=�C�/�/��=�C�/�/�!�J�.�.��
�
�&��L�!�0�#�a�K�$+�
&��������K�#�,�#��	����F�F�F�gC
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�C
)�����0����V)�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�	�	�	��L�L�N��
�
�
�
�
�
�	���s~�
J�I1�J�I1�2J�;I1�6F*�7B3I1�*
I�4B
I�>I1�J�I�I1�J�1
I;�;J�>I;�?J�%J-�,J-c��PK�|j���s|����d{V��dStj��}|����d{V��}|j|�d��ks|j|�d��kr7t�
d|��|����d{V��dS|j�
��}|jr|j|ks|jr|����d{V��dSdS)z!Check whether the agent activatedN�status�idzGot a corrupted token: %r)r�r_r!rrr�r(r�r)rHr��
reactivater^r`r'r#r*r")rJ�licr}r#s    r�ensure_is_activated_and_validz3IndependentAgentIDAPI.ensure_is_activated_and_valid
sJ�����&�-�-�/�/�	��,�,�.�.� � � � � � � ��F��"�$�$���)�)�+�+�+�+�+�+�+�+����3�7�7��$
�$
�
�
�
�_�����
�
�
-�
-��L�L�4�e�<�<�<��.�.�"�"�"�"�"�"�"�"�"��F��}�&�&�(�(���{�	�e�j�D�0�0�E�4D�0��)�)�+�+����������1�0rc�>�|j���jSr])rtr�r�rPs rr�z)IndependentAgentIDAPI._get_credentials_tss���%�*�*�,�,�5�5rc	��K�|j���r|���td<dS|���s6t
�d��|����d{V��dStj	��rL|���td<|�
��r|����d{V��dStj��}|st
�d��dS|j
4�d{V��|j���r0|���td<	ddd���d{V��dS|j���}|j���}|���}|�|j|||���}|td<d}	|�|���d{V��|j���|j�d���|����d{V��n�#t0$r�}t
�d||��|jr|jd	krd}nr|jrI|jd
ks|jdkr3|t4kr(|�d|j|d
z|t:���n"t
�d|j||��Yd}~nd}~wwxYwddd���d{V��n#1�d{V��swxYwY|r|�d|����d{V��dSdS)Nr#z&need to register first before activatez9Can't continue iaid activation: no valid license is found)r#r��licenseFTr�z.Something went wrong on activate %r attempt %s�r�r�r!r3rBz-Failed to activate (%s) after %s attempts: %r�r�r�) r�r_rarrurHr�r r�is_freeryr"r�_activate_lockr^r`rtr�rn�ACTIVATE_URLr��touchrzr�r
r�r�rMr!�_ACTIVATE_MINIMUM_TIMEOUTr�r�)	rJr9r�r#r��credentials_tsr��need_to_registerr~s	         rr!zIndependentAgentIDAPI.activate!sc�����"�)�)�+�+�	������A�f�I��F�� � �"�"�	��N�N�C�D�D�D��,�,�.�.� � � � � � � ��F�����	������A�f�I��#�#�%�%�
"��i�i�k�k�!�!�!�!�!�!�!��F��"�$�$���	��N�N�K�
�
�
�
�F��%�2	"�2	"�2	"�2	"�2	"�2	"�2	"�2	"��&�-�-�/�/�
��L�L�N�N��&�	��2	"�2	"�2	"�2	"�2	"�2	"�2	"�2	"�2	"�2	"�2	"�2	"�2	"�2	"��=�*�*�,�,�D��-�7�7�9�9�H� �4�4�6�6�N��l�l�� �t�h��#���G��A�f�I�$��$
"��'�'��0�0�0�0�0�0�0�0�0��'�-�-�/�/�/�B�#�*�*�d�*�;�;�;��i�i�k�k�!�!�!�!�!�!�!�!��C�
�
�
����D������
�=��Q�]�c�%9�%9�'+�$�$��M����#�-�-���#�1E�1E��*�,�,�
�M�M�"����!�� '� 9�"������L�L�G��(���	������������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��,�O�O�O�O�O�O�O�O�O�O�O�	P�	PsD�!7L�+A0L�4I�6L�
K=�B#K8�3L�8K=�=L�
L�Lc��tK�|j�d���|����d{V��dS)NTr�)r�r�r!rPs rr�z IndependentAgentIDAPI.reactivateksF������&�&�$�&�7�7�7��l�l�n�n���������rc��K�|���st�d��dS|j���}|j���}|���}|�|j||���}	|�	|���d{V��}tt|j��|d|j�
��d|���d���dS#t$r�}t�d||��|t"kr]|j�|jdkr"|�d	|j|d
z|���nW|jdkr|�d
|����d{V��n(t�d|j||��Yd}~dSYd}~dSYd}~dSd}~wwxYw)Nz#need to register first before login)r#r�r}r�r�r�z/Something wrong happened on login %r attempt %sr�r"r3rOr�Tr�z*Failed to login (%s) after %s attempts: %r)rurHr�r^r`rtr�rn�	LOGIN_URLr�rr.rzr_r[r
r�r�r�rMr"r r�)rJr9r#r�r�r�r�r~s        rr"zIndependentAgentIDAPI.loginpsS����� � �"�"�	��L�L�>�?�?�?��F��}�&�&�(�(���)�3�3�5�5���0�0�2�2���,�,�s�}�4�(�,�K�K��	��,�,�W�5�5�5�5�5�5�5�5�F�.
��C�'�(�(��w���*�1�1�3�3���H�H�J�J�!�

�
�
�
�
�
��-�	�	�	��N�N�A�1�g�
�
�
���#�#��=�(�A�M�S�,@�,@��M�M����G�a�K��"������]�c�)�)��,�,�"��'��������������@��$���	�������������������������	���s�D
�

G�B&G�G)Nrb)FNr3)r3).rrr�API_PATHrr	�	_BASE_URL�formatr�r�r�r�r�IAID_DIRr^rtrzr�rCrr�r4�Lockr�rr/�staticmethodrr;�classmethodrMrQrUr[rarnrurr�ryr r�r�r!r�r"rrrrr*s������#�H��7�3�=�(�/�/�*�*E�*E�F�F�L��7�3�=�(�/�/�*�*E�*E�F�F�L����
�x���w�'?�'?�@�@�I���������(E�(E�F�F�J��t�%�&�&�H��6�!�I�!�O�3����-�O�"�%5�5�������F�
+�*�,�,�N�!�W�\�^�^�N��Y�d���������������FG����H�����\���EF�@�@�@�8�@�@�@��[�@� �;�;��[�;��E�E��[�E��/�/��\�/�����[��
�	
�	
�	
��\�	
��
�
��[�
��
N�
N��[�
N��O�i�O�O�O��[�O��,�,��[�,��P�P�P��[�P�d����[��$�6�6��[�6��GP�GP�GP��[�GP�R����[���(�(�(��[�(�(�(rr))r4rXrhr�r6r��dataclassesr�loggingr�pathlibr�typingr�urllib.parser�urllib.requestr�defence360agent.api.serverr	r
�!defence360agent.contracts.licenser�defence360agent.utilsrr
�defence360agent.utils.commonr�&defence360agent.internals.global_scoper�1defence360agent.internals.deadlock_detecting_lockrrrrHr�r8r��RuntimeErrorrrrrr�<module>r�s�������
�
�
�
�����	�	�	�	�
�
�
�
�����!�!�!�!�!�!������������������� � � � � � �"�"�"�"�"�"�4�4�4�4�4�4�4�4�8�8�8�8�8�8�B�B�B�B�B�B�B�B�,�,�,�,�,�,�4�4�4�4�4�4���������

��8�	�	���
������/�/�/�/�/�\�/�/�/�o�o�o�o�o�C�o�o�o�o�ordefence360agent/internals/__pycache__/lazy_load.cpython-311.opt-1.pyc0000644000000000000000000000107700000000000022401 0ustar  �

}Ir����� �Gd�d��ZdS)c��eZdZdZdZdS)�
CoreSource)z"defence360agent.contracts.messages)zdefence360agent.simple_rpcz0defence360agent.feature_management.rpc.endpointsN)�__name__�
__module__�__qualname__�MESSAGES�	ENDPOINTS���X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/lazy_load.pyrrs������6�H��I�I�Ir
rN)rr	r
r�<module>rs7�����������r
defence360agent/internals/__pycache__/lazy_load.cpython-311.pyc0000644000000000000000000000107700000000000021442 0ustar  �

}Ir����� �Gd�d��ZdS)c��eZdZdZdZdS)�
CoreSource)z"defence360agent.contracts.messages)zdefence360agent.simple_rpcz0defence360agent.feature_management.rpc.endpointsN)�__name__�
__module__�__qualname__�MESSAGES�	ENDPOINTS���X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/lazy_load.pyrrs������6�H��I�I�Ir
rN)rr	r
r�<module>rs7�����������r
defence360agent/internals/__pycache__/logger.cpython-311.opt-1.pyc0000644000000000000000000005142600000000000021705 0ustar  �

�H�5Ң���ddlZddlZddlZddlZddlZddlZddlZddlZddlZddl	m
Z
mZddlm
Z
ddlZddlZddlmZmZddlmZddlmZddlmZddlmZmZdd	lmZej�d
d��Zej e!��Z"d#d
�Z#Gd�d��Z$e
d��d���Z%d�Z&d�Z'd�Z(d�Z)d�Z*d�Z+d�Z,d�Z-de.fd�Z/d�Z0d�Z1e
de.fd���Z2e
de.fd ���Z3Gd!�d"��Z4dS)$�N)�contextmanager�suppress)�	lru_cache)�config�sentry)�
AcronisBackup)�Logger)�Sentry)�antivirus_mode�is_root_user)�tags�IMUNIFY360_LOGGING_PREFIX�Fc���	tj}n#ttf$rd}YnwxYw|r�t	jtj|tjj	d���t	j
��5}tj���
��D]\}}|�||���dtjd��i|_ddd��n#1swxYwYddd�Sd	d
d�S)NT�on)�dsn�debug�release�attach_stacktrace�id�	server_id�ERRORz-sentry_sdk.integrations.logging.SentryHandler)�level�class�NOTSETzlogging.NullHandler)r
�ENABLE�KeyError�AssertionError�
sentry_sdk�init�DSNr�Core�VERSION�configure_scoperr
�items�set_tag�tag�user)r�error_reporting�scoper'�values     �U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/logger.py�_sentry_initr-sR��� �-�����n�%�������������
����
���K�'�"�		
�	
�	
�	
��
'�
)�
)�	9�U�$�k�m�m�1�1�3�3�
*�
*�
��U��
�
�c�5�)�)�)�)���
�;� 7� 7�8�E�J�	9�	9�	9�	9�	9�	9�	9�	9�	9�	9�	9����	9�	9�	9�	9�
�D�
�
�	
��*�
�
�	
s��%�%�.AC�C�Cc�H�eZdZdejjzZed���Zd�Z	dS)�_LoggerDynConfigz/var/log/%sc�x�dtjj�dtj��ptj����S)Nz	/var/log/z_user_logs/)rr"�PRODUCT�getpass�getuser�os�getuid��r,�
_user_log_dirz_LoggerDynConfig._user_log_dir:s7���
�K�����O���,�����,�
�	
r7c�,�t��}|r|jn|���|_dgd�dgd�dgd�d�dt	��ddd|jzd	d
d�ddd|jzd	d
d�ddd
|jzd	d
d�ddd|jzd	d
d�ddd|jzd	d
d�ddddd�ddd|jzd	d
d�d�dgd�d�dddt
�d�iddt
�d�idd 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),N�DEBUG)r�handlers�INFO)�network�"defence360agent.internals.the_sink�
event_hook��WARNING�abstimestampz%s/error.logzlogging.FileHandler�utf8)r�	formatter�filenamer�encodingz%s/network.logz%s/debug.logz%s/console.log�	eventhookz%s/hook.logzlogging.StreamHandlerzext://sys.stderr)rDr�streamr�reltimestampz%s/process_message.log)rDrrErrF)r�	error_log�network_log�	debug_log�console_log�hook_log�console�process_message_logr)rMrJr�logs�formatz*%(levelname)-7s [+%(relativeCreated)5dms] z%(name)50s|%(message)sz%(levelname)-7s [%(asctime)s] z%(name)s: %(message)sz%(created)d : %(message)s)rIrBrGF)�loggers�versionr;�root�mkdir�
formatters�disable_existing_loggersrS�AcronisClientInstallerr;�acronis_installer_logrz$logging.handlers.RotatingFileHandler�maxBytes�backupCount)r�
_ROOT_LOG_DIRr8�log_dirr-�PREFIX�mutableDictConfigr4�path�joinr�LOG_NAME�values�get�Config�MAX_LOG_FILE_SIZE�BACKUP_COUNT)�self�is_root�handlers   r,�__init__z_LoggerDynConfig.__init__As����.�.��-4�N�t�)�)�$�:L�:L�:N�:N���
%� "���%� "�7�7�$� "����� �&�.�.�&�!/� .��� =�2� &���%�!/� 0�4�<� ?�2� &� � �%�!/� .��� =�2� &���$�!/� 0�4�<� ?�2� &� � �$�!,� -��� <�2� &���"0�4�0�#�	��"0�%� 8�4�<� G�2� &�(�(�W7�7�r"��������:�!�:�:�:�!��'��'�'�'�!�'�(C�D���).�Gd"
�d"
���N��G
�G
���y�)�*B�C�
(������T�\�=�3I�J�J�*��G
�G
���z�*�+B�C��
	A� �1�*�=�D�D�F�F�
A�
A���;�;�w�'�'�+@�@�@�'M�G�G�$�*0�*B�G�J�'�-3�-@�G�M�*��
	A�
	A�
A�
Ar7N)
�__name__�
__module__�__qualname__rr"r1r]�staticmethodr8rlr6r7r,r/r/7sU������!�F�K�$7�7�M��
�
��\�
�BA�BA�BA�BA�BAr7r/r@c��t��S�N)r/r6r7r,�
_late_initrs�s�����r7c��tt��5tj�d��cddd��S#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)r�OSErrorr4ra�existsr6r7r,�_we_are_in_cagefsrw�s���
�'�	�	�.�.��w�~�~�n�-�-�.�.�.�.�.�.�.�.�.�.�.�.����.�.�.�.�.�.s�A�A�Ac��d�}|||��tj|��D]d\}}}|D],}|tj�||��|���-|D],}|tj�||��|���-�edS)z�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.
    c���	tj||��dS#t$r>}tj�d�||����Yd}~dSd}~wwxYw)Nz [WARNING] cannot chmod on {}: {})r4�chmod�PermissionError�sys�stderr�writerR)�
file_dir_path�
permission�es   r,�	_os_chmodz"_chmod_log_dirs.<locals>._os_chmod�s���	��H�]�J�/�/�/�/�/���	�	�	��J���2�9�9�-��K�K�
�
�
�
�
�
�
�
�
�����	���s��
A!�3A�A!N)r4�walkrarb)	�dirname�dir_perm�	file_permr�ra�dirs�files�	directory�names	         r,�_chmod_log_dirsr��s�������I�g�x� � � ��W�W�-�-�;�;���d�E��	?�	?�I��I�b�g�l�l�4��3�3�X�>�>�>�>��	;�	;�D��I�b�g�l�l�4��.�.�	�:�:�:�:�	;�;�;r7c�*�tjd��rdS	tj��t	��j}tj|tjd���t|tjtj
��tj�
t	��j��tt _dS#t$$ret'��sSt)jt j���t j�dtjjz��YdSYdSt4$rTt)jt j���t j�dtjjz��YdSwxYw)z>
    Re-catch with _LoggerDynConfig and re-open log files
    �IMUNIFY360_DISABLE_LOGGINGT��exist_ok)�filez%s logger is not available.
N)r4�getenvr
�cached_fillrsr^�makedirsrf�LOG_DIR_PERMr��
LOG_FILE_PERM�loggingr�
dictConfigr`�_log_uncaught_exceptionsr|�
excepthookrurw�	traceback�	print_excr}r~r"r1�	Exception)r^s r,�reconfigurer��s���
�y�-�.�.�6���	6������ �l�l�*�G��K���!4�t�D�D�D�D��G�V�%8�&�:N�O�O�O��N�%�%�j�l�l�&D�E�E�E�,6�C�N�N�N��+�
	�
	�
	�%�&�&�
��#���4�4�4�4��
� � �3�f�k�6I�I�������
�
�
�
�	�	�	���S�Z�0�0�0�0��J���/�&�+�2E�E�
�
�
�
�
�
�	���s�BC�A(F�5AF�Fc��t|t��rtj|||��dSt�d|||f���dS)Nzuncaught exception)�exc_info)�
issubclass�KeyboardInterruptr|�__excepthook__�logger�critical)�exc_type�	exc_value�
exc_tracebacks   r,r�r�s`���(�-�.�.����8�Y�
�>�>�>���
�O�O���)�]�'K������r7c���t|��5}tj|��}ddd��n#1swxYwYt��j�|��t
��dSrr)�open�yaml�	safe_loadrsr`�updater�)rE�config_filers   r,�update_logging_config_from_filer�$s���	
�h���-�;����,�,��-�-�-�-�-�-�-�-�-�-�-����-�-�-�-��L�L�"�)�)�&�1�1�1��M�M�M�M�Ms�1�5�5c���tjj}t��jd���D].}|�tj|��j���/d�|D��S)NrSc��g|]C}t|d��r1t|jd��r|jtjk�<|j��DS)rH�fileno)�hasattrrHr|r})�.0�hs  r,�
<listcomp>zget_fds.<locals>.<listcomp>1s_�����
��1�h����
�A�H�h�'�'�	�

�H��
�"�"�		
��
#�"�"r7)r�rUr;rsr`�keys�extend�	getLogger)r;�_loggers  r,�get_fdsr�,sw���|�$�H��<�<�1�)�<�A�A�C�C�=�=������)�'�2�2�;�<�<�<�<�������r7c�l�d�t��jd���D��S)Nc�,�g|]\}}d|v�	|d��S)rEr6)r��_rds   r,r�z&get_log_file_names.<locals>.<listcomp>;s6������A�v�����	�z����r7r;)rsr`r%r6r7r,�get_log_file_namesr�:s;����#���7�
�C�I�I�K�K����r7c��|tjvr,tjdtj|jz��Stjd|z��S)Nznetwork.)r|�modulesr�r�rm)r�s r,�getNetworkLoggerr�BsE���s�{���� ��c�k�$�.?�.H�!H�I�I�I�� ��d�!2�3�3�3r7�returnc�(�t��jS)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��
�<�<��r7c��tjr8t��jddd�d��|dkr8t��jddd�d��|dkr8t��jdd	d�d
��|dkr2t��jdd�d
��t��jddd�d��t��dS)NrSrYr;rZ�r=rK�r>rP�rUrLr?rN)r�disabledrsr`�appendr�)�verboses r,�setLogLevelr�Us����*����&�y�1�2J�K��	
�
�&�(�
)�
)�
)��!�|�|����&�y�1�)�<��	
�
�&��
�
�
��!�|�|����&�y�1�0�	
�
�	��f�2�3�3�3��!�|�|����&�v�.�z�:�A�A�+�N�N�N��L�L�"�9�-�l�;�J�G�N�N������M�M�M�M�Mr7c�f�|t��jddd<t��dS)z'
    also results in reconfigure()
    r;rOrN)rsr`r�)�newloglevels r,�setConsoleLogLevelr�ls4��	��L�L�"�:�.�y�9����M�M�M�M�Mr7�scan_idc#�JK�tj�t��jd��}t|d��5}|�tjd���d|�d���|V�|�d��ddd��dS#1swxYwYdS)Nzaibolit_actions.log�a�%Y-%m-%d %H:%M:%S� | �

)	r4rarbrsr^r�r~�time�strftime)r�ra�fs   r,�openAibolitActionsLogr�xs�����
�7�<�<�
���,�.C�D�D�D�	
�d�C����A�	���4�=�!4�5�5�F�F�'�F�F�F�G�G�G�����	���������������������������s�AB�B�Bc#�zK�t��j}tj|d���tj�|d��}t
|d��5}|�tj	d���d|�d���|V�|�d��ddd��dS#1swxYwYdS)NTr�zmds_actions.logr�r�r�r�)
rsr^r4r�rarbr�r~r�r�)r�r^rar�s    r,�openMdsActionsLogr��s������l�l�"�G��K��$�'�'�'�'�
�7�<�<��!2�3�3�D�	
�d�C����A�	���4�=�!4�5�5�F�F�'�F�F�F�G�G�G�����	���������������������������s�AB0�0B4�7B4c�4�eZdZGd�d��Zd�Zd�ZdS)�EventHookLoggerc�B�eZdZGd�d��Zd�Zd	d�Zd�Zd�ZdS)
�EventHookLogger._EventLoggerc�8�eZdZdZd�Zd�Zd�Zd
d�Zd�Zd�Z	d	S)�(EventHookLogger._EventLogger._HookLoggerzD{uuid:s} : {action:s} {native:s}: {event:s} : {subtype:s} : {path:s}c��||_|j|_|j|_|j|_|j|_||_dSrr)ra�event�subtype�uuid�log�native)ri�parentrar�s    r,rlz1EventHookLogger._EventLogger._HookLogger.__init__�s8�� ��	�#�\��
�%�~���"�K��	�!�:���$����r7c��|Srrr6�ris r,�	__enter__z2EventHookLogger._EventLogger._HookLogger.__enter__�s���r7c��dSrrr6�rir��exc_val�exc_tbs    r,�__exit__z1EventHookLogger._EventLogger._HookLogger.__exit__�s���r7rc���t|j��||jrdnd|j|j|jd�}|jjdi|��}|rd�||g��}|�	|��dS)Nznative r)r��actionr�r�r�raz : r6)
�strr�r�r�r�ra�tplrRrbr�)rir��message�data�msgs     r,�_logz-EventHookLogger._EventLogger._HookLogger._log�s�����	�N�N�$�+/�;�>�i�i�B�!�Z�#�|� �I�
���&�d�h�o�-�-��-�-���5��*�*�c�7�^�4�4�C�����
�
�
�
�
r7c�0�|�d��dS)N�started)r�r�s r,�beginz.EventHookLogger._EventLogger._HookLogger.begin�s���	�	�)�$�$�$�$�$r7c��|dkrdnd}|r$d�|t|��g��}|rBt|t��r|�d���}d�||g��}|�d|��dS)	Nr�OKr�:�backslashreplace)�errors�
�done)rbr��
isinstance�bytes�decoder�)ri�	exit_code�errr�s    r,�finishz/EventHookLogger._EventLogger._HookLogger.finish�s���"+�q�.�.�$�$�g���B�!�h�h���Y���'@�A�A�G��8�!�#�u�-�-�D�!�j�j�0B�j�C�C��"�i�i��#��7�7�G��	�	�&�'�*�*�*�*�*r7N)r)
rmrnror�rlr�r�r�r�rr6r7r,�_HookLoggerr��s}������5�
�

%�
%�
%�
�
�
�
�
�
�
�
�
�
� 
%�
%�
%�	
+�	
+�	
+�	
+�	
+r7rc�j�||_||_tj��|_|j|_dSrr)r�r�r��uuid4r�)rir�r�r�s    r,rlz%EventHookLogger._EventLogger.__init__�s*���D�J�"�D�L��
���D�I��z�D�H�H�Hr7Fc�2�|�|||���S)N)r�)r)rirar�s   r,�__call__z%EventHookLogger._EventLogger.__call__�s���#�#�D�$�v�#�>�>�>r7c��|Srrr6r�s r,r�z&EventHookLogger._EventLogger.__enter__�s���Kr7c��dSrrr6r�s    r,r�z%EventHookLogger._EventLogger.__exit__�s���Dr7N�F)rmrnrorrlrr�r�r6r7r,�_EventLoggerr��s~������0	+�0	+�0	+�0	+�0	+�0	+�0	+�0	+�d	"�	"�	"�	?�	?�	?�	?�	�	�	�	�	�	�	�	r7rc�F�tjd��}|j|_dS)Nr?)r�r��infor�)rir�s  r,rlzEventHookLogger.__init__�s���"�<�0�0���;����r7c�0�|�|||��Srr)r)rir�r�s   r,rzEventHookLogger.__call__�s��� � ��u�g�6�6�6r7N)rmrnrorrlrr6r7r,r�r��sc������@�@�@�@�@�@�@�@�D���7�7�7�7�7r7r�r)5r2r��logging.config�logging.handlersr4r|r�r�r��
contextlibrr�	functoolsrrr��defence360agent.contractsrr� defence360agent.contracts.configrr	rfr
�defence360agent.utilsrr�defence360agent.applicationr
�environrer_r�rmr�r-r/rsrwr�r�r�r�r�r�r�r�r^r�r�r�r�r�r6r7r,�<module>rs�������������������	�	�	�	�
�
�
�
�������������/�/�/�/�/�/�/�/���������������4�4�4�4�4�4�4�4�:�:�:�:�:�:�=�=�=�=�=�=�3�3�3�3�3�3�>�>�>�>�>�>�>�>�,�,�,�,�,�,�	����3�R�	8�	8��	��	�8�	$�	$��
�
�
�
�:LA�LA�LA�LA�LA�LA�LA�LA�^��1��������
.�
.�
.� ;�;�;�2#6�#6�#6�L������������4�4�4� �� � � � ����.�����3���������s�������H7�H7�H7�H7�H7�H7�H7�H7�H7�H7r7defence360agent/internals/__pycache__/logger.cpython-311.pyc0000644000000000000000000005142600000000000020746 0ustar  �

�H�5Ң���ddlZddlZddlZddlZddlZddlZddlZddlZddlZddl	m
Z
mZddlm
Z
ddlZddlZddlmZmZddlmZddlmZddlmZddlmZmZdd	lmZej�d
d��Zej e!��Z"d#d
�Z#Gd�d��Z$e
d��d���Z%d�Z&d�Z'd�Z(d�Z)d�Z*d�Z+d�Z,d�Z-de.fd�Z/d�Z0d�Z1e
de.fd���Z2e
de.fd ���Z3Gd!�d"��Z4dS)$�N)�contextmanager�suppress)�	lru_cache)�config�sentry)�
AcronisBackup)�Logger)�Sentry)�antivirus_mode�is_root_user)�tags�IMUNIFY360_LOGGING_PREFIX�Fc���	tj}n#ttf$rd}YnwxYw|r�t	jtj|tjj	d���t	j
��5}tj���
��D]\}}|�||���dtjd��i|_ddd��n#1swxYwYddd�Sd	d
d�S)NT�on)�dsn�debug�release�attach_stacktrace�id�	server_id�ERRORz-sentry_sdk.integrations.logging.SentryHandler)�level�class�NOTSETzlogging.NullHandler)r
�ENABLE�KeyError�AssertionError�
sentry_sdk�init�DSNr�Core�VERSION�configure_scoperr
�items�set_tag�tag�user)r�error_reporting�scoper'�values     �U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/logger.py�_sentry_initr-sR��� �-�����n�%�������������
����
���K�'�"�		
�	
�	
�	
��
'�
)�
)�	9�U�$�k�m�m�1�1�3�3�
*�
*�
��U��
�
�c�5�)�)�)�)���
�;� 7� 7�8�E�J�	9�	9�	9�	9�	9�	9�	9�	9�	9�	9�	9����	9�	9�	9�	9�
�D�
�
�	
��*�
�
�	
s��%�%�.AC�C�Cc�H�eZdZdejjzZed���Zd�Z	dS)�_LoggerDynConfigz/var/log/%sc�x�dtjj�dtj��ptj����S)Nz	/var/log/z_user_logs/)rr"�PRODUCT�getpass�getuser�os�getuid��r,�
_user_log_dirz_LoggerDynConfig._user_log_dir:s7���
�K�����O���,�����,�
�	
r7c�,�t��}|r|jn|���|_dgd�dgd�dgd�d�dt	��ddd|jzd	d
d�ddd|jzd	d
d�ddd
|jzd	d
d�ddd|jzd	d
d�ddd|jzd	d
d�ddddd�ddd|jzd	d
d�d�dgd�d�dddt
�d�iddt
�d�idd 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),N�DEBUG)r�handlers�INFO)�network�"defence360agent.internals.the_sink�
event_hook��WARNING�abstimestampz%s/error.logzlogging.FileHandler�utf8)r�	formatter�filenamer�encodingz%s/network.logz%s/debug.logz%s/console.log�	eventhookz%s/hook.logzlogging.StreamHandlerzext://sys.stderr)rDr�streamr�reltimestampz%s/process_message.log)rDrrErrF)r�	error_log�network_log�	debug_log�console_log�hook_log�console�process_message_logr)rMrJr�logs�formatz*%(levelname)-7s [+%(relativeCreated)5dms] z%(name)50s|%(message)sz%(levelname)-7s [%(asctime)s] z%(name)s: %(message)sz%(created)d : %(message)s)rIrBrGF)�loggers�versionr;�root�mkdir�
formatters�disable_existing_loggersrS�AcronisClientInstallerr;�acronis_installer_logrz$logging.handlers.RotatingFileHandler�maxBytes�backupCount)r�
_ROOT_LOG_DIRr8�log_dirr-�PREFIX�mutableDictConfigr4�path�joinr�LOG_NAME�values�get�Config�MAX_LOG_FILE_SIZE�BACKUP_COUNT)�self�is_root�handlers   r,�__init__z_LoggerDynConfig.__init__As����.�.��-4�N�t�)�)�$�:L�:L�:N�:N���
%� "���%� "�7�7�$� "����� �&�.�.�&�!/� .��� =�2� &���%�!/� 0�4�<� ?�2� &� � �%�!/� .��� =�2� &���$�!/� 0�4�<� ?�2� &� � �$�!,� -��� <�2� &���"0�4�0�#�	��"0�%� 8�4�<� G�2� &�(�(�W7�7�r"��������:�!�:�:�:�!��'��'�'�'�!�'�(C�D���).�Gd"
�d"
���N��G
�G
���y�)�*B�C�
(������T�\�=�3I�J�J�*��G
�G
���z�*�+B�C��
	A� �1�*�=�D�D�F�F�
A�
A���;�;�w�'�'�+@�@�@�'M�G�G�$�*0�*B�G�J�'�-3�-@�G�M�*��
	A�
	A�
A�
Ar7N)
�__name__�
__module__�__qualname__rr"r1r]�staticmethodr8rlr6r7r,r/r/7sU������!�F�K�$7�7�M��
�
��\�
�BA�BA�BA�BA�BAr7r/r@c��t��S�N)r/r6r7r,�
_late_initrs�s�����r7c��tt��5tj�d��cddd��S#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)r�OSErrorr4ra�existsr6r7r,�_we_are_in_cagefsrw�s���
�'�	�	�.�.��w�~�~�n�-�-�.�.�.�.�.�.�.�.�.�.�.�.����.�.�.�.�.�.s�A�A�Ac��d�}|||��tj|��D]d\}}}|D],}|tj�||��|���-|D],}|tj�||��|���-�edS)z�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.
    c���	tj||��dS#t$r>}tj�d�||����Yd}~dSd}~wwxYw)Nz [WARNING] cannot chmod on {}: {})r4�chmod�PermissionError�sys�stderr�writerR)�
file_dir_path�
permission�es   r,�	_os_chmodz"_chmod_log_dirs.<locals>._os_chmod�s���	��H�]�J�/�/�/�/�/���	�	�	��J���2�9�9�-��K�K�
�
�
�
�
�
�
�
�
�����	���s��
A!�3A�A!N)r4�walkrarb)	�dirname�dir_perm�	file_permr�ra�dirs�files�	directory�names	         r,�_chmod_log_dirsr��s�������I�g�x� � � ��W�W�-�-�;�;���d�E��	?�	?�I��I�b�g�l�l�4��3�3�X�>�>�>�>��	;�	;�D��I�b�g�l�l�4��.�.�	�:�:�:�:�	;�;�;r7c�*�tjd��rdS	tj��t	��j}tj|tjd���t|tjtj
��tj�
t	��j��tt _dS#t$$ret'��sSt)jt j���t j�dtjjz��YdSYdSt4$rTt)jt j���t j�dtjjz��YdSwxYw)z>
    Re-catch with _LoggerDynConfig and re-open log files
    �IMUNIFY360_DISABLE_LOGGINGT��exist_ok)�filez%s logger is not available.
N)r4�getenvr
�cached_fillrsr^�makedirsrf�LOG_DIR_PERMr��
LOG_FILE_PERM�loggingr�
dictConfigr`�_log_uncaught_exceptionsr|�
excepthookrurw�	traceback�	print_excr}r~r"r1�	Exception)r^s r,�reconfigurer��s���
�y�-�.�.�6���	6������ �l�l�*�G��K���!4�t�D�D�D�D��G�V�%8�&�:N�O�O�O��N�%�%�j�l�l�&D�E�E�E�,6�C�N�N�N��+�
	�
	�
	�%�&�&�
��#���4�4�4�4��
� � �3�f�k�6I�I�������
�
�
�
�	�	�	���S�Z�0�0�0�0��J���/�&�+�2E�E�
�
�
�
�
�
�	���s�BC�A(F�5AF�Fc��t|t��rtj|||��dSt�d|||f���dS)Nzuncaught exception)�exc_info)�
issubclass�KeyboardInterruptr|�__excepthook__�logger�critical)�exc_type�	exc_value�
exc_tracebacks   r,r�r�s`���(�-�.�.����8�Y�
�>�>�>���
�O�O���)�]�'K������r7c���t|��5}tj|��}ddd��n#1swxYwYt��j�|��t
��dSrr)�open�yaml�	safe_loadrsr`�updater�)rE�config_filers   r,�update_logging_config_from_filer�$s���	
�h���-�;����,�,��-�-�-�-�-�-�-�-�-�-�-����-�-�-�-��L�L�"�)�)�&�1�1�1��M�M�M�M�Ms�1�5�5c���tjj}t��jd���D].}|�tj|��j���/d�|D��S)NrSc��g|]C}t|d��r1t|jd��r|jtjk�<|j��DS)rH�fileno)�hasattrrHr|r})�.0�hs  r,�
<listcomp>zget_fds.<locals>.<listcomp>1s_�����
��1�h����
�A�H�h�'�'�	�

�H��
�"�"�		
��
#�"�"r7)r�rUr;rsr`�keys�extend�	getLogger)r;�_loggers  r,�get_fdsr�,sw���|�$�H��<�<�1�)�<�A�A�C�C�=�=������)�'�2�2�;�<�<�<�<�������r7c�l�d�t��jd���D��S)Nc�,�g|]\}}d|v�	|d��S)rEr6)r��_rds   r,r�z&get_log_file_names.<locals>.<listcomp>;s6������A�v�����	�z����r7r;)rsr`r%r6r7r,�get_log_file_namesr�:s;����#���7�
�C�I�I�K�K����r7c��|tjvr,tjdtj|jz��Stjd|z��S)Nznetwork.)r|�modulesr�r�rm)r�s r,�getNetworkLoggerr�BsE���s�{���� ��c�k�$�.?�.H�!H�I�I�I�� ��d�!2�3�3�3r7�returnc�(�t��jS)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��
�<�<��r7c��tjr8t��jddd�d��|dkr8t��jddd�d��|dkr8t��jdd	d�d
��|dkr2t��jdd�d
��t��jddd�d��t��dS)NrSrYr;rZ�r=rK�r>rP�rUrLr?rN)r�disabledrsr`�appendr�)�verboses r,�setLogLevelr�Us����*����&�y�1�2J�K��	
�
�&�(�
)�
)�
)��!�|�|����&�y�1�)�<��	
�
�&��
�
�
��!�|�|����&�y�1�0�	
�
�	��f�2�3�3�3��!�|�|����&�v�.�z�:�A�A�+�N�N�N��L�L�"�9�-�l�;�J�G�N�N������M�M�M�M�Mr7c�f�|t��jddd<t��dS)z'
    also results in reconfigure()
    r;rOrN)rsr`r�)�newloglevels r,�setConsoleLogLevelr�ls4��	��L�L�"�:�.�y�9����M�M�M�M�Mr7�scan_idc#�JK�tj�t��jd��}t|d��5}|�tjd���d|�d���|V�|�d��ddd��dS#1swxYwYdS)Nzaibolit_actions.log�a�%Y-%m-%d %H:%M:%S� | �

)	r4rarbrsr^r�r~�time�strftime)r�ra�fs   r,�openAibolitActionsLogr�xs�����
�7�<�<�
���,�.C�D�D�D�	
�d�C����A�	���4�=�!4�5�5�F�F�'�F�F�F�G�G�G�����	���������������������������s�AB�B�Bc#�zK�t��j}tj|d���tj�|d��}t
|d��5}|�tj	d���d|�d���|V�|�d��ddd��dS#1swxYwYdS)NTr�zmds_actions.logr�r�r�r�)
rsr^r4r�rarbr�r~r�r�)r�r^rar�s    r,�openMdsActionsLogr��s������l�l�"�G��K��$�'�'�'�'�
�7�<�<��!2�3�3�D�	
�d�C����A�	���4�=�!4�5�5�F�F�'�F�F�F�G�G�G�����	���������������������������s�AB0�0B4�7B4c�4�eZdZGd�d��Zd�Zd�ZdS)�EventHookLoggerc�B�eZdZGd�d��Zd�Zd	d�Zd�Zd�ZdS)
�EventHookLogger._EventLoggerc�8�eZdZdZd�Zd�Zd�Zd
d�Zd�Zd�Z	d	S)�(EventHookLogger._EventLogger._HookLoggerzD{uuid:s} : {action:s} {native:s}: {event:s} : {subtype:s} : {path:s}c��||_|j|_|j|_|j|_|j|_||_dSrr)ra�event�subtype�uuid�log�native)ri�parentrar�s    r,rlz1EventHookLogger._EventLogger._HookLogger.__init__�s8�� ��	�#�\��
�%�~���"�K��	�!�:���$����r7c��|Srrr6�ris r,�	__enter__z2EventHookLogger._EventLogger._HookLogger.__enter__�s���r7c��dSrrr6�rir��exc_val�exc_tbs    r,�__exit__z1EventHookLogger._EventLogger._HookLogger.__exit__�s���r7rc���t|j��||jrdnd|j|j|jd�}|jjdi|��}|rd�||g��}|�	|��dS)Nznative r)r��actionr�r�r�raz : r6)
�strr�r�r�r�ra�tplrRrbr�)rir��message�data�msgs     r,�_logz-EventHookLogger._EventLogger._HookLogger._log�s�����	�N�N�$�+/�;�>�i�i�B�!�Z�#�|� �I�
���&�d�h�o�-�-��-�-���5��*�*�c�7�^�4�4�C�����
�
�
�
�
r7c�0�|�d��dS)N�started)r�r�s r,�beginz.EventHookLogger._EventLogger._HookLogger.begin�s���	�	�)�$�$�$�$�$r7c��|dkrdnd}|r$d�|t|��g��}|rBt|t��r|�d���}d�||g��}|�d|��dS)	Nr�OKr�:�backslashreplace)�errors�
�done)rbr��
isinstance�bytes�decoder�)ri�	exit_code�errr�s    r,�finishz/EventHookLogger._EventLogger._HookLogger.finish�s���"+�q�.�.�$�$�g���B�!�h�h���Y���'@�A�A�G��8�!�#�u�-�-�D�!�j�j�0B�j�C�C��"�i�i��#��7�7�G��	�	�&�'�*�*�*�*�*r7N)r)
rmrnror�rlr�r�r�r�rr6r7r,�_HookLoggerr��s}������5�
�

%�
%�
%�
�
�
�
�
�
�
�
�
�
� 
%�
%�
%�	
+�	
+�	
+�	
+�	
+r7rc�j�||_||_tj��|_|j|_dSrr)r�r�r��uuid4r�)rir�r�r�s    r,rlz%EventHookLogger._EventLogger.__init__�s*���D�J�"�D�L��
���D�I��z�D�H�H�Hr7Fc�2�|�|||���S)N)r�)r)rirar�s   r,�__call__z%EventHookLogger._EventLogger.__call__�s���#�#�D�$�v�#�>�>�>r7c��|Srrr6r�s r,r�z&EventHookLogger._EventLogger.__enter__�s���Kr7c��dSrrr6r�s    r,r�z%EventHookLogger._EventLogger.__exit__�s���Dr7N�F)rmrnrorrlrr�r�r6r7r,�_EventLoggerr��s~������0	+�0	+�0	+�0	+�0	+�0	+�0	+�0	+�d	"�	"�	"�	?�	?�	?�	?�	�	�	�	�	�	�	�	r7rc�F�tjd��}|j|_dS)Nr?)r�r��infor�)rir�s  r,rlzEventHookLogger.__init__�s���"�<�0�0���;����r7c�0�|�|||��Srr)r)rir�r�s   r,rzEventHookLogger.__call__�s��� � ��u�g�6�6�6r7N)rmrnrorrlrr6r7r,r�r��sc������@�@�@�@�@�@�@�@�D���7�7�7�7�7r7r�r)5r2r��logging.config�logging.handlersr4r|r�r�r��
contextlibrr�	functoolsrrr��defence360agent.contractsrr� defence360agent.contracts.configrr	rfr
�defence360agent.utilsrr�defence360agent.applicationr
�environrer_r�rmr�r-r/rsrwr�r�r�r�r�r�r�r�r^r�r�r�r�r�r6r7r,�<module>rs�������������������	�	�	�	�
�
�
�
�������������/�/�/�/�/�/�/�/���������������4�4�4�4�4�4�4�4�:�:�:�:�:�:�=�=�=�=�=�=�3�3�3�3�3�3�>�>�>�>�>�>�>�>�,�,�,�,�,�,�	����3�R�	8�	8��	��	�8�	$�	$��
�
�
�
�:LA�LA�LA�LA�LA�LA�LA�LA�^��1��������
.�
.�
.� ;�;�;�2#6�#6�#6�L������������4�4�4� �� � � � ����.�����3���������s�������H7�H7�H7�H7�H7�H7�H7�H7�H7�H7r7defence360agent/internals/__pycache__/logging_protocol.cpython-311.opt-1.pyc0000644000000000000000000000710400000000000023767 0ustar  �

��y�~&���4�ddlZGd�dej��ZdS)�Nc�2�eZdZd�Zd�Zd�Zd�Zd�Zd�ZdS)�LoggingProtocolc�0�||_||_||_dS�N)�_logger�_network_logger�_real_protocol)�self�logger�network_logger�
real_protocols    �_/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/logging_protocol.py�__init__zLoggingProtocol.__init__s�����-���+�����c�p����j�d������fd���dS)NzConnection made.c�8���j����Sr)r	�connection_made�r
�	transports��r�<lambda>z1LoggingProtocol.connection_made.<locals>.<lambda>s���T�0�@�@��K�K�r�r�debug�_handlers``rrzLoggingProtocol.connection_made
sA������"�"�#5�6�6�6����K�K�K�K�K�L�L�L�L�Lrc�p����j�d������fd���dS)NzConnection lost.c�8���j����Sr)r	�connection_lost)�excr
s��rrz1LoggingProtocol.connection_lost.<locals>.<lambda>s���T�0�@�@��E�E�rr)r
rs``rrzLoggingProtocol.connection_lostsA������"�"�#5�6�6�6����E�E�E�E�E�F�F�F�F�Frc������j�d�����������fd���dS)Nzdatagram_received: {!r}c�:���j�����Sr)r	�datagram_received)�addr�datar
s���rrz3LoggingProtocol.datagram_received.<locals>.<lambda>s���T�0�B�B�4��N�N�r�rr�formatr)r
r"r!s```rr z!LoggingProtocol.datagram_receivedsS�������"�"�#<�#C�#C�D�#I�#I�J�J�J����N�N�N�N�N�N�O�O�O�O�Orc�����j�d����������fd���dS)Nzdata_received: {!r}c�8���j����Sr)r	�
data_received)r"r
s��rrz/LoggingProtocol.data_received.<locals>.<lambda>s���T�0�>�>�t�D�D�rr#)r
r"s``rr'zLoggingProtocol.data_receivedsO������"�"�#8�#?�#?��#E�#E�F�F�F����D�D�D�D�D�E�E�E�E�Erc��	|��dS#t$r2}|j�t|����Yd}~dSd}~wwxYwr)�	Exceptionr�	exception�str)r
�impl�es   rrzLoggingProtocol._handlesc��	+��D�F�F�F�F�F���	+�	+�	+��L�"�"�3�q�6�6�*�*�*�*�*�*�*�*�*�����	+���s�
�
A
�'A�A
N)	�__name__�
__module__�__qualname__rrrr r'r�rrrrsz������,�,�,�
M�M�M�G�G�G�P�P�P�F�F�F�+�+�+�+�+rr)�asyncio�Protocolrr1rr�<module>r4sE������+�+�+�+�+�g�&�+�+�+�+�+rdefence360agent/internals/__pycache__/logging_protocol.cpython-311.pyc0000644000000000000000000000710400000000000023030 0ustar  �

��y�~&���4�ddlZGd�dej��ZdS)�Nc�2�eZdZd�Zd�Zd�Zd�Zd�Zd�ZdS)�LoggingProtocolc�0�||_||_||_dS�N)�_logger�_network_logger�_real_protocol)�self�logger�network_logger�
real_protocols    �_/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/logging_protocol.py�__init__zLoggingProtocol.__init__s�����-���+�����c�p����j�d������fd���dS)NzConnection made.c�8���j����Sr)r	�connection_made�r
�	transports��r�<lambda>z1LoggingProtocol.connection_made.<locals>.<lambda>s���T�0�@�@��K�K�r�r�debug�_handlers``rrzLoggingProtocol.connection_made
sA������"�"�#5�6�6�6����K�K�K�K�K�L�L�L�L�Lrc�p����j�d������fd���dS)NzConnection lost.c�8���j����Sr)r	�connection_lost)�excr
s��rrz1LoggingProtocol.connection_lost.<locals>.<lambda>s���T�0�@�@��E�E�rr)r
rs``rrzLoggingProtocol.connection_lostsA������"�"�#5�6�6�6����E�E�E�E�E�F�F�F�F�Frc������j�d�����������fd���dS)Nzdatagram_received: {!r}c�:���j�����Sr)r	�datagram_received)�addr�datar
s���rrz3LoggingProtocol.datagram_received.<locals>.<lambda>s���T�0�B�B�4��N�N�r�rr�formatr)r
r"r!s```rr z!LoggingProtocol.datagram_receivedsS�������"�"�#<�#C�#C�D�#I�#I�J�J�J����N�N�N�N�N�N�O�O�O�O�Orc�����j�d����������fd���dS)Nzdata_received: {!r}c�8���j����Sr)r	�
data_received)r"r
s��rrz/LoggingProtocol.data_received.<locals>.<lambda>s���T�0�>�>�t�D�D�rr#)r
r"s``rr'zLoggingProtocol.data_receivedsO������"�"�#8�#?�#?��#E�#E�F�F�F����D�D�D�D�D�E�E�E�E�Erc��	|��dS#t$r2}|j�t|����Yd}~dSd}~wwxYwr)�	Exceptionr�	exception�str)r
�impl�es   rrzLoggingProtocol._handlesc��	+��D�F�F�F�F�F���	+�	+�	+��L�"�"�3�q�6�6�*�*�*�*�*�*�*�*�*�����	+���s�
�
A
�'A�A
N)	�__name__�
__module__�__qualname__rrrr r'r�rrrrsz������,�,�,�
M�M�M�G�G�G�P�P�P�F�F�F�+�+�+�+�+rr)�asyncio�Protocolrr1rr�<module>r4sE������+�+�+�+�+�g�&�+�+�+�+�+rdefence360agent/internals/__pycache__/message_status_publisher.cpython-311.opt-1.pyc0000644000000000000000000002211700000000000025525 0ustar  �

|�e>�kQ7����dZddlZddlZddlZddlZddlZddlZddlZddl	Z
ddlZ
ddlZddl
mZeje��ZdZej�dd��Ze�d��dzZej�d	d
��ZdZdZd
ZGd�d��Zdefd�ZGd�d��Ze��Z ej!e j"��e��Z#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)�
is_enabledz/var/imunify360/iaid�IMUNIFY_PROXY_URLzhttp://127.0.0.1:11234�/z/api/v1/mqtt-publish�IMUNIFY_PROXY_API_KEY����c�6�eZdZdZdd�Zdefd�Zdeddfd�ZdS)	�Genz_ID + monotonic counter generator.

    Each instance has its own UUID and its own counter.
    �returnNc�~�tj��j|_d|_tj��|_dS)Nr)�uuid�uuid4�hex�id�_counter�	threading�Lock�_lock��selfs �g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/message_status_publisher.py�__init__zGen.__init__:s,���*�,�,�"�����
��^�%�%��
�
�
�c�v�|j5|j}|xjdz
c_|cddd��S#1swxYwYdS)N�)rr)r�values  r�_nextz	Gen._next?s���
�Z�	�	��M�E��M�M�Q��M�M��	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	�	s�.�2�2�msgc�H�|j|d<|���|d<dS)z>Add message_reporter_id and message_reporter_increment to msg.�message_reporter_id�message_reporter_incrementN)rr)rr s  r�enrichz
Gen.enrichEs(��%)�W��!�"�,0�J�J�L�L��(�)�)�)r�r
N)	�__name__�
__module__�__qualname__�__doc__r�intr�dictr$�rrrr4so��������
&�&�&�&�
�s�����9�$�9�4�9�9�9�9�9�9rrr
c���	tt��5}|������cddd��S#1swxYwYdS#t$rYdSwxYw)Nr)�open�
_IAID_PATH�read�strip�OSError)�fs r�
_read_iaidr4Ks����
�*�
�
�	$���6�6�8�8�>�>�#�#�	$�	$�	$�	$�	$�	$�	$�	$�	$�	$�	$�	$����	$�	$�	$�	$�	$�	$�������r�r����s3�A�&A	�A�	A
�
A�A
�A�
A$�#A$c�V�eZdZd
d�Zd
d�Zdedededdfd�Zd	eded
eddfd�Z	d
d�Z
dS)�MessageStatusPublisherr
Nc���d|_tj��|_d|_t
j�td���|_	tj
t��|_dS)NrFz
msg-status)�max_workers�thread_name_prefix)
�_iaidrr�
_init_lock�_initialized�
concurrent�futures�ThreadPoolExecutor�_MAX_WORKERS�_pool�BoundedSemaphore�
_MAX_INFLIGHT�	_inflightrs rrzMessageStatusPublisher.__init__Ts]����
�#�.�*�*���!����'�:�:�$�+�;�
�
��
�#�3�M�B�B����rc� �|jrdS|j5|jr	ddd��dSt��|_|js.t�dt��	ddd��dSd|_ddd��dS#1swxYwYdS)NzImsg-status: iaid not available yet (file %s missing or empty), will retryT)r<r;r4r:�logger�infor/rs r�_ensure_initializedz*MessageStatusPublisher._ensure_initialized^s ����	��F�
�_�	%�	%�� �
��	%�	%�	%�	%�	%�	%�	%�	%�$���D�J��:�
����*�����
�	%�	%�	%�	%�	%�	%�	%�	%�!%�D��	%�	%�	%�	%�	%�	%�	%�	%�	%�	%�	%�	%����	%�	%�	%�	%�	%�	%s�	B�;B�/B�B�
Br �reporter_gen�stagec�v��td��sdS|�dd��}|�d��sdS�j�d���st�d||��dSt
j��|j|���|�dd��|�d	d
��||d�}	�j	�
�j|||��}n*#t$r�j�
��YdSwxYw|��fd���dS)
z3Publish a status record via HTTP POST to the proxy.�
mqtt_trackingN�methodrr"F)�blockingz3msg-status: queue full, dropping stage=%s method=%sr#r)�	timestamp�reporter_id�reporter_incrementr"r#�message_typerJc�6���j���S)N)rD�release)�_rs �r�<lambda>z/MessageStatusPublisher.report.<locals>.<lambda>�s���4�>�+A�+A�+C�+C�r)r�getrD�acquirerF�warning�timerrrA�submit�_do_post�RuntimeErrorrT�add_done_callback)rr rIrJrM�record�futures`      r�reportzMessageStatusPublisher.reportnse���
�/�*�*�	��F�����2�&�&���w�w�,�-�-�	��F�
�~�%�%�u�%�5�5�	��N�N�E���
�
�
�

�F�����'�?�".�"4�"4�"6�"6�#&�7�7�+@�"�#E�#E�*-�'�'�,�a�+�+�#��

�

��	��Z�&�&�t�}�f�e�V�L�L�F�F���	�	�	��N�"�"�$�$�$��F�F�	����	� � �!C�!C�!C�!C�D�D�D�D�Ds�"C7�7#D�Dr_rMc�,�|���|jsdS|j|d<	tj|�����}ddi}t
r
t
|d<tj�t||d���}tj�
|t���5}|���ddd��dS#1swxYwYdS#t$r(}t�d|||��Yd}~dSd}~wwxYw)	N�iaidzContent-Typezapplication/jsonz	X-API-Key�POST)�data�headersrM)�timeoutz.msg-status: POST failed stage=%s method=%s: %r)rHr:�json�dumps�encode�_PROXY_API_KEY�urllib�request�Request�_PUBLISH_ENDPOINT�urlopen�
_POST_TIMEOUTr0�	ExceptionrFrY)	rr_rJrM�payloadrf�req�resp�es	         rr\zMessageStatusPublisher._do_post�s}��� � �"�"�"��z�	��F����v��	��j��(�(�/�/�1�1�G�%�'9�:�G��
6�'5���$��.�(�(�!����	)���C���'�'��]�'�C�C�
�t��	�	����
�
�
�
�
�
�
�
�
�
�
�
����
�
�
�
�
�
���	�	�	��N�N�@����	
�
�
�
�
�
�
�
�
�����	���s<�B	C!�2C�C!�C�C!�C�C!�!
D�+D�Dc�<�|j�d���dS)NF)�wait)rA�shutdownrs rryzMessageStatusPublisher.shutdown�s!���
�����'�'�'�'�'rr%)r&r'r(rrHr+r�strrar\ryr,rrr6r6Ss�������C�C�C�C�%�%�%�%� 'E�$�'E�c�'E�#�'E�$�'E�'E�'E�'E�R�t��C���������4(�(�(�(�(�(rr6)$r)�atexit�concurrent.futuresr=rh�logging�osrrZ�urllib.errorrl�urllib.requestr�'defence360agent.internals.feature_flagsr�	getLoggerr&rFr/�environrW�
_PROXY_URL�rstriprorkrqr@rCrrzr4r6�	publisher�registerry�message_id_genr,rr�<module>r�s�����(�
�
�
�������������	�	�	�	���������������������>�>�>�>�>�>�	��	�8�	$�	$��
#�
�
�Z�^�^�/�1I�
J�
J�
��%�%�c�*�*�-C�C��
���� 7��<�<���
����
�9�9�9�9�9�9�9�9�.�C�����_(�_(�_(�_(�_(�_(�_(�_(�D
#�"�$�$�	����	�"�#�#�#�������rdefence360agent/internals/__pycache__/message_status_publisher.cpython-311.pyc0000644000000000000000000002211700000000000024566 0ustar  �

|�e>�kQ7����dZddlZddlZddlZddlZddlZddlZddlZddl	Z
ddlZ
ddlZddl
mZeje��ZdZej�dd��Ze�d��dzZej�d	d
��ZdZdZd
ZGd�d��Zdefd�ZGd�d��Ze��Z ej!e j"��e��Z#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)�
is_enabledz/var/imunify360/iaid�IMUNIFY_PROXY_URLzhttp://127.0.0.1:11234�/z/api/v1/mqtt-publish�IMUNIFY_PROXY_API_KEY����c�6�eZdZdZdd�Zdefd�Zdeddfd�ZdS)	�Genz_ID + monotonic counter generator.

    Each instance has its own UUID and its own counter.
    �returnNc�~�tj��j|_d|_tj��|_dS)Nr)�uuid�uuid4�hex�id�_counter�	threading�Lock�_lock��selfs �g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/message_status_publisher.py�__init__zGen.__init__:s,���*�,�,�"�����
��^�%�%��
�
�
�c�v�|j5|j}|xjdz
c_|cddd��S#1swxYwYdS)N�)rr)r�values  r�_nextz	Gen._next?s���
�Z�	�	��M�E��M�M�Q��M�M��	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	�	s�.�2�2�msgc�H�|j|d<|���|d<dS)z>Add message_reporter_id and message_reporter_increment to msg.�message_reporter_id�message_reporter_incrementN)rr)rr s  r�enrichz
Gen.enrichEs(��%)�W��!�"�,0�J�J�L�L��(�)�)�)r�r
N)	�__name__�
__module__�__qualname__�__doc__r�intr�dictr$�rrrr4so��������
&�&�&�&�
�s�����9�$�9�4�9�9�9�9�9�9rrr
c���	tt��5}|������cddd��S#1swxYwYdS#t$rYdSwxYw)Nr)�open�
_IAID_PATH�read�strip�OSError)�fs r�
_read_iaidr4Ks����
�*�
�
�	$���6�6�8�8�>�>�#�#�	$�	$�	$�	$�	$�	$�	$�	$�	$�	$�	$�	$����	$�	$�	$�	$�	$�	$�������r�r����s3�A�&A	�A�	A
�
A�A
�A�
A$�#A$c�V�eZdZd
d�Zd
d�Zdedededdfd�Zd	eded
eddfd�Z	d
d�Z
dS)�MessageStatusPublisherr
Nc���d|_tj��|_d|_t
j�td���|_	tj
t��|_dS)NrFz
msg-status)�max_workers�thread_name_prefix)
�_iaidrr�
_init_lock�_initialized�
concurrent�futures�ThreadPoolExecutor�_MAX_WORKERS�_pool�BoundedSemaphore�
_MAX_INFLIGHT�	_inflightrs rrzMessageStatusPublisher.__init__Ts]����
�#�.�*�*���!����'�:�:�$�+�;�
�
��
�#�3�M�B�B����rc� �|jrdS|j5|jr	ddd��dSt��|_|js.t�dt��	ddd��dSd|_ddd��dS#1swxYwYdS)NzImsg-status: iaid not available yet (file %s missing or empty), will retryT)r<r;r4r:�logger�infor/rs r�_ensure_initializedz*MessageStatusPublisher._ensure_initialized^s ����	��F�
�_�	%�	%�� �
��	%�	%�	%�	%�	%�	%�	%�	%�$���D�J��:�
����*�����
�	%�	%�	%�	%�	%�	%�	%�	%�!%�D��	%�	%�	%�	%�	%�	%�	%�	%�	%�	%�	%�	%����	%�	%�	%�	%�	%�	%s�	B�;B�/B�B�
Br �reporter_gen�stagec�v��td��sdS|�dd��}|�d��sdS�j�d���st�d||��dSt
j��|j|���|�dd��|�d	d
��||d�}	�j	�
�j|||��}n*#t$r�j�
��YdSwxYw|��fd���dS)
z3Publish a status record via HTTP POST to the proxy.�
mqtt_trackingN�methodrr"F)�blockingz3msg-status: queue full, dropping stage=%s method=%sr#r)�	timestamp�reporter_id�reporter_incrementr"r#�message_typerJc�6���j���S)N)rD�release)�_rs �r�<lambda>z/MessageStatusPublisher.report.<locals>.<lambda>�s���4�>�+A�+A�+C�+C�r)r�getrD�acquirerF�warning�timerrrA�submit�_do_post�RuntimeErrorrT�add_done_callback)rr rIrJrM�record�futures`      r�reportzMessageStatusPublisher.reportnse���
�/�*�*�	��F�����2�&�&���w�w�,�-�-�	��F�
�~�%�%�u�%�5�5�	��N�N�E���
�
�
�

�F�����'�?�".�"4�"4�"6�"6�#&�7�7�+@�"�#E�#E�*-�'�'�,�a�+�+�#��

�

��	��Z�&�&�t�}�f�e�V�L�L�F�F���	�	�	��N�"�"�$�$�$��F�F�	����	� � �!C�!C�!C�!C�D�D�D�D�Ds�"C7�7#D�Dr_rMc�,�|���|jsdS|j|d<	tj|�����}ddi}t
r
t
|d<tj�t||d���}tj�
|t���5}|���ddd��dS#1swxYwYdS#t$r(}t�d|||��Yd}~dSd}~wwxYw)	N�iaidzContent-Typezapplication/jsonz	X-API-Key�POST)�data�headersrM)�timeoutz.msg-status: POST failed stage=%s method=%s: %r)rHr:�json�dumps�encode�_PROXY_API_KEY�urllib�request�Request�_PUBLISH_ENDPOINT�urlopen�
_POST_TIMEOUTr0�	ExceptionrFrY)	rr_rJrM�payloadrf�req�resp�es	         rr\zMessageStatusPublisher._do_post�s}��� � �"�"�"��z�	��F����v��	��j��(�(�/�/�1�1�G�%�'9�:�G��
6�'5���$��.�(�(�!����	)���C���'�'��]�'�C�C�
�t��	�	����
�
�
�
�
�
�
�
�
�
�
�
����
�
�
�
�
�
���	�	�	��N�N�@����	
�
�
�
�
�
�
�
�
�����	���s<�B	C!�2C�C!�C�C!�C�C!�!
D�+D�Dc�<�|j�d���dS)NF)�wait)rA�shutdownrs rryzMessageStatusPublisher.shutdown�s!���
�����'�'�'�'�'rr%)r&r'r(rrHr+r�strrar\ryr,rrr6r6Ss�������C�C�C�C�%�%�%�%� 'E�$�'E�c�'E�#�'E�$�'E�'E�'E�'E�R�t��C���������4(�(�(�(�(�(rr6)$r)�atexit�concurrent.futuresr=rh�logging�osrrZ�urllib.errorrl�urllib.requestr�'defence360agent.internals.feature_flagsr�	getLoggerr&rFr/�environrW�
_PROXY_URL�rstriprorkrqr@rCrrzr4r6�	publisher�registerry�message_id_genr,rr�<module>r�s�����(�
�
�
�������������	�	�	�	���������������������>�>�>�>�>�>�	��	�8�	$�	$��
#�
�
�Z�^�^�/�1I�
J�
J�
��%�%�c�*�*�-C�C��
���� 7��<�<���
����
�9�9�9�9�9�9�9�9�.�C�����_(�_(�_(�_(�_(�_(�_(�_(�D
#�"�$�$�	����	�"�#�#�#�������rdefence360agent/internals/__pycache__/persistent_message.cpython-311.opt-1.pyc0000644000000000000000000001503500000000000024326 0ustar  �

���	|�v��j�ddlZddlZddlmZddlmZddlmZee��Z	Gd�d��Z
dS)�N)�	getLogger)�db)�
MessageToSendc���eZdZdZdd�Zdd�Zdefd�Zdefd	�Z	de
fd
�Zede
fd���Z
ede
fd���Zdd
efd�Zdeeeefddfd�Zdefd�ZdS)�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
    ���Nc�Z�||_||_g|_|pt|_d|_dS�Nr)�
_buffer_limit�_storage_limit�_bufferr�_model�
dropped_total)�self�buffer_limit�
storage_limit�models    �a/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/persistent_message.py�__init__z PersistentMessagesQueue.__init__s3��)���+�������,�}���������returnc��|jr�tj��5|j�|j��|j|jz
}|dkrQ|j�|��}|xj|z
c_t�
d||j|j��g|_ddd��dS#1swxYwYdSdS)NrzcPersistent message queue overflow: dropped %d oldest message(s), storage_limit=%d, dropped_total=%d)rr�atomicr�insert_many�storage_sizer
�
delete_oldr�logger�warning)r�need_to_remove�removeds   r�push_buffer_to_storagez.PersistentMessagesQueue.push_buffer_to_storages���<�	"�����
"�
"���'�'���5�5�5�!%�!2�T�5H�!H��!�A�%�%�"�k�4�4�^�D�D�G��&�&�'�1�&�&��N�N�J���+��*���� "���%
"�
"�
"�
"�
"�
"�
"�
"�
"�
"�
"�
"����
"�
"�
"�
"�
"�
"�	"�	"s�B
B5�5B9�<B9c��g}tj��5|t|j�|jj|jj�������z
}|j����	��ddd��n#1swxYwY||j
z
}g|_
t|��S�N)rr�listr�select�	timestamp�message�tuples�delete�executer�sorted)r�itemss  r�pop_allzPersistentMessagesQueue.pop_all5s�����
�Y�[�[�	+�	+��T���"�"��K�)�4�;�+>����&�(�(���
�E�

�K��� � �(�(�*�*�*�
	+�	+�	+�	+�	+�	+�	+�	+�	+�	+�	+����	+�	+�	+�	+�	���������e�}�}�s�A=B�B#�&B#c�2�|���dkSr)�qsize�rs r�emptyzPersistentMessagesQueue.emptyBs���z�z�|�|�q� � rc�:�|jt|j��zSr$)r�lenrr1s rr0zPersistentMessagesQueue.qsizeEs��� �3�t�|�#4�#4�4�4rc�*�t|j��Sr$)r4rr1s r�buffer_sizez#PersistentMessagesQueue.buffer_sizeHs���4�<� � � rc�X�|j������Sr$)rr&�countr1s rrz$PersistentMessagesQueue.storage_sizeLs"���{�!�!�#�#�)�)�+�+�+rr(c��|�tj��}|j�||f��|j|jkr|���dSdSr$)�timer�appendr6rr")rr(r's   r�putzPersistentMessagesQueue.putPs_�����	���I�����Y��0�1�1�1���t�1�1�1��'�'�)�)�)�)�)�2�1r�messagesc��|j�|��|j|jkr|���dSdSr$)r�extendr6rr")rr=s  r�put_manyz PersistentMessagesQueue.put_manyWsI������H�%�%�%���t�1�1�1��'�'�)�)�)�)�)�2�1rc�d�|jsq|jdkrf|j�|j���}|xjd�|���D��z
c_|j�|��|jr|j�d��\}}|Stj	���)Nr)�limitc�"�g|]}|dd���
S)�N�)�.0�items  r�
<listcomp>z/PersistentMessagesQueue.get.<locals>.<listcomp>as ��A�A�A�$�T�!�"�"�X�A�A�Ar)
rrr�
get_oldestrr)�	delete_in�pop�queue�Empty)rr-�_r(s    r�getzPersistentMessagesQueue.get\s����|�	)�� 1�Q� 6� 6��K�*�*��1C�*�D�D�E��L�L�A�A�%�,�,�.�.�A�A�A�A�L�L��K�!�!�%�(�(�(��<�	���)�)�!�,�,�J�A�w��N��k�m�m�r)rr	N)rNr$)�__name__�
__module__�__qualname__�__doc__rr"r%r.�boolr2�intr0�propertyr6r�bytesr<�tuple�floatr@rOrErrrrsP������
�
�����"�"�"�"�,������!�t�!�!�!�!�5�s�5�5�5�5��!�S�!�!�!��X�!��,�c�,�,�,��X�,�*�*�5�*�*�*�*�*��e�E�5�L�&9�!:�*�t�*�*�*�*�
�U������rr)rLr:�loggingr�defence360agent.model.instancer�&defence360agent.model.messages_to_sendrrPrrrErr�<module>r]s�����������������-�-�-�-�-�-�@�@�@�@�@�@�	��8�	�	��\�\�\�\�\�\�\�\�\�\rdefence360agent/internals/__pycache__/persistent_message.cpython-311.pyc0000644000000000000000000001503500000000000023367 0ustar  �

���	|�v��j�ddlZddlZddlmZddlmZddlmZee��Z	Gd�d��Z
dS)�N)�	getLogger)�db)�
MessageToSendc���eZdZdZdd�Zdd�Zdefd�Zdefd	�Z	de
fd
�Zede
fd���Z
ede
fd���Zdd
efd�Zdeeeefddfd�Zdefd�ZdS)�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
    ���Nc�Z�||_||_g|_|pt|_d|_dS�Nr)�
_buffer_limit�_storage_limit�_bufferr�_model�
dropped_total)�self�buffer_limit�
storage_limit�models    �a/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/persistent_message.py�__init__z PersistentMessagesQueue.__init__s3��)���+�������,�}���������returnc��|jr�tj��5|j�|j��|j|jz
}|dkrQ|j�|��}|xj|z
c_t�
d||j|j��g|_ddd��dS#1swxYwYdSdS)NrzcPersistent message queue overflow: dropped %d oldest message(s), storage_limit=%d, dropped_total=%d)rr�atomicr�insert_many�storage_sizer
�
delete_oldr�logger�warning)r�need_to_remove�removeds   r�push_buffer_to_storagez.PersistentMessagesQueue.push_buffer_to_storages���<�	"�����
"�
"���'�'���5�5�5�!%�!2�T�5H�!H��!�A�%�%�"�k�4�4�^�D�D�G��&�&�'�1�&�&��N�N�J���+��*���� "���%
"�
"�
"�
"�
"�
"�
"�
"�
"�
"�
"�
"����
"�
"�
"�
"�
"�
"�	"�	"s�B
B5�5B9�<B9c��g}tj��5|t|j�|jj|jj�������z
}|j����	��ddd��n#1swxYwY||j
z
}g|_
t|��S�N)rr�listr�select�	timestamp�message�tuples�delete�executer�sorted)r�itemss  r�pop_allzPersistentMessagesQueue.pop_all5s�����
�Y�[�[�	+�	+��T���"�"��K�)�4�;�+>����&�(�(���
�E�

�K��� � �(�(�*�*�*�
	+�	+�	+�	+�	+�	+�	+�	+�	+�	+�	+����	+�	+�	+�	+�	���������e�}�}�s�A=B�B#�&B#c�2�|���dkSr)�qsize�rs r�emptyzPersistentMessagesQueue.emptyBs���z�z�|�|�q� � rc�:�|jt|j��zSr$)r�lenrr1s rr0zPersistentMessagesQueue.qsizeEs��� �3�t�|�#4�#4�4�4rc�*�t|j��Sr$)r4rr1s r�buffer_sizez#PersistentMessagesQueue.buffer_sizeHs���4�<� � � rc�X�|j������Sr$)rr&�countr1s rrz$PersistentMessagesQueue.storage_sizeLs"���{�!�!�#�#�)�)�+�+�+rr(c��|�tj��}|j�||f��|j|jkr|���dSdSr$)�timer�appendr6rr")rr(r's   r�putzPersistentMessagesQueue.putPs_�����	���I�����Y��0�1�1�1���t�1�1�1��'�'�)�)�)�)�)�2�1r�messagesc��|j�|��|j|jkr|���dSdSr$)r�extendr6rr")rr=s  r�put_manyz PersistentMessagesQueue.put_manyWsI������H�%�%�%���t�1�1�1��'�'�)�)�)�)�)�2�1rc�d�|jsq|jdkrf|j�|j���}|xjd�|���D��z
c_|j�|��|jr|j�d��\}}|Stj	���)Nr)�limitc�"�g|]}|dd���
S)�N�)�.0�items  r�
<listcomp>z/PersistentMessagesQueue.get.<locals>.<listcomp>as ��A�A�A�$�T�!�"�"�X�A�A�Ar)
rrr�
get_oldestrr)�	delete_in�pop�queue�Empty)rr-�_r(s    r�getzPersistentMessagesQueue.get\s����|�	)�� 1�Q� 6� 6��K�*�*��1C�*�D�D�E��L�L�A�A�%�,�,�.�.�A�A�A�A�L�L��K�!�!�%�(�(�(��<�	���)�)�!�,�,�J�A�w��N��k�m�m�r)rr	N)rNr$)�__name__�
__module__�__qualname__�__doc__rr"r%r.�boolr2�intr0�propertyr6r�bytesr<�tuple�floatr@rOrErrrrsP������
�
�����"�"�"�"�,������!�t�!�!�!�!�5�s�5�5�5�5��!�S�!�!�!��X�!��,�c�,�,�,��X�,�*�*�5�*�*�*�*�*��e�E�5�L�&9�!:�*�t�*�*�*�*�
�U������rr)rLr:�loggingr�defence360agent.model.instancer�&defence360agent.model.messages_to_sendrrPrrrErr�<module>r]s�����������������-�-�-�-�-�-�@�@�@�@�@�@�	��8�	�	��\�\�\�\�\�\�\�\�\�\rdefence360agent/internals/__pycache__/the_sink.cpython-311.opt-1.pyc0000644000000000000000000004747700000000000022245 0ustar  �

'Nd�gG���ddlZddlZddlZddlZddlZddlZddlZddlmZddl	m
Z
mZddlm
Z
ddlmZmZddlmZmZmZddlmZddlmZmZmZdd	lmZeje��Ze��Z ej!d
ddg��Z"Gd
�de
��Z#Gd�de��Z$d�Z%Gd�de&��Z'Gd�de&��Z(Gd�dej)��Z*dS)�N)�
attrgetter)�Message�Reject)�BaseMessageProcessor)�
is_enabled�mqtt_tracked_methods)�Gen�message_id_gen�	publisher)�safe_cancel_task)�DAY�ServiceBase�
rate_limit)�g�ProcessingMessage�message�
start_timec�2�eZdZd�Zd�Zd�Zd�Zd�Zd�ZdS)�TheSinkc��t|td�����|_||_t	|t|j����|_|t_dS)N�PROCESSING_ORDER)�key)	�sortedr�_sinks_ordered�_loop�TaskManager�MessageProcessor�
_task_managerr�sink)�self�	sink_list�loops   �W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/the_sink.py�__init__zTheSink.__init__#s`��$��:�&8�9�9�
�
�
�����
�(��"�4�#6�7�7�
�
��������c�8�|jj�d|jj��S)N�.)�	__class__�
__module__�__name__�r s r#�__repr__zTheSink.__repr__-s ���.�3�3�3�T�^�5L�5L�M�Mr%c����fd�|jD��}t|��dks
Jd���tt|��d��S)ze
        introspection: decompose a specific role
        :return classobj: instance or None
        c�4��g|]}t|����|��S�)�
isinstance)�.0r�classobjs  �r#�
<listcomp>z%TheSink.decompose.<locals>.<listcomp>5s8���
�
�
��J�t�X�4N�4N�
��
�
�
r%�zAmbiguous requestN)r�len�next�iter)r r2�optionss ` r#�	decomposezTheSink.decompose0sf���

�
�
�
�!�0�
�
�
���7�|�|�q� � � �"5� � � ��D��M�M�4�(�(�(r%c�8�|j���dS)z�
        Make sure to run message processing bus only
        when every MessageSource (or MessageSource+MessageSink mix)
        got initialized
        N)r�startr+s r#r;z
TheSink.start;s��	
�� � �"�"�"�"�"r%c��XK�t�d��|j���t�d��|j�d����d{V��t�d��|j����d{V��dS)Nzshutdown the sink startedzwait for current tasks���timeoutzfinish wait task)�logger�infor�should_stop�wait_current_tasks�waitr+s r#�shutdownzTheSink.shutdownCs��������/�0�0�0���&�&�(�(�(����,�-�-�-�� �3�3�A�3�>�>�>�>�>�>�>�>�>����&�'�'�'�� �%�%�'�'�'�'�'�'�'�'�'�'�'r%c��JK�|j�|���d{V��dS�N)r�push_msg)r rs  r#�process_messagezTheSink.process_messageKs5����� �)�)�'�2�2�2�2�2�2�2�2�2�2�2r%N)	r*r)�__qualname__r$r,r9r;rErIr/r%r#rr"sq���������N�N�N�	)�	)�	)�#�#�#�(�(�(�3�3�3�3�3r%rc�p��eZdZdZdZdZ�fd�Zd�Zed���Z	dd�Z
d	�Zd
�Ze
d���Z�xZS)
ri��r=�c�~��t���|��t|j���|_|j|_|j|_||_	tj��|_tttj���|_|�tj��|_dS)N)�maxsize)�period�on_drop)�superr$�MessageQueue�MAXSIZE�_queue�CONCURRENCY�_concurrency�TIMEOUT�_process_message_timeout�_msg_processor�weakref�WeakSet�tasksrr
r@�warning�_throttled_logger�error�throttled_log_error)r r"�
msg_processorr(s   �r#r$zTaskManager.__init__Ws����
���������"�4�<�8�8�8��� �,���(,���%�+����_�&�&��
�!+�3���!O�!O�!O���#'�#9�#9�&�,�#G�#G�� � � r%c��,K�|j���s/|j�t|�����d{V��dS|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)	rT�full�put�MessageComparabler^�should_be_called�current_processing_messages�qsizer`)r �msg�argss   r#rHzTaskManager.push_msgas������{���!�!�	,��+�/�/�"3�C�"8�"8�9�9�9�9�9�9�9�9�9�9�9��%�6�
�O��K��4�����O��K�%�%�'�'��4����
%�D�$�d�+�+�+�+r%c�>�td�|jD����S)Nc3�K�|]R}|����|jjtt	j��|jjz
d��fV��SdS)�N)�done�processing_msgr�round�time�	monotonicr)r1�tasks  r#�	<genexpr>z:TaskManager.current_processing_messages.<locals>.<genexpr>�sq����
�
�
��9�9�;�;�

��#�+��d�n�&�&��)<�)G�G��K�K�
�
�
�
�
�
�
r%)�tupler\r+s r#rgz'TaskManager.current_processing_messages|s6���
�
�
�
�
�
�
�
�
�	
r%Nc��K�|jrOd�|jD��}t�d|��t	j|j|����d{V��dSdS)Nc�j�g|]0\}}|�d��|�d��|f��1S)�method�
message_id)�get)r1�m�lastings   r#r3z2TaskManager.wait_current_tasks.<locals>.<listcomp>�sI������A�w����x���!�%�%��"5�"5�w�?���r%z#Waiting for %r processing to finishr>)r\rgr@rA�asynciorD)r r?�msg_to_processs   r#rCzTaskManager.wait_current_tasks�s������:�		<���"&�"B����N�
�K�K�5��
�
�
��,�t�z�7�;�;�;�;�;�;�;�;�;�;�;�;�		<�		<r%c���K�tj|j���	|j�s4t�d|j�����	|�����d{V��|j�	���d{V��}n#tj
$rYn�wxYw|j�|�
|j����}t|jt!j����|_|��fd���|�|j��|j�|��|j��4|j���}|r4t�d|j�����dSdS#t�d��YdSxYw)NzMessage queue size: %sc�,������SrG)�release)�_�	semaphores �r#�<lambda>z"TaskManager._run.<locals>.<lambda>�s���i�.?�.?�.A�.A�r%z3There is still %s unprocessed messages in the queue� Error during message processing:)r}�BoundedSemaphorerV�_should_stopr@�debugrTrh�_TaskManager__limit_concurrencyrz�CancelledErrorr�create_taskrYrirrqrrro�add_done_callback�_on_msg_processedr\�addr]�	exception)r �msg_comparable�t�unprocessedr�s    @r#�_runzTaskManager._run�s�������,�T�->�?�?�	�	A��'�
"����5�t�{�7H�7H�7J�7J�K�K�K���2�2�9�=�=�=�=�=�=�=�=�=�+/�;�?�?�+<�+<�%<�%<�%<�%<�%<�%<�N�N���-�����E������J�*�*��'�'��(:�;�;����$5�"�&���(8�(8�$�$�� ��#�#�$A�$A�$A�$A�B�B�B��#�#�D�$:�;�;�;��
���q�!�!�!��'�
"� �+�+�+�-�-�K��
����I��K�%�%�'�'������
�
��
	A����?�@�@�@�@�@�@���s0�:F+�:B�F+�B&�#F+�%B&�&DF+�+G
c���K�		tj|���|j����d{V��S#tj$r|�d|j��YnwxYw�e)z;Try to acquire *semaphore* in a loop, log error on timeout.Tr>Nz+Message hasn't been processed in %s seconds)r}�wait_for�acquirerX�TimeoutErrorr`)r r�s  r#�__limit_concurrencyzTaskManager.__limit_concurrency�s�����
	�	
�$�-��%�%�'�'� �9�������������'�
�
�
��(�(�A��1������
����
	s�28�*A%�$A%c�n�|���}|rt�d|���dSdS)Nr�)�exc_info)r�r@)�future�es  r#r�zTaskManager._on_msg_processed�sH���������	M����?�!��L�L�L�L�L�	M�	Mr%rG)r*r)rJrSrUrWr$rH�propertyrgrCr�r��staticmethodr��
__classcell__�r(s@r#rrOs���������G��K��G�H�H�H�H�H�,�,�,�6�

�

��X�

�
<�
<�
<�
<�A�A�A�8����M�M��\�M�M�M�M�Mr%rc��`K�|���st|���d{V��dSdSrG)rnr)rss r#�cancel_taskr��sF�����9�9�;�;�%��t�$�$�$�$�$�$�$�$�$�$�$�%�%r%c�$�eZdZdZd�Zd�Zd�ZdS)rrLc��||_tj��|_t	d���t
j��|_dS)NrL)rO)�sinksrZ�WeakValueDictionary�locksrr@r_r`)r r�s  r#r$zMessageProcessor.__init__�sC����
��0�2�2��
�#=�:�W�#=�#=�#=��L�$
�$
�� � � r%c��ZK�|�d��}|rv|j�|tj����}|4�d{V��|�|���d{V��ddd���d{V��dS#1�d{V��swxYwYdS|�|���d{V��dS)N�attackers_ip)rzr��
setdefaultr}�Lock�_call_unlocked)r ri�ip�locks    r#�__call__zMessageProcessor.__call__�sT����
�W�W�^�
$�
$��
�	+��:�(�(��W�\�^�^�<�<�D��
/�
/�
/�
/�
/�
/�
/�
/��)�)�#�.�.�.�.�.�.�.�.�.�
/�
/�
/�
/�
/�
/�
/�
/�
/�
/�
/�
/�
/�
/�
/�
/�
/�
/�
/�
/�
/�
/�
/�
/����
/�
/�
/�
/�
/�
/��%�%�c�*�*�*�*�*�*�*�*�*�*�*s�A=�=
B�
Bc
��K�td��r;|�d��t��vrd|vrtj|��tj|td���tj	��}|j
D�]�}	tj|�
|����}tjtj|��|j����d{V��}t#|t$��r|}�nV#tj$rYt)|���d{V���n_t*$rJ}t,�dt1|��|��Yd}~t)|���d{V��dSd}~wtj$r�t5j��}|�|���|�d	��t,�d
|||j|�����Yt)|���d{V��dSt@$r6t,�!d||��Yt)|���d{V��dSwxYwt)|���d{V�����#t)|���d{V��wxYwtj	��|z
}t,�d||��||j"kr|�#d
|||j"��dSdS)N�
mqtt_trackingrx�message_reporter_idzagent-sink-received)�stager>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_sinkrqrrr�r}r�rIr��shield�TIMEOUT_TO_SINK_PROCESSr0rr�r�rr@rA�strr��io�StringIO�print_stack�seekr_�read�	Exceptionr��PROCESSING_TIME_THRESHOLDr`)	r rir;r�process_message_task�	processedr��stack�processing_times	         r#r�zMessageProcessor._call_unlocked�s�����
��'�'�	'�����!�!�%9�%;�%;�;�;�%�S�0�0��!�#�&�&�&����0�8M�N�N�N�N��� � ���J�*	8�*	8�D�)
8�'.�':��(�(��-�-�(�(�$�#*�"2�
�N�#7�8�8� �8�
#�#�#�������	�D�i��1�1�$�#�C���7�)�
�
�
��8"�"6�7�7�7�7�7�7�7�7�7�7�7�7�
�
�
����0�#�a�&�&�#�>�>�>�����2"�"6�7�7�7�7�7�7�7�7�7�7�7�7�����1�'�
�
�
�
��
�
��$�0�0�e�0�<�<�<��
�
�1�
�
�
����$����0��J�J�L�L�
����"�"6�7�7�7�7�7�7�7�7�7�7�7�7��
�
�
�� � �!<�c�4�H�H�H��
"�"6�7�7�7�7�7�7�7�7�7�7�7�7�
����"�"6�7�7�7�7�7�7�7�7�7�7��k�"6�7�7�7�7�7�7�7�7�7�7�����.�*�*�U�2�����2�C��I�I�I��S�:�:�:��$�$�(����-�
�
�
�
�
�;�:sV�AC:�!I'�:I
�	I'�"	I
�+)E0�I'�0BI
�5I'�%I
�3I'�I
�
I'�'I>N)r*r)rJr�r$r�r�r/r%r#rr�sL������"��
�
�
�+�+�+�E�E�E�E�Er%rc�B��eZdZdZdZe�fd���Zd�Zd�Z�xZ	S)rez#Wrapper to make message comparable.���c���|xjdz
c_t���|��}|j|jf|_||_|S�Nr4)�indexrQ�__new__�PRIORITY�priorityri)�clsri�rvr(s   �r#r�zMessageComparable.__new__/sC����	�	�Q��	�	�
�W�W�_�_�S�
!�
!���l�C�I�-�������	r%c�@�|j�|j��SrG)r��__lt__)r �others  r#r�zMessageComparable.__lt__7s���}�#�#�E�N�3�3�3r%c�Z�d�|jj|j|j���S)Nz'<{klass}({msg!r}), priority={priority}>)�klassrir�)�formatr(r*rir�r+s r#r,zMessageComparable.__repr__:s2��8�?�?��.�)����]�@�
�
�	
r%)
r*r)rJ�__doc__r�r�r�r�r,r�r�s@r#rere)sm�������-�-�
�E�������\��4�4�4�
�
�
�
�
�
�
r%rec�4��eZdZ�fd�Zdef�fd�Zd�Z�xZS)rRc���t��j|i|��tj��|_d|j_d|j_dS)N�2i�)rQr$�reprlib�Repr�_repr�	maxstring�maxtuple)r rj�kwargsr(s   �r#r$zMessageQueue.__init__CsF��������$�)�&�)�)�)��\�^�^��
�!��
��"��
���r%�itemc��V�K�t���|���d{V��SrG)rQrd)r r�r(s  �r#rdzMessageQueue.putIs/������W�W�[�[��&�&�&�&�&�&�&�&�&r%c��ttjd�|jD�������d�d���}d|j�d|����d|j�|���d�S)	Nc�0�g|]}|jjj��Sr/)rir(rJ)r1r�s  r#r3z(MessageQueue.__str__.<locals>.<listcomp>Ps ��I�I�I�T���#�0�I�I�Ir%c��|dSr�r/)r�s r#r�z&MessageQueue.__str__.<locals>.<lambda>Rs
��T�!�W�r%T)r�reversez<PriorityQueue maxsize=z
; queue_size=z queue_counter=�>)	r�collections�CounterrT�itemsrNrhr��repr)r �
msg_countss  r#�__str__zMessageQueue.__str__Ls������I�I�T�[�I�I�I�
�
��e�g�g�$�$��
�
�
�
�
<�d�l�
<�
<��*�*�,�,�
<�
<�!�Z�_�_�Z�8�8�
<�
<�
<�	
r%)r*r)rJr$rerdr�r�r�s@r#rRrRBsm�������#�#�#�#�#�'�/�'�'�'�'�'�'�

�

�

�

�

�

�

r%rR)+r}r�r�r�rqrZ�logging�operatorr�"defence360agent.contracts.messagesrr�!defence360agent.contracts.pluginsr�'defence360agent.internals.feature_flagsrr�2defence360agent.internals.message_status_publisherr	r
r�defence360agent.utilsr�defence360agent.utils.commonr
rr�&defence360agent.internals.global_scoper�	getLoggerr*r@r��
namedtuplerrrr��objectrre�
PriorityQueuerRr/r%r#�<module>r�sh����������	�	�	�	�����������������������>�>�>�>�>�>�>�>�B�B�B�B�B�B�������������������
3�2�2�2�2�2�E�E�E�E�E�E�E�E�E�E�4�4�4�4�4�4�	��	�8�	$�	$���S�U�U��*�K�*��)�\�2����
*3�*3�*3�*3�*3�"�*3�*3�*3�ZwM�wM�wM�wM�wM�+�wM�wM�wM�t%�%�%�
X�X�X�X�X�v�X�X�X�v
�
�
�
�
��
�
�
�2
�
�
�
�
�7�(�
�
�
�
�
r%defence360agent/internals/__pycache__/the_sink.cpython-311.pyc0000644000000000000000000004747700000000000021306 0ustar  �

'Nd�gG���ddlZddlZddlZddlZddlZddlZddlZddlmZddl	m
Z
mZddlm
Z
ddlmZmZddlmZmZmZddlmZddlmZmZmZdd	lmZeje��Ze��Z ej!d
ddg��Z"Gd
�de
��Z#Gd�de��Z$d�Z%Gd�de&��Z'Gd�de&��Z(Gd�dej)��Z*dS)�N)�
attrgetter)�Message�Reject)�BaseMessageProcessor)�
is_enabled�mqtt_tracked_methods)�Gen�message_id_gen�	publisher)�safe_cancel_task)�DAY�ServiceBase�
rate_limit)�g�ProcessingMessage�message�
start_timec�2�eZdZd�Zd�Zd�Zd�Zd�Zd�ZdS)�TheSinkc��t|td�����|_||_t	|t|j����|_|t_dS)N�PROCESSING_ORDER)�key)	�sortedr�_sinks_ordered�_loop�TaskManager�MessageProcessor�
_task_managerr�sink)�self�	sink_list�loops   �W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/the_sink.py�__init__zTheSink.__init__#s`��$��:�&8�9�9�
�
�
�����
�(��"�4�#6�7�7�
�
��������c�8�|jj�d|jj��S)N�.)�	__class__�
__module__�__name__�r s r#�__repr__zTheSink.__repr__-s ���.�3�3�3�T�^�5L�5L�M�Mr%c����fd�|jD��}t|��dks
Jd���tt|��d��S)ze
        introspection: decompose a specific role
        :return classobj: instance or None
        c�4��g|]}t|����|��S�)�
isinstance)�.0r�classobjs  �r#�
<listcomp>z%TheSink.decompose.<locals>.<listcomp>5s8���
�
�
��J�t�X�4N�4N�
��
�
�
r%�zAmbiguous requestN)r�len�next�iter)r r2�optionss ` r#�	decomposezTheSink.decompose0sf���

�
�
�
�!�0�
�
�
���7�|�|�q� � � �"5� � � ��D��M�M�4�(�(�(r%c�8�|j���dS)z�
        Make sure to run message processing bus only
        when every MessageSource (or MessageSource+MessageSink mix)
        got initialized
        N)r�startr+s r#r;z
TheSink.start;s��	
�� � �"�"�"�"�"r%c��XK�t�d��|j���t�d��|j�d����d{V��t�d��|j����d{V��dS)Nzshutdown the sink startedzwait for current tasks���timeoutzfinish wait task)�logger�infor�should_stop�wait_current_tasks�waitr+s r#�shutdownzTheSink.shutdownCs��������/�0�0�0���&�&�(�(�(����,�-�-�-�� �3�3�A�3�>�>�>�>�>�>�>�>�>����&�'�'�'�� �%�%�'�'�'�'�'�'�'�'�'�'�'r%c��JK�|j�|���d{V��dS�N)r�push_msg)r rs  r#�process_messagezTheSink.process_messageKs5����� �)�)�'�2�2�2�2�2�2�2�2�2�2�2r%N)	r*r)�__qualname__r$r,r9r;rErIr/r%r#rr"sq���������N�N�N�	)�	)�	)�#�#�#�(�(�(�3�3�3�3�3r%rc�p��eZdZdZdZdZ�fd�Zd�Zed���Z	dd�Z
d	�Zd
�Ze
d���Z�xZS)
ri��r=�c�~��t���|��t|j���|_|j|_|j|_||_	tj��|_tttj���|_|�tj��|_dS)N)�maxsize)�period�on_drop)�superr$�MessageQueue�MAXSIZE�_queue�CONCURRENCY�_concurrency�TIMEOUT�_process_message_timeout�_msg_processor�weakref�WeakSet�tasksrr
r@�warning�_throttled_logger�error�throttled_log_error)r r"�
msg_processorr(s   �r#r$zTaskManager.__init__Ws����
���������"�4�<�8�8�8��� �,���(,���%�+����_�&�&��
�!+�3���!O�!O�!O���#'�#9�#9�&�,�#G�#G�� � � r%c��,K�|j���s/|j�t|�����d{V��dS|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)	rT�full�put�MessageComparabler^�should_be_called�current_processing_messages�qsizer`)r �msg�argss   r#rHzTaskManager.push_msgas������{���!�!�	,��+�/�/�"3�C�"8�"8�9�9�9�9�9�9�9�9�9�9�9��%�6�
�O��K��4�����O��K�%�%�'�'��4����
%�D�$�d�+�+�+�+r%c�>�td�|jD����S)Nc3�K�|]R}|����|jjtt	j��|jjz
d��fV��SdS)�N)�done�processing_msgr�round�time�	monotonicr)r1�tasks  r#�	<genexpr>z:TaskManager.current_processing_messages.<locals>.<genexpr>�sq����
�
�
��9�9�;�;�

��#�+��d�n�&�&��)<�)G�G��K�K�
�
�
�
�
�
�
r%)�tupler\r+s r#rgz'TaskManager.current_processing_messages|s6���
�
�
�
�
�
�
�
�
�	
r%Nc��K�|jrOd�|jD��}t�d|��t	j|j|����d{V��dSdS)Nc�j�g|]0\}}|�d��|�d��|f��1S)�method�
message_id)�get)r1�m�lastings   r#r3z2TaskManager.wait_current_tasks.<locals>.<listcomp>�sI������A�w����x���!�%�%��"5�"5�w�?���r%z#Waiting for %r processing to finishr>)r\rgr@rA�asynciorD)r r?�msg_to_processs   r#rCzTaskManager.wait_current_tasks�s������:�		<���"&�"B����N�
�K�K�5��
�
�
��,�t�z�7�;�;�;�;�;�;�;�;�;�;�;�;�		<�		<r%c���K�tj|j���	|j�s4t�d|j�����	|�����d{V��|j�	���d{V��}n#tj
$rYn�wxYw|j�|�
|j����}t|jt!j����|_|��fd���|�|j��|j�|��|j��4|j���}|r4t�d|j�����dSdS#t�d��YdSxYw)NzMessage queue size: %sc�,������SrG)�release)�_�	semaphores �r#�<lambda>z"TaskManager._run.<locals>.<lambda>�s���i�.?�.?�.A�.A�r%z3There is still %s unprocessed messages in the queue� Error during message processing:)r}�BoundedSemaphorerV�_should_stopr@�debugrTrh�_TaskManager__limit_concurrencyrz�CancelledErrorr�create_taskrYrirrqrrro�add_done_callback�_on_msg_processedr\�addr]�	exception)r �msg_comparable�t�unprocessedr�s    @r#�_runzTaskManager._run�s�������,�T�->�?�?�	�	A��'�
"����5�t�{�7H�7H�7J�7J�K�K�K���2�2�9�=�=�=�=�=�=�=�=�=�+/�;�?�?�+<�+<�%<�%<�%<�%<�%<�%<�N�N���-�����E������J�*�*��'�'��(:�;�;����$5�"�&���(8�(8�$�$�� ��#�#�$A�$A�$A�$A�B�B�B��#�#�D�$:�;�;�;��
���q�!�!�!��'�
"� �+�+�+�-�-�K��
����I��K�%�%�'�'������
�
��
	A����?�@�@�@�@�@�@���s0�:F+�:B�F+�B&�#F+�%B&�&DF+�+G
c���K�		tj|���|j����d{V��S#tj$r|�d|j��YnwxYw�e)z;Try to acquire *semaphore* in a loop, log error on timeout.Tr>Nz+Message hasn't been processed in %s seconds)r}�wait_for�acquirerX�TimeoutErrorr`)r r�s  r#�__limit_concurrencyzTaskManager.__limit_concurrency�s�����
	�	
�$�-��%�%�'�'� �9�������������'�
�
�
��(�(�A��1������
����
	s�28�*A%�$A%c�n�|���}|rt�d|���dSdS)Nr�)�exc_info)r�r@)�future�es  r#r�zTaskManager._on_msg_processed�sH���������	M����?�!��L�L�L�L�L�	M�	Mr%rG)r*r)rJrSrUrWr$rH�propertyrgrCr�r��staticmethodr��
__classcell__�r(s@r#rrOs���������G��K��G�H�H�H�H�H�,�,�,�6�

�

��X�

�
<�
<�
<�
<�A�A�A�8����M�M��\�M�M�M�M�Mr%rc��`K�|���st|���d{V��dSdSrG)rnr)rss r#�cancel_taskr��sF�����9�9�;�;�%��t�$�$�$�$�$�$�$�$�$�$�$�%�%r%c�$�eZdZdZd�Zd�Zd�ZdS)rrLc��||_tj��|_t	d���t
j��|_dS)NrL)rO)�sinksrZ�WeakValueDictionary�locksrr@r_r`)r r�s  r#r$zMessageProcessor.__init__�sC����
��0�2�2��
�#=�:�W�#=�#=�#=��L�$
�$
�� � � r%c��ZK�|�d��}|rv|j�|tj����}|4�d{V��|�|���d{V��ddd���d{V��dS#1�d{V��swxYwYdS|�|���d{V��dS)N�attackers_ip)rzr��
setdefaultr}�Lock�_call_unlocked)r ri�ip�locks    r#�__call__zMessageProcessor.__call__�sT����
�W�W�^�
$�
$��
�	+��:�(�(��W�\�^�^�<�<�D��
/�
/�
/�
/�
/�
/�
/�
/��)�)�#�.�.�.�.�.�.�.�.�.�
/�
/�
/�
/�
/�
/�
/�
/�
/�
/�
/�
/�
/�
/�
/�
/�
/�
/�
/�
/�
/�
/�
/�
/����
/�
/�
/�
/�
/�
/��%�%�c�*�*�*�*�*�*�*�*�*�*�*s�A=�=
B�
Bc
��K�td��r;|�d��t��vrd|vrtj|��tj|td���tj	��}|j
D�]�}	tj|�
|����}tjtj|��|j����d{V��}t#|t$��r|}�nV#tj$rYt)|���d{V���n_t*$rJ}t,�dt1|��|��Yd}~t)|���d{V��dSd}~wtj$r�t5j��}|�|���|�d	��t,�d
|||j|�����Yt)|���d{V��dSt@$r6t,�!d||��Yt)|���d{V��dSwxYwt)|���d{V�����#t)|���d{V��wxYwtj	��|z
}t,�d||��||j"kr|�#d
|||j"��dSdS)N�
mqtt_trackingrx�message_reporter_idzagent-sink-received)�stager>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_sinkrqrrr�r}r�rIr��shield�TIMEOUT_TO_SINK_PROCESSr0rr�r�rr@rA�strr��io�StringIO�print_stack�seekr_�read�	Exceptionr��PROCESSING_TIME_THRESHOLDr`)	r rir;r�process_message_task�	processedr��stack�processing_times	         r#r�zMessageProcessor._call_unlocked�s�����
��'�'�	'�����!�!�%9�%;�%;�;�;�%�S�0�0��!�#�&�&�&����0�8M�N�N�N�N��� � ���J�*	8�*	8�D�)
8�'.�':��(�(��-�-�(�(�$�#*�"2�
�N�#7�8�8� �8�
#�#�#�������	�D�i��1�1�$�#�C���7�)�
�
�
��8"�"6�7�7�7�7�7�7�7�7�7�7�7�7�
�
�
����0�#�a�&�&�#�>�>�>�����2"�"6�7�7�7�7�7�7�7�7�7�7�7�7�����1�'�
�
�
�
��
�
��$�0�0�e�0�<�<�<��
�
�1�
�
�
����$����0��J�J�L�L�
����"�"6�7�7�7�7�7�7�7�7�7�7�7�7��
�
�
�� � �!<�c�4�H�H�H��
"�"6�7�7�7�7�7�7�7�7�7�7�7�7�
����"�"6�7�7�7�7�7�7�7�7�7�7��k�"6�7�7�7�7�7�7�7�7�7�7�����.�*�*�U�2�����2�C��I�I�I��S�:�:�:��$�$�(����-�
�
�
�
�
�;�:sV�AC:�!I'�:I
�	I'�"	I
�+)E0�I'�0BI
�5I'�%I
�3I'�I
�
I'�'I>N)r*r)rJr�r$r�r�r/r%r#rr�sL������"��
�
�
�+�+�+�E�E�E�E�Er%rc�B��eZdZdZdZe�fd���Zd�Zd�Z�xZ	S)rez#Wrapper to make message comparable.���c���|xjdz
c_t���|��}|j|jf|_||_|S�Nr4)�indexrQ�__new__�PRIORITY�priorityri)�clsri�rvr(s   �r#r�zMessageComparable.__new__/sC����	�	�Q��	�	�
�W�W�_�_�S�
!�
!���l�C�I�-�������	r%c�@�|j�|j��SrG)r��__lt__)r �others  r#r�zMessageComparable.__lt__7s���}�#�#�E�N�3�3�3r%c�Z�d�|jj|j|j���S)Nz'<{klass}({msg!r}), priority={priority}>)�klassrir�)�formatr(r*rir�r+s r#r,zMessageComparable.__repr__:s2��8�?�?��.�)����]�@�
�
�	
r%)
r*r)rJ�__doc__r�r�r�r�r,r�r�s@r#rere)sm�������-�-�
�E�������\��4�4�4�
�
�
�
�
�
�
r%rec�4��eZdZ�fd�Zdef�fd�Zd�Z�xZS)rRc���t��j|i|��tj��|_d|j_d|j_dS)N�2i�)rQr$�reprlib�Repr�_repr�	maxstring�maxtuple)r rj�kwargsr(s   �r#r$zMessageQueue.__init__CsF��������$�)�&�)�)�)��\�^�^��
�!��
��"��
���r%�itemc��V�K�t���|���d{V��SrG)rQrd)r r�r(s  �r#rdzMessageQueue.putIs/������W�W�[�[��&�&�&�&�&�&�&�&�&r%c��ttjd�|jD�������d�d���}d|j�d|����d|j�|���d�S)	Nc�0�g|]}|jjj��Sr/)rir(rJ)r1r�s  r#r3z(MessageQueue.__str__.<locals>.<listcomp>Ps ��I�I�I�T���#�0�I�I�Ir%c��|dSr�r/)r�s r#r�z&MessageQueue.__str__.<locals>.<lambda>Rs
��T�!�W�r%T)r�reversez<PriorityQueue maxsize=z
; queue_size=z queue_counter=�>)	r�collections�CounterrT�itemsrNrhr��repr)r �
msg_countss  r#�__str__zMessageQueue.__str__Ls������I�I�T�[�I�I�I�
�
��e�g�g�$�$��
�
�
�
�
<�d�l�
<�
<��*�*�,�,�
<�
<�!�Z�_�_�Z�8�8�
<�
<�
<�	
r%)r*r)rJr$rerdr�r�r�s@r#rRrRBsm�������#�#�#�#�#�'�/�'�'�'�'�'�'�

�

�

�

�

�

�

r%rR)+r}r�r�r�rqrZ�logging�operatorr�"defence360agent.contracts.messagesrr�!defence360agent.contracts.pluginsr�'defence360agent.internals.feature_flagsrr�2defence360agent.internals.message_status_publisherr	r
r�defence360agent.utilsr�defence360agent.utils.commonr
rr�&defence360agent.internals.global_scoper�	getLoggerr*r@r��
namedtuplerrrr��objectrre�
PriorityQueuerRr/r%r#�<module>r�sh����������	�	�	�	�����������������������>�>�>�>�>�>�>�>�B�B�B�B�B�B�������������������
3�2�2�2�2�2�E�E�E�E�E�E�E�E�E�E�4�4�4�4�4�4�	��	�8�	$�	$���S�U�U��*�K�*��)�\�2����
*3�*3�*3�*3�*3�"�*3�*3�*3�ZwM�wM�wM�wM�wM�+�wM�wM�wM�t%�%�%�
X�X�X�X�X�v�X�X�X�v
�
�
�
�
��
�
�
�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/feature_flags.py0000644000000000000000000002274600000000000015741 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"

_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.py0000644000000000000000000001326600000000000020233 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 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)

    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):
            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.py0000644000000000000000000000721600000000000017031 0ustar  import queue
import time
from logging import getLogger

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

    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
                    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 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()

    def get(self) -> bytes:
        if not self._buffer and self.storage_size != 0:
            # reduce the number of database calls
            items = self._model.get_oldest(limit=self._buffer_limit)
            # not save id value
            self._buffer += [item[1:] for item in items.tuples()]
            self._model.delete_in(items)

        if self._buffer:
            _, message = self._buffer.pop(0)
            return message
        raise queue.Empty()
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"<PriorityQueue maxsize={self.maxsize}; "
            f"queue_size={self.qsize()} "
            f"queue_counter={self._repr.repr(msg_counts)}>"
        )
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/__init__.py0000644000000000000000000000000000000000000015021 0ustar  defence360agent/migrations/__pycache__/0000755000000000000000000000000000000000000015132 5ustar  defence360agent/migrations/__pycache__/001_initial.cpython-311.opt-1.pyc0000644000000000000000000001144200000000000022606 0ustar  �

�-�$��}����dZddlZGd�dej��ZGd�dej��ZGd�dej��ZGd	�d
ej��Zdd�Zdd
�Z	dS)a�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)

�Nc�\�eZdZejdd���Zejd���Zejd���Zej	d���Z
ejd���Zejd���Zejd���Z
ejd���Zejd���ZGd�d��ZdS)�IncidentT��primary_key�null�rc��eZdZdZdS)�
Incident.Meta�incidentN��__name__�
__module__�__qualname__�db_table���[/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/001_initial.py�Metar
#s���������rrN)r
rr�pw�IntegerField�id�	CharField�plugin�rule�
FloatField�	timestamp�retries�severity�name�description�abuserrrrrrrs�������	���T��	5�	5�	5�B�
�R�\�t�
$�
$�
$�F��2�<�T�"�"�"�D���
�4�(�(�(�I��b�o�4�(�(�(�G��r��D�)�)�)�H��2�<�T�"�"�"�D��"�,�D�)�)�)�K�
�R�\�t�
$�
$�
$�F����������rrc��eZdZejdd���Zejdejd��g���Zejdd���Z	Gd�d	��Z
d
S)�IPListTFrz$listname in ('WHITE','BLACK','GRAY'))r�constraintsr)�defaultrc��eZdZdZdS)�IPList.Meta�iplistNrrrrrr'/s���������rrN)r
rrrr�ip�Check�listnamer�
expirationrrrrr#r#'s�������	���$�U�	3�	3�	3�B��r�|�
��R�X�D�E�E�F����H�!�����6�6�6�J����������rr#c���eZdZejdd���Zejd���Zejd���Zej	d���Z
ejd���ZGd�d��ZdS)�BlocklistHistoryTrrc��eZdZdZdS)�BlocklistHistory.Meta�blocklist_historyNrrrrrr0:s������&���rrN)
r
rrrrrrrrrrr)rrrrr.r.3s�������	���T��	5�	5�	5�B�
�R�\�t�
$�
$�
$�F��2�<�T�"�"�"�D���
�4�(�(�(�I�	���4�	 �	 �	 �B�'�'�'�'�'�'�'�'�'�'rr.c�L�eZdZejdd���ZGd�d��ZdS)�LastSynclistTrc��eZdZdZdS)�LastSynclist.Meta�
last_synclistNrrrrrr5As������"���rrN)r
rrrrrrrrrr3r3>sR��������
�$�T�:�:�:�I�#�#�#�#�#�#�#�#�#�#rr3Fc���|�t��|�t��|�t��|�t��dS)z%In memory of former create_db() (RIP)N)�create_modelrr#r.r3��migrator�database�fake�kwargss    r�migrater>Es[��
���(�#�#�#����&�!�!�!����*�+�+�+����,�'�'�'�'�'rc��dS)zNothing to rollback.Nrr9s    r�rollbackr@Ns���r)F)
�__doc__�peeweer�Modelrr#r.r3r>r@rrr�<module>rDs�����(���������r�x����	�	�	�	�	�R�X�	�	�	�'�'�'�'�'�r�x�'�'�'�#�#�#�#�#�2�8�#�#�#�(�(�(�(������rdefence360agent/migrations/__pycache__/001_initial.cpython-311.pyc0000644000000000000000000001144200000000000021647 0ustar  �

�-�$��}����dZddlZGd�dej��ZGd�dej��ZGd�dej��ZGd	�d
ej��Zdd�Zdd
�Z	dS)a�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)

�Nc�\�eZdZejdd���Zejd���Zejd���Zej	d���Z
ejd���Zejd���Zejd���Z
ejd���Zejd���ZGd�d��ZdS)�IncidentT��primary_key�null�rc��eZdZdZdS)�
Incident.Meta�incidentN��__name__�
__module__�__qualname__�db_table���[/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/001_initial.py�Metar
#s���������rrN)r
rr�pw�IntegerField�id�	CharField�plugin�rule�
FloatField�	timestamp�retries�severity�name�description�abuserrrrrrrs�������	���T��	5�	5�	5�B�
�R�\�t�
$�
$�
$�F��2�<�T�"�"�"�D���
�4�(�(�(�I��b�o�4�(�(�(�G��r��D�)�)�)�H��2�<�T�"�"�"�D��"�,�D�)�)�)�K�
�R�\�t�
$�
$�
$�F����������rrc��eZdZejdd���Zejdejd��g���Zejdd���Z	Gd�d	��Z
d
S)�IPListTFrz$listname in ('WHITE','BLACK','GRAY'))r�constraintsr)�defaultrc��eZdZdZdS)�IPList.Meta�iplistNrrrrrr'/s���������rrN)r
rrrr�ip�Check�listnamer�
expirationrrrrr#r#'s�������	���$�U�	3�	3�	3�B��r�|�
��R�X�D�E�E�F����H�!�����6�6�6�J����������rr#c���eZdZejdd���Zejd���Zejd���Zej	d���Z
ejd���ZGd�d��ZdS)�BlocklistHistoryTrrc��eZdZdZdS)�BlocklistHistory.Meta�blocklist_historyNrrrrrr0:s������&���rrN)
r
rrrrrrrrrrr)rrrrr.r.3s�������	���T��	5�	5�	5�B�
�R�\�t�
$�
$�
$�F��2�<�T�"�"�"�D���
�4�(�(�(�I�	���4�	 �	 �	 �B�'�'�'�'�'�'�'�'�'�'rr.c�L�eZdZejdd���ZGd�d��ZdS)�LastSynclistTrc��eZdZdZdS)�LastSynclist.Meta�
last_synclistNrrrrrr5As������"���rrN)r
rrrrrrrrrr3r3>sR��������
�$�T�:�:�:�I�#�#�#�#�#�#�#�#�#�#rr3Fc���|�t��|�t��|�t��|�t��dS)z%In memory of former create_db() (RIP)N)�create_modelrr#r.r3��migrator�database�fake�kwargss    r�migrater>Es[��
���(�#�#�#����&�!�!�!����*�+�+�+����,�'�'�'�'�'rc��dS)zNothing to rollback.Nrr9s    r�rollbackr@Ns���r)F)
�__doc__�peeweer�Modelrr#r.r3r>r@rrr�<module>rDs�����(���������r�x����	�	�	�	�	�R�X�	�	�	�'�'�'�'�'�r�x�'�'�'�#�#�#�#�#�2�8�#�#�#�(�(�(�(������rdefence360agent/migrations/__pycache__/002_infected_domain_list.cpython-311.opt-1.pyc0000644000000000000000000000435200000000000025323 0ustar  �

ZU�������H�dZddlZGd�dej��Zdd�Zdd�ZdS)	a�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)

�Nc��eZdZejd���Zejd���Zejd���Zej	��Z
Gd�d��ZdS)�InfectedDomainListT)�primary_keyF)�nullc��eZdZdZdS)�InfectedDomainList.Meta�infected_domain_listN)�__name__�
__module__�__qualname__�db_table���h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/002_infected_domain_list.py�Metars������)���rrN)r
rr�pw�IntegerField�id�	CharField�name�threat_type�
FloatField�	timestamprrrrrrs�������	���T�	*�	*�	*�B��2�<�U�#�#�#�D��"�,�E�*�*�*�K���
���I�*�*�*�*�*�*�*�*�*�*rrFc�:�|�t��dS�N)�create_modelr��migrator�database�fake�kwargss    r�migrater"#������,�-�-�-�-�-rc�:�|�t��dSr)�remove_modelrrs    r�rollbackr&'r#r)F)�__doc__�peeweer�Modelrr"r&rrr�<module>r*s{����*����*�*�*�*�*���*�*�*�.�.�.�.�.�.�.�.�.�.rdefence360agent/migrations/__pycache__/002_infected_domain_list.cpython-311.pyc0000644000000000000000000000435200000000000024364 0ustar  �

ZU�������H�dZddlZGd�dej��Zdd�Zdd�ZdS)	a�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)

�Nc��eZdZejd���Zejd���Zejd���Zej	��Z
Gd�d��ZdS)�InfectedDomainListT)�primary_keyF)�nullc��eZdZdZdS)�InfectedDomainList.Meta�infected_domain_listN)�__name__�
__module__�__qualname__�db_table���h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/002_infected_domain_list.py�Metars������)���rrN)r
rr�pw�IntegerField�id�	CharField�name�threat_type�
FloatField�	timestamprrrrrrs�������	���T�	*�	*�	*�B��2�<�U�#�#�#�D��"�,�E�*�*�*�K���
���I�*�*�*�*�*�*�*�*�*�*rrFc�:�|�t��dS�N)�create_modelr��migrator�database�fake�kwargss    r�migrater"#������,�-�-�-�-�-rc�:�|�t��dSr)�remove_modelrrs    r�rollbackr&'r#r)F)�__doc__�peeweer�Modelrr"r&rrr�<module>r*s{����*����*�*�*�*�*���*�*�*�.�.�.�.�.�.�.�.�.�.rdefence360agent/migrations/__pycache__/003_import_from_list.cpython-311.opt-1.pyc0000644000000000000000000000410600000000000024546 0ustar  �

�>A�f��h��6�dZddlZddlZddlmZdd�Zdd�ZdS)a�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)

�N)�IntegerFieldFc��|jd}|�|tjd���t	dd�������dS)zWrite your migrations here.�iplistT)�nullc�B�ttj����S)N)�int�time���d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/003_import_from_list.py�<lambda>zmigrate.<locals>.<lambda>#s��c�$�)�+�+�6F�6F�r)r�default)�
imported_from�ctimeN)�orm�
add_fields�pw�	CharFieldr��migrator�database�fake�kwargs�IPLists     r�migraters^���\�(�
#�F������l��-�-�-���.F�.F�G�G�G������rc�N�|jd}|�|dd��dS)z$Write your rollback migrations here.rr�createdN)r�
remove_fieldsrs     r�rollbackr's.���\�(�
#�F����6�?�I�>�>�>�>�>r)F)�__doc__r	�peeweerrrrr
rr�<module>r"sj����(��������������	�	�	�	�?�?�?�?�?�?rdefence360agent/migrations/__pycache__/003_import_from_list.cpython-311.pyc0000644000000000000000000000410600000000000023607 0ustar  �

�>A�f��h��6�dZddlZddlZddlmZdd�Zdd�ZdS)a�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)

�N)�IntegerFieldFc��|jd}|�|tjd���t	dd�������dS)zWrite your migrations here.�iplistT)�nullc�B�ttj����S)N)�int�time���d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/003_import_from_list.py�<lambda>zmigrate.<locals>.<lambda>#s��c�$�)�+�+�6F�6F�r)r�default)�
imported_from�ctimeN)�orm�
add_fields�pw�	CharFieldr��migrator�database�fake�kwargs�IPLists     r�migraters^���\�(�
#�F������l��-�-�-���.F�.F�G�G�G������rc�N�|jd}|�|dd��dS)z$Write your rollback migrations here.rr�createdN)r�
remove_fieldsrs     r�rollbackr's.���\�(�
#�F����6�?�I�>�>�>�>�>r)F)�__doc__r	�peeweerrrrr
rr�<module>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  �

>�Vy����"�dZddlZdd�Zdd�ZdS)a�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)

�NFc�t�|jd}|�|tjd������dS)N�infected_domain_listT)�null)�username)�orm�
add_fields�pw�	CharField��migrator�database�fake�kwargs�InfectedDomainLists     �x/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/004_add_username_to_infected_domain_list.py�migraters=��!��&<�=�����*�R�\�t�5L�5L�5L��M�M�M�M�M�c�L�|jd}|�|d��dS)Nrr)r�
remove_fieldsrs     r�rollbackrs-��!��&<�=�����-�z�:�:�:�:�:r)F)�__doc__�peeweer	rr�rr�<module>rsS����*����N�N�N�N�;�;�;�;�;�;rdefence360agent/migrations/__pycache__/004_add_username_to_infected_domain_list.cpython-311.pyc0000644000000000000000000000327200000000000027577 0ustar  �

>�Vy����"�dZddlZdd�Zdd�ZdS)a�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)

�NFc�t�|jd}|�|tjd������dS)N�infected_domain_listT)�null)�username)�orm�
add_fields�pw�	CharField��migrator�database�fake�kwargs�InfectedDomainLists     �x/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/004_add_username_to_infected_domain_list.py�migraters=��!��&<�=�����*�R�\�t�5L�5L�5L��M�M�M�M�M�c�L�|jd}|�|d��dS)Nrr)r�
remove_fieldsrs     r�rollbackrs-��!��&<�=�����-�z�:�:�:�:�:r)F)�__doc__�peeweer	rr�rr�<module>rsS����*����N�N�N�N�;�;�;�;�;�;rdefence360agent/migrations/__pycache__/005_timeout_in_iplist.cpython-311.opt-1.pyc0000644000000000000000000000331100000000000024715 0ustar  �

��H�����"�dZddlZdd�Zdd�ZdS)a�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)

�NFc�t�|jd}|�|tjd������dS)zWrite your migrations here.�iplistT)�null)�deepN)�orm�
add_fields�pw�IntegerField��migrator�database�fake�kwargs�IPLists     �e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/005_timeout_in_iplist.py�migraters:��
�\�(�
#�F�����R�_�$�%?�%?�%?��@�@�@�@�@�c�L�|jd}|�|d��dS)z$Write your rollback migrations here.rrN)r�
remove_fieldsrs     r�rollbackr s*��
�\�(�
#�F����6�6�*�*�*�*�*r)F)�__doc__�peeweer	rr�rr�<module>rsS����*����A�A�A�A�+�+�+�+�+�+rdefence360agent/migrations/__pycache__/005_timeout_in_iplist.cpython-311.pyc0000644000000000000000000000331100000000000023756 0ustar  �

��H�����"�dZddlZdd�Zdd�ZdS)a�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)

�NFc�t�|jd}|�|tjd������dS)zWrite your migrations here.�iplistT)�null)�deepN)�orm�
add_fields�pw�IntegerField��migrator�database�fake�kwargs�IPLists     �e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/005_timeout_in_iplist.py�migraters:��
�\�(�
#�F�����R�_�$�%?�%?�%?��@�@�@�@�@�c�L�|jd}|�|d��dS)z$Write your rollback migrations here.rrN)r�
remove_fieldsrs     r�rollbackr s*��
�\�(�
#�F����6�6�*�*�*�*�*r)F)�__doc__�peeweer	rr�rr�<module>rsS����*����A�A�A�A�+�+�+�+�+�+rdefence360agent/migrations/__pycache__/006_comment_in_plist.cpython-311.opt-1.pyc0000644000000000000000000000331000000000000024520 0ustar  �

X��N#��"�dZddlZdd�Zdd�ZdS)a�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)

�NFc�t�|jd}|�|tjd������dS)zWrite your migrations here.�iplistT)�null)�commentN)�orm�
add_fields�pw�	CharField��migrator�database�fake�kwargs�IPLists     �d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/006_comment_in_plist.py�migraters:��
�\�(�
#�F�������$�(?�(?�(?��@�@�@�@�@�c�L�|jd}|�|d��dS)z$Write your rollback migrations here.rrN)r�
remove_fieldsrs     r�rollbackr s*��
�\�(�
#�F����6�9�-�-�-�-�-r)F)�__doc__�peeweer	rr�rr�<module>rsS����*����A�A�A�A�.�.�.�.�.�.rdefence360agent/migrations/__pycache__/006_comment_in_plist.cpython-311.pyc0000644000000000000000000000331000000000000023561 0ustar  �

X��N#��"�dZddlZdd�Zdd�ZdS)a�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)

�NFc�t�|jd}|�|tjd������dS)zWrite your migrations here.�iplistT)�null)�commentN)�orm�
add_fields�pw�	CharField��migrator�database�fake�kwargs�IPLists     �d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/006_comment_in_plist.py�migraters:��
�\�(�
#�F�������$�(?�(?�(?��@�@�@�@�@�c�L�|jd}|�|d��dS)z$Write your rollback migrations here.rrN)r�
remove_fieldsrs     r�rollbackr s*��
�\�(�
#�F����6�9�-�-�-�-�-r)F)�__doc__�peeweer	rr�rr�<module>rsS����*����A�A�A�A�.�.�.�.�.�.rdefence360agent/migrations/__pycache__/007_add_country_code_fields.cpython-311.opt-1.pyc0000644000000000000000000000555700000000000026030 0ustar  �

.
(	Q���H�dZddlZGd�dej��Zdd�Zdd�ZdS)	a�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)

�Nc�p�eZdZejddd���Zejd���ZGd�d��ZdS)	�Country�TF)�
max_length�primary_key�null�rc��eZdZdZdS)�Country.Meta�countryN)�__name__�
__module__�__qualname__�db_table���k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/007_add_country_code_fields.py�Metars���������rrN)r
rr�pw�	CharField�code�namerrrrrrsh�������2�<�1�$�U�C�C�C�D��2�<�U�#�#�#�D����������rrFc�.�|jd}|jd}|�t��|�|t	jtd������|�|t	jtd������dS)zWrite your migrations here.�iplist�incidentTr	)rN)�orm�create_modelr�
add_fieldsr�ForeignKeyField��migrator�database�fake�kwargs�IPList�Incidents      r�migrater'!s����\�(�
#�F��|�J�'�H����'�"�"�"������(:�7��(N�(N�(N��O�O�O�����"�,�W�4�@�@�@������rc���|jd}|jd}|�|d��|�|d��|�t��dS)z$Write your rollback migrations here.rrrN)r�
remove_fields�remove_modelrr s      r�rollbackr+/s`��
�\�(�
#�F��|�J�'�H����6�9�-�-�-����8�Y�/�/�/����'�"�"�"�"�"r)F)�__doc__�peeweer�Modelrr'r+rrr�<module>r/s{����*���������b�h��������#�#�#�#�#�#rdefence360agent/migrations/__pycache__/007_add_country_code_fields.cpython-311.pyc0000644000000000000000000000555700000000000025071 0ustar  �

.
(	Q���H�dZddlZGd�dej��Zdd�Zdd�ZdS)	a�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)

�Nc�p�eZdZejddd���Zejd���ZGd�d��ZdS)	�Country�TF)�
max_length�primary_key�null�rc��eZdZdZdS)�Country.Meta�countryN)�__name__�
__module__�__qualname__�db_table���k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/007_add_country_code_fields.py�Metars���������rrN)r
rr�pw�	CharField�code�namerrrrrrsh�������2�<�1�$�U�C�C�C�D��2�<�U�#�#�#�D����������rrFc�.�|jd}|jd}|�t��|�|t	jtd������|�|t	jtd������dS)zWrite your migrations here.�iplist�incidentTr	)rN)�orm�create_modelr�
add_fieldsr�ForeignKeyField��migrator�database�fake�kwargs�IPList�Incidents      r�migrater'!s����\�(�
#�F��|�J�'�H����'�"�"�"������(:�7��(N�(N�(N��O�O�O�����"�,�W�4�@�@�@������rc���|jd}|jd}|�|d��|�|d��|�t��dS)z$Write your rollback migrations here.rrrN)r�
remove_fields�remove_modelrr s      r�rollbackr+/s`��
�\�(�
#�F��|�J�'�H����6�9�-�-�-����8�Y�/�/�/����'�"�"�"�"�"r)F)�__doc__�peeweer�Modelrr'r+rrr�<module>r/s{����*���������b�h��������#�#�#�#�#�#rdefence360agent/migrations/__pycache__/008_fill_countries.cpython-311.opt-1.pyc0000644000000000000000000000105200000000000024201 0ustar  �

��KYk���dd�Zdd�ZdS)Fc��dS)zWrite your migrations here.N���migrator�database�fake�kwargss    �b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/008_fill_countries.py�migrater
����D�c��dS)z$Write your rollback migrations here.Nrrs    r	�rollbackrrrN)F)r
rrrr	�<module>rs7��	�	�	�	�
	�	�	�	�	�	rdefence360agent/migrations/__pycache__/008_fill_countries.cpython-311.pyc0000644000000000000000000000105200000000000023242 0ustar  �

��KYk���dd�Zdd�ZdS)Fc��dS)zWrite your migrations here.N���migrator�database�fake�kwargss    �b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/008_fill_countries.py�migrater
����D�c��dS)z$Write your rollback migrations here.Nrrs    r	�rollbackrrrN)F)r
rrrr	�<module>rs7��	�	�	�	�
	�	�	�	�	�	rdefence360agent/migrations/__pycache__/009_drop_blocklist_history.cpython-311.opt-1.pyc0000644000000000000000000000470500000000000025764 0ustar  �

�K�
����H�dZddlZGd�dej��Zdd�Zdd�ZdS)	a�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)

�Nc���eZdZejdd���Zejd���Zejd���Zej	d���Z
ejd���ZGd�d��ZdS)�BlocklistHistoryT)�primary_key�null)rc��eZdZdZdS)�BlocklistHistory.Meta�blocklist_historyN)�__name__�
__module__�__qualname__�db_table���j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/009_drop_blocklist_history.py�Metars������&���rrN)
r
rr�pw�IntegerField�id�	CharField�plugin�rule�
FloatField�	timestamp�iprrrrrrs�������	���T��	5�	5�	5�B�
�R�\�t�
$�
$�
$�F��2�<�T�"�"�"�D���
�4�(�(�(�I�	���4�	 �	 �	 �B�'�'�'�'�'�'�'�'�'�'rrFc�J�|jd}|�|��dS)zWrite your migrations here.r	N)�orm�remove_model)�migrator�database�fake�kwargsrs     r�migrater"#s+���|�$7�8�����*�+�+�+�+�+rc�:�|�t��dS)z$Write your rollback migrations here.N)�create_modelr)rrr r!s    r�rollbackr%)s�����*�+�+�+�+�+r)F)�__doc__�peeweer�Modelrr"r%rrr�<module>r)s{����(����'�'�'�'�'�r�x�'�'�'�,�,�,�,�,�,�,�,�,�,rdefence360agent/migrations/__pycache__/009_drop_blocklist_history.cpython-311.pyc0000644000000000000000000000470500000000000025025 0ustar  �

�K�
����H�dZddlZGd�dej��Zdd�Zdd�ZdS)	a�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)

�Nc���eZdZejdd���Zejd���Zejd���Zej	d���Z
ejd���ZGd�d��ZdS)�BlocklistHistoryT)�primary_key�null)rc��eZdZdZdS)�BlocklistHistory.Meta�blocklist_historyN)�__name__�
__module__�__qualname__�db_table���j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/009_drop_blocklist_history.py�Metars������&���rrN)
r
rr�pw�IntegerField�id�	CharField�plugin�rule�
FloatField�	timestamp�iprrrrrrs�������	���T��	5�	5�	5�B�
�R�\�t�
$�
$�
$�F��2�<�T�"�"�"�D���
�4�(�(�(�I�	���4�	 �	 �	 �B�'�'�'�'�'�'�'�'�'�'rrFc�J�|jd}|�|��dS)zWrite your migrations here.r	N)�orm�remove_model)�migrator�database�fake�kwargsrs     r�migrater"#s+���|�$7�8�����*�+�+�+�+�+rc�:�|�t��dS)z$Write your rollback migrations here.N)�create_modelr)rrr r!s    r�rollbackr%)s�����*�+�+�+�+�+r)F)�__doc__�peeweer�Modelrr"r%rrr�<module>r)s{����(����'�'�'�'�'�r�x�'�'�'�,�,�,�,�,�,�,�,�,�,rdefence360agent/migrations/__pycache__/010_drop_country_entities.cpython-311.opt-1.pyc0000644000000000000000000000355100000000000025612 0ustar  �

��m7� ���dZdd�Zdd�ZdS)a�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)

Fc�.�|jd}|jd}|jd}|�|d��|�|d��|�|d��|�|d��|�|��dS)zWrite your migrations here.�iplist�incident�countryN)�orm�
drop_index�
remove_fields�remove_model)�migrator�database�fake�kwargs�IPList�Incident�Countrys       �i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/010_drop_country_entities.py�migraters����\�(�
#�F��|�J�'�H��l�9�%�G�����	�*�*�*�����)�,�,�,����6�9�-�-�-����8�Y�/�/�/����'�"�"�"�"�"�c��dS)z$Write your rollback migrations here.N�)r
rrr
s    r�rollbackr&s���DrN)F)�__doc__rrrrr�<module>rsA����,#�#�#�#�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/010_drop_country_entities.cpython-311.pyc0000644000000000000000000000355100000000000024653 0ustar  �

��m7� ���dZdd�Zdd�ZdS)a�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)

Fc�.�|jd}|jd}|jd}|�|d��|�|d��|�|d��|�|d��|�|��dS)zWrite your migrations here.�iplist�incident�countryN)�orm�
drop_index�
remove_fields�remove_model)�migrator�database�fake�kwargs�IPList�Incident�Countrys       �i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/010_drop_country_entities.py�migraters����\�(�
#�F��|�J�'�H��l�9�%�G�����	�*�*�*�����)�,�,�,����6�9�-�-�-����8�Y�/�/�/����'�"�"�"�"�"�c��dS)z$Write your rollback migrations here.N�)r
rrr
s    r�rollbackr&s���DrN)F)�__doc__rrrrr�<module>rsA����,#�#�#�#�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/011_create_new_country_entities.cpython-311.opt-1.pyc0000644000000000000000000001216600000000000026765 0ustar  �

ɘg��g����dZddlmZddlZGd�dej��ZGd�dej��ZGd�d	ej��Zd
d�Zd
d�Z	dS)a�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)

�)�timeNc��eZdZejdd���Zejddd���Zejd���ZGd�d��Zd	S)
�CountryTF��primary_key�null�)�
max_length�uniquer�rc��eZdZdZdS)�Country.Meta�countryN��__name__�
__module__�__qualname__�db_table���o/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/011_create_new_country_entities.py�Metar s���������rrN)	rrr�pw�	CharField�id�code�namerrrrrrs~������	���$�U�	3�	3�	3�B��2�<�1�T��>�>�>�D��2�<�U�#�#�#�D����������rrc�p�eZdZejed���Zejdd���ZGd�d��Z	dS)�CountrySubnetsFr�)r
rc��eZdZdZdS)�CountrySubnets.Meta�country_subnetsNrrrrrr"*s������$���rrN)
rrrr�ForeignKeyFieldrrr�ip_netrrrrrr$sk������ �b� ��u�5�5�5�G��R�\�R�e�
4�
4�
4�F�%�%�%�%�%�%�%�%�%�%rrc���eZdZdZdZeefZejedd���Z	ej
dejd��g���Zej
dd��	��Zej
d�
��ZGd�d��Zd
S)�CountryList�WHITE�BLACKTFrzlistname in ('WHITE','BLACK'))r�constraintsc�8�tt����S)N)�intrrrr�<lambda>zCountryList.<lambda>:s��s�4�6�6�{�{�r)r�defaultrc��eZdZdZdS)�CountryList.Meta�country_listNrrrrrr0>s������!���rrN)rrrr(r)�IP_LISTSrr$rrr�Check�listname�IntegerField�ctime�commentrrrrr'r'.s��������E��E��u�~�H� �b� ��d��G�G�G�G��r�|�
����*I�!J�!J� K����H�
�B�O��/B�/B�C�C�C�E��b�l��%�%�%�G�"�"�"�"�"�"�"�"�"�"rr'Fc��|jd}|jd}|�t��|�|t	jtd������|�|t	jtd������|�t��|�t��dS)zWrite your migrations here.�iplist�incidentTr)rN)�orm�create_modelr�
add_fieldsrr$rr')�migrator�database�fake�kwargs�IPList�Incidents      r�migraterDBs����\�(�
#�F��|�J�'�H����'�"�"�"������(:�7��(N�(N�(N��O�O�O�����"�,�W�4�@�@�@�����
���.�)�)�)����+�&�&�&�&�&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	         r�rollbackrHTs����l�9�%�G��\�"3�4�N��,�~�.�K�
�\�(�
#�F��|�J�'�H����6�9�-�-�-����8�Y�/�/�/����.�)�)�)����+�&�&�&����'�"�"�"�"�"r)F)
�__doc__r�peeweer�Modelrrr'rDrHrrr�<module>rLs�����*���������������b�h����%�%�%�%�%�R�X�%�%�%�"�"�"�"�"�"�(�"�"�"�('�'�'�'�$
#�
#�
#�
#�
#�
#rdefence360agent/migrations/__pycache__/011_create_new_country_entities.cpython-311.pyc0000644000000000000000000001216600000000000026026 0ustar  �

ɘg��g����dZddlmZddlZGd�dej��ZGd�dej��ZGd�d	ej��Zd
d�Zd
d�Z	dS)a�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)

�)�timeNc��eZdZejdd���Zejddd���Zejd���ZGd�d��Zd	S)
�CountryTF��primary_key�null�)�
max_length�uniquer�rc��eZdZdZdS)�Country.Meta�countryN��__name__�
__module__�__qualname__�db_table���o/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/011_create_new_country_entities.py�Metar s���������rrN)	rrr�pw�	CharField�id�code�namerrrrrrs~������	���$�U�	3�	3�	3�B��2�<�1�T��>�>�>�D��2�<�U�#�#�#�D����������rrc�p�eZdZejed���Zejdd���ZGd�d��Z	dS)�CountrySubnetsFr�)r
rc��eZdZdZdS)�CountrySubnets.Meta�country_subnetsNrrrrrr"*s������$���rrN)
rrrr�ForeignKeyFieldrrr�ip_netrrrrrr$sk������ �b� ��u�5�5�5�G��R�\�R�e�
4�
4�
4�F�%�%�%�%�%�%�%�%�%�%rrc���eZdZdZdZeefZejedd���Z	ej
dejd��g���Zej
dd��	��Zej
d�
��ZGd�d��Zd
S)�CountryList�WHITE�BLACKTFrzlistname in ('WHITE','BLACK'))r�constraintsc�8�tt����S)N)�intrrrr�<lambda>zCountryList.<lambda>:s��s�4�6�6�{�{�r)r�defaultrc��eZdZdZdS)�CountryList.Meta�country_listNrrrrrr0>s������!���rrN)rrrr(r)�IP_LISTSrr$rrr�Check�listname�IntegerField�ctime�commentrrrrr'r'.s��������E��E��u�~�H� �b� ��d��G�G�G�G��r�|�
����*I�!J�!J� K����H�
�B�O��/B�/B�C�C�C�E��b�l��%�%�%�G�"�"�"�"�"�"�"�"�"�"rr'Fc��|jd}|jd}|�t��|�|t	jtd������|�|t	jtd������|�t��|�t��dS)zWrite your migrations here.�iplist�incidentTr)rN)�orm�create_modelr�
add_fieldsrr$rr')�migrator�database�fake�kwargs�IPList�Incidents      r�migraterDBs����\�(�
#�F��|�J�'�H����'�"�"�"������(:�7��(N�(N�(N��O�O�O�����"�,�W�4�@�@�@�����
���.�)�)�)����+�&�&�&�&�&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	         r�rollbackrHTs����l�9�%�G��\�"3�4�N��,�~�.�K�
�\�(�
#�F��|�J�'�H����6�9�-�-�-����8�Y�/�/�/����.�)�)�)����+�&�&�&����'�"�"�"�"�"r)F)
�__doc__r�peeweer�Modelrrr'rDrHrrr�<module>rLs�����*���������������b�h����%�%�%�%�%�R�X�%�%�%�"�"�"�"�"�"�(�"�"�"�('�'�'�'�$
#�
#�
#�
#�
#�
#rdefence360agent/migrations/__pycache__/012_fill_countries_and_subnets.cpython-311.opt-1.pyc0000644000000000000000000000117600000000000026570 0ustar  �

�`I�%� ����dZdd�Zdd�ZdS)zpPeewee migrations: ::

UPD: migration not needed anymore, countries and subnets are loaded after
files update.

Fc��dS�N���migrator�database�fake�kwargss    �n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/012_fill_countries_and_subnets.py�migrater	����D�c��dSrrrs    r
�rollbackr
rr
N)F)�__doc__rrrr
r
�<module>rsA����	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/012_fill_countries_and_subnets.cpython-311.pyc0000644000000000000000000000117600000000000025631 0ustar  �

�`I�%� ����dZdd�Zdd�ZdS)zpPeewee migrations: ::

UPD: migration not needed anymore, countries and subnets are loaded after
files update.

Fc��dS�N���migrator�database�fake�kwargss    �n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/012_fill_countries_and_subnets.py�migrater	����D�c��dSrrrs    r
�rollbackr
rr
N)F)�__doc__rrrr
r
�<module>rsA����	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/013_add_indexes_to_iplist.cpython-311.opt-1.pyc0000644000000000000000000000311400000000000025512 0ustar  �

�д�eKJe���dZdd�Zdd�ZdS)a�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)

Fc�L�|jd}|�|d��dS)zWrite your migrations here.�iplist�listnameN)�orm�	add_index��migrator�database�fake�kwargs�IPLists     �i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/013_add_indexes_to_iplist.py�migraters*��
�\�(�
#�F����v�z�*�*�*�*�*�c�L�|jd}|�|d��dS)z$Write your rollback migrations here.rrN)r�
drop_indexrs     r
�rollbackrs*��
�\�(�
#�F�����
�+�+�+�+�+rN)F)�__doc__rr�rr
�<module>rsA����,+�+�+�+�,�,�,�,�,�,rdefence360agent/migrations/__pycache__/013_add_indexes_to_iplist.cpython-311.pyc0000644000000000000000000000311400000000000024553 0ustar  �

�д�eKJe���dZdd�Zdd�ZdS)a�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)

Fc�L�|jd}|�|d��dS)zWrite your migrations here.�iplist�listnameN)�orm�	add_index��migrator�database�fake�kwargs�IPLists     �i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/013_add_indexes_to_iplist.py�migraters*��
�\�(�
#�F����v�z�*�*�*�*�*�c�L�|jd}|�|d��dS)z$Write your rollback migrations here.rrN)r�
drop_indexrs     r
�rollbackrs*��
�\�(�
#�F�����
�+�+�+�+�+rN)F)�__doc__rr�rr
�<module>rsA����,+�+�+�+�,�,�,�,�,�,rdefence360agent/migrations/__pycache__/014_add_malware_hits.cpython-311.opt-1.pyc0000644000000000000000000001077700000000000024462 0ustar  �

٨$$ՏH���dZddlZGd�dej��ZGd�dej��ZGd�dej��Zdd
�Zdd�ZdS)
a�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)

�Nc��eZdZGd�d��Zejd���Zejd���Zejd���Z	ejdej
d��g���Zejd���Zejdd	�
��Z
dS)�MalwareScanc��eZdZdZdS)�MalwareScan.Meta�
malware_scansN��__name__�
__module__�__qualname__�db_table���d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/014_add_malware_hits.py�Metars������"���rrT��primary_keyF��nullz!type in ('on-demand', 'realtime'))r�constraintsr�r�defaultN)r	r
rr�pw�	CharField�scanid�IntegerField�started�	completed�Check�type�path�total_filesr
rrrrs�������#�#�#�#�#�#�#�#��R�\�d�
+�
+�
+�F��b�o�5�)�)�)�G����U�+�+�+�I��2�<�
����*M�!N�!N� O����D��2�<�U�#�#�#�D�!�"�/�u�a�8�8�8�K�K�Krrc��eZdZGd�d��Zejd���Zejed���Z	ej
d���Zej
d���Zej
d���Z
ejdd���ZdS)	�
MalwareHitc��eZdZdZdS)�MalwareHit.Meta�malware_hitsNrr
rrrr%(s������!���rrTrFrrN)r	r
rrrr�id�ForeignKeyFieldrrr�user�	orig_filer�BooleanField�restoredr
rrr#r#'s�������"�"�"�"�"�"�"�"�
���T�	*�	*�	*�B�
�R�
��%�
8�
8�
8�F��2�<�U�#�#�#�D����%�(�(�(�I��2�<�U�#�#�#�D��r��E�5�9�9�9�H�H�Hrr#c�J�eZdZGd�d��Zejd���ZdS)�MalwareIgnorePathc��eZdZdZdS)�MalwareIgnorePath.Meta�malware_ignore_pathNrr
rrrr05s������(���rrTrN)r	r
rrrrr r
rrr.r.4sL������)�)�)�)�)�)�)�)��2�<�D�)�)�)�D�D�Drr.Fc��|�t��|�t��|�t��dS)zWrite your migrations here.N)�create_modelrr#r.)�migrator�database�fake�kwargss    r�migrater8;sE�����+�&�&�&����*�%�%�%����+�,�,�,�,�,rc��|jd}|jd}|jd}|jd}|�|��|�|��|�|��|�|��dS)z$Write your rollback migrations here.rr&r1�malware_stanned_statN)�orm�
drop_model)r4r5r6r7rr#r.�MalwareScannedStats        r�rollbackr>Bs����,��/�K���n�-�J� ��%:�;��!��&<�=�����
�#�#�#�����$�$�$����)�*�*�*����*�+�+�+�+�+r)F)	�__doc__�peeweer�Modelrr#r.r8r>r
rr�<module>rBs�����*����9�9�9�9�9�"�(�9�9�9�
:�
:�
:�
:�
:���
:�
:�
:�*�*�*�*�*���*�*�*�-�-�-�-�
,�
,�
,�
,�
,�
,rdefence360agent/migrations/__pycache__/014_add_malware_hits.cpython-311.pyc0000644000000000000000000001077700000000000023523 0ustar  �

٨$$ՏH���dZddlZGd�dej��ZGd�dej��ZGd�dej��Zdd
�Zdd�ZdS)
a�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)

�Nc��eZdZGd�d��Zejd���Zejd���Zejd���Z	ejdej
d��g���Zejd���Zejdd	�
��Z
dS)�MalwareScanc��eZdZdZdS)�MalwareScan.Meta�
malware_scansN��__name__�
__module__�__qualname__�db_table���d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/014_add_malware_hits.py�Metars������"���rrT��primary_keyF��nullz!type in ('on-demand', 'realtime'))r�constraintsr�r�defaultN)r	r
rr�pw�	CharField�scanid�IntegerField�started�	completed�Check�type�path�total_filesr
rrrrs�������#�#�#�#�#�#�#�#��R�\�d�
+�
+�
+�F��b�o�5�)�)�)�G����U�+�+�+�I��2�<�
����*M�!N�!N� O����D��2�<�U�#�#�#�D�!�"�/�u�a�8�8�8�K�K�Krrc��eZdZGd�d��Zejd���Zejed���Z	ej
d���Zej
d���Zej
d���Z
ejdd���ZdS)	�
MalwareHitc��eZdZdZdS)�MalwareHit.Meta�malware_hitsNrr
rrrr%(s������!���rrTrFrrN)r	r
rrrr�id�ForeignKeyFieldrrr�user�	orig_filer�BooleanField�restoredr
rrr#r#'s�������"�"�"�"�"�"�"�"�
���T�	*�	*�	*�B�
�R�
��%�
8�
8�
8�F��2�<�U�#�#�#�D����%�(�(�(�I��2�<�U�#�#�#�D��r��E�5�9�9�9�H�H�Hrr#c�J�eZdZGd�d��Zejd���ZdS)�MalwareIgnorePathc��eZdZdZdS)�MalwareIgnorePath.Meta�malware_ignore_pathNrr
rrrr05s������(���rrTrN)r	r
rrrrr r
rrr.r.4sL������)�)�)�)�)�)�)�)��2�<�D�)�)�)�D�D�Drr.Fc��|�t��|�t��|�t��dS)zWrite your migrations here.N)�create_modelrr#r.)�migrator�database�fake�kwargss    r�migrater8;sE�����+�&�&�&����*�%�%�%����+�,�,�,�,�,rc��|jd}|jd}|jd}|jd}|�|��|�|��|�|��|�|��dS)z$Write your rollback migrations here.rr&r1�malware_stanned_statN)�orm�
drop_model)r4r5r6r7rr#r.�MalwareScannedStats        r�rollbackr>Bs����,��/�K���n�-�J� ��%:�;��!��&<�=�����
�#�#�#�����$�$�$����)�*�*�*����*�+�+�+�+�+r)F)	�__doc__�peeweer�Modelrr#r.r8r>r
rr�<module>rBs�����*����9�9�9�9�9�"�(�9�9�9�
:�
:�
:�
:�
:���
:�
:�
:�*�*�*�*�*���*�*�*�-�-�-�-�
,�
,�
,�
,�
,�
,rdefence360agent/migrations/__pycache__/015_add_iplist_expiration_index.cpython-311.opt-1.pyc0000644000000000000000000000152300000000000026726 0ustar  �

�-��q�V����dd�Zdd�ZdS)Fc�L�|jd}|�|d��dS)zWrite your migrations here.�iplist�
expirationN)�orm�	add_index��migrator�database�fake�kwargs�IPLists     �o/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/015_add_iplist_expiration_index.py�migraters*��
�\�(�
#�F����v�|�,�,�,�,�,�c�L�|jd}|�|d��dS)z$Write your rollback migrations here.rrN)r�
drop_indexrs     r
�rollbackrs*��
�\�(�
#�F������-�-�-�-�-rN)F)rr�rr
�<module>rs7��-�-�-�-�.�.�.�.�.�.rdefence360agent/migrations/__pycache__/015_add_iplist_expiration_index.cpython-311.pyc0000644000000000000000000000152300000000000025767 0ustar  �

�-��q�V����dd�Zdd�ZdS)Fc�L�|jd}|�|d��dS)zWrite your migrations here.�iplist�
expirationN)�orm�	add_index��migrator�database�fake�kwargs�IPLists     �o/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/015_add_iplist_expiration_index.py�migraters*��
�\�(�
#�F����v�|�,�,�,�,�,�c�L�|jd}|�|d��dS)z$Write your rollback migrations here.rrN)r�
drop_indexrs     r
�rollbackrs*��
�\�(�
#�F������-�-�-�-�-rN)F)rr�rr
�<module>rs7��-�-�-�-�.�.�.�.�.�.rdefence360agent/migrations/__pycache__/016_fix_autowhitelist_expiration.cpython-311.opt-1.pyc0000644000000000000000000000200300000000000027171 0ustar  �

��SM��
��&�ddlmZdZdd�Zdd�ZdS)�)�timei7�AFc���|jd}|�t����|jdk|jt
��z
tkz�����dS)N�iplist)�
expiration�WHITE)�orm�update�_MAX_TIMEOUT�where�listnamerr�execute)�migrator�database�fake�kwargs�IPListModels     �p/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/016_fix_autowhitelist_expiration.py�migratersg���,�x�(�K����,��/�/�5�5�	�	��	(��!�D�F�F�*�\�9�	;����g�i�i�i�i�i�c��dS)N�)rrrrs    r�rollbackrs���DrN)F)rr
rrrrr�<module>rsN��������������	�	�	�	�	�	rdefence360agent/migrations/__pycache__/016_fix_autowhitelist_expiration.cpython-311.pyc0000644000000000000000000000200300000000000026232 0ustar  �

��SM��
��&�ddlmZdZdd�Zdd�ZdS)�)�timei7�AFc���|jd}|�t����|jdk|jt
��z
tkz�����dS)N�iplist)�
expiration�WHITE)�orm�update�_MAX_TIMEOUT�where�listnamerr�execute)�migrator�database�fake�kwargs�IPListModels     �p/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/016_fix_autowhitelist_expiration.py�migratersg���,�x�(�K����,��/�/�5�5�	�	��	(��!�D�F�F�*�\�9�	;����g�i�i�i�i�i�c��dS)N�)rrrrs    r�rollbackrs���DrN)F)rr
rrrrr�<module>rsN��������������	�	�	�	�	�	rdefence360agent/migrations/__pycache__/017_remove_sensor_prefix.cpython-311.opt-1.pyc0000644000000000000000000000134100000000000025424 0ustar  �

�j��$
�D���dZdd�Zdd�ZdS)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��dS�N���migrator�database�fake�kwargss    �h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/017_remove_sensor_prefix.py�migrater
����D�c��dSrrrs    r
�rollbackrrr
N)F)�__doc__rrrr
r
�<module>rsA����	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/017_remove_sensor_prefix.cpython-311.pyc0000644000000000000000000000134100000000000024465 0ustar  �

�j��$
�D���dZdd�Zdd�ZdS)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��dS�N���migrator�database�fake�kwargss    �h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/017_remove_sensor_prefix.py�migrater
����D�c��dSrrrs    r
�rollbackrrr
N)F)�__doc__rrrr
r
�<module>rsA����	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/018_license_info.cpython-311.opt-1.pyc0000644000000000000000000000316700000000000023627 0ustar  �

�C��L��D�ddlZGd�dej��Zdd�Zdd�ZdS)�Nc��eZdZGd�d��Zejd���Zejdd���Zejd���Z	ej
d���Zd	S)
�Licensec��eZdZdZdS)�License.Meta�licenseN)�__name__�
__module__�__qualname__�db_table���`/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/018_license_info.py�Metars���������r
rT)�primary_keyFr)�null�default)rN)rr	r
r�pw�BooleanField�status�IntegerField�
expiration�limit�	CharField�redirect_urlrr
rrrs����������������R�_��
.�
.�
.�F� ���e�Q�7�7�7�J��B�O��&�&�&�E��2�<�T�*�*�*�L�L�Lr
rFc�:�|�t��dS)zWrite your migrations here.N)�create_modelr)�migrator�database�fake�kwargss    r�migrater!s�����'�"�"�"�"�"r
c�J�|jd}|�|��dS)z$Write your rollback migrations here.rN)�orm�
drop_model)rrrr rs     r�rollbackr%s(���l�9�%�G����� � � � � r
)F)�peeweer�Modelrr!r%rr
r�<module>r(so������+�+�+�+�+�b�h�+�+�+�#�#�#�#�
!�!�!�!�!�!r
defence360agent/migrations/__pycache__/018_license_info.cpython-311.pyc0000644000000000000000000000316700000000000022670 0ustar  �

�C��L��D�ddlZGd�dej��Zdd�Zdd�ZdS)�Nc��eZdZGd�d��Zejd���Zejdd���Zejd���Z	ej
d���Zd	S)
�Licensec��eZdZdZdS)�License.Meta�licenseN)�__name__�
__module__�__qualname__�db_table���`/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/018_license_info.py�Metars���������r
rT)�primary_keyFr)�null�default)rN)rr	r
r�pw�BooleanField�status�IntegerField�
expiration�limit�	CharField�redirect_urlrr
rrrs����������������R�_��
.�
.�
.�F� ���e�Q�7�7�7�J��B�O��&�&�&�E��2�<�T�*�*�*�L�L�Lr
rFc�:�|�t��dS)zWrite your migrations here.N)�create_modelr)�migrator�database�fake�kwargss    r�migrater!s�����'�"�"�"�"�"r
c�J�|jd}|�|��dS)z$Write your rollback migrations here.rN)�orm�
drop_model)rrrr rs     r�rollbackr%s(���l�9�%�G����� � � � � r
)F)�peeweer�Modelrr!r%rr
r�<module>r(so������+�+�+�+�+�b�h�+�+�+�#�#�#�#�
!�!�!�!�!�!r
defence360agent/migrations/__pycache__/019_purge_old_configs.cpython-311.opt-1.pyc0000644000000000000000000000124300000000000024654 0ustar  �

W�i��B����dZdd�Zdd�ZdS)z^
Purge old configs from config file to prevent of "Unknown field" errors.
UPD: Not actual yet
Fc��dS)zWrite your migrations here.N���migrator�database�fake�kwargss    �e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/019_purge_old_configs.py�migrater
�����c��dS)z$Write your rollback migrations here.Nrrs    r	�rollbackrrrN)F)�__doc__r
rrrr	�<module>rsA����&�&�&�&�/�/�/�/�/�/rdefence360agent/migrations/__pycache__/019_purge_old_configs.cpython-311.pyc0000644000000000000000000000124300000000000023715 0ustar  �

W�i��B����dZdd�Zdd�ZdS)z^
Purge old configs from config file to prevent of "Unknown field" errors.
UPD: Not actual yet
Fc��dS)zWrite your migrations here.N���migrator�database�fake�kwargss    �e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/019_purge_old_configs.py�migrater
�����c��dS)z$Write your rollback migrations here.Nrrs    r	�rollbackrrrN)F)�__doc__r
rrrr	�<module>rsA����&�&�&�&�/�/�/�/�/�/rdefence360agent/migrations/__pycache__/020_malware_scan_types.cpython-311.opt-1.pyc0000644000000000000000000000247200000000000025041 0ustar  �

 9��Q o2���dZdd�Zdd�ZdS)a�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)

Fc��dS)zWrite your migrations here.N���migrator�database�fake�kwargss    �f/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/020_malware_scan_types.py�migrater
s	��
	�D�c��dS)z$Write your rollback migrations here.Nrrs    r	�rollbackr
s	��	�DrN)F)�__doc__r
r
rrr	�<module>rsA����,	�	�	�	�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/020_malware_scan_types.cpython-311.pyc0000644000000000000000000000247200000000000024102 0ustar  �

 9��Q o2���dZdd�Zdd�ZdS)a�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)

Fc��dS)zWrite your migrations here.N���migrator�database�fake�kwargss    �f/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/020_malware_scan_types.py�migrater
s	��
	�D�c��dS)z$Write your rollback migrations here.Nrrs    r	�rollbackr
s	��	�DrN)F)�__doc__r
r
rrr	�<module>rsA����,	�	�	�	�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/021_add_testing_repo.cpython-311.opt-1.pyc0000644000000000000000000000575700000000000024505 0ustar  �

\���&����ddlZddlZddlmZddlmZmZeje��Z	ed��Z
dZd�e��Z
dZd�Zd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\t���sAt�t�|t
t�����dSdSt�d�|����dS)N)��)�version�	CHECKSITE�RPM_KEYzVersion {} is not supported)	�TEST_REPO_PATH�exists�
write_text�
TEMPLATE_REPO�formatr
r�logger�info)r	s �d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/021_add_testing_repo.py�install_repors����&����$�$�&�&�	��%�%��$�$�#�y�'�%���
�
�
�
�
�	�	�	���1�8�8��A�A�B�B�B�B�B�Fc�V�|rdS	tj��tjzrPd}t��}|�d��rd}n|�d��rd}t|��dSdS#t$r&}t�d|��Yd}~dSd}~wwxYw)N�6r�7rz)Unable to add imunify360-testing repo: %s)	r�id_like�RHEL_FEDORA_CENTOSr�
startswithr�	Exceptionr�warning)�migrator�database�fake�kwargsr	�full_version�es       r�migrater$'s�������G�� �"�"�]�%E�E�	"��G�%�<�<�L��&�&�s�+�+�
�����(�(��-�-�
�����!�!�!�!�!�	"�	"���G�G�G����B�A�F�F�F�F�F�F�F�F�F�����G���s�A.A8�8
B(�B#�#B(c��|rdS	tjt��dS#t$r2}t�t
|����Yd}~dSd}~wwxYw)N)�os�removerrrr�str)rrr r!r#s     r�rollbackr)9ss�������
�	�.�!�!�!�!�!���������s�1�v�v�����������������s�!�
A�'A�A)F)�loggingr&�pathlibr�defence360agent.utilsrr�	getLogger�__name__rrr
rrrrr$r)�rr�<module>r0s�������	�	�	�	�������;�;�;�;�;�;�;�;�	��	�8�	$�	$����@�A�A��?�	�
%�
,�
,�Y�
7�
7��	�
�	C�	C�	C�G�G�G�G�$�����rdefence360agent/migrations/__pycache__/021_add_testing_repo.cpython-311.pyc0000644000000000000000000000575700000000000023546 0ustar  �

\���&����ddlZddlZddlmZddlmZmZeje��Z	ed��Z
dZd�e��Z
dZd�Zd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\t���sAt�t�|t
t�����dSdSt�d�|����dS)N)��)�version�	CHECKSITE�RPM_KEYzVersion {} is not supported)	�TEST_REPO_PATH�exists�
write_text�
TEMPLATE_REPO�formatr
r�logger�info)r	s �d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/021_add_testing_repo.py�install_repors����&����$�$�&�&�	��%�%��$�$�#�y�'�%���
�
�
�
�
�	�	�	���1�8�8��A�A�B�B�B�B�B�Fc�V�|rdS	tj��tjzrPd}t��}|�d��rd}n|�d��rd}t|��dSdS#t$r&}t�d|��Yd}~dSd}~wwxYw)N�6r�7rz)Unable to add imunify360-testing repo: %s)	r�id_like�RHEL_FEDORA_CENTOSr�
startswithr�	Exceptionr�warning)�migrator�database�fake�kwargsr	�full_version�es       r�migrater$'s�������G�� �"�"�]�%E�E�	"��G�%�<�<�L��&�&�s�+�+�
�����(�(��-�-�
�����!�!�!�!�!�	"�	"���G�G�G����B�A�F�F�F�F�F�F�F�F�F�����G���s�A.A8�8
B(�B#�#B(c��|rdS	tjt��dS#t$r2}t�t
|����Yd}~dSd}~wwxYw)N)�os�removerrrr�str)rrr r!r#s     r�rollbackr)9ss�������
�	�.�!�!�!�!�!���������s�1�v�v�����������������s�!�
A�'A�A)F)�loggingr&�pathlibr�defence360agent.utilsrr�	getLogger�__name__rrr
rrrrr$r)�rr�<module>r0s�������	�	�	�	�������;�;�;�;�;�;�;�;�	��	�8�	$�	$����@�A�A��?�	�
%�
,�
,�Y�
7�
7��	�
�	C�	C�	C�G�G�G�G�$�����rdefence360agent/migrations/__pycache__/022_mod_security_vendors_migrations.cpython-311.opt-1.pyc0000644000000000000000000000105000000000000027654 0ustar  �

����f^����dZdd�Zdd�ZdS)z
No need to user now
Fc��dS�N���migrator�database�fake�kwargss    �s/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/022_mod_security_vendors_migrations.py�migrater����D�c��dSrrrs    r
�rollbackr
rr
N)F)�__doc__rrrr
r
�<module>rsA����
	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/022_mod_security_vendors_migrations.cpython-311.pyc0000644000000000000000000000105000000000000026715 0ustar  �

����f^����dZdd�Zdd�ZdS)z
No need to user now
Fc��dS�N���migrator�database�fake�kwargss    �s/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/022_mod_security_vendors_migrations.py�migrater����D�c��dSrrrs    r
�rollbackr
rr
N)F)�__doc__rrrr
r
�<module>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  �

��A�`��L�dZddlZddlZddlmZmZdZdZddd�Zdd
�Z	dd�Z
dS)
z�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.
�N)�IConfigFile�LocalConfig�MOD_SEC_BLOCK_BY_CUSTOM_RULE�33332�
�x)�max_incident_repetition�check_periodFc��|rdSt��}tj�|j��sdStj�|j��sdStj|j|jdz��|���}t|�	ti��t<|�|d���dS)N�.oldF)�validate)
r�os�path�exists�isfile�shutil�copyfile�config_to_dict�RULE_VALUES�
setdefault�SECTION�RULE_ID�dict_to_config)�migrator�database�fake�kwargs�local_config�new_confs      �z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/023_add_default_rule_in_modsec_custom_conf.py�migrater!s������� +�
�
�L�
�7�>�>�,�+�,�,����
�7�>�>�,�+�,�,����
�O�L�%�|�'8�6�'A�B�B�B��*�*�,�,�H�0;�H�����$�$�W�-�����5��9�9�9�9�9�c��|rdSt��}|jdz}tj�|��rt	j||j��dSdS)Nr)rrrrr�move)rrrrr�olds      r �rollbackr&s`������ +�
�
�L�
�
�f�
$�C�	�w�~�~�c���,���C��*�+�+�+�+�+�,�,r")F)�__doc__rr� defence360agent.contracts.configrrrrrr!r&�r"r �<module>r*s�����
�	�	�	�
�
�
�
�E�E�E�E�E�E�E�E�
(��
��*,�c�B�B��:�:�:�:�,�,�,�,�,�,r"defence360agent/migrations/__pycache__/023_add_default_rule_in_modsec_custom_conf.cpython-311.pyc0000644000000000000000000000411300000000000030121 0ustar  �

��A�`��L�dZddlZddlZddlmZmZdZdZddd�Zdd
�Z	dd�Z
dS)
z�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.
�N)�IConfigFile�LocalConfig�MOD_SEC_BLOCK_BY_CUSTOM_RULE�33332�
�x)�max_incident_repetition�check_periodFc��|rdSt��}tj�|j��sdStj�|j��sdStj|j|jdz��|���}t|�	ti��t<|�|d���dS)N�.oldF)�validate)
r�os�path�exists�isfile�shutil�copyfile�config_to_dict�RULE_VALUES�
setdefault�SECTION�RULE_ID�dict_to_config)�migrator�database�fake�kwargs�local_config�new_confs      �z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/023_add_default_rule_in_modsec_custom_conf.py�migrater!s������� +�
�
�L�
�7�>�>�,�+�,�,����
�7�>�>�,�+�,�,����
�O�L�%�|�'8�6�'A�B�B�B��*�*�,�,�H�0;�H�����$�$�W�-�����5��9�9�9�9�9�c��|rdSt��}|jdz}tj�|��rt	j||j��dSdS)Nr)rrrrr�move)rrrrr�olds      r �rollbackr&s`������ +�
�
�L�
�
�f�
$�C�	�w�~�~�c���,���C��*�+�+�+�+�+�,�,r")F)�__doc__rr� defence360agent.contracts.configrrrrrr!r&�r"r �<module>r*s�����
�	�	�	�
�
�
�
�E�E�E�E�E�E�E�E�
(��
��*,�c�B�B��:�:�:�:�,�,�,�,�,�,r"defence360agent/migrations/__pycache__/024_ignore_from_graylist.cpython-311.opt-1.pyc0000644000000000000000000000262300000000000025407 0ustar  �

�l�q]���D�ddlZGd�dej��Zdd�Zdd�ZdS)�Nc�L�eZdZejdd���ZGd�d��ZdS)�
IgnoreListTF)�primary_key�nullc��eZdZdZdS)�IgnoreList.Meta�ignore_listN)�__name__�
__module__�__qualname__�db_table���h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/024_ignore_from_graylist.py�Metars������ ���rrN)r
rr�pw�	CharField�iprrrrrrsR������	���$�U�	3�	3�	3�B�!�!�!�!�!�!�!�!�!�!rrFc�:�|�t��dS)zWrite your migrations here.N)�create_modelr)�migrator�database�fake�kwargss    r�migraters�����*�%�%�%�%�%rc�J�|jd}|�|��dS)z$Write your rollback migrations here.r	N)�orm�
drop_model)rrrrrs     r�rollbackrs(����m�,�J����
�#�#�#�#�#r)F)�peeweer�Modelrrrrrr�<module>r"so������!�!�!�!�!���!�!�!�&�&�&�&�
$�$�$�$�$�$rdefence360agent/migrations/__pycache__/024_ignore_from_graylist.cpython-311.pyc0000644000000000000000000000262300000000000024450 0ustar  �

�l�q]���D�ddlZGd�dej��Zdd�Zdd�ZdS)�Nc�L�eZdZejdd���ZGd�d��ZdS)�
IgnoreListTF)�primary_key�nullc��eZdZdZdS)�IgnoreList.Meta�ignore_listN)�__name__�
__module__�__qualname__�db_table���h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/024_ignore_from_graylist.py�Metars������ ���rrN)r
rr�pw�	CharField�iprrrrrrsR������	���$�U�	3�	3�	3�B�!�!�!�!�!�!�!�!�!�!rrFc�:�|�t��dS)zWrite your migrations here.N)�create_modelr)�migrator�database�fake�kwargss    r�migraters�����*�%�%�%�%�%rc�J�|jd}|�|��dS)z$Write your rollback migrations here.r	N)�orm�
drop_model)rrrrrs     r�rollbackrs(����m�,�J����
�#�#�#�#�#r)F)�peeweer�Modelrrrrrr�<module>r"so������!�!�!�!�!���!�!�!�&�&�&�&�
$�$�$�$�$�$rdefence360agent/migrations/__pycache__/025_malware_config_realtime.cpython-311.opt-1.pyc0000644000000000000000000000302600000000000026021 0ustar  �

~Q�\-y���2�ddlZddlZddlmZdd�Zdd�ZdS)�N)�LocalConfigFc��|rdSt��}tj�|j��sdSt	|j��5}tj|��}ddd��n#1swxYwY|�di��}|�dd��}||d<||d<|�	|d���dS)N�MALWARE_SCANNING�enable_scan_uploaded_filesT�enable_scan_pure_ftpd�enable_scan_modsecF)�validate)
r�os�path�exists�open�yaml�	safe_load�
setdefault�pop�dict_to_config)	�migrator�database�fake�kwargs�local_config�f�conf�malware_settings�values	         �k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/025_malware_config_realtime.py�migraters�������=�=�L�
�7�>�>�,�+�,�,����	
�l��	 �	 �!�A��~�a� � ��!�!�!�!�!�!�!�!�!�!�!����!�!�!�!����'9�2�>�>��� � �!=�t�D�D�E�05��,�-�-2��)�*�����u��5�5�5�5�5s�
A.�.A2�5A2c��dS)N�)rrrrs    r�rollbackr s���D�)F)r
r� defence360agent.contracts.configrrr rr!r�<module>r#s[��	�	�	�	�����8�8�8�8�8�8�6�6�6�6�(	�	�	�	�	�	r!defence360agent/migrations/__pycache__/025_malware_config_realtime.cpython-311.pyc0000644000000000000000000000302600000000000025062 0ustar  �

~Q�\-y���2�ddlZddlZddlmZdd�Zdd�ZdS)�N)�LocalConfigFc��|rdSt��}tj�|j��sdSt	|j��5}tj|��}ddd��n#1swxYwY|�di��}|�dd��}||d<||d<|�	|d���dS)N�MALWARE_SCANNING�enable_scan_uploaded_filesT�enable_scan_pure_ftpd�enable_scan_modsecF)�validate)
r�os�path�exists�open�yaml�	safe_load�
setdefault�pop�dict_to_config)	�migrator�database�fake�kwargs�local_config�f�conf�malware_settings�values	         �k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/025_malware_config_realtime.py�migraters�������=�=�L�
�7�>�>�,�+�,�,����	
�l��	 �	 �!�A��~�a� � ��!�!�!�!�!�!�!�!�!�!�!����!�!�!�!����'9�2�>�>��� � �!=�t�D�D�E�05��,�-�-2��)�*�����u��5�5�5�5�5s�
A.�.A2�5A2c��dS)N�)rrrrs    r�rollbackr s���D�)F)r
r� defence360agent.contracts.configrrr rr!r�<module>r#s[��	�	�	�	�����8�8�8�8�8�8�6�6�6�6�(	�	�	�	�	�	r!defence360agent/migrations/__pycache__/026_remove_old_temporary_file.cpython-311.opt-1.pyc0000644000000000000000000000246600000000000026426 0ustar  �

'ѝ�n�l-��.�ddlZddlZddlZdd�Zdd�ZdS)�NFc��|rdStj��}tj�|d��}tj�|��rtj|��tj�|d��}tj|��D]5}tj�|��rtj|���6dS)Nzpredict_model_description.jsonzimunify360*)�tempfile�
gettempdir�os�path�join�isfile�remove�glob)�migrator�database�fake�kwargs�tmp_dirr�pattern�filenames        �m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/026_remove_old_temporary_file.py�migraters��������!�#�#�G��7�<�<��!A�B�B�D�	�w�~�~�d����
�	�$�����g�l�l�7�M�2�2�G��I�g�&�&� � ��
�7�>�>�(�#�#�	 ��I�h����� � �c��dS)N�)rr
rrs    r�rollbackrs���Dr)F)rrrrrrrr�<module>rsR������	�	�	�	����� � � � �$	�	�	�	�	�	rdefence360agent/migrations/__pycache__/026_remove_old_temporary_file.cpython-311.pyc0000644000000000000000000000246600000000000025467 0ustar  �

'ѝ�n�l-��.�ddlZddlZddlZdd�Zdd�ZdS)�NFc��|rdStj��}tj�|d��}tj�|��rtj|��tj�|d��}tj|��D]5}tj�|��rtj|���6dS)Nzpredict_model_description.jsonzimunify360*)�tempfile�
gettempdir�os�path�join�isfile�remove�glob)�migrator�database�fake�kwargs�tmp_dirr�pattern�filenames        �m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/026_remove_old_temporary_file.py�migraters��������!�#�#�G��7�<�<��!A�B�B�D�	�w�~�~�d����
�	�$�����g�l�l�7�M�2�2�G��I�g�&�&� � ��
�7�>�>�(�#�#�	 ��I�h����� � �c��dS)N�)rr
rrs    r�rollbackrs���Dr)F)rrrrrrrr�<module>rsR������	�	�	�	����� � � � �$	�	�	�	�	�	rdefence360agent/migrations/__pycache__/027_disable_comdo_fp_rules.cpython-311.opt-1.pyc0000644000000000000000000000114600000000000025650 0ustar  �

��t�
E�l���dZdd�Zdd�ZdS)z\
Current migration doesn't needed,
because apache will be restarted in the other migrations
Fc��dS�N���migrator�database�fake�kwargss    �j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/027_disable_comdo_fp_rules.py�migrater����D�c��dSrrrs    r
�rollbackrrr
N)F)�__doc__rrrr
r
�<module>rsA����	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/027_disable_comdo_fp_rules.cpython-311.pyc0000644000000000000000000000114600000000000024711 0ustar  �

��t�
E�l���dZdd�Zdd�ZdS)z\
Current migration doesn't needed,
because apache will be restarted in the other migrations
Fc��dS�N���migrator�database�fake�kwargss    �j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/027_disable_comdo_fp_rules.py�migrater����D�c��dSrrrs    r
�rollbackrrr
N)F)�__doc__rrrr
r
�<module>rsA����	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/028_set_permanent_ttl_for_blacklist.cpython-311.opt-1.pyc0000644000000000000000000000165300000000000027616 0ustar  �

��X�;�����dZdd�Zdd�ZdS)�Fc���|jd}|�t����|jdk|jtkz�����dS)N�iplist)�
expiration�BLACK)�orm�update�
PERMANENT_TTL�where�listnamer�execute)�migrator�database�fake�kwargs�IPListModels     �s/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/028_set_permanent_ttl_for_blacklist.py�migraters^���,�x�(�K����-��0�0�6�6�	�	��	(��!�]�2�	4����g�i�i�i�i�i�c��dS)N�)r
rrrs    r�rollbackr
s���DrN)F)r	rrrrr�<module>rs<���
�����	�	�	�	�	�	rdefence360agent/migrations/__pycache__/028_set_permanent_ttl_for_blacklist.cpython-311.pyc0000644000000000000000000000165300000000000026657 0ustar  �

��X�;�����dZdd�Zdd�ZdS)�Fc���|jd}|�t����|jdk|jtkz�����dS)N�iplist)�
expiration�BLACK)�orm�update�
PERMANENT_TTL�where�listnamer�execute)�migrator�database�fake�kwargs�IPListModels     �s/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/028_set_permanent_ttl_for_blacklist.py�migraters^���,�x�(�K����-��0�0�6�6�	�	��	(��!�]�2�	4����g�i�i�i�i�i�c��dS)N�)r
rrrs    r�rollbackr
s���DrN)F)r	rrrrr�<module>rs<���
�����	�	�	�	�	�	rdefence360agent/migrations/__pycache__/029_custom_quarantine.cpython-311.opt-1.pyc0000644000000000000000000000076300000000000024734 0ustar  �

�;x\=c�����dZd�Zd�ZdS)z# Quarantine is removed in DEF-15234c��dS�N���_�__s  �e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/029_custom_quarantine.py�migrater	����D�c��dSrrrs  r�rollbackr
r
rN)�__doc__r	r
rrr�<module>rs3��)�)�	�	�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/029_custom_quarantine.cpython-311.pyc0000644000000000000000000000076300000000000023775 0ustar  �

�;x\=c�����dZd�Zd�ZdS)z# Quarantine is removed in DEF-15234c��dS�N���_�__s  �e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/029_custom_quarantine.py�migrater	����D�c��dSrrrs  r�rollbackr
r
rN)�__doc__r	r
rrr�<module>rs3��)�)�	�	�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/030_rename_max_incident_repetition.cpython-311.opt-1.pyc0000644000000000000000000000401700000000000027412 0ustar  �

�?w<���l�ddlmZmZddlmZe��de��fdefd���Zdd�ZdS)	�)�IConfig�LocalConfig)�log_error_and_ignoreF�config_filec��|rdS|���}|sdS|�di��}|�dd��}|r||d<|�di��}|���D]}	|	�dd��}|r||	d<� |�d��rD|�di��|d<|�dd��}
|
r|djdi|
��|�|dd	�
��dS)N�MOD_SEC_BLOCK_BY_SEVERITY�max_incident_repetition�
max_incidents�MOD_SEC_BLOCK_BY_CUSTOM_RULE�
INCIDENT_LIST�INCIDENT_LOGGING�AUTOCLEANUPFT)�validate�	overwrite�)�config_to_dict�
setdefault�pop�values�get�update�dict_to_config)�migrator�database�faker�kwargs�config�block_by_severity�value�custom_rule_list�custom_rule_conf�auto_cleanup_confs           �r/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/030_rename_max_incident_repetition.py�migrater$sZ������
�
'�
'�
)�
)�F������)�)�*E�r�J�J���!�!�";�T�B�B�E��3�-2��/�*��(�(�)G��L�L��,�3�3�5�5�6�6�� �$�$�%>��E�E���	6�05��_�-��
�z�z�/�"�"�C�%+�Z�Z���%D�%D��!�"�"�J�J�}�d�;�;���	C�-�F�%�&�-�B�B�0A�B�B�B����v����F�F�F�F�F�c��dS)Nr)rrrrs    r#�rollbackr'+s���Dr%N)F)� defence360agent.contracts.configrr�defence360agent.utilsrr$r'rr%r#�<module>r*s���A�A�A�A�A�A�A�A�6�6�6�6�6�6�����
�&�;�=�=�	"G�"G��	"G�"G�"G���"G�J	�	�	�	�	�	r%defence360agent/migrations/__pycache__/030_rename_max_incident_repetition.cpython-311.pyc0000644000000000000000000000401700000000000026453 0ustar  �

�?w<���l�ddlmZmZddlmZe��de��fdefd���Zdd�ZdS)	�)�IConfig�LocalConfig)�log_error_and_ignoreF�config_filec��|rdS|���}|sdS|�di��}|�dd��}|r||d<|�di��}|���D]}	|	�dd��}|r||	d<� |�d��rD|�di��|d<|�dd��}
|
r|djdi|
��|�|dd	�
��dS)N�MOD_SEC_BLOCK_BY_SEVERITY�max_incident_repetition�
max_incidents�MOD_SEC_BLOCK_BY_CUSTOM_RULE�
INCIDENT_LIST�INCIDENT_LOGGING�AUTOCLEANUPFT)�validate�	overwrite�)�config_to_dict�
setdefault�pop�values�get�update�dict_to_config)�migrator�database�faker�kwargs�config�block_by_severity�value�custom_rule_list�custom_rule_conf�auto_cleanup_confs           �r/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/030_rename_max_incident_repetition.py�migrater$sZ������
�
'�
'�
)�
)�F������)�)�*E�r�J�J���!�!�";�T�B�B�E��3�-2��/�*��(�(�)G��L�L��,�3�3�5�5�6�6�� �$�$�%>��E�E���	6�05��_�-��
�z�z�/�"�"�C�%+�Z�Z���%D�%D��!�"�"�J�J�}�d�;�;���	C�-�F�%�&�-�B�B�0A�B�B�B����v����F�F�F�F�F�c��dS)Nr)rrrrs    r#�rollbackr'+s���Dr%N)F)� defence360agent.contracts.configrr�defence360agent.utilsrr$r'rr%r#�<module>r*s���A�A�A�A�A�A�A�A�6�6�6�6�6�6�����
�&�;�=�=�	"G�"G��	"G�"G�"G���"G�J	�	�	�	�	�	r%defence360agent/migrations/__pycache__/031_add_mode_field.cpython-311.opt-1.pyc0000644000000000000000000000207300000000000024057 0ustar  �

���E�s��F�ddlZddlZeje��Zdd�Zdd�ZdS)�NFc�t�|jd}|�|tjd������dS)zWrite your migrations here.�malware_hitsT)�null)�modeN)�orm�
add_fields�pw�IntegerField��migrator�database�fake�kwargs�MalwareHitss     �b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/031_add_mode_field.py�migrater	s<���,�~�.�K�����"�/�t�*D�*D�*D��E�E�E�E�E�c�L�|jd}|�|d��dS)z$Write your rollback migrations here.rrN)r�
remove_fieldsrs     r�rollbackrs*���,�~�.�K����;��/�/�/�/�/r)F)�logging�peeweer	�	getLogger�__name__�loggerrr�rr�<module>rsd����������
��	�8�	$�	$��F�F�F�F�0�0�0�0�0�0rdefence360agent/migrations/__pycache__/031_add_mode_field.cpython-311.pyc0000644000000000000000000000207300000000000023120 0ustar  �

���E�s��F�ddlZddlZeje��Zdd�Zdd�ZdS)�NFc�t�|jd}|�|tjd������dS)zWrite your migrations here.�malware_hitsT)�null)�modeN)�orm�
add_fields�pw�IntegerField��migrator�database�fake�kwargs�MalwareHitss     �b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/031_add_mode_field.py�migrater	s<���,�~�.�K�����"�/�t�*D�*D�*D��E�E�E�E�E�c�L�|jd}|�|d��dS)z$Write your rollback migrations here.rrN)r�
remove_fieldsrs     r�rollbackrs*���,�~�.�K����;��/�/�/�/�/r)F)�logging�peeweer	�	getLogger�__name__�loggerrr�rr�<module>rsd����������
��	�8�	$�	$��F�F�F�F�0�0�0�0�0�0rdefence360agent/migrations/__pycache__/031_modsec_config_for_plesk_include.cpython-311.opt-1.pyc0000644000000000000000000000445200000000000027531 0ustar  �

�Pj��z����ddlmZddlmZddlmZee��Zejdd���Zejdd���Z	dS)	�)�	getLogger)�run_coro)�antivirus_modeFc��	ddlm}ddlm}n#t$rYdSwxYw	|s5|���r!t
|�����sdS|���ddl	m
}|��dS#t$r3}t�
dt|����Yd}~dSd}~wwxYw�Nr)�Plesk)�ModSecSettings)�graceful_restart_syncz"Error during web-server update: %s)�im360.subsys.panels.pleskr�&im360.subsys.panels.plesk.mod_securityr	�ImportError�is_installedr�installed_modsec�include_modsec_conf�!defence360agent.subsys.web_serverr
�	Exception�logger�warning�str��migrator�database�fake�kwargsrr	r
�es        �s/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/031_modsec_config_for_plesk_include.py�migrater	� ���3�3�3�3�3�3�I�I�I�I�I�I�I�������������
E��	��%�%�'�'�	��E�2�2�4�4�5�5�	�

�F��*�*�,�,�,�K�K�K�K�K�K����������E�E�E����;�S��V�V�D�D�D�D�D�D�D�D�D�����E����)��
��7B�$B�
B=�
(B8�8B=c��	ddlm}ddlm}n#t$rYdSwxYw	|s5|���r!t
|�����sdS|���ddl	m
}|��dS#t$r3}t�
dt|����Yd}~dSd}~wwxYwr)rrrr	r
rrr�revert_conf_includerr
rrrrrs        r�rollbackr"!rrN)F)
�loggingr�defence360agent.utilsrr�__name__r�skiprr"��r�<module>r)s���������*�*�*�*�*�*�0�0�0�0�0�0�	��8�	�	����E�E�E���E�.��E�E�E���E�E�Er(defence360agent/migrations/__pycache__/031_modsec_config_for_plesk_include.cpython-311.pyc0000644000000000000000000000445200000000000026572 0ustar  �

�Pj��z����ddlmZddlmZddlmZee��Zejdd���Zejdd���Z	dS)	�)�	getLogger)�run_coro)�antivirus_modeFc��	ddlm}ddlm}n#t$rYdSwxYw	|s5|���r!t
|�����sdS|���ddl	m
}|��dS#t$r3}t�
dt|����Yd}~dSd}~wwxYw�Nr)�Plesk)�ModSecSettings)�graceful_restart_syncz"Error during web-server update: %s)�im360.subsys.panels.pleskr�&im360.subsys.panels.plesk.mod_securityr	�ImportError�is_installedr�installed_modsec�include_modsec_conf�!defence360agent.subsys.web_serverr
�	Exception�logger�warning�str��migrator�database�fake�kwargsrr	r
�es        �s/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/031_modsec_config_for_plesk_include.py�migrater	� ���3�3�3�3�3�3�I�I�I�I�I�I�I�������������
E��	��%�%�'�'�	��E�2�2�4�4�5�5�	�

�F��*�*�,�,�,�K�K�K�K�K�K����������E�E�E����;�S��V�V�D�D�D�D�D�D�D�D�D�����E����)��
��7B�$B�
B=�
(B8�8B=c��	ddlm}ddlm}n#t$rYdSwxYw	|s5|���r!t
|�����sdS|���ddl	m
}|��dS#t$r3}t�
dt|����Yd}~dSd}~wwxYwr)rrrr	r
rrr�revert_conf_includerr
rrrrrs        r�rollbackr"!rrN)F)
�loggingr�defence360agent.utilsrr�__name__r�skiprr"��r�<module>r)s���������*�*�*�*�*�*�0�0�0�0�0�0�	��8�	�	����E�E�E���E�.��E�E�E���E�E�Er(defence360agent/migrations/__pycache__/032_chmod_quarantine.cpython-311.opt-1.pyc0000644000000000000000000000076200000000000024505 0ustar  �

�;x\=c�����dZd�Zd�ZdS)z# Quarantine is removed in DEF-15234c��dS�N���_�__s  �d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/032_chmod_quarantine.py�migrater	����D�c��dSrrrs  r�rollbackr
r
rN)�__doc__r	r
rrr�<module>rs3��)�)�	�	�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/032_chmod_quarantine.cpython-311.pyc0000644000000000000000000000076200000000000023546 0ustar  �

�;x\=c�����dZd�Zd�ZdS)z# Quarantine is removed in DEF-15234c��dS�N���_�__s  �d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/032_chmod_quarantine.py�migrater	����D�c��dSrrrs  r�rollbackr
r
rN)�__doc__r	r
rrr�<module>rs3��)�)�	�	�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/033_disable_cphulk.cpython-311.opt-1.pyc0000644000000000000000000000272500000000000024137 0ustar  �

�>�Hc:���Z�ddlZddlZddlmZddlmZee��Zd�Zdd�Z	dd�Z
dS)	�N)�	Packaging)�	getLoggerc��	tjdtjzdg��dS#tj$r%}t
�|��Yd}~dSd}~wwxYw)Nz %s/scripts/disable_3rd_party_idsz	--nocheck)�
subprocess�
check_callr�DATADIR�CalledProcessError�logger�error)�es �b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/033_disable_cphulk.py�disable_3rdpartyr
sx�����2�Y�5F�F��
�	
�	
�	
�	
�	
���(�������Q�����������������s�#'�A�A�AFc�|�|s)tj�tj��sdSt��dS�N)�os�path�isfilerrr��migrator�database�fake�kwargss    r
�migraters9����2�7�>�>�)�"3�4�4����������c��dSr�rs    r
�rollbackrs���Dr)F)rr� defence360agent.contracts.configr�loggingr�__name__r
rrrrrr
�<module>r!s���	�	�	�	�����6�6�6�6�6�6�������	��8�	�	��	�	�	�����	�	�	�	�	�	rdefence360agent/migrations/__pycache__/033_disable_cphulk.cpython-311.pyc0000644000000000000000000000272500000000000023200 0ustar  �

�>�Hc:���Z�ddlZddlZddlmZddlmZee��Zd�Zdd�Z	dd�Z
dS)	�N)�	Packaging)�	getLoggerc��	tjdtjzdg��dS#tj$r%}t
�|��Yd}~dSd}~wwxYw)Nz %s/scripts/disable_3rd_party_idsz	--nocheck)�
subprocess�
check_callr�DATADIR�CalledProcessError�logger�error)�es �b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/033_disable_cphulk.py�disable_3rdpartyr
sx�����2�Y�5F�F��
�	
�	
�	
�	
�	
���(�������Q�����������������s�#'�A�A�AFc�|�|s)tj�tj��sdSt��dS�N)�os�path�isfilerrr��migrator�database�fake�kwargss    r
�migraters9����2�7�>�>�)�"3�4�4����������c��dSr�rs    r
�rollbackrs���Dr)F)rr� defence360agent.contracts.configr�loggingr�__name__r
rrrrrr
�<module>r!s���	�	�	�	�����6�6�6�6�6�6�������	��8�	�	��	�	�	�����	�	�	�	�	�	rdefence360agent/migrations/__pycache__/034_hits_extras.cpython-311.opt-1.pyc0000644000000000000000000000360700000000000023524 0ustar  �

�_�44w%���F�ddlZddlZeje��Zdd�Zdd�ZdS)�NFc���|jd�G�fd�dtj��}|�|��dS)zWrite your migrations here.�malware_hitsc���eZdZGd�d��Zejd���Zej�dd���Zej	d���Z
ej	d���Zd	S)
� migrate.<locals>.MalwareHitExtrac��eZdZdZdS)�%migrate.<locals>.MalwareHitExtra.Meta�malware_hit_extrasN)�__name__�
__module__�__qualname__�db_table���_/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/034_hits_extras.py�Metars������+�H�H�HrrT)�primary_keyF�extras)�null�related_name)rN)r
rrr�pw�IntegerField�id�ForeignKeyField�hit�	CharField�name�value)�
MalwareHits�r�MalwareHitExtrars��������	,�	,�	,�	,�	,�	,�	,�	,��R�_��
.�
.�
.�� �b� ��%�h�O�O�O���r�|��'�'�'�����%�(�(�(���rrN)�ormr�Model�create_model)�migrator�database�fake�kwargsrrs     @r�migrater'	sc�����n�-�J�)�)�)�)�)�)�)�"�(�)�)�)�
���/�*�*�*�*�*rc�J�|jd}|�|��dS)z$Write your rollback migrations here.r	N)r �remove_model)r#r$r%r&rs     r�rollbackr*s)���l�#7�8�O����/�*�*�*�*�*r)F)�logging�peeweer�	getLoggerr
�loggerr'r*rrr�<module>r/s`����������
��	�8�	$�	$��+�+�+�+�"+�+�+�+�+�+rdefence360agent/migrations/__pycache__/034_hits_extras.cpython-311.pyc0000644000000000000000000000360700000000000022565 0ustar  �

�_�44w%���F�ddlZddlZeje��Zdd�Zdd�ZdS)�NFc���|jd�G�fd�dtj��}|�|��dS)zWrite your migrations here.�malware_hitsc���eZdZGd�d��Zejd���Zej�dd���Zej	d���Z
ej	d���Zd	S)
� migrate.<locals>.MalwareHitExtrac��eZdZdZdS)�%migrate.<locals>.MalwareHitExtra.Meta�malware_hit_extrasN)�__name__�
__module__�__qualname__�db_table���_/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/034_hits_extras.py�Metars������+�H�H�HrrT)�primary_keyF�extras)�null�related_name)rN)r
rrr�pw�IntegerField�id�ForeignKeyField�hit�	CharField�name�value)�
MalwareHits�r�MalwareHitExtrars��������	,�	,�	,�	,�	,�	,�	,�	,��R�_��
.�
.�
.�� �b� ��%�h�O�O�O���r�|��'�'�'�����%�(�(�(���rrN)�ormr�Model�create_model)�migrator�database�fake�kwargsrrs     @r�migrater'	sc�����n�-�J�)�)�)�)�)�)�)�"�(�)�)�)�
���/�*�*�*�*�*rc�J�|jd}|�|��dS)z$Write your rollback migrations here.r	N)r �remove_model)r#r$r%r&rs     r�rollbackr*s)���l�#7�8�O����/�*�*�*�*�*r)F)�logging�peeweer�	getLoggerr
�loggerr'r*rrr�<module>r/s`����������
��	�8�	$�	$��+�+�+�+�"+�+�+�+�+�+rdefence360agent/migrations/__pycache__/035_add_dos_expiration_field.cpython-311.opt-1.pyc0000644000000000000000000000166000000000000026167 0ustar  �

,[�`Ӛw����ddlZdd�Zdd�ZdS)�NFc�v�|jd}|�|tjdd������dS)N�iplistrT)�default�null)�dos_expiration)�orm�
add_fields�pw�IntegerField��migrator�database�fake�kwargs�IPLists     �l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/035_add_dos_expiration_field.py�migratersI��
�\�(�
#�F�����r��q�t�D�D�D�������c�L�|jd}|�|d��dS)Nrr)r�
remove_fieldsrs     r�rollbackrs+��
�\�(�
#�F����6�#3�4�4�4�4�4r)F)�peeweer
rr�rr�<module>rsC����������5�5�5�5�5�5rdefence360agent/migrations/__pycache__/035_add_dos_expiration_field.cpython-311.pyc0000644000000000000000000000166000000000000025230 0ustar  �

,[�`Ӛw����ddlZdd�Zdd�ZdS)�NFc�v�|jd}|�|tjdd������dS)N�iplistrT)�default�null)�dos_expiration)�orm�
add_fields�pw�IntegerField��migrator�database�fake�kwargs�IPLists     �l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/035_add_dos_expiration_field.py�migratersI��
�\�(�
#�F�����r��q�t�D�D�D�������c�L�|jd}|�|d��dS)Nrr)r�
remove_fieldsrs     r�rollbackrs+��
�\�(�
#�F����6�#3�4�4�4�4�4r)F)�peeweer
rr�rr�<module>rsC����������5�5�5�5�5�5rdefence360agent/migrations/__pycache__/036_add_block_port.cpython-311.opt-1.pyc0000644000000000000000000000605600000000000024140 0ustar  �

�R�}������j�ddlZGd�dej��ZGd�dej��Zd	d�Zd	d�ZdS)
�Nc��eZdZdZejd���Zejdejd��g���Z	ejd���Z
Gd�d��Zd	S)
�BlockedPortz+
    Port + protocol for blocking data
    F��nullzproto in ('tcp', 'udp', 'all'))r�constraintsTc��eZdZdZdZdS)�BlockedPort.Meta�blocked_port)))�port�protoTN��__name__�
__module__�__qualname__�db_table�indexes���b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/036_add_block_port.py�Metar	s������!��
���rrN)rrr�__doc__�pw�IntegerFieldr�	CharField�Checkr�commentrrrrrrs����������2�?��&�&�&�D��B�L�
����*J�!K�!K� L�
�
�
�E��b�l��%�%�%�G�
�
�
�
�
�
�
�
�
�
rrc��eZdZdZejeddd���Zejd���Z	ejd���Z
Gd�d	��Zd
S)�
IgnoredByPortz)
    Ignored IPs for port + protocol
    F�CASCADE�ips)r�	on_delete�related_namerTc��eZdZdZdZdS)�IgnoredByPort.Meta�ignored_by_port_proto)))�
port_proto�ipTNr
rrrrr$$s������*��
���rrN)rrrrr�ForeignKeyFieldrr&rr'rrrrrrrs���������$��#��%�9�5����J�
���5�	!�	!�	!�B��b�l��%�%�%�G�
�
�
�
�
�
�
�
�
�
rrFc���|�t��|�t��|jd}|�|tjd������dS)N�iplistTr)�full_access)�create_modelrr�orm�
add_fieldsr�BooleanField)�migrator�database�fake�kwargs�IPLists     r�migrater5-sb�����+�&�&�&����-�(�(�(�
�\�(�
#�F�����B�O��,F�,F�,F��G�G�G�G�Grc���|jd}|jd}|jd}|�|��|�|��|�|d��dS)Nr
�blocked_port_ipr*r+)r-�remove_model�
remove_fields)r0r1r2r3rrr4s       r�rollbackr:5sk���,�~�.�K��L�!2�3�M�
�\�(�
#�F����+�&�&�&����-�(�(�(����6�=�1�1�1�1�1r)F)�peeweer�Modelrrr5r:rrr�<module>r=s�������
�
�
�
�
�"�(�
�
�
�*
�
�
�
�
�B�H�
�
�
�(H�H�H�H�2�2�2�2�2�2rdefence360agent/migrations/__pycache__/036_add_block_port.cpython-311.pyc0000644000000000000000000000605600000000000023201 0ustar  �

�R�}������j�ddlZGd�dej��ZGd�dej��Zd	d�Zd	d�ZdS)
�Nc��eZdZdZejd���Zejdejd��g���Z	ejd���Z
Gd�d��Zd	S)
�BlockedPortz+
    Port + protocol for blocking data
    F��nullzproto in ('tcp', 'udp', 'all'))r�constraintsTc��eZdZdZdZdS)�BlockedPort.Meta�blocked_port)))�port�protoTN��__name__�
__module__�__qualname__�db_table�indexes���b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/036_add_block_port.py�Metar	s������!��
���rrN)rrr�__doc__�pw�IntegerFieldr�	CharField�Checkr�commentrrrrrrs����������2�?��&�&�&�D��B�L�
����*J�!K�!K� L�
�
�
�E��b�l��%�%�%�G�
�
�
�
�
�
�
�
�
�
rrc��eZdZdZejeddd���Zejd���Z	ejd���Z
Gd�d	��Zd
S)�
IgnoredByPortz)
    Ignored IPs for port + protocol
    F�CASCADE�ips)r�	on_delete�related_namerTc��eZdZdZdZdS)�IgnoredByPort.Meta�ignored_by_port_proto)))�
port_proto�ipTNr
rrrrr$$s������*��
���rrN)rrrrr�ForeignKeyFieldrr&rr'rrrrrrrs���������$��#��%�9�5����J�
���5�	!�	!�	!�B��b�l��%�%�%�G�
�
�
�
�
�
�
�
�
�
rrFc���|�t��|�t��|jd}|�|tjd������dS)N�iplistTr)�full_access)�create_modelrr�orm�
add_fieldsr�BooleanField)�migrator�database�fake�kwargs�IPLists     r�migrater5-sb�����+�&�&�&����-�(�(�(�
�\�(�
#�F�����B�O��,F�,F�,F��G�G�G�G�Grc���|jd}|jd}|jd}|�|��|�|��|�|d��dS)Nr
�blocked_port_ipr*r+)r-�remove_model�
remove_fields)r0r1r2r3rrr4s       r�rollbackr:5sk���,�~�.�K��L�!2�3�M�
�\�(�
#�F����+�&�&�&����-�(�(�(����6�=�1�1�1�1�1r)F)�peeweer�Modelrrr5r:rrr�<module>r=s�������
�
�
�
�
�"�(�
�
�
�*
�
�
�
�
�B�H�
�
�
�(H�H�H�H�2�2�2�2�2�2rdefence360agent/migrations/__pycache__/037_disabled_rules.cpython-311.opt-1.pyc0000644000000000000000000000544500000000000024155 0ustar  �

�����?����F�ddlZddlZeje��Zdd�Zdd�ZdS)�NFc���Gd�dtj���G�fd�dtj��}|����|�|��dS)zWrite your migrations here.c��eZdZGd�d��Zej��Zejd���Zejd���Z	ej
d���ZdS)�migrate.<locals>.DisabledRulec��eZdZdZdZdS)�"migrate.<locals>.DisabledRule.Meta�disabled_rules)))�plugin�rule_idTN)�__name__�
__module__�__qualname__�db_table�indexes���b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/037_disabled_rules.py�Metar
s������'�H�6�G�G�GrrF��nullN)rrr
r�pw�PrimaryKeyField�id�	CharFieldr	r
�	TextField�namerrr�DisabledRulers�������	7�	7�	7�	7�	7�	7�	7�	7� �R�
�
!�
!�����5�)�)�)���"�,�E�*�*�*���r�|��'�'�'���rrc�r��eZdZej�dd���Zejd���ZGd�d��ZdS)	�#migrate.<locals>.DisabledRuleDomain�domains�CASCADE)�backref�	on_deleteFrc�4�eZdZdZejdd��ZdS)�(migrate.<locals>.DisabledRuleDomain.Meta�disabled_rules_domains�disabled_rule_id_id�domainN)rrr
rr�CompositeKey�primary_keyrrrrr$s'������/�H�)�"�/�*?��J�J�K�K�KrrN)	rrr
r�ForeignKeyFieldr&rr'r)rs�r�DisabledRuleDomainrs~�������0�b�0��)�y�
�
�
�����5�)�)�)��	K�	K�	K�	K�	K�	K�	K�	K�	K�	Krr+N)r�Model�create_model)�migrator�database�fake�kwargsr+rs     @r�migrater2	s����(�(�(�(�(�r�x�(�(�(�K�K�K�K�K�K�K�R�X�K�K�K�
���,�'�'�'����,�-�-�-�-�-rc��|�|jd��|�|jd��dS)z$Write your rollback migrations here.r%rN)�remove_model�orm)r.r/r0r1s    r�rollbackr6$s@�����(�,�'?�@�A�A�A����(�,�'7�8�9�9�9�9�9r)F)�logging�peeweer�	getLoggerr�loggerr2r6rrr�<module>r;s`����������
��	�8�	$�	$��.�.�.�.�6:�:�:�:�:�:rdefence360agent/migrations/__pycache__/037_disabled_rules.cpython-311.pyc0000644000000000000000000000544500000000000023216 0ustar  �

�����?����F�ddlZddlZeje��Zdd�Zdd�ZdS)�NFc���Gd�dtj���G�fd�dtj��}|����|�|��dS)zWrite your migrations here.c��eZdZGd�d��Zej��Zejd���Zejd���Z	ej
d���ZdS)�migrate.<locals>.DisabledRulec��eZdZdZdZdS)�"migrate.<locals>.DisabledRule.Meta�disabled_rules)))�plugin�rule_idTN)�__name__�
__module__�__qualname__�db_table�indexes���b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/037_disabled_rules.py�Metar
s������'�H�6�G�G�GrrF��nullN)rrr
r�pw�PrimaryKeyField�id�	CharFieldr	r
�	TextField�namerrr�DisabledRulers�������	7�	7�	7�	7�	7�	7�	7�	7� �R�
�
!�
!�����5�)�)�)���"�,�E�*�*�*���r�|��'�'�'���rrc�r��eZdZej�dd���Zejd���ZGd�d��ZdS)	�#migrate.<locals>.DisabledRuleDomain�domains�CASCADE)�backref�	on_deleteFrc�4�eZdZdZejdd��ZdS)�(migrate.<locals>.DisabledRuleDomain.Meta�disabled_rules_domains�disabled_rule_id_id�domainN)rrr
rr�CompositeKey�primary_keyrrrrr$s'������/�H�)�"�/�*?��J�J�K�K�KrrN)	rrr
r�ForeignKeyFieldr&rr'r)rs�r�DisabledRuleDomainrs~�������0�b�0��)�y�
�
�
�����5�)�)�)��	K�	K�	K�	K�	K�	K�	K�	K�	K�	Krr+N)r�Model�create_model)�migrator�database�fake�kwargsr+rs     @r�migrater2	s����(�(�(�(�(�r�x�(�(�(�K�K�K�K�K�K�K�R�X�K�K�K�
���,�'�'�'����,�-�-�-�-�-rc��|�|jd��|�|jd��dS)z$Write your rollback migrations here.r%rN)�remove_model�orm)r.r/r0r1s    r�rollbackr6$s@�����(�,�'?�@�A�A�A����(�,�'7�8�9�9�9�9�9r)F)�logging�peeweer�	getLoggerr�loggerr2r6rrr�<module>r;s`����������
��	�8�	$�	$��.�.�.�.�6:�:�:�:�:�:rdefence360agent/migrations/__pycache__/038_disabled_rules_import.cpython-311.opt-1.pyc0000644000000000000000000000252200000000000025541 0ustar  �

_D��J�-��h�ddlZddlmZmZeje��Zde��fdefd�Zdd�ZdS)�N)�IConfig�LocalConfigF�config_filec���|rdS|���}|sdS|�di��|�di���dg��|�|dd���dS)zWrite your migrations here.N�OSSEC�MOD_SEC_BLOCK_BY_SEVERITY�ignoreTF)�	overwrite�validate)�config_to_dict�pop�get�dict_to_config)�migrator�database�faker�kwargs�configs      �i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/038_disabled_rules_import.py�migraters�������
�
'�
'�
)�
)�F�����
�J�J�w������J�J�*�B�/�/�3�3�H�b�A�A�A����v����F�F�F�F�F�c��dS)z$Write your rollback migrations here.N�)rrrrs    r�rollbackr s���Dr)F)	�logging� defence360agent.contracts.configrr�	getLogger�__name__�loggerrrrrr�<module>r s�������A�A�A�A�A�A�A�A�	��	�8�	$�	$��
�&�;�=�=�	G�G��	G�G�G�G�0	�	�	�	�	�	rdefence360agent/migrations/__pycache__/038_disabled_rules_import.cpython-311.pyc0000644000000000000000000000252200000000000024602 0ustar  �

_D��J�-��h�ddlZddlmZmZeje��Zde��fdefd�Zdd�ZdS)�N)�IConfig�LocalConfigF�config_filec���|rdS|���}|sdS|�di��|�di���dg��|�|dd���dS)zWrite your migrations here.N�OSSEC�MOD_SEC_BLOCK_BY_SEVERITY�ignoreTF)�	overwrite�validate)�config_to_dict�pop�get�dict_to_config)�migrator�database�faker�kwargs�configs      �i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/038_disabled_rules_import.py�migraters�������
�
'�
'�
)�
)�F�����
�J�J�w������J�J�*�B�/�/�3�3�H�b�A�A�A����v����F�F�F�F�F�c��dS)z$Write your rollback migrations here.N�)rrrrs    r�rollbackr s���Dr)F)	�logging� defence360agent.contracts.configrr�	getLogger�__name__�loggerrrrrr�<module>r s�������A�A�A�A�A�A�A�A�	��	�8�	$�	$��
�&�;�=�=�	G�G��	G�G�G�G�0	�	�	�	�	�	rdefence360agent/migrations/__pycache__/039_fix_malware_hits.cpython-311.opt-1.pyc0000644000000000000000000000273000000000000024515 0ustar  �

���Qd���>�ddlZeje��Zdd�Zdd�ZdS)�NFc��|�d��|�d��|�d��|�d��dS)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)�sql��migrator�database�fake�kwargss    �d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/039_fix_malware_hits.py�migraters`��
�L�L�
	����
�L�L�J�K�K�K��L�L�*�+�+�+��L�L�F�G�G�G�G�G�c��dS)z$Write your rollback migrations here.N�rs    r
�rollbackrs���Dr)F)�logging�	getLogger�__name__�loggerrrrrr
�<module>rsX������
��	�8�	$�	$��H�H�H�H�*	�	�	�	�	�	rdefence360agent/migrations/__pycache__/039_fix_malware_hits.cpython-311.pyc0000644000000000000000000000273000000000000023556 0ustar  �

���Qd���>�ddlZeje��Zdd�Zdd�ZdS)�NFc��|�d��|�d��|�d��|�d��dS)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)�sql��migrator�database�fake�kwargss    �d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/039_fix_malware_hits.py�migraters`��
�L�L�
	����
�L�L�J�K�K�K��L�L�*�+�+�+��L�L�F�G�G�G�G�G�c��dS)z$Write your rollback migrations here.N�rs    r
�rollbackrs���Dr)F)�logging�	getLogger�__name__�loggerrrrrr
�<module>rsX������
��	�8�	$�	$��H�H�H�H�*	�	�	�	�	�	rdefence360agent/migrations/__pycache__/040_ignore_mod_sec_rule_214920.cpython-311.opt-1.pyc0000644000000000000000000000105500000000000026003 0ustar  �

Nj�4_���dZdd�Zdd�ZdS)z#Migration was buggy, so skipping itFc��dS�N���migrator�database�fake�kwargss    �n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/040_ignore_mod_sec_rule_214920.py�migrater����D�c��dSrrrs    r
�rollbackrrr
N)F)�__doc__rrrr
r
�<module>rs=��)�)�	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/040_ignore_mod_sec_rule_214920.cpython-311.pyc0000644000000000000000000000105500000000000025044 0ustar  �

Nj�4_���dZdd�Zdd�ZdS)z#Migration was buggy, so skipping itFc��dS�N���migrator�database�fake�kwargss    �n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/040_ignore_mod_sec_rule_214920.py�migrater����D�c��dSrrrs    r
�rollbackrrr
N)F)�__doc__rrrr
r
�<module>rs=��)�)�	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/041_fix_invalid_ignore_filed.cpython-311.opt-1.pyc0000644000000000000000000000111600000000000026160 0ustar  �

:6RmLko���dZdd�Zdd�ZdS)zFAdding 214920 rule to ignored on disabled rules level in 038 migrationFc��dS�N���migrator�database�fake�kwargss    �l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/041_fix_invalid_ignore_filed.py�migrater����D�c��dSrrrs    r
�rollbackrrr
N)F)�__doc__rrrr
r
�<module>rs=��L�L�	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/041_fix_invalid_ignore_filed.cpython-311.pyc0000644000000000000000000000111600000000000025221 0ustar  �

:6RmLko���dZdd�Zdd�ZdS)zFAdding 214920 rule to ignored on disabled rules level in 038 migrationFc��dS�N���migrator�database�fake�kwargss    �l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/041_fix_invalid_ignore_filed.py�migrater����D�c��dSrrrs    r
�rollbackrrr
N)F)�__doc__rrrr
r
�<module>rs=��L�L�	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/042_rebuildinstalledssldb.cpython-311.opt-1.pyc0000644000000000000000000000301100000000000025531 0ustar  �

���9��;����ddlZddlZddlmZeje��Zejdd���Zejdd���Z	dS)�N)�antivirus_modeFc��|rdS	ddlm}n#t$rYdSwxYw|���rX	t	jdg��dS#t$r3}t�dt|����Yd}~dSd}~wwxYwdS)Nr)�cPanelz/scripts/rebuildinstalledssldbz#Failed to rebuild cpanel ssl db: %s)
�im360.subsys.panels.cpanelr�ImportError�is_installed�
subprocess�run�	Exception�logger�warning�str)�migrator�database�fake�kwargsr�es      �i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/042_rebuildinstalledssldb.py�migrater	s��������5�5�5�5�5�5�5������������������J�	J��N�<�=�>�>�>�>�>���	J�	J�	J��N�N�@�#�a�&�&�I�I�I�I�I�I�I�I�I�����	J����J�Js#�
�
��A
�

B�(B�Bc��dS)N�)rrrrs    r�rollbackrs���D�)F)
�loggingr	�defence360agent.utilsr�	getLogger�__name__r�skiprrrrr�<module>rs�����������0�0�0�0�0�0�	��	�8�	$�	$����J�J�J���J���	�	�	���	�	�	rdefence360agent/migrations/__pycache__/042_rebuildinstalledssldb.cpython-311.pyc0000644000000000000000000000301100000000000024572 0ustar  �

���9��;����ddlZddlZddlmZeje��Zejdd���Zejdd���Z	dS)�N)�antivirus_modeFc��|rdS	ddlm}n#t$rYdSwxYw|���rX	t	jdg��dS#t$r3}t�dt|����Yd}~dSd}~wwxYwdS)Nr)�cPanelz/scripts/rebuildinstalledssldbz#Failed to rebuild cpanel ssl db: %s)
�im360.subsys.panels.cpanelr�ImportError�is_installed�
subprocess�run�	Exception�logger�warning�str)�migrator�database�fake�kwargsr�es      �i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/042_rebuildinstalledssldb.py�migrater	s��������5�5�5�5�5�5�5������������������J�	J��N�<�=�>�>�>�>�>���	J�	J�	J��N�N�@�#�a�&�&�I�I�I�I�I�I�I�I�I�����	J����J�Js#�
�
��A
�

B�(B�Bc��dS)N�)rrrrs    r�rollbackrs���D�)F)
�loggingr	�defence360agent.utilsr�	getLogger�__name__r�skiprrrrr�<module>rs�����������0�0�0�0�0�0�	��	�8�	$�	$����J�J�J���J���	�	�	���	�	�	rdefence360agent/migrations/__pycache__/043_disable_dos_scan_by_default.cpython-311.opt-1.pyc0000644000000000000000000000177000000000000026640 0ustar  �

.�cϸbR{��"�ddlmZdd�Zdd�ZdS)�)�
ConfigFileFc��|rdSt��}|���}|sdS|�di��}d|d<|�|d���dS)N�DOSF�enabled)�validate)r�config_to_dict�
setdefault�dict_to_config)�migrator�database�fake�kwargs�config_file�config�dos_settingss       �o/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/043_disable_dos_scan_by_default.py�migraterst�������,�,�K�
�
'�
'�
)�
)�F������$�$�U�B�/�/�L�#�L������v���6�6�6�6�6�c��dS)N�)rrr
rs    r�rollbackrs���DrN)F)� defence360agent.contracts.configrrrrrr�<module>rsI��7�7�7�7�7�7�
7�
7�
7�
7�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/043_disable_dos_scan_by_default.cpython-311.pyc0000644000000000000000000000177000000000000025701 0ustar  �

.�cϸbR{��"�ddlmZdd�Zdd�ZdS)�)�
ConfigFileFc��|rdSt��}|���}|sdS|�di��}d|d<|�|d���dS)N�DOSF�enabled)�validate)r�config_to_dict�
setdefault�dict_to_config)�migrator�database�fake�kwargs�config_file�config�dos_settingss       �o/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/043_disable_dos_scan_by_default.py�migraterst�������,�,�K�
�
'�
'�
)�
)�F������$�$�U�B�/�/�L�#�L������v���6�6�6�6�6�c��dS)N�)rrr
rs    r�rollbackrs���DrN)F)� defence360agent.contracts.configrrrrrr�<module>rsI��7�7�7�7�7�7�
7�
7�
7�
7�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/044_ignore_virtfs_on_cpanel.cpython-311.opt-1.pyc0000644000000000000000000000200300000000000026053 0ustar  �

�&'�z=��&�dZddlmZdd�Zdd�ZdS)z[
This migration adds cpanel virtfs directory (/home/virtfs) to ignore
for malware scanning
�)�cPanelFc�~�|s8tj��r'|jd}|�d���dSdSdS)N�malware_ignore_pathz/home/virtfs)�path)r�is_installed�orm�
get_or_create)�migrator�database�fake�kwargs�MalwareIgnorePaths     �k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/044_ignore_virtfs_on_cpanel.py�migratersY���=�f�)�+�+�=�$�L�)>�?���'�'�^�'�<�<�<�<�<�=�=�=�=�c��dS)N�)r
rrr
s    r�rollbackrs���DrN)F)�__doc__�$defence360agent.subsys.panels.cpanelrrrrrr�<module>rsU����8�7�7�7�7�7�=�=�=�=�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/044_ignore_virtfs_on_cpanel.cpython-311.pyc0000644000000000000000000000200300000000000025114 0ustar  �

�&'�z=��&�dZddlmZdd�Zdd�ZdS)z[
This migration adds cpanel virtfs directory (/home/virtfs) to ignore
for malware scanning
�)�cPanelFc�~�|s8tj��r'|jd}|�d���dSdSdS)N�malware_ignore_pathz/home/virtfs)�path)r�is_installed�orm�
get_or_create)�migrator�database�fake�kwargs�MalwareIgnorePaths     �k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/044_ignore_virtfs_on_cpanel.py�migratersY���=�f�)�+�+�=�$�L�)>�?���'�'�^�'�<�<�<�<�<�=�=�=�=�c��dS)N�)r
rrr
s    r�rollbackrs���DrN)F)�__doc__�$defence360agent.subsys.panels.cpanelrrrrrr�<module>rsU����8�7�7�7�7�7�=�=�=�=�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/045_ignore_vdserver_dir_in_csf.cpython-311.opt-1.pyc0000644000000000000000000000157500000000000026555 0ustar  �

��v�������.�ddlZddlmZdZdd�Zdd�ZdS)�N)�append_with_newlinez/etc/csf/csf.fignoreFc��|s;tj�t��rt	td��dSdSdS)Nz/tmp/.vdserver
)�os�path�isfile�CSF_FIGNOREr��migrator�database�fake�kwargss    �n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/045_ignore_vdserver_dir_in_csf.py�migratersJ���=�b�g�n�n�[�1�1�=��K�);�<�<�<�<�<�=�=�=�=�c��dS)N�r	s    r�rollbackr
s���Dr)F)r�defence360agent.utilsrrrrrrr�<module>rsW��	�	�	�	�5�5�5�5�5�5�$��=�=�=�=�
	�	�	�	�	�	rdefence360agent/migrations/__pycache__/045_ignore_vdserver_dir_in_csf.cpython-311.pyc0000644000000000000000000000157500000000000025616 0ustar  �

��v�������.�ddlZddlmZdZdd�Zdd�ZdS)�N)�append_with_newlinez/etc/csf/csf.fignoreFc��|s;tj�t��rt	td��dSdSdS)Nz/tmp/.vdserver
)�os�path�isfile�CSF_FIGNOREr��migrator�database�fake�kwargss    �n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/045_ignore_vdserver_dir_in_csf.py�migratersJ���=�b�g�n�n�[�1�1�=��K�);�<�<�<�<�<�=�=�=�=�c��dS)N�r	s    r�rollbackr
s���Dr)F)r�defence360agent.utilsrrrrrrr�<module>rsW��	�	�	�	�5�5�5�5�5�5�$��=�=�=�=�
	�	�	�	�	�	rdefence360agent/migrations/__pycache__/046_foreign_key_fix.cpython-311.opt-1.pyc0000644000000000000000000000264300000000000024340 0ustar  �

��p�+�ִ��>�ddlZeje��Zdd�Zdd�ZdS)�NFc��|�d��|�d��|�d��|�d��dS)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)�sql��migrator�database�fake�kwargss    �c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/046_foreign_key_fix.py�migratersr��
�L�L�		����
�L�L�M����
�L�L�0�1�1�1��L�L�I������c��dS)z$Write your rollback migrations here.N�rs    r
�rollbackrs���Dr)F)�logging�	getLogger�__name__�loggerrrrrr
�<module>rsT������
��	�8�	$�	$������0	�	�	�	�	�	rdefence360agent/migrations/__pycache__/046_foreign_key_fix.cpython-311.pyc0000644000000000000000000000264300000000000023401 0ustar  �

��p�+�ִ��>�ddlZeje��Zdd�Zdd�ZdS)�NFc��|�d��|�d��|�d��|�d��dS)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)�sql��migrator�database�fake�kwargss    �c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/046_foreign_key_fix.py�migratersr��
�L�L�		����
�L�L�M����
�L�L�0�1�1�1��L�L�I������c��dS)z$Write your rollback migrations here.N�rs    r
�rollbackrs���Dr)F)�logging�	getLogger�__name__�loggerrrrrr
�<module>rsT������
��	�8�	$�	$������0	�	�	�	�	�	rdefence360agent/migrations/__pycache__/047_license_in_file.cpython-311.opt-1.pyc0000644000000000000000000000250200000000000024273 0ustar  �

0�]:�{���.�ddlZddlmZdZdd�Zdd�ZdS)�N)�
model_to_dictz /var/imunify360/license_old.jsonFc�(�|rdS|jd}|�ddd����\}}ttd��5}t	jt
|��|��ddd��n#1swxYwY|�|��dS)N�licenseTr)�status�
expiration)�defaults�w)�orm�
get_or_create�open�FALLBACK_LICENSE_FILE�json�dumpr�remove_model)�migrator�database�fake�kwargs�LicenseModel�lic�_�fs        �c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/047_license_in_file.py�migraters��������<�	�*�L�
�
'�
'���
�
�(���F�C��
�#�S�	)�	)�)�Q��	�-��$�$�a�(�(�(�)�)�)�)�)�)�)�)�)�)�)����)�)�)�)����,�'�'�'�'�'s�#A2�2A6�9A6c��dS)N�)rrrrs    r�rollbackrs���D�)F)r�playhouse.shortcutsrr
rrrrr�<module>r sX������-�-�-�-�-�-�:��(�(�(�(�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/047_license_in_file.cpython-311.pyc0000644000000000000000000000250200000000000023334 0ustar  �

0�]:�{���.�ddlZddlmZdZdd�Zdd�ZdS)�N)�
model_to_dictz /var/imunify360/license_old.jsonFc�(�|rdS|jd}|�ddd����\}}ttd��5}t	jt
|��|��ddd��n#1swxYwY|�|��dS)N�licenseTr)�status�
expiration)�defaults�w)�orm�
get_or_create�open�FALLBACK_LICENSE_FILE�json�dumpr�remove_model)�migrator�database�fake�kwargs�LicenseModel�lic�_�fs        �c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/047_license_in_file.py�migraters��������<�	�*�L�
�
'�
'���
�
�(���F�C��
�#�S�	)�	)�)�Q��	�-��$�$�a�(�(�(�)�)�)�)�)�)�)�)�)�)�)����)�)�)�)����,�'�'�'�'�'s�#A2�2A6�9A6c��dS)N�)rrrrs    r�rollbackrs���D�)F)r�playhouse.shortcutsrr
rrrrr�<module>r sX������-�-�-�-�-�-�:��(�(�(�(�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/048_malware_hits_vendor_field.cpython-311.opt-1.pyc0000644000000000000000000000203600000000000026366 0ustar  �

;���I��F�ddlZddlZeje��Zdd�Zdd�ZdS)�NFc�v�|jd}|�|tjdd������dS)N�malware_hitsF�clamav)�null�default)�vendor)�orm�
add_fields�pw�	CharField��migrator�database�fake�kwargs�MalwareHitss     �m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/048_malware_hits_vendor_field.py�migrater	sI���,�~�.�K�����B�L�e�X�F�F�F�������c�L�|jd}|�|d��dS)Nrr)r	�
remove_fieldsr
s     r�rollbackrs*���,�~�.�K����;��1�1�1�1�1r)F)�logging�peeweer�	getLogger�__name__�loggerrr�rr�<module>rs`����������
��	�8�	$�	$������2�2�2�2�2�2rdefence360agent/migrations/__pycache__/048_malware_hits_vendor_field.cpython-311.pyc0000644000000000000000000000203600000000000025427 0ustar  �

;���I��F�ddlZddlZeje��Zdd�Zdd�ZdS)�NFc�v�|jd}|�|tjdd������dS)N�malware_hitsF�clamav)�null�default)�vendor)�orm�
add_fields�pw�	CharField��migrator�database�fake�kwargs�MalwareHitss     �m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/048_malware_hits_vendor_field.py�migrater	sI���,�~�.�K�����B�L�e�X�F�F�F�������c�L�|jd}|�|d��dS)Nrr)r	�
remove_fieldsr
s     r�rollbackrs*���,�~�.�K����;��1�1�1�1�1r)F)�logging�peeweer�	getLogger�__name__�loggerrr�rr�<module>rs`����������
��	�8�	$�	$������2�2�2�2�2�2rdefence360agent/migrations/__pycache__/049_add_auto_added_field_to_iplist.cpython-311.opt-1.pyc0000644000000000000000000000216300000000000027323 0ustar  �

|&wbI�[���"�dZddlZdd�Zdd�ZdS)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}|�|tjdd������dS)N�iplistFT)�default�null)�auto_whitelisted)�orm�
add_fields�pw�BooleanField��migrator�database�fake�kwargs�IPLists     �r/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/049_add_auto_added_field_to_iplist.py�migrater	sI��
�\�(�
#�F��������T�!J�!J�!J�������c�L�|jd}|�|d��dS)Nrr)r�
remove_fieldsrs     r�rollbackrs+��
�\�(�
#�F����6�#5�6�6�6�6�6r)F)�__doc__�peeweer
rr�rr�<module>rsO����
��������7�7�7�7�7�7rdefence360agent/migrations/__pycache__/049_add_auto_added_field_to_iplist.cpython-311.pyc0000644000000000000000000000216300000000000026364 0ustar  �

|&wbI�[���"�dZddlZdd�Zdd�ZdS)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}|�|tjdd������dS)N�iplistFT)�default�null)�auto_whitelisted)�orm�
add_fields�pw�BooleanField��migrator�database�fake�kwargs�IPLists     �r/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/049_add_auto_added_field_to_iplist.py�migrater	sI��
�\�(�
#�F��������T�!J�!J�!J�������c�L�|jd}|�|d��dS)Nrr)r�
remove_fieldsrs     r�rollbackrs+��
�\�(�
#�F����6�#5�6�6�6�6�6r)F)�__doc__�peeweer
rr�rr�<module>rsO����
��������7�7�7�7�7�7rdefence360agent/migrations/__pycache__/050_fill_auto_whitelisted.cpython-311.opt-1.pyc0000644000000000000000000000202000000000000025534 0ustar  �

9Kɏ�.����dZdd�Zdd�ZdS)zw
Filling `auto_whitelisted` filed that was added in previous 049 migration.
Matching IPs that were auto added earlier.
Fc���|jd}|�d����|j�d�������dS)N�iplistT)�auto_whitelistedzIP auto-whitelisted with)�orm�update�where�comment�
startswith�execute)�migrator�database�fake�kwargs�IPLists     �i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/050_fill_auto_whitelisted.py�migratersR��
�\�(�
#�F�
�M�M�4�M�(�(�.�.���!�!�"<�=�=���
�g�i�i�i�i�i�c��dS)N�)rrr
rs    r�rollbackrs���DrN)F)�__doc__rrrrr�<module>rsA��������	�	�	�	�	�	rdefence360agent/migrations/__pycache__/050_fill_auto_whitelisted.cpython-311.pyc0000644000000000000000000000202000000000000024575 0ustar  �

9Kɏ�.����dZdd�Zdd�ZdS)zw
Filling `auto_whitelisted` filed that was added in previous 049 migration.
Matching IPs that were auto added earlier.
Fc���|jd}|�d����|j�d�������dS)N�iplistT)�auto_whitelistedzIP auto-whitelisted with)�orm�update�where�comment�
startswith�execute)�migrator�database�fake�kwargs�IPLists     �i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/050_fill_auto_whitelisted.py�migratersR��
�\�(�
#�F�
�M�M�4�M�(�(�.�.���!�!�"<�=�=���
�g�i�i�i�i�i�c��dS)N�)rrr
rs    r�rollbackrs���DrN)F)�__doc__rrrrr�<module>rsA��������	�	�	�	�	�	rdefence360agent/migrations/__pycache__/051_cleanup_vd_license.cpython-311.opt-1.pyc0000644000000000000000000000462100000000000025005 0ustar  �

xnT䎳���l�ddlZddlZddlmZeje��ZGd�d��Zdd�Zdd�Z	dS)	�N)�suppressc��eZdZdZd�Zd�ZdS)�VirusdieLicensez/usr/local/vdserver/config.jsonc�0�|�d��dS)N�)�
_write_key)�selfs �f/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/051_cleanup_vd_license.py�
unregisterzVirusdieLicense.unregisters�����������c��t|j��5}tj|��}ddd��n#1swxYwY||d<t|jd��5}tj||ddd���ddd��dS#1swxYwYdS)N�	vdbApiKey�wT�)�,z: )�	sort_keys�indent�
separators)�open�CONFIG_FILE�json�load�dump)r	�key�	read_file�content�
write_files     r
rzVirusdieLicense._write_keys��
�$�"�
#�
#�	+�y��i�	�*�*�G�	+�	+�	+�	+�	+�	+�	+�	+�	+�	+�	+����	+�	+�	+�	+� #����
�$�"�C�
(�
(�	�J��I�����&�
�
�
�
�	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	�	s�6�:�:�B�B�	BN)�__name__�
__module__�__qualname__rrr�rr
rrs7������3�K����
�
�
�
�
rrFc��tt��5t�����ddd��dS#1swxYwYdS)zWrite your migrations here.N)r�FileNotFoundErrorrr��migrator�database�fake�kwargss    r
�migrater)s���
�#�	$�	$�'�'����$�$�&�&�&�'�'�'�'�'�'�'�'�'�'�'�'����'�'�'�'�'�'s�!A�A�
Ac��dS)z$Write your rollback migrations here.Nr!r$s    r
�rollbackr+%s���Dr)F)
r�logging�
contextlibr�	getLoggerr�loggerrr)r+r!rr
�<module>r0s�����������������	��	�8�	$�	$����������,'�'�'�'�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/051_cleanup_vd_license.cpython-311.pyc0000644000000000000000000000462100000000000024046 0ustar  �

xnT䎳���l�ddlZddlZddlmZeje��ZGd�d��Zdd�Zdd�Z	dS)	�N)�suppressc��eZdZdZd�Zd�ZdS)�VirusdieLicensez/usr/local/vdserver/config.jsonc�0�|�d��dS)N�)�
_write_key)�selfs �f/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/051_cleanup_vd_license.py�
unregisterzVirusdieLicense.unregisters�����������c��t|j��5}tj|��}ddd��n#1swxYwY||d<t|jd��5}tj||ddd���ddd��dS#1swxYwYdS)N�	vdbApiKey�wT�)�,z: )�	sort_keys�indent�
separators)�open�CONFIG_FILE�json�load�dump)r	�key�	read_file�content�
write_files     r
rzVirusdieLicense._write_keys��
�$�"�
#�
#�	+�y��i�	�*�*�G�	+�	+�	+�	+�	+�	+�	+�	+�	+�	+�	+����	+�	+�	+�	+� #����
�$�"�C�
(�
(�	�J��I�����&�
�
�
�
�	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	�	s�6�:�:�B�B�	BN)�__name__�
__module__�__qualname__rrr�rr
rrs7������3�K����
�
�
�
�
rrFc��tt��5t�����ddd��dS#1swxYwYdS)zWrite your migrations here.N)r�FileNotFoundErrorrr��migrator�database�fake�kwargss    r
�migrater)s���
�#�	$�	$�'�'����$�$�&�&�&�'�'�'�'�'�'�'�'�'�'�'�'����'�'�'�'�'�'s�!A�A�
Ac��dS)z$Write your rollback migrations here.Nr!r$s    r
�rollbackr+%s���Dr)F)
r�logging�
contextlibr�	getLoggerr�loggerrr)r+r!rr
�<module>r0s�����������������	��	�8�	$�	$����������,'�'�'�'�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/052_whitelisted_crawlers.cpython-311.opt-1.pyc0000644000000000000000000000516000000000000025412 0ustar  �

��`��<��F�ddlZddlZeje��Zdd�Zdd�ZdS)�NFc���Gd�dtj���G�fd�dtj��}|����|�|��dS)zWrite your migrations here.c�h�eZdZGd�d��Zej��Zejd���ZdS)�#migrate.<locals>.WhitelistedCrawlerc��eZdZdZdS)�(migrate.<locals>.WhitelistedCrawler.Meta�whitelisted_crawlersN��__name__�
__module__�__qualname__�db_table���h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/052_whitelisted_crawlers.py�Metar
s������-�H�H�HrrF��nullN)	r
rrr�pw�PrimaryKeyField�id�	TextField�descriptionrrr�WhitelistedCrawlerrs\������	.�	.�	.�	.�	.�	.�	.�	.� �R�
�
!�
!��"�b�l��.�.�.���rrc���eZdZGd�d��Zej��Zej�ddd���Zej	d���Z
dS)	�)migrate.<locals>.WhitelistedCrawlerDomainc��eZdZdZdS)�.migrate.<locals>.WhitelistedCrawlerDomain.Meta�whitelisted_crawler_domainsNr	rrrrrs������4�H�H�HrrF�CASCADE�domains)r�	on_delete�related_namerN)r
rrrrrr�ForeignKeyField�crawlerr�domain)rs�r�WhitelistedCrawlerDomainrs��������	5�	5�	5�	5�	5�	5�	5�	5� �R�
�
!�
!��$�"�$����"�	
�
�
�����5�)�)�)���rr&N)r�Model�create_model)�migrator�database�fake�kwargsr&rs     @r�migrater-	s����/�/�/�/�/�R�X�/�/�/�*�*�*�*�*�*�*�2�8�*�*�*�
���,�-�-�-����2�3�3�3�3�3rc��|�|jd��|�|jd��dS)z$Write your rollback migrations here.rrN)�remove_model�orm)r)r*r+r,s    r�rollbackr1$s@�����(�,�'=�>�?�?�?����(�,�'D�E�F�F�F�F�Fr)F)�logging�peeweer�	getLoggerr
�loggerr-r1rrr�<module>r6sf����������
��	�8�	$�	$��4�4�4�4�6G�G�G�G�G�Grdefence360agent/migrations/__pycache__/052_whitelisted_crawlers.cpython-311.pyc0000644000000000000000000000516000000000000024453 0ustar  �

��`��<��F�ddlZddlZeje��Zdd�Zdd�ZdS)�NFc���Gd�dtj���G�fd�dtj��}|����|�|��dS)zWrite your migrations here.c�h�eZdZGd�d��Zej��Zejd���ZdS)�#migrate.<locals>.WhitelistedCrawlerc��eZdZdZdS)�(migrate.<locals>.WhitelistedCrawler.Meta�whitelisted_crawlersN��__name__�
__module__�__qualname__�db_table���h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/052_whitelisted_crawlers.py�Metar
s������-�H�H�HrrF��nullN)	r
rrr�pw�PrimaryKeyField�id�	TextField�descriptionrrr�WhitelistedCrawlerrs\������	.�	.�	.�	.�	.�	.�	.�	.� �R�
�
!�
!��"�b�l��.�.�.���rrc���eZdZGd�d��Zej��Zej�ddd���Zej	d���Z
dS)	�)migrate.<locals>.WhitelistedCrawlerDomainc��eZdZdZdS)�.migrate.<locals>.WhitelistedCrawlerDomain.Meta�whitelisted_crawler_domainsNr	rrrrrs������4�H�H�HrrF�CASCADE�domains)r�	on_delete�related_namerN)r
rrrrrr�ForeignKeyField�crawlerr�domain)rs�r�WhitelistedCrawlerDomainrs��������	5�	5�	5�	5�	5�	5�	5�	5� �R�
�
!�
!��$�"�$����"�	
�
�
�����5�)�)�)���rr&N)r�Model�create_model)�migrator�database�fake�kwargsr&rs     @r�migrater-	s����/�/�/�/�/�R�X�/�/�/�*�*�*�*�*�*�*�2�8�*�*�*�
���,�-�-�-����2�3�3�3�3�3rc��|�|jd��|�|jd��dS)z$Write your rollback migrations here.rrN)�remove_model�orm)r)r*r+r,s    r�rollbackr1$s@�����(�,�'=�>�?�?�?����(�,�'D�E�F�F�F�F�Fr)F)�logging�peeweer�	getLoggerr
�loggerr-r1rrr�<module>r6sf����������
��	�8�	$�	$��4�4�4�4�6G�G�G�G�G�Grdefence360agent/migrations/__pycache__/053_populate_whitelisted_crawlers.cpython-311.opt-1.pyc0000644000000000000000000000404100000000000027321 0ustar  �

D���o�����h�ddlZeje��Zdddgfdgd�fddgfd	d
dgfgZdd
�Zdd�ZdS)�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]+}	|�||	�������,�\	ddd��dS#1swxYwYdS)zWrite your migrations here.N�whitelisted_crawlers�whitelisted_crawler_domains)�description)�crawler�domain)�orm�atomic�DATA�insert�execute)
�migrator�database�fake�kwargs�wc�wcd�descr�domains�inserted_id�ds
          �q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/053_populate_whitelisted_crawlers.py�migrater"s'������	��,�	-�B�
�,�4�
5�C�	���	�	�D�D�"�	D�	D�N�E�7��)�)��)�6�6�>�>�@�@�K��
D�
D���
�
�;�q�
�9�9�A�A�C�C�C�C�
D�	D�D�D�D�D�D�D�D�D�D�D�D�D����D�D�D�D�D�Ds�A$B%�%B)�,B)c��dS)z$Write your rollback migrations here.N�)rrrrs    r�rollbackr1s���D�)F)�logging�	getLogger�__name__�loggerrrrrrr�<module>r"s�������
��	�8�	$�	$��	L�	�(�)��
d�	5�4�4��
X�	��	�
Q�
�{�#��%��8D�D�D�D�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/053_populate_whitelisted_crawlers.cpython-311.pyc0000644000000000000000000000404100000000000026362 0ustar  �

D���o�����h�ddlZeje��Zdddgfdgd�fddgfd	d
dgfgZdd
�Zdd�ZdS)�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]+}	|�||	�������,�\	ddd��dS#1swxYwYdS)zWrite your migrations here.N�whitelisted_crawlers�whitelisted_crawler_domains)�description)�crawler�domain)�orm�atomic�DATA�insert�execute)
�migrator�database�fake�kwargs�wc�wcd�descr�domains�inserted_id�ds
          �q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/053_populate_whitelisted_crawlers.py�migrater"s'������	��,�	-�B�
�,�4�
5�C�	���	�	�D�D�"�	D�	D�N�E�7��)�)��)�6�6�>�>�@�@�K��
D�
D���
�
�;�q�
�9�9�A�A�C�C�C�C�
D�	D�D�D�D�D�D�D�D�D�D�D�D�D����D�D�D�D�D�Ds�A$B%�%B)�,B)c��dS)z$Write your rollback migrations here.N�)rrrrs    r�rollbackr1s���D�)F)�logging�	getLogger�__name__�loggerrrrrrr�<module>r"s�������
��	�8�	$�	$��	L�	�(�)��
d�	5�4�4��
X�	��	�
Q�
�{�#��%��8D�D�D�D�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/054_add_malicious_and_added_date_fileds.cpython-311.opt-1.pyc0000644000000000000000000000302500000000000030246 0ustar  �

��Z ͌���2�ddlmZddlmZmZdd�Zdd�ZdS)�)�time)�BooleanField�IntegerFieldFc���|jd}|�|tdd������|jd}|�|tdd�������dS)N�malware_hitsF)�null�default)�	malicious�malware_ignore_pathc�8�tt����S)N)�intr���w/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/054_add_malicious_and_added_date_fileds.py�<lambda>zmigrate.<locals>.<lambda>s��C����K�K�r)�
added_date)�orm�
add_fieldsrr��migrator�database�fake�kwargs�MalwareHits�MalwareIgnorePaths      r�migraters����,�~�.�K�����|���F�F�F�����!��%:�;�������U�4G�4G�H�H�H������rc��|jd}|�|d��|jd}|�|d��dS)Nrr
rr)r�
remove_fieldsrs      r�rollbackrsO���,�~�.�K����;��4�4�4� ��%:�;�����,�l�;�;�;�;�;rN)F)r�peeweerrrrrrr�<module>r!sa��������-�-�-�-�-�-�-�-�	�	�	�	�<�<�<�<�<�<rdefence360agent/migrations/__pycache__/054_add_malicious_and_added_date_fileds.cpython-311.pyc0000644000000000000000000000302500000000000027307 0ustar  �

��Z ͌���2�ddlmZddlmZmZdd�Zdd�ZdS)�)�time)�BooleanField�IntegerFieldFc���|jd}|�|tdd������|jd}|�|tdd�������dS)N�malware_hitsF)�null�default)�	malicious�malware_ignore_pathc�8�tt����S)N)�intr���w/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/054_add_malicious_and_added_date_fileds.py�<lambda>zmigrate.<locals>.<lambda>s��C����K�K�r)�
added_date)�orm�
add_fieldsrr��migrator�database�fake�kwargs�MalwareHits�MalwareIgnorePaths      r�migraters����,�~�.�K�����|���F�F�F�����!��%:�;�������U�4G�4G�H�H�H������rc��|jd}|�|d��|jd}|�|d��dS)Nrr
rr)r�
remove_fieldsrs      r�rollbackrsO���,�~�.�K����;��4�4�4� ��%:�;�����,�l�;�;�;�;�;rN)F)r�peeweerrrrrrr�<module>r!sa��������-�-�-�-�-�-�-�-�	�	�	�	�<�<�<�<�<�<rdefence360agent/migrations/__pycache__/055_migrate_move_to_quar_option.cpython-311.opt-1.pyc0000644000000000000000000000242500000000000026767 0ustar  �

�����`����@�ddlmZmZde��d�defd�Zd�ZdS)�)�
ConfigFile�IConfigF)�fake�config_filerc��|rdS|���x}sdS|�di��}|�dd��|�dd��|�dd��|�|dd���dS)N�MALWARE_SCANNING�leave_suspicious�max_days_in_quarantine�move_to_quarantineFT)�	overwrite�validate)�config_to_dict�get�pop�dict_to_config)rr�_�__�config�malware_settingss      �o/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/055_migrate_move_to_quar_option.py�migraters�������!�0�0�2�2�2�F�����z�z�"4�b�9�9�����+�T�2�2�2����1�4�8�8�8����-�u�5�5�5����v����F�F�F�F�F�c��dS)N�)rrs  r�rollbackrs���DrN)� defence360agent.contracts.configrrrrrrr�<module>rso��@�@�@�@�@�@�@�@��:�:�<�<�
G�
G�
G��
G�
G�
G�
G�	�	�	�	�	rdefence360agent/migrations/__pycache__/055_migrate_move_to_quar_option.cpython-311.pyc0000644000000000000000000000242500000000000026030 0ustar  �

�����`����@�ddlmZmZde��d�defd�Zd�ZdS)�)�
ConfigFile�IConfigF)�fake�config_filerc��|rdS|���x}sdS|�di��}|�dd��|�dd��|�dd��|�|dd���dS)N�MALWARE_SCANNING�leave_suspicious�max_days_in_quarantine�move_to_quarantineFT)�	overwrite�validate)�config_to_dict�get�pop�dict_to_config)rr�_�__�config�malware_settingss      �o/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/055_migrate_move_to_quar_option.py�migraters�������!�0�0�2�2�2�F�����z�z�"4�b�9�9�����+�T�2�2�2����1�4�8�8�8����-�u�5�5�5����v����F�F�F�F�F�c��dS)N�)rrs  r�rollbackrs���DrN)� defence360agent.contracts.configrrrrrrr�<module>rso��@�@�@�@�@�@�@�@��:�:�<�<�
G�
G�
G��
G�
G�
G�
G�	�	�	�	�	rdefence360agent/migrations/__pycache__/056_populate_malicious_with_quarantined.cpython-311.opt-1.pyc0000644000000000000000000000100500000000000030505 0ustar  �

�;x\=c�����dZd�Zd�ZdS)z# Quarantine is removed in DEF-15234c��dS�N���_�__s  �w/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/056_populate_malicious_with_quarantined.py�migrater	����D�c��dSrrrs  r�rollbackr
r
rN)�__doc__r	r
rrr�<module>rs3��)�)�	�	�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/056_populate_malicious_with_quarantined.cpython-311.pyc0000644000000000000000000000100500000000000027546 0ustar  �

�;x\=c�����dZd�Zd�ZdS)z# Quarantine is removed in DEF-15234c��dS�N���_�__s  �w/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/056_populate_malicious_with_quarantined.py�migrater	����D�c��dSrrrs  r�rollbackr
r
rN)�__doc__r	r
rrr�<module>rs3��)�)�	�	�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/057_filename_is_blob.cpython-311.opt-1.pyc0000644000000000000000000000315200000000000024440 0ustar  �

�>� G ��>�ddlZeje��Zdd�Zdd�ZdS)�NFc��|�d��|�d��|�d��|�d��dS)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)�sql��migrator�database�fake�kwargss    �d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/057_filename_is_blob.py�migraters`��
�L�L�	����
�L�L�J�K�K�K��L�L�*�+�+�+��L�L�F�G�G�G�G�G�c��dS)z$Write your rollback migrations here.N�rs    r
�rollbackr!s���Dr)F)�logging�	getLogger�__name__�loggerrrrrr
�<module>rsX������
��	�8�	$�	$��H�H�H�H�4	�	�	�	�	�	rdefence360agent/migrations/__pycache__/057_filename_is_blob.cpython-311.pyc0000644000000000000000000000315200000000000023501 0ustar  �

�>� G ��>�ddlZeje��Zdd�Zdd�ZdS)�NFc��|�d��|�d��|�d��|�d��dS)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)�sql��migrator�database�fake�kwargss    �d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/057_filename_is_blob.py�migraters`��
�L�L�	����
�L�L�J�K�K�K��L�L�*�+�+�+��L�L�F�G�G�G�G�G�c��dS)z$Write your rollback migrations here.N�rs    r
�rollbackr!s���Dr)F)�logging�	getLogger�__name__�loggerrrrrr
�<module>rsX������
��	�8�	$�	$��H�H�H�H�4	�	�	�	�	�	rdefence360agent/migrations/__pycache__/058_convert_license_last_attempt.cpython-311.opt-1.pyc0000644000000000000000000000057400000000000027140 0ustar  �

��a�����dd�ZdS)Fc��dS)N�)�migrator�database�fake�kwargss    �p/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/058_convert_license_last_attempt.py�migrater	s���D�N)F)r	rr
r�<module>rs#��	�	�	�	�	�	r
defence360agent/migrations/__pycache__/058_convert_license_last_attempt.cpython-311.pyc0000644000000000000000000000057400000000000026201 0ustar  �

��a�����dd�ZdS)Fc��dS)N�)�migrator�database�fake�kwargss    �p/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/058_convert_license_last_attempt.py�migrater	s���D�N)F)r	rr
r�<module>rs#��	�	�	�	�	�	r
defence360agent/migrations/__pycache__/059_scans_error_field.cpython-311.opt-1.pyc0000644000000000000000000000164400000000000024660 0ustar  �

0���4d���ddlZdd�Zdd�ZdS)�NFc�v�|jd}|�|tjdd������dS)N�
malware_scansT)�default�null)�error)�orm�
add_fields�pw�	TextField��migrator�database�fake�kwargs�MalwareScanss     �e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/059_scans_error_field.py�migratersI���<��0�L�����B�L��D�A�A�A�������c�L�|jd}|�|d��dS)Nrr)r�
remove_fieldsrs     r�rollbackrs*���<��0�L����<��1�1�1�1�1r)F)�peeweer
rr�rr�<module>rsC����������2�2�2�2�2�2rdefence360agent/migrations/__pycache__/059_scans_error_field.cpython-311.pyc0000644000000000000000000000164400000000000023721 0ustar  �

0���4d���ddlZdd�Zdd�ZdS)�NFc�v�|jd}|�|tjdd������dS)N�
malware_scansT)�default�null)�error)�orm�
add_fields�pw�	TextField��migrator�database�fake�kwargs�MalwareScanss     �e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/059_scans_error_field.py�migratersI���<��0�L�����B�L��D�A�A�A�������c�L�|jd}|�|d��dS)Nrr)r�
remove_fieldsrs     r�rollbackrs*���<��0�L����<��1�1�1�1�1r)F)�peeweer
rr�rr�<module>rsC����������2�2�2�2�2�2rdefence360agent/migrations/__pycache__/061_migrate_backup_system_conf.cpython-311.opt-1.pyc0000644000000000000000000000406200000000000026551 0ustar  �

��r�|�"���dZddlZddlmZddlmZmZmZmZddl	m
Z
e
jde��dfdedeefd	���Ze
jdd
���Z
dS)zT
Migrate backup config from user oriented config file
to the separate internal file
�N)�Optional)�BackupConfig�IConfig�IConfigFile�LocalConfig)�antivirus_modeF�config_file�backup_config_filec��|rdS|�t��}|���x}sdStj�|j��rdS|�di��}d|�dd��|�dd��d�i}|�|dd���|�|dd���dS)	N�BACKUP_RESTORE�
BACKUP_SYSTEM�enabledF�
backup_system)rrT)�	overwrite�validate)r�config_to_dict�os�path�exists�get�pop�dict_to_config)	�migrator�database�faker	r
�kwargs�config_from�backup_conf_current�	config_tos	         �n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/061_migrate_backup_system_conf.py�migrater!s��������!�)�^�^��&�5�5�7�7�7�K����
�w�~�~�(�-�.�.����%�/�/�*:�B�?�?���*�.�.�y�%�@�@�0�4�4�_�d�K�K�
�
��I��%�%��T�E�&�������{�d�U��K�K�K�K�K�c��dS)z$Write your rollback migrations here.N�)rrrrs    r �rollbackr%2s	��	�Dr")F)�__doc__r�typingr� defence360agent.contracts.configrrrr�defence360agent.utilsr�skipr!r%r$r"r �<module>r+s����
�	�	�	�������������������1�0�0�0�0�0���
�&�;�=�=�04�L�L��	L�
!��-�L�L�L���L�@��	�	�	���	�	�	r"defence360agent/migrations/__pycache__/061_migrate_backup_system_conf.cpython-311.pyc0000644000000000000000000000406200000000000025612 0ustar  �

��r�|�"���dZddlZddlmZddlmZmZmZmZddl	m
Z
e
jde��dfdedeefd	���Ze
jdd
���Z
dS)zT
Migrate backup config from user oriented config file
to the separate internal file
�N)�Optional)�BackupConfig�IConfig�IConfigFile�LocalConfig)�antivirus_modeF�config_file�backup_config_filec��|rdS|�t��}|���x}sdStj�|j��rdS|�di��}d|�dd��|�dd��d�i}|�|dd���|�|dd���dS)	N�BACKUP_RESTORE�
BACKUP_SYSTEM�enabledF�
backup_system)rrT)�	overwrite�validate)r�config_to_dict�os�path�exists�get�pop�dict_to_config)	�migrator�database�faker	r
�kwargs�config_from�backup_conf_current�	config_tos	         �n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/061_migrate_backup_system_conf.py�migrater!s��������!�)�^�^��&�5�5�7�7�7�K����
�w�~�~�(�-�.�.����%�/�/�*:�B�?�?���*�.�.�y�%�@�@�0�4�4�_�d�K�K�
�
��I��%�%��T�E�&�������{�d�U��K�K�K�K�K�c��dS)z$Write your rollback migrations here.N�)rrrrs    r �rollbackr%2s	��	�Dr")F)�__doc__r�typingr� defence360agent.contracts.configrrrr�defence360agent.utilsr�skipr!r%r$r"r �<module>r+s����
�	�	�	�������������������1�0�0�0�0�0���
�&�;�=�=�04�L�L��	L�
!��-�L�L�L���L�@��	�	�	���	�	�	r"defence360agent/migrations/__pycache__/062_drop_malware_extra_data.cpython-311.opt-1.pyc0000644000000000000000000000135200000000000026033 0ustar  �

?ԡ1Ή����dd�Zdd�ZdS)Fc�J�|jd}|�|��dS)zWrite your migrations here.�malware_hit_extrasN)�orm�remove_model)�migrator�database�fake�kwargs�MalwareExtraDatas     �k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/062_drop_malware_extra_data.py�migraters+���|�$8�9�����*�+�+�+�+�+�c��dS)z$Write your rollback migrations here.N�)rrrr	s    r�rollbackrs���Dr
N)F)rrrr
r�<module>rs7��,�,�,�,�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/062_drop_malware_extra_data.cpython-311.pyc0000644000000000000000000000135200000000000025074 0ustar  �

?ԡ1Ή����dd�Zdd�ZdS)Fc�J�|jd}|�|��dS)zWrite your migrations here.�malware_hit_extrasN)�orm�remove_model)�migrator�database�fake�kwargs�MalwareExtraDatas     �k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/062_drop_malware_extra_data.py�migraters+���|�$8�9�����*�+�+�+�+�+�c��dS)z$Write your rollback migrations here.N�)rrrr	s    r�rollbackrs���Dr
N)F)rrrr
r�<module>rs7��,�,�,�,�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/062_fix_null_expiration.cpython-311.opt-1.pyc0000644000000000000000000000205500000000000025246 0ustar  �

���R�����dZdd�Zdd�ZdS)z>
Fix IPs that were added with NULL expiration to the WB lists
Fc��|jd}|�d����|j�ddg��|j���z�����dS)N�iplist�)�
expiration�WHITE�BLACK)�orm�update�where�listname�in_r�is_null�execute)�migrator�database�fake�kwargs�IPListModels     �g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/062_fix_null_expiration.py�migraterss���,�x�(�K����!��$�$�*�*�	�	�	!�	!�7�G�"4�	5�	5��!�)�)�+�+�	-����g�i�i�i�i�i�c��dS)N�)rrrrs    r�rollbackrs���DrN)F)�__doc__rrrrr�<module>rsA����
����	�	�	�	�	�	rdefence360agent/migrations/__pycache__/062_fix_null_expiration.cpython-311.pyc0000644000000000000000000000205500000000000024307 0ustar  �

���R�����dZdd�Zdd�ZdS)z>
Fix IPs that were added with NULL expiration to the WB lists
Fc��|jd}|�d����|j�ddg��|j���z�����dS)N�iplist�)�
expiration�WHITE�BLACK)�orm�update�where�listname�in_r�is_null�execute)�migrator�database�fake�kwargs�IPListModels     �g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/062_fix_null_expiration.py�migraterss���,�x�(�K����!��$�$�*�*�	�	�	!�	!�7�G�"4�	5�	5��!�)�)�+�+�	-����g�i�i�i�i�i�c��dS)N�)rrrrs    r�rollbackrs���DrN)F)�__doc__rrrrr�<module>rsA����
����	�	�	�	�	�	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  �

��x�mp���dd�Zdd�ZdS)Fc���|jd}|�|j����|jdk|j|jkz�����dS)N�iplist)�
expiration�GRAY)�orm�update�dos_expiration�where�listnamer�execute)�migrator�database�fake�kwargs�IPListModels     �/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/063_fix_graylist_doslist_expiration_discrepancy.py�migraterse���,�x�(�K����+�"<��=�=�C�C�	�	��	'��!�K�$>�>�	@����g�i�i�i�i�i�c��dS)N�)rr
rrs    r�rollbackr	s���DrN)F)rrrrr�<module>rs7������	�	�	�	�	�	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  �

��x�mp���dd�Zdd�ZdS)Fc���|jd}|�|j����|jdk|j|jkz�����dS)N�iplist)�
expiration�GRAY)�orm�update�dos_expiration�where�listnamer�execute)�migrator�database�fake�kwargs�IPListModels     �/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/063_fix_graylist_doslist_expiration_discrepancy.py�migraterse���,�x�(�K����+�"<��=�=�C�C�	�	��	'��!�K�$>�>�	@����g�i�i�i�i�i�c��dS)N�)rr
rrs    r�rollbackr	s���DrN)F)rrrrr�<module>rs7������	�	�	�	�	�	rdefence360agent/migrations/__pycache__/064_chmod_i360deploy_log.cpython-311.opt-1.pyc0000644000000000000000000000170300000000000025076 0ustar  �

���0բN��.�ddlmZddlZdZdd�Zdd�ZdS)�)�suppressNz/var/log/i360deploy.logFc��tt��5tjtd��ddd��dS#1swxYwYdS)Ni�)r�FileNotFoundError�os�chmod�I360DEPLOY_LOG��migrator�database�fake�kwargss    �h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/064_chmod_i360deploy_log.py�migraters���	�#�	$�	$�(�(�
����'�'�'�(�(�(�(�(�(�(�(�(�(�(�(����(�(�(�(�(�(s�=�A�Ac��dS)N�r	s    r�rollbackrs���D�)F)�
contextlibrrrrrrrr�<module>rsW��������	�	�	�	�*��(�(�(�(�
	�	�	�	�	�	rdefence360agent/migrations/__pycache__/064_chmod_i360deploy_log.cpython-311.pyc0000644000000000000000000000170300000000000024137 0ustar  �

���0բN��.�ddlmZddlZdZdd�Zdd�ZdS)�)�suppressNz/var/log/i360deploy.logFc��tt��5tjtd��ddd��dS#1swxYwYdS)Ni�)r�FileNotFoundError�os�chmod�I360DEPLOY_LOG��migrator�database�fake�kwargss    �h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/064_chmod_i360deploy_log.py�migraters���	�#�	$�	$�(�(�
����'�'�'�(�(�(�(�(�(�(�(�(�(�(�(����(�(�(�(�(�(s�=�A�Ac��dS)N�r	s    r�rollbackrs���D�)F)�
contextlibrrrrrrrr�<module>rsW��������	�	�	�	�*��(�(�(�(�
	�	�	�	�	�	rdefence360agent/migrations/__pycache__/065_remove_capture_csf_lock_from_config.cpython-311.opt-1.pyc0000644000000000000000000000242600000000000030424 0ustar  �

7��c)���R�ddlZddlZddlmZeje��Zdd�Zdd�ZdS)�N)�LocalConfigFc��|rdSt��}tj�|j��sdS|���}d|vr/|�d��|�|dd���dSdS)N�CSF_COOPERATIONTF)�	overwrite�validate)r�os�path�exists�config_to_dict�pop�dict_to_config)�migrator�database�fake�kwargs�local_config�configs      �w/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/065_remove_capture_csf_lock_from_config.py�migrater	s��������=�=�L�
�7�>�>�,�+�,�,����
�
(�
(�
*�
*�F��F�"�"��
�
�$�%�%�%��#�#�F�d�U�#�K�K�K�K�K�#�"�c��dS)z$Write your rollback migrations here.N�)rrrrs    r�rollbackrs���Dr)F)	�loggingr� defence360agent.contracts.configr�	getLogger�__name__�loggerrrrrr�<module>rsq������	�	�	�	�8�8�8�8�8�8�	��	�8�	$�	$��L�L�L�L�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/065_remove_capture_csf_lock_from_config.cpython-311.pyc0000644000000000000000000000242600000000000027465 0ustar  �

7��c)���R�ddlZddlZddlmZeje��Zdd�Zdd�ZdS)�N)�LocalConfigFc��|rdSt��}tj�|j��sdS|���}d|vr/|�d��|�|dd���dSdS)N�CSF_COOPERATIONTF)�	overwrite�validate)r�os�path�exists�config_to_dict�pop�dict_to_config)�migrator�database�fake�kwargs�local_config�configs      �w/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/065_remove_capture_csf_lock_from_config.py�migrater	s��������=�=�L�
�7�>�>�,�+�,�,����
�
(�
(�
*�
*�F��F�"�"��
�
�$�%�%�%��#�#�F�d�U�#�K�K�K�K�K�#�"�c��dS)z$Write your rollback migrations here.N�)rrrrs    r�rollbackrs���Dr)F)	�loggingr� defence360agent.contracts.configr�	getLogger�__name__�loggerrrrrr�<module>rsq������	�	�	�	�8�8�8�8�8�8�	��	�8�	$�	$��L�L�L�L�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/066_eula_table.cpython-311.opt-1.pyc0000644000000000000000000000261600000000000023270 0ustar  �

�x�{�8����D�ddlZGd�dej��Zdd�Zdd�ZdS)�Nc�n�eZdZGd�d��Zejd���Zejdd���ZdS)�Eulac��eZdZdZdS)�	Eula.Meta�eulaN)�__name__�
__module__�__qualname__�db_table���^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/066_eula_table.py�Metars���������r
rT)�primary_keyN)�null�default)	rr	r
r�pw�	DateField�updated�IntegerField�acceptedrr
rrrsb���������������b�l�t�,�,�,�G��r��D�$�7�7�7�H�H�Hr
rFc�:�|�t��dS)N)�create_modelr)�migrator�database�fake�kwargss    r�migraters�����$�����r
c�J�|jd}|�|��dS)Nr)�orm�remove_model)rrrrrs     r�rollbackr"s(���<���D����$�����r
)F)�peeweer�Modelrrr"rr
r�<module>r%so������8�8�8�8�8�2�8�8�8�8� � � � � � � � � � r
defence360agent/migrations/__pycache__/066_eula_table.cpython-311.pyc0000644000000000000000000000261600000000000022331 0ustar  �

�x�{�8����D�ddlZGd�dej��Zdd�Zdd�ZdS)�Nc�n�eZdZGd�d��Zejd���Zejdd���ZdS)�Eulac��eZdZdZdS)�	Eula.Meta�eulaN)�__name__�
__module__�__qualname__�db_table���^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/066_eula_table.py�Metars���������r
rT)�primary_keyN)�null�default)	rr	r
r�pw�	DateField�updated�IntegerField�acceptedrr
rrrsb���������������b�l�t�,�,�,�G��r��D�$�7�7�7�H�H�Hr
rFc�:�|�t��dS)N)�create_modelr)�migrator�database�fake�kwargss    r�migraters�����$�����r
c�J�|jd}|�|��dS)Nr)�orm�remove_model)rrrrrs     r�rollbackr"s(���<���D����$�����r
)F)�peeweer�Modelrrr"rr
r�<module>r%so������8�8�8�8�8�2�8�8�8�8� � � � � � � � � � r
defence360agent/migrations/__pycache__/067_drop_fields_from_modsec_conf.cpython-311.opt-1.pyc0000644000000000000000000000231300000000000027042 0ustar  �

%�k�<{U��@�ddlmZmZde��fdefd�Zdd�ZdS)�)�IConfig�LocalConfigF�config_filec���|rdS|���}|sdS|�di��}|�dd��|�dd��|�|dd���dS)N�MOD_SEC�
was_installed�
OWASP_deletedFT)�validate�	overwrite)�config_to_dict�
setdefault�pop�dict_to_config)�migrator�database�faker�kwargs�conf�mod_sec_settingss       �p/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/067_drop_fields_from_modsec_conf.py�migraters��������%�%�'�'�D��������y�"�5�5������$�/�/�/�����$�/�/�/����t�e�t��D�D�D�D�D�c��dS)N�)rrrrs    r�rollbackrs���DrN)F)� defence360agent.contracts.configrrrrrrr�<module>rsr��A�A�A�A�A�A�A�A�
�&�;�=�=�	E�E��	E�E�E�E�*	�	�	�	�	�	rdefence360agent/migrations/__pycache__/067_drop_fields_from_modsec_conf.cpython-311.pyc0000644000000000000000000000231300000000000026103 0ustar  �

%�k�<{U��@�ddlmZmZde��fdefd�Zdd�ZdS)�)�IConfig�LocalConfigF�config_filec���|rdS|���}|sdS|�di��}|�dd��|�dd��|�|dd���dS)N�MOD_SEC�
was_installed�
OWASP_deletedFT)�validate�	overwrite)�config_to_dict�
setdefault�pop�dict_to_config)�migrator�database�faker�kwargs�conf�mod_sec_settingss       �p/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/067_drop_fields_from_modsec_conf.py�migraters��������%�%�'�'�D��������y�"�5�5������$�/�/�/�����$�/�/�/����t�e�t��D�D�D�D�D�c��dS)N�)rrrrs    r�rollbackrs���DrN)F)� defence360agent.contracts.configrrrrrrr�<module>rsr��A�A�A�A�A�A�A�A�
�&�;�=�=�	E�E��	E�E�E�E�*	�	�	�	�	�	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  �

�q0�
��J�ddlZddlmZeje��Zdd�Zdd�ZdS)�N)�
ConfigFileFc��|rdSt��}|���}|sdSd|vr/|�d��|�|dd���dSdS)N�IPTABLES_RULE_CHECKTF)�	overwrite�validate)r�config_to_dict�pop�dict_to_config)�migrator�database�fake�kwargs�config_file�configs      �{/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/068_remove_rules_check_interval_from_config.py�migraters}�������,�,�K�
�
'�
'�
)�
)�F�������&�&��
�
�(�)�)�)��"�"�6�T�E�"�J�J�J�J�J�'�&�c��dS)z$Write your rollback migrations here.N�)rrr
rs    r�rollbackrs���Dr)F)�logging� defence360agent.contracts.configr�	getLogger�__name__�loggerrrrrr�<module>rsh������7�7�7�7�7�7�	��	�8�	$�	$��K�K�K�K�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/068_remove_rules_check_interval_from_config.cpython-311.pyc0000644000000000000000000000223700000000000030355 0ustar  �

�q0�
��J�ddlZddlmZeje��Zdd�Zdd�ZdS)�N)�
ConfigFileFc��|rdSt��}|���}|sdSd|vr/|�d��|�|dd���dSdS)N�IPTABLES_RULE_CHECKTF)�	overwrite�validate)r�config_to_dict�pop�dict_to_config)�migrator�database�fake�kwargs�config_file�configs      �{/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/068_remove_rules_check_interval_from_config.py�migraters}�������,�,�K�
�
'�
'�
)�
)�F�������&�&��
�
�(�)�)�)��"�"�6�T�E�"�J�J�J�J�J�'�&�c��dS)z$Write your rollback migrations here.N�)rrr
rs    r�rollbackrs���Dr)F)�logging� defence360agent.contracts.configr�	getLogger�__name__�loggerrrrrr�<module>rsh������7�7�7�7�7�7�	��	�8�	$�	$��K�K�K�K�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/069_incidents_domain_field.cpython-311.opt-1.pyc0000644000000000000000000000163000000000000025643 0ustar  �

Hsb|#������ddlZdd�Zdd�ZdS)�NFc�v�|jd}|�|tjdd������dS)N�incidentT)�default�null)�domain)�orm�
add_fields�pw�	TextField��migrator�database�fake�kwargs�Incidents     �j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/069_incidents_domain_field.py�migraters<���|�J�'�H�������d��)N�)N�)N��O�O�O�O�O�c�L�|jd}|�|d��dS)Nrr)r�
remove_fieldsrs     r�rollbackr	s*���|�J�'�H����8�X�.�.�.�.�.r)F)�peeweer
rr�rr�<module>rsG������P�P�P�P�
/�/�/�/�/�/rdefence360agent/migrations/__pycache__/069_incidents_domain_field.cpython-311.pyc0000644000000000000000000000163000000000000024704 0ustar  �

Hsb|#������ddlZdd�Zdd�ZdS)�NFc�v�|jd}|�|tjdd������dS)N�incidentT)�default�null)�domain)�orm�
add_fields�pw�	TextField��migrator�database�fake�kwargs�Incidents     �j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/069_incidents_domain_field.py�migraters<���|�J�'�H�������d��)N�)N�)N��O�O�O�O�O�c�L�|jd}|�|d��dS)Nrr)r�
remove_fieldsrs     r�rollbackr	s*���|�J�'�H����8�X�.�.�.�.�.r)F)�peeweer
rr�rr�<module>rsG������P�P�P�P�
/�/�/�/�/�/rdefence360agent/migrations/__pycache__/070_modsec_incident_names.cpython-311.opt-1.pyc0000644000000000000000000000605600000000000025502 0ustar  �

*=/��Z����(�ddlmZd�Zdd�Zdd�ZdS)�)�
TemporaryFilec�<�|�dd���dS)N�||���maxsplitr)�split)�descriptions �i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/070_modsec_incident_names.py�extract_namers �����T�A��.�.�q�1�1�Fc
��
�|jd�
�
fd�}td���5}|��D];\}}|�d�|t	|�������<|�d��|���5|D]}|�dd�	��\}}	�
�|	�	���
���
�
jt|��k���
����	ddd��n#1swxYwYddd��dS#1swxYwYdS)z�
    This migration extracts incident name from whole mod_security message
    Centos6 version of sqlite does not have instr(), using slow python-based
    way
    �incidentc3�R�K�	���j�j����jdk����j�d����������Ed{V��dS#t$rYdSwxYw)N�modsecr)	�select�idr
�where�plugin�contains�tuples�iterator�RuntimeError)rs�r�select_incidentsz!migrate.<locals>.select_incidentss������		������X�-A�B�B���x��(�2�3�3���x�+�4�4�T�:�:�;�;���������
�
�
�
�
�
�
�
�
���	�	�	��F�F�	���s�BB�
B&�%B&zw+)�modez{},{}
r�,rr)�nameN)�ormr�write�formatr�seek�atomicr	�update�striprr�int�execute)�migrator�database�fake�kwargsr�f�id_�desc�linerrs          @r�migrater/s�����|�J�'�H�
�
�
�
�
�
�D�	!�	!�	!�	�Q�)�)�+�+�	?�	?�I�C��
�G�G�I�$�$�S�,�t�*<�*<�=�=�>�>�>�>�	���q�	�	�	�
�_�_�
�
�	�	��
�
�� �J�J�s�Q�J�7�7�	��T����T�Z�Z�\�\��2�2�8�8��K�3�s�8�8�+����'�)�)�)�)�	
�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�		�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	�	s7�A0D<�BD$�D<�$D(	�(D<�+D(	�,D<�<E�Ec��dS)z$Write your rollback migrations here.N�)r'r(r)r*s    r�rollbackr2)s���Dr
N)F)�tempfilerrr/r2r1r
r�<module>r4sY��"�"�"�"�"�"�2�2�2�����B	�	�	�	�	�	r
defence360agent/migrations/__pycache__/070_modsec_incident_names.cpython-311.pyc0000644000000000000000000000605600000000000024543 0ustar  �

*=/��Z����(�ddlmZd�Zdd�Zdd�ZdS)�)�
TemporaryFilec�<�|�dd���dS)N�||���maxsplitr)�split)�descriptions �i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/070_modsec_incident_names.py�extract_namers �����T�A��.�.�q�1�1�Fc
��
�|jd�
�
fd�}td���5}|��D];\}}|�d�|t	|�������<|�d��|���5|D]}|�dd�	��\}}	�
�|	�	���
���
�
jt|��k���
����	ddd��n#1swxYwYddd��dS#1swxYwYdS)z�
    This migration extracts incident name from whole mod_security message
    Centos6 version of sqlite does not have instr(), using slow python-based
    way
    �incidentc3�R�K�	���j�j����jdk����j�d����������Ed{V��dS#t$rYdSwxYw)N�modsecr)	�select�idr
�where�plugin�contains�tuples�iterator�RuntimeError)rs�r�select_incidentsz!migrate.<locals>.select_incidentss������		������X�-A�B�B���x��(�2�3�3���x�+�4�4�T�:�:�;�;���������
�
�
�
�
�
�
�
�
���	�	�	��F�F�	���s�BB�
B&�%B&zw+)�modez{},{}
r�,rr)�nameN)�ormr�write�formatr�seek�atomicr	�update�striprr�int�execute)�migrator�database�fake�kwargsr�f�id_�desc�linerrs          @r�migrater/s�����|�J�'�H�
�
�
�
�
�
�D�	!�	!�	!�	�Q�)�)�+�+�	?�	?�I�C��
�G�G�I�$�$�S�,�t�*<�*<�=�=�>�>�>�>�	���q�	�	�	�
�_�_�
�
�	�	��
�
�� �J�J�s�Q�J�7�7�	��T����T�Z�Z�\�\��2�2�8�8��K�3�s�8�8�+����'�)�)�)�)�	
�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�		�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	�	s7�A0D<�BD$�D<�$D(	�(D<�+D(	�,D<�<E�Ec��dS)z$Write your rollback migrations here.N�)r'r(r)r*s    r�rollbackr2)s���Dr
N)F)�tempfilerrr/r2r1r
r�<module>r4sY��"�"�"�"�"�"�2�2�2�����B	�	�	�	�	�	r
defence360agent/migrations/__pycache__/071_malware_hits_hash_size_fields.cpython-311.opt-1.pyc0000644000000000000000000000222200000000000027222 0ustar  �

�xV v��F�ddlZddlZeje��Zdd�Zdd�ZdS)�NFc��|jd}|�|tjd���tjd������dS)zWrite your migrations here.�malware_hitsT)�null)�size�hashN)�orm�
add_fields�pw�	CharField��migrator�database�fake�kwargs�MalwareHitss     �q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/071_malware_hits_hash_size_fields.py�migratersV���,�~�.�K�����"�,�D�1�1�1���$�8O�8O�8O�������c�N�|jd}|�|dd��dS)z$Write your rollback migrations here.rrrN)r�
remove_fieldsrs     r�rollbackrs,���,�~�.�K����;���7�7�7�7�7r)F)�logging�peeweer
�	getLogger�__name__�loggerrr�rr�<module>rs^����������	��	�8�	$�	$������8�8�8�8�8�8rdefence360agent/migrations/__pycache__/071_malware_hits_hash_size_fields.cpython-311.pyc0000644000000000000000000000222200000000000026263 0ustar  �

�xV v��F�ddlZddlZeje��Zdd�Zdd�ZdS)�NFc��|jd}|�|tjd���tjd������dS)zWrite your migrations here.�malware_hitsT)�null)�size�hashN)�orm�
add_fields�pw�	CharField��migrator�database�fake�kwargs�MalwareHitss     �q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/071_malware_hits_hash_size_fields.py�migratersV���,�~�.�K�����"�,�D�1�1�1���$�8O�8O�8O�������c�N�|jd}|�|dd��dS)z$Write your rollback migrations here.rrrN)r�
remove_fieldsrs     r�rollbackrs,���,�~�.�K����;���7�7�7�7�7r)F)�logging�peeweer
�	getLogger�__name__�loggerrr�rr�<module>rs^����������	��	�8�	$�	$������8�8�8�8�8�8rdefence360agent/migrations/__pycache__/072_add_malware_history_table.cpython-311.opt-1.pyc0000644000000000000000000000375400000000000026364 0ustar  �

�C�ָJ�<��\�ddlmZddlZddlmZGd�dej��Zd	d�Zd	d�ZdS)
�)�timeN)�
FilenameFieldc���eZdZGd�d��Zed���Zejd���Zejd���Z	ejd���Z
ejd���Zejdd����Z
dS)	�MalwareHistoryc��eZdZdZdS)�MalwareHistory.Meta�malware_historyN)�__name__�
__module__�__qualname__�db_table���m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/072_add_malware_history_table.py�Metar	s������$���rrF)�nullTc�8�tt����S�N)�intrrrr�<lambda>zMalwareHistory.<lambda>s��s�4�6�6�{�{�r)r�defaultN)r
rrrr�path�pw�	CharField�event�	initiator�cause�
file_owner�IntegerField�ctimerrrrrs�������%�%�%�%�%�%�%�%��=�e�$�$�$�D��B�L�e�$�$�$�E����%�(�(�(�I��B�L�e�$�$�$�E����4�(�(�(�J��B�O��/B�/B�C�C�C�E�E�ErrFc�:�|�t��dSr)�create_modelr)�migrator�database�fake�kwargss    r�migrater's�����.�)�)�)�)�)rc�J�|jd}|�|��dS)Nr	)�orm�remove_model)r#r$r%r&rs     r�rollbackr+s)���\�"3�4�N����.�)�)�)�)�)r)F)	r�peeweer�$defence360agent.model.simplificationr�Modelrr'r+rrr�<module>r/s�������������>�>�>�>�>�>�	D�	D�	D�	D�	D�R�X�	D�	D�	D�*�*�*�*�*�*�*�*�*�*rdefence360agent/migrations/__pycache__/072_add_malware_history_table.cpython-311.pyc0000644000000000000000000000375400000000000025425 0ustar  �

�C�ָJ�<��\�ddlmZddlZddlmZGd�dej��Zd	d�Zd	d�ZdS)
�)�timeN)�
FilenameFieldc���eZdZGd�d��Zed���Zejd���Zejd���Z	ejd���Z
ejd���Zejdd����Z
dS)	�MalwareHistoryc��eZdZdZdS)�MalwareHistory.Meta�malware_historyN)�__name__�
__module__�__qualname__�db_table���m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/072_add_malware_history_table.py�Metar	s������$���rrF)�nullTc�8�tt����S�N)�intrrrr�<lambda>zMalwareHistory.<lambda>s��s�4�6�6�{�{�r)r�defaultN)r
rrrr�path�pw�	CharField�event�	initiator�cause�
file_owner�IntegerField�ctimerrrrrs�������%�%�%�%�%�%�%�%��=�e�$�$�$�D��B�L�e�$�$�$�E����%�(�(�(�I��B�L�e�$�$�$�E����4�(�(�(�J��B�O��/B�/B�C�C�C�E�E�ErrFc�:�|�t��dSr)�create_modelr)�migrator�database�fake�kwargss    r�migrater's�����.�)�)�)�)�)rc�J�|jd}|�|��dS)Nr	)�orm�remove_model)r#r$r%r&rs     r�rollbackr+s)���\�"3�4�N����.�)�)�)�)�)r)F)	r�peeweer�$defence360agent.model.simplificationr�Modelrr'r+rrr�<module>r/s�������������>�>�>�>�>�>�	D�	D�	D�	D�	D�R�X�	D�	D�	D�*�*�*�*�*�*�*�*�*�*rdefence360agent/migrations/__pycache__/072_captcha_stat.cpython-311.opt-1.pyc0000644000000000000000000000502500000000000023623 0ustar  �

B��z!u��j�ddlZGd�dej��ZGd�dej��Zd	d�Zd	d�ZdS)
�Nc��eZdZdZejdd���Zejddd���Zejd���ZGd�d	��Z	d
S)�Countryz(
    Contains country code and name
    TF)�primary_key�null�)�
max_length�uniquer�rc��eZdZdZdS)�Country.Meta�countryN)�__name__�
__module__�__qualname__�db_table���`/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/072_captcha_stat.py�Metar
s���������rrN)
rrr�__doc__�pw�	CharField�id�code�namerrrrrrs���������
���$�U�	3�	3�	3�B��2�<�1�T��>�>�>�D��2�<�U�#�#�#�D����������rrc��eZdZGd�d��Zejd���Zejd���Zeje	d���Z
ejd���Zejd���Z
ejd���ZdS)�CaptchaStatc�:�eZdZdZejddddd��ZdS)�CaptchaStat.Meta�captcha_stat�event�ipr
�domain�	timestampN)rrrrr�CompositeKeyrrrrrrs1������!��%�b�o��T�9�h��
�
���rrFr
TN)rrrrr�	TextFieldr!r"�ForeignKeyFieldrr
r#�IntegerFieldr$�countrrrrrs�������
�
�
�
�
�
�
�
�
�B�L�e�$�$�$�E�	���5�	!�	!�	!�B� �b� ��t�4�4�4�G�
�R�\�t�
$�
$�
$�F����U�+�+�+�I��B�O��'�'�'�E�E�ErrFc�:�|�t��dS)N)�create_modelr)�migrator�database�fake�kwargss    r�migrater0!s�����+�&�&�&�&�&rc�J�|jd}|�|��dS)Nr )�orm�remove_model)r,r-r.r/�css     r�rollbackr5%s(��	��n�	%�B����"�����r)F)�peeweer�Modelrrr0r5rrr�<module>r8s�������
�
�
�
�
�b�h�
�
�
�
(�
(�
(�
(�
(�"�(�
(�
(�
(� '�'�'�'������rdefence360agent/migrations/__pycache__/072_captcha_stat.cpython-311.pyc0000644000000000000000000000502500000000000022664 0ustar  �

B��z!u��j�ddlZGd�dej��ZGd�dej��Zd	d�Zd	d�ZdS)
�Nc��eZdZdZejdd���Zejddd���Zejd���ZGd�d	��Z	d
S)�Countryz(
    Contains country code and name
    TF)�primary_key�null�)�
max_length�uniquer�rc��eZdZdZdS)�Country.Meta�countryN)�__name__�
__module__�__qualname__�db_table���`/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/072_captcha_stat.py�Metar
s���������rrN)
rrr�__doc__�pw�	CharField�id�code�namerrrrrrs���������
���$�U�	3�	3�	3�B��2�<�1�T��>�>�>�D��2�<�U�#�#�#�D����������rrc��eZdZGd�d��Zejd���Zejd���Zeje	d���Z
ejd���Zejd���Z
ejd���ZdS)�CaptchaStatc�:�eZdZdZejddddd��ZdS)�CaptchaStat.Meta�captcha_stat�event�ipr
�domain�	timestampN)rrrrr�CompositeKeyrrrrrrs1������!��%�b�o��T�9�h��
�
���rrFr
TN)rrrrr�	TextFieldr!r"�ForeignKeyFieldrr
r#�IntegerFieldr$�countrrrrrs�������
�
�
�
�
�
�
�
�
�B�L�e�$�$�$�E�	���5�	!�	!�	!�B� �b� ��t�4�4�4�G�
�R�\�t�
$�
$�
$�F����U�+�+�+�I��B�O��'�'�'�E�E�ErrFc�:�|�t��dS)N)�create_modelr)�migrator�database�fake�kwargss    r�migrater0!s�����+�&�&�&�&�&rc�J�|jd}|�|��dS)Nr )�orm�remove_model)r,r-r.r/�css     r�rollbackr5%s(��	��n�	%�B����"�����r)F)�peeweer�Modelrrr0r5rrr�<module>r8s�������
�
�
�
�
�b�h�
�
�
�
(�
(�
(�
(�
(�"�(�
(�
(�
(� '�'�'�'������rdefence360agent/migrations/__pycache__/072_extend_last_synclist.cpython-311.opt-1.pyc0000644000000000000000000000221100000000000025421 0ustar  �

r5�j�W†���dd�Zdd�ZdS)Fc��|�d��|�d��|�d��|�d��dS)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)�sql��migrator�database�fake�kwargss    �h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/072_extend_last_synclist.py�migrater
si���L�L�	
����
�L�L�	D����
�L�L�+�,�,�,��L�L�H�I�I�I�I�I�c��dS)z$Write your rollback migrations here.N�rs    r	�rollbackrs���rN)F)r
rr
rr	�<module>rs;��J�J�J�J�"/�/�/�/�/�/rdefence360agent/migrations/__pycache__/072_extend_last_synclist.cpython-311.pyc0000644000000000000000000000221100000000000024462 0ustar  �

r5�j�W†���dd�Zdd�ZdS)Fc��|�d��|�d��|�d��|�d��dS)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)�sql��migrator�database�fake�kwargss    �h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/072_extend_last_synclist.py�migrater
si���L�L�	
����
�L�L�	D����
�L�L�+�,�,�,��L�L�H�I�I�I�I�I�c��dS)z$Write your rollback migrations here.N�rs    r	�rollbackrs���rN)F)r
rr
rr	�<module>rs;��J�J�J�J�"/�/�/�/�/�/rdefence360agent/migrations/__pycache__/073_drop_dos_expiration.cpython-311.opt-1.pyc0000644000000000000000000000231700000000000025242 0ustar  �

�M�$֏���F�ddlZddlZeje��Zdd�Zdd�ZdS)�NFc���|jd}|�|tjdd������|�d��|�|d��dS)zWrite your migrations here.�iplistF)�null�default)�
no_captchazGUPDATE iplist SET no_captcha=1 WHERE listname='GRAY' AND dos_expiration�dos_expirationN)�orm�
add_fields�pw�BooleanField�sql�
remove_fields)�migrator�database�fake�kwargs�IPLists     �g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/073_drop_dos_expiration.py�migraters|��
�\�(�
#�F������?��u�=�=�=�����
�L�L�	3����
���6�#3�4�4�4�4�4�c��dS)z$Write your rollback migrations here.N�)rrrrs    r�rollbackrs���Dr)F)�logging�peeweer�	getLogger�__name__�loggerrrrrr�<module>rs^����������	��	�8�	$�	$��5�5�5�5�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/073_drop_dos_expiration.cpython-311.pyc0000644000000000000000000000231700000000000024303 0ustar  �

�M�$֏���F�ddlZddlZeje��Zdd�Zdd�ZdS)�NFc���|jd}|�|tjdd������|�d��|�|d��dS)zWrite your migrations here.�iplistF)�null�default)�
no_captchazGUPDATE iplist SET no_captcha=1 WHERE listname='GRAY' AND dos_expiration�dos_expirationN)�orm�
add_fields�pw�BooleanField�sql�
remove_fields)�migrator�database�fake�kwargs�IPLists     �g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/073_drop_dos_expiration.py�migraters|��
�\�(�
#�F������?��u�=�=�=�����
�L�L�	3����
���6�#3�4�4�4�4�4�c��dS)z$Write your rollback migrations here.N�)rrrrs    r�rollbackrs���Dr)F)�logging�peeweer�	getLogger�__name__�loggerrrrrr�<module>rs^����������	��	�8�	$�	$��5�5�5�5�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/074_ip_as_int.cpython-311.opt-1.pyc0000644000000000000000000000557400000000000023145 0ustar  �

a;�����R�ddlZddlmZddlZeje��Zdd�Zdd�ZdS)�N)�timeFc���|jd�G�fd�dtj��}|�|��dS)zWrite your migrations here.�countryc�2��eZdZejd���Zejdejd��g���Zejdd���Z	ejd���Z
ejdd��	��Zejd���Zejd���Z
ej�d���Zejdd�	��Zejd���Zejdd�	��Zejd���Zejd���Zejd���ZGd
�d��ZdS)
�migrate.<locals>.IPListNewF)�nullz$listname in ('WHITE','BLACK','GRAY'))r�constraintsrT)�defaultrc�8�tt����S)N)�intr���]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/074_ip_as_int.py�<lambda>z#migrate.<locals>.IPListNew.<lambda>s��s�4�6�6�{�{�r)rr
c�6�eZdZdZejddd��ZdS)�migrate.<locals>.IPListNew.Meta�
iplist_new�network_address�netmask�versionN)�__name__�
__module__�__qualname__�db_table�pw�CompositeKey�primary_keyr
rr�Metar)s-������#�H�)�"�/�!�9�i���K�K�KrrN)rrrr�	CharField�ip�Check�listname�IntegerField�
expiration�
imported_from�ctime�deep�comment�ForeignKeyFieldr�BooleanField�
no_captcha�full_access�auto_whitelistedrrrr)�Countrys�r�	IPListNewrs���������R�\�u�
%�
%�
%���2�<��!���"H�I�I�J�
�
�
��%�R�_��D�
�
�
�
�%���$�/�/�/�
�����2�2�
�
�
���r��D�)�)�)���"�,�D�)�)�)��$�"�$�W�4�8�8�8��$�R�_�%��?�?�?�
�%�b�o�4�0�0�0��*�2�?��e�D�D�D��)�"�/�u�5�5�5��!�"�/�u�-�-�-��!�"�/�u�-�-�-��	�	�	�	�	�	�	�	�	�	rr/N)�ormr�Model�create_model)�migrator�database�fake�kwargsr/r.s     @r�migrater7
sd����l�9�%�G��������B�H����@
���)�$�$�$�$�$rc��dS)z$Write your rollback migrations here.Nr
)r3r4r5r6s    r�rollbackr92s���Dr)F)	�loggingr�peeweer�	getLoggerr�loggerr7r9r
rr�<module>r>ss����������������
��	�8�	$�	$��%%�%%�%%�%%�P	�	�	�	�	�	rdefence360agent/migrations/__pycache__/074_ip_as_int.cpython-311.pyc0000644000000000000000000000557400000000000022206 0ustar  �

a;�����R�ddlZddlmZddlZeje��Zdd�Zdd�ZdS)�N)�timeFc���|jd�G�fd�dtj��}|�|��dS)zWrite your migrations here.�countryc�2��eZdZejd���Zejdejd��g���Zejdd���Z	ejd���Z
ejdd��	��Zejd���Zejd���Z
ej�d���Zejdd�	��Zejd���Zejdd�	��Zejd���Zejd���Zejd���ZGd
�d��ZdS)
�migrate.<locals>.IPListNewF)�nullz$listname in ('WHITE','BLACK','GRAY'))r�constraintsrT)�defaultrc�8�tt����S)N)�intr���]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/074_ip_as_int.py�<lambda>z#migrate.<locals>.IPListNew.<lambda>s��s�4�6�6�{�{�r)rr
c�6�eZdZdZejddd��ZdS)�migrate.<locals>.IPListNew.Meta�
iplist_new�network_address�netmask�versionN)�__name__�
__module__�__qualname__�db_table�pw�CompositeKey�primary_keyr
rr�Metar)s-������#�H�)�"�/�!�9�i���K�K�KrrN)rrrr�	CharField�ip�Check�listname�IntegerField�
expiration�
imported_from�ctime�deep�comment�ForeignKeyFieldr�BooleanField�
no_captcha�full_access�auto_whitelistedrrrr)�Countrys�r�	IPListNewrs���������R�\�u�
%�
%�
%���2�<��!���"H�I�I�J�
�
�
��%�R�_��D�
�
�
�
�%���$�/�/�/�
�����2�2�
�
�
���r��D�)�)�)���"�,�D�)�)�)��$�"�$�W�4�8�8�8��$�R�_�%��?�?�?�
�%�b�o�4�0�0�0��*�2�?��e�D�D�D��)�"�/�u�5�5�5��!�"�/�u�-�-�-��!�"�/�u�-�-�-��	�	�	�	�	�	�	�	�	�	rr/N)�ormr�Model�create_model)�migrator�database�fake�kwargsr/r.s     @r�migrater7
sd����l�9�%�G��������B�H����@
���)�$�$�$�$�$rc��dS)z$Write your rollback migrations here.Nr
)r3r4r5r6s    r�rollbackr92s���Dr)F)	�loggingr�peeweer�	getLoggerr�loggerr7r9r
rr�<module>r>ss����������������
��	�8�	$�	$��%%�%%�%%�%%�P	�	�	�	�	�	rdefence360agent/migrations/__pycache__/075_ips_as_int.cpython-311.opt-1.pyc0000644000000000000000000000626300000000000023325 0ustar  �

�xV���j��N�ddlZddlZddlZeje��Zdd�Zdd�ZdS)�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
��|�d��d	S)zWrite your migrations here.�
iplist_new�iplistc3��K�	�����������Ed{V��dS#t$rYdSwxYw)N)�select�dicts�iterator�RuntimeError)�IPLists��^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/075_ips_as_int.py�
iplist_selectzmigrate.<locals>.iplist_selectsp�����	��}�}�V�,�,�2�2�4�4�=�=�?�?�?�?�?�?�?�?�?�?�?���	�	�	��F�F�	���s�?A�
A�Ar)�pack_ip_network�ip)�network_address�netmask�versionzError 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"))�orm�im360.utils.netr�atomic�	ipaddress�
ip_network�
ValueError�update�insert�execute�pw�IntegrityError�logger�warning�ImportError�sql)�migrator�database�fake�kwargs�	IPListNewr
r�ip_objr�net�maskr�ers             @r�migrater+
sZ�����\�*�I�
�\�(�
#�F������@�3�3�3�3�3�3��_�_�
�
�	@�	@�'�-�/�/�
@�
@���"�-�f�T�l�;�;�B�B��!�����H�����&5�_�R�%8�%8�"��T�7��
�
�+.�#'�#*������@��$�$�V�,�,�4�4�6�6�6�6���(�@�@�@��N�N�#;�Q�?�?�?�?�?�?�?�?�����@����%
@�	@�	@�	@�	@�	@�	@�	@�	@�	@�	@�	@����	@�	@�	@�	@����
�
�
���
����2
�L�L�$�%�%�%��L�L�:�;�;�;��L�L�J�K�K�K��L�L�N�O�O�O��L�L�>�?�?�?�?�?sp�D�
D
�
A%�$D
�%
A2�/D
�1A2�2+D
�'C�D
�C:�C5�0D
�5C:�:D
�
D�D�
D#�"D#c��dS)z$Write your rollback migrations here.N�)r"r#r$r%s    r�rollbackr.8s���D�)F)	�logging�peeweerr�	getLogger�__name__rr+r.r-r/r�<module>r4sq��������������
��	�8�	$�	$��+@�+@�+@�+@�\	�	�	�	�	�	r/defence360agent/migrations/__pycache__/075_ips_as_int.cpython-311.pyc0000644000000000000000000000626300000000000022366 0ustar  �

�xV���j��N�ddlZddlZddlZeje��Zdd�Zdd�ZdS)�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
��|�d��d	S)zWrite your migrations here.�
iplist_new�iplistc3��K�	�����������Ed{V��dS#t$rYdSwxYw)N)�select�dicts�iterator�RuntimeError)�IPLists��^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/075_ips_as_int.py�
iplist_selectzmigrate.<locals>.iplist_selectsp�����	��}�}�V�,�,�2�2�4�4�=�=�?�?�?�?�?�?�?�?�?�?�?���	�	�	��F�F�	���s�?A�
A�Ar)�pack_ip_network�ip)�network_address�netmask�versionzError 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"))�orm�im360.utils.netr�atomic�	ipaddress�
ip_network�
ValueError�update�insert�execute�pw�IntegrityError�logger�warning�ImportError�sql)�migrator�database�fake�kwargs�	IPListNewr
r�ip_objr�net�maskr�ers             @r�migrater+
sZ�����\�*�I�
�\�(�
#�F������@�3�3�3�3�3�3��_�_�
�
�	@�	@�'�-�/�/�
@�
@���"�-�f�T�l�;�;�B�B��!�����H�����&5�_�R�%8�%8�"��T�7��
�
�+.�#'�#*������@��$�$�V�,�,�4�4�6�6�6�6���(�@�@�@��N�N�#;�Q�?�?�?�?�?�?�?�?�����@����%
@�	@�	@�	@�	@�	@�	@�	@�	@�	@�	@�	@����	@�	@�	@�	@����
�
�
���
����2
�L�L�$�%�%�%��L�L�:�;�;�;��L�L�J�K�K�K��L�L�N�O�O�O��L�L�>�?�?�?�?�?sp�D�
D
�
A%�$D
�%
A2�/D
�1A2�2+D
�'C�D
�C:�C5�0D
�5C:�:D
�
D�D�
D#�"D#c��dS)z$Write your rollback migrations here.N�)r"r#r$r%s    r�rollbackr.8s���D�)F)	�logging�peeweerr�	getLogger�__name__rr+r.r-r/r�<module>r4sq��������������
��	�8�	$�	$��+@�+@�+@�+@�\	�	�	�	�	�	r/defence360agent/migrations/__pycache__/076_hash_model.cpython-311.opt-1.pyc0000644000000000000000000000074500000000000023300 0ustar  �

�Y��l���dd�Zdd�ZdS)Fc��dS�N���migrator�database�fake�kwargss    �^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/076_hash_model.py�migrater����D�c��dSrrrs    r
�rollbackrrr
N)F)rrrr
r
�<module>rs7��	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/076_hash_model.cpython-311.pyc0000644000000000000000000000074500000000000022341 0ustar  �

�Y��l���dd�Zdd�ZdS)Fc��dS�N���migrator�database�fake�kwargss    �^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/076_hash_model.py�migrater����D�c��dSrrrs    r
�rollbackrrr
N)F)rrrr
r
�<module>rs7��	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/077_alter_malware_scan.cpython-311.opt-1.pyc0000644000000000000000000000275100000000000025020 0ustar  �

3;z��g��"�dZddlZdd�Zdd�ZdS)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_scans�realtime)�type�inotifyT�)�null�default)�pathFz5type in ('on-demand', 'realtime', 'malware-response'))r	�constraints)	�orm�update�wherer�execute�
change_fields�pw�	CharField�Check)�migrator�database�fake�kwargs�MalwareScans     �f/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/077_alter_malware_scan.py�migraters����,��/�K����J��'�'�-�-���I�%���
�g�i�i�i�����"�,�D�"�=�=�=�����
����
�\����K����
�
�
��
�
�
�
�
�c��dS)z$Write your rollback migrations here.N�)rrrrs    r�rollbackrs���Dr)F)�__doc__�peeweerrrrrr�<module>r"sO������������.	�	�	�	�	�	rdefence360agent/migrations/__pycache__/077_alter_malware_scan.cpython-311.pyc0000644000000000000000000000275100000000000024061 0ustar  �

3;z��g��"�dZddlZdd�Zdd�ZdS)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_scans�realtime)�type�inotifyT�)�null�default)�pathFz5type in ('on-demand', 'realtime', 'malware-response'))r	�constraints)	�orm�update�wherer�execute�
change_fields�pw�	CharField�Check)�migrator�database�fake�kwargs�MalwareScans     �f/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/077_alter_malware_scan.py�migraters����,��/�K����J��'�'�-�-���I�%���
�g�i�i�i�����"�,�D�"�=�=�=�����
����
�\����K����
�
�
��
�
�
�
�
�c��dS)z$Write your rollback migrations here.N�)rrrrs    r�rollbackrs���Dr)F)�__doc__�peeweerrrrrr�<module>r"sO������������.	�	�	�	�	�	rdefence360agent/migrations/__pycache__/078_fix_signatures_permissions.cpython-311.opt-1.pyc0000644000000000000000000000121300000000000026653 0ustar  �

��� S�l���dZdd�Zdd�ZdS)zRRemoved, as DEF-11611 will fix folder creations so it will not be needed
anymore.
Fc��dS)N���migrator�database�fake�kwargss    �n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/078_fix_signatures_permissions.py�migrater
s���D�c��dS)z$Write your rollback migrations here.Nrrs    r	�rollbackr

s���DrN)F)�__doc__r
r
rrr	�<module>rsA����
	�	�	�	�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/078_fix_signatures_permissions.cpython-311.pyc0000644000000000000000000000121300000000000025714 0ustar  �

��� S�l���dZdd�Zdd�ZdS)zRRemoved, as DEF-11611 will fix folder creations so it will not be needed
anymore.
Fc��dS)N���migrator�database�fake�kwargss    �n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/078_fix_signatures_permissions.py�migrater
s���D�c��dS)z$Write your rollback migrations here.Nrrs    r	�rollbackr

s���DrN)F)�__doc__r
r
rrr	�<module>rsA����
	�	�	�	�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/079_add_uid_gid_fields.cpython-311.opt-1.pyc0000644000000000000000000000211000000000000024726 0ustar  �

	e]����F�ddlZddlZeje��Zdd�Zdd�ZdS)�NFc��|jd}|�|tjd���tjd������dS)N�malware_hitsT)�null)�uid�gid)�orm�
add_fields�pw�IntegerField��migrator�database�fake�kwargs�
MalwareHits     �f/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/079_add_uid_gid_fields.py�migrater	sX����n�-�J������O��&�&�&��O��&�&�&�������c�N�|jd}|�|dd��dS)Nrrr)r�
remove_fieldsrs     r�rollbackrs,����n�-�J����:�u�e�4�4�4�4�4r)F)�logging�peeweer
�	getLogger�__name__�loggerrr�rr�<module>rs`����������
��	�8�	$�	$������5�5�5�5�5�5rdefence360agent/migrations/__pycache__/079_add_uid_gid_fields.cpython-311.pyc0000644000000000000000000000211000000000000023767 0ustar  �

	e]����F�ddlZddlZeje��Zdd�Zdd�ZdS)�NFc��|jd}|�|tjd���tjd������dS)N�malware_hitsT)�null)�uid�gid)�orm�
add_fields�pw�IntegerField��migrator�database�fake�kwargs�
MalwareHits     �f/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/079_add_uid_gid_fields.py�migrater	sX����n�-�J������O��&�&�&��O��&�&�&�������c�N�|jd}|�|dd��dS)Nrrr)r�
remove_fieldsrs     r�rollbackrs,����n�-�J����:�u�e�4�4�4�4�4r)F)�logging�peeweer
�	getLogger�__name__�loggerrr�rr�<module>rs`����������
��	�8�	$�	$������5�5�5�5�5�5rdefence360agent/migrations/__pycache__/080_populate_uid_gid_size_hash_fields.cpython-311.opt-1.pyc0000644000000000000000000000703000000000000030062 0ustar  �

�q�?�fʧ��f�ddlZddlZddlZddlZddlZeje��Zdd�Zdd�Z	dd�Z
dS)	�N�c���|��}d}	tj||��}|sn(|�|��|t|��z
}�@|���|fS)zARead an open file descriptor in chunks; return (hexdigest, size).r)�os�read�update�len�	hexdigest)�fd�	hash_func�	chunksize�hash_�size�chunks      �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���I�K�K�E��D�����I�&�&���	��
���U������E�
�
�����?�?���d�"�"�Fc�N�|jd}	|���D�]�}|j�dd���}	t	j|tjtjztjz��}nn#t$rt�d|��Y��t$r;}|j
tjkr t�d|��Yd}~���d}~wwxYw	t	j|��}	t!j|	j��s2t�d|��	t	j|����(|j�6|js/t	j|dd��|	j|	jc|_|_t7|t8j��\|_|_t	j|��n#t	j|��wxYw|� �����dS#tB$r%}t�"|��Yd}~dSd}~wwxYw)	N�malware_hitszutf-8�surrogateescape)�errorsz(Malware file %s does not exist, skippingz&Malware file %s is a symlink, skippingz/Malware file %s is not a regular file, skippingr)#�orm�select�	orig_file�encoder�open�O_RDONLY�
O_NOFOLLOW�
O_NONBLOCK�FileNotFoundError�logger�warning�OSError�errno�ELOOP�fstat�stat�S_ISREG�st_mode�close�mode�restored�fchown�st_uid�st_gid�uid�gidr�hashlib�sha256�hashr�save�	Exception�	exception)
�migrator�database�fake�kwargs�
MalwareHit�hit�pathr
�e�sts
          r�migrater@s4����n�-�J�*��$�$�&�&�'	�'	�C��=�'�'��8I�'�J�J�D�

��W���K�"�-�/�"�-�?������%�
�
�
����>��������
�
�
��7�e�k�)�)��N�N�@�$�����H�H�H�H������

����
��X�b�\�\���|�B�J�/�/���N�N�I�������������
�8�'���'��I�b�!�Q�'�'�'�')�y�"�)�$�C�G�S�W�%;�B���%O�%O�"���#�(��������������������H�H�J�J�J�J�O'	�'	��P������������������������sy�3G5�9A=�<G5�=%C(�"G5�$	C(�-0C#�G5�"C#�#C(�(G5�,AG�5G5�A$G�/G5�G�G5�5
H$�?H�H$c��dS)N�)r7r8r9r:s    r�rollbackrCFs���Dr)r)F)r#r1�loggingrr&�	getLogger�__name__r rr@rCrBrr�<module>rGs���������������	�	�	�	�����	��	�8�	$�	$��
#�
#�
#�
#�,�,�,�,�^	�	�	�	�	�	rdefence360agent/migrations/__pycache__/080_populate_uid_gid_size_hash_fields.cpython-311.pyc0000644000000000000000000000703000000000000027123 0ustar  �

�q�?�fʧ��f�ddlZddlZddlZddlZddlZeje��Zdd�Zdd�Z	dd�Z
dS)	�N�c���|��}d}	tj||��}|sn(|�|��|t|��z
}�@|���|fS)zARead an open file descriptor in chunks; return (hexdigest, size).r)�os�read�update�len�	hexdigest)�fd�	hash_func�	chunksize�hash_�size�chunks      �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���I�K�K�E��D�����I�&�&���	��
���U������E�
�
�����?�?���d�"�"�Fc�N�|jd}	|���D�]�}|j�dd���}	t	j|tjtjztjz��}nn#t$rt�d|��Y��t$r;}|j
tjkr t�d|��Yd}~���d}~wwxYw	t	j|��}	t!j|	j��s2t�d|��	t	j|����(|j�6|js/t	j|dd��|	j|	jc|_|_t7|t8j��\|_|_t	j|��n#t	j|��wxYw|� �����dS#tB$r%}t�"|��Yd}~dSd}~wwxYw)	N�malware_hitszutf-8�surrogateescape)�errorsz(Malware file %s does not exist, skippingz&Malware file %s is a symlink, skippingz/Malware file %s is not a regular file, skippingr)#�orm�select�	orig_file�encoder�open�O_RDONLY�
O_NOFOLLOW�
O_NONBLOCK�FileNotFoundError�logger�warning�OSError�errno�ELOOP�fstat�stat�S_ISREG�st_mode�close�mode�restored�fchown�st_uid�st_gid�uid�gidr�hashlib�sha256�hashr�save�	Exception�	exception)
�migrator�database�fake�kwargs�
MalwareHit�hit�pathr
�e�sts
          r�migrater@s4����n�-�J�*��$�$�&�&�'	�'	�C��=�'�'��8I�'�J�J�D�

��W���K�"�-�/�"�-�?������%�
�
�
����>��������
�
�
��7�e�k�)�)��N�N�@�$�����H�H�H�H������

����
��X�b�\�\���|�B�J�/�/���N�N�I�������������
�8�'���'��I�b�!�Q�'�'�'�')�y�"�)�$�C�G�S�W�%;�B���%O�%O�"���#�(��������������������H�H�J�J�J�J�O'	�'	��P������������������������sy�3G5�9A=�<G5�=%C(�"G5�$	C(�-0C#�G5�"C#�#C(�(G5�,AG�5G5�A$G�/G5�G�G5�5
H$�?H�H$c��dS)N�)r7r8r9r:s    r�rollbackrCFs���Dr)r)F)r#r1�loggingrr&�	getLogger�__name__r rr@rCrBrr�<module>rGs���������������	�	�	�	�����	��	�8�	$�	$��
#�
#�
#�
#�,�,�,�,�^	�	�	�	�	�	rdefence360agent/migrations/__pycache__/081_fix_clamscan_broken_symlink.cpython-311.opt-1.pyc0000644000000000000000000000076600000000000026731 0ustar  �

�Y��l���dd�Zdd�ZdS)Fc��dS�N���migrator�database�fake�kwargss    �o/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/081_fix_clamscan_broken_symlink.py�migrater����D�c��dSrrrs    r
�rollbackrrr
N)F)rrrr
r
�<module>rs7��	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/081_fix_clamscan_broken_symlink.cpython-311.pyc0000644000000000000000000000076600000000000025772 0ustar  �

�Y��l���dd�Zdd�ZdS)Fc��dS�N���migrator�database�fake�kwargss    �o/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/081_fix_clamscan_broken_symlink.py�migrater����D�c��dSrrrs    r
�rollbackrrr
N)F)rrrr
r
�<module>rs7��	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/082_add_cl_on_premise_backup_option.cpython-311.opt-1.pyc0000644000000000000000000000077200000000000027535 0ustar  �

�tTnGaQ����dd�Zdd�ZdS)Fc��dS�N���migrator�database�fake�kwargss    �s/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/082_add_cl_on_premise_backup_option.py�migrater
����D�c��dSrrrs    r
�rollbackrrr
N)F)rrrr
r
�<module>rs7��	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/082_add_cl_on_premise_backup_option.cpython-311.pyc0000644000000000000000000000077200000000000026576 0ustar  �

�tTnGaQ����dd�Zdd�ZdS)Fc��dS�N���migrator�database�fake�kwargss    �s/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/082_add_cl_on_premise_backup_option.py�migrater
����D�c��dSrrrs    r
�rollbackrrr
N)F)rrrr
r
�<module>rs7��	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/082_add_manual_flag.cpython-311.opt-1.pyc0000644000000000000000000000163200000000000024244 0ustar  �

���E�9���ddlZdd�Zdd�ZdS)�NFc�v�|jd}|�|tjdd������dS)N�iplistFT)�null�default)�manual)�orm�
add_fields�pw�BooleanField��migrator�database�fake�kwargs�IPLists     �c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/082_add_manual_flag.py�migratersI��
�\�(�
#�F�����r��E�4�@�@�@�������c�L�|jd}|�|d��dS)Nrr)r�
remove_fieldsrs     r�rollbackrs*��
�\�(�
#�F����6�8�,�,�,�,�,r)F)�peeweer
rr�rr�<module>rsC����������-�-�-�-�-�-rdefence360agent/migrations/__pycache__/082_add_manual_flag.cpython-311.pyc0000644000000000000000000000163200000000000023305 0ustar  �

���E�9���ddlZdd�Zdd�ZdS)�NFc�v�|jd}|�|tjdd������dS)N�iplistFT)�null�default)�manual)�orm�
add_fields�pw�BooleanField��migrator�database�fake�kwargs�IPLists     �c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/082_add_manual_flag.py�migratersI��
�\�(�
#�F�����r��E�4�@�@�@�������c�L�|jd}|�|d��dS)Nrr)r�
remove_fieldsrs     r�rollbackrs*��
�\�(�
#�F����6�8�,�,�,�,�,r)F)�peeweer
rr�rr�<module>rsC����������-�-�-�-�-�-rdefence360agent/migrations/__pycache__/083_drop_no_captcha_field.cpython-311.opt-1.pyc0000644000000000000000000000311300000000000025451 0ustar  �

��/��9����ddlZdd�Zdd�ZdS)�NFc��|jd}|�d��|�d��|�d��|�d��|�d��|�|d��dS)N�iplistz0UPDATE 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=1z�UPDATE iplist SET comment='Automatically blocked due to distributed attack', imported_from='Imunify360' WHERE listname='BLACK' AND manual=0�
no_captcha)�orm�sql�
remove_fields��migrator�database�fake�kwargs�IPLists     �i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/083_drop_no_captcha_field.py�migraters���
�\�(�
#�F��L�L�C�D�D�D��L�L�D�E�E�E��L�L�D�E�E�E��L�L�	1����
�L�L�	/����
���6�<�0�0�0�0�0�c�t�|jd}|�|tjd������dS)NrF)�default)r)r�
add_fields�pw�BooleanFieldr	s     r�rollbackrs:��
�\�(�
#�F�����2�?�5�+I�+I�+I��J�J�J�J�Jr)F)�peeweerrr�rr�<module>rsI������1�1�1�1�$K�K�K�K�K�Krdefence360agent/migrations/__pycache__/083_drop_no_captcha_field.cpython-311.pyc0000644000000000000000000000311300000000000024512 0ustar  �

��/��9����ddlZdd�Zdd�ZdS)�NFc��|jd}|�d��|�d��|�d��|�d��|�d��|�|d��dS)N�iplistz0UPDATE 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=1z�UPDATE iplist SET comment='Automatically blocked due to distributed attack', imported_from='Imunify360' WHERE listname='BLACK' AND manual=0�
no_captcha)�orm�sql�
remove_fields��migrator�database�fake�kwargs�IPLists     �i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/083_drop_no_captcha_field.py�migraters���
�\�(�
#�F��L�L�C�D�D�D��L�L�D�E�E�E��L�L�D�E�E�E��L�L�	1����
�L�L�	/����
���6�<�0�0�0�0�0�c�t�|jd}|�|tjd������dS)NrF)�default)r)r�
add_fields�pw�BooleanFieldr	s     r�rollbackrs:��
�\�(�
#�F�����2�?�5�+I�+I�+I��J�J�J�J�Jr)F)�peeweerrr�rr�<module>rsI������1�1�1�1�$K�K�K�K�K�Krdefence360agent/migrations/__pycache__/084_country_subnets_fields.cpython-311.opt-1.pyc0000644000000000000000000000240500000000000025763 0ustar  �

�p��Es���ddlZdd�Zdd�ZdS)�NFc���|jd}|�|dd��|�|tjd���tjd���tjd������dS)N�country_subnets�ip_net�ipT)�null)�network_address�netmask�version)�orm�rename_field�
add_fields�pw�IntegerField��migrator�database�fake�kwargs�CountrySubnetss     �j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/084_country_subnets_fields.py�migraters����\�"3�4�N����.�(�D�9�9�9�������T�2�2�2���T�*�*�*���T�*�*�*�	������c�~�|jd}|�|dd��|�|ddd��dS)Nrrrrr	r
)rr�
remove_fieldsrs     r�rollbackrsS���\�"3�4�N����.�$��9�9�9�����)�9�i�����r)F)�peeweerrr�rr�<module>rsC���������������rdefence360agent/migrations/__pycache__/084_country_subnets_fields.cpython-311.pyc0000644000000000000000000000240500000000000025024 0ustar  �

�p��Es���ddlZdd�Zdd�ZdS)�NFc���|jd}|�|dd��|�|tjd���tjd���tjd������dS)N�country_subnets�ip_net�ipT)�null)�network_address�netmask�version)�orm�rename_field�
add_fields�pw�IntegerField��migrator�database�fake�kwargs�CountrySubnetss     �j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/084_country_subnets_fields.py�migraters����\�"3�4�N����.�(�D�9�9�9�������T�2�2�2���T�*�*�*���T�*�*�*�	������c�~�|jd}|�|dd��|�|ddd��dS)Nrrrrr	r
)rr�
remove_fieldsrs     r�rollbackrsS���\�"3�4�N����.�$��9�9�9�����)�9�i�����r)F)�peeweerrr�rr�<module>rsC���������������rdefence360agent/migrations/__pycache__/085_country_subnets_fields.cpython-311.opt-1.pyc0000644000000000000000000000231200000000000025761 0ustar  �

�rG��c��>�ddlZeje��Zdd�Zdd�ZdS)�NFc���|rdS|jd}|�d��|�|d��|�|d��|�|d��dS)N�country_subnetszDELETE FROM country_subnets�network_address�netmask�version)�orm�sql�add_not_null��migrator�database�fake�kwargs�CountrySubnetss     �j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/085_country_subnets_fields.py�migraters��������\�"3�4�N��L�L�.�/�/�/����.�*;�<�<�<���������
���.�)�4�4�4�4�4�c�P�|jd}|�|ddd��dS)Nrrrr)r�
drop_not_nullrs     r�rollbackrs;���\�"3�4�N�����)�9�i�����r)F)�logging�	getLogger�__name__�loggerrr�rr�<module>rsR������	��	�8�	$�	$��5�5�5�5������rdefence360agent/migrations/__pycache__/085_country_subnets_fields.cpython-311.pyc0000644000000000000000000000231200000000000025022 0ustar  �

�rG��c��>�ddlZeje��Zdd�Zdd�ZdS)�NFc���|rdS|jd}|�d��|�|d��|�|d��|�|d��dS)N�country_subnetszDELETE FROM country_subnets�network_address�netmask�version)�orm�sql�add_not_null��migrator�database�fake�kwargs�CountrySubnetss     �j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/085_country_subnets_fields.py�migraters��������\�"3�4�N��L�L�.�/�/�/����.�*;�<�<�<���������
���.�)�4�4�4�4�4�c�P�|jd}|�|ddd��dS)Nrrrr)r�
drop_not_nullrs     r�rollbackrs;���\�"3�4�N�����)�9�i�����r)F)�logging�	getLogger�__name__�loggerrr�rr�<module>rsR������	��	�8�	$�	$��5�5�5�5������rdefence360agent/migrations/__pycache__/086_ignored_by_port_fields.cpython-311.opt-1.pyc0000644000000000000000000000240000000000000025677 0ustar  �

?��K
���ddlZdd�Zdd�ZdS)�NFc
��|jd}|jd}|�|tjd���tjd���tjd���tj|d������dS)N�ignored_by_port_proto�countryT)�null)�network_address�netmask�versionr)�orm�
add_fields�pw�IntegerField�ForeignKeyField)�migrator�database�fake�kwargs�
IgnoredByPort�Countrys      �j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/086_ignored_by_port_fields.py�migraters����L�!8�9�M��l�9�%�G�������T�2�2�2���T�*�*�*���T�*�*�*��"�7��6�6�6�������c�R�|jd}|�|dddd��dS)Nrrrr	r)r
�
remove_fields)rrrrrs     r�rollbackrs=���L�!8�9�M�����(�)�Y�	�����r)F)�peeweerrr�rr�<module>rsC������	�	�	�	������rdefence360agent/migrations/__pycache__/086_ignored_by_port_fields.cpython-311.pyc0000644000000000000000000000240000000000000024740 0ustar  �

?��K
���ddlZdd�Zdd�ZdS)�NFc
��|jd}|jd}|�|tjd���tjd���tjd���tj|d������dS)N�ignored_by_port_proto�countryT)�null)�network_address�netmask�versionr)�orm�
add_fields�pw�IntegerField�ForeignKeyField)�migrator�database�fake�kwargs�
IgnoredByPort�Countrys      �j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/086_ignored_by_port_fields.py�migraters����L�!8�9�M��l�9�%�G�������T�2�2�2���T�*�*�*���T�*�*�*��"�7��6�6�6�������c�R�|jd}|�|dddd��dS)Nrrrr	r)r
�
remove_fields)rrrrrs     r�rollbackrs=���L�!8�9�M�����(�)�Y�	�����r)F)�peeweerrr�rr�<module>rsC������	�	�	�	������rdefence360agent/migrations/__pycache__/087_ignored_by_port_fields.cpython-311.opt-1.pyc0000644000000000000000000000665100000000000025714 0ustar  �

?���]���J�ddlZddlmZeje��Zdd�Zdd�ZdS)�N)�
ip_networkFc��|jd}	ddlm}|�|j��������}d�|D��}n#t$rg}YnwxYw|D]�}	|t|����\}	}
}|�	|	|
|����
|j|k������f#t$r`t�d|��|����
|j|k�����Y��wxYw|r�ddlm}	|���5}
|D][}|
�|��}|�	|����
|j|k������\	ddd��n#1swxYwYn*#t($rt�d	��YnwxYw|�|d
��|�|d��|�|d��dS)
N�ignored_by_port_protor)�pack_ip_networkc��g|]\}|��S�r)�.0�ips  �j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/087_ignored_by_port_fields.py�
<listcomp>zmigrate.<locals>.<listcomp>s�����c�b�r����)�network_address�netmask�versionzInvalid IP network %s)�geo)�countryz2Failed to update countries data in ignored_by_portrrr)�orm�im360.utils.netr�selectr
�distinct�tuples�ImportErrorr�update�where�execute�
ValueError�logger�warning�delete�defence360agent.internalsr�reader�get_id�OSError�add_not_null)�migrator�database�fake�kwargs�
IgnoredByPortr�q�ipsr
�net�maskrr�
geo_readerrs               r�migrater/s����L�!8�9�M� �3�3�3�3�3�3�
� � ��!1�2�2�;�;�=�=�D�D�F�F����Q�������	�������������	6�	6��	6�!0���B���!@�!@��C��w�

� � � #�T�7�
!�
�
��e�M�$��*�+�+�G�G�I�I�I�I��
�	K�	K�	K��N�N�2�B�7�7�7�� � �"�"�(�(��)9�R�)?�@�@�H�H�J�J�J�J�J�	K�����
�1�1�1�1�1�1�
	������
>���>�>�B�(�/�/��3�3�G�!�(�(� '�)����e�M�,��2�3�3�G�G�I�I�I�I�	>�
>�
>�
>�
>�
>�
>�
>�
>�
>�
>�
>����
>�
>�
>�
>����	�	�	��N�N�D�
�
�
�
�
�	����

���-�):�;�;�;���������
���-��3�3�3�3�3s[�A � A/�.A/�7C�A'E�E�G�$AG�G�G�G�G�G�$H�Hc�P�|jd}|�|ddd��dS)Nrrrr)r�
drop_not_null)r%r&r'r(r)s     r�rollbackr23s;���L�!8�9�M�����(�)�Y�����r
)F)�logging�	ipaddressr�	getLogger�__name__rr/r2rr
r�<module>r7se������ � � � � � �	��	�8�	$�	$��)4�)4�)4�)4�X�����r
defence360agent/migrations/__pycache__/087_ignored_by_port_fields.cpython-311.pyc0000644000000000000000000000665100000000000024755 0ustar  �

?���]���J�ddlZddlmZeje��Zdd�Zdd�ZdS)�N)�
ip_networkFc��|jd}	ddlm}|�|j��������}d�|D��}n#t$rg}YnwxYw|D]�}	|t|����\}	}
}|�	|	|
|����
|j|k������f#t$r`t�d|��|����
|j|k�����Y��wxYw|r�ddlm}	|���5}
|D][}|
�|��}|�	|����
|j|k������\	ddd��n#1swxYwYn*#t($rt�d	��YnwxYw|�|d
��|�|d��|�|d��dS)
N�ignored_by_port_protor)�pack_ip_networkc��g|]\}|��S�r)�.0�ips  �j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/087_ignored_by_port_fields.py�
<listcomp>zmigrate.<locals>.<listcomp>s�����c�b�r����)�network_address�netmask�versionzInvalid IP network %s)�geo)�countryz2Failed to update countries data in ignored_by_portrrr)�orm�im360.utils.netr�selectr
�distinct�tuples�ImportErrorr�update�where�execute�
ValueError�logger�warning�delete�defence360agent.internalsr�reader�get_id�OSError�add_not_null)�migrator�database�fake�kwargs�
IgnoredByPortr�q�ipsr
�net�maskrr�
geo_readerrs               r�migrater/s����L�!8�9�M� �3�3�3�3�3�3�
� � ��!1�2�2�;�;�=�=�D�D�F�F����Q�������	�������������	6�	6��	6�!0���B���!@�!@��C��w�

� � � #�T�7�
!�
�
��e�M�$��*�+�+�G�G�I�I�I�I��
�	K�	K�	K��N�N�2�B�7�7�7�� � �"�"�(�(��)9�R�)?�@�@�H�H�J�J�J�J�J�	K�����
�1�1�1�1�1�1�
	������
>���>�>�B�(�/�/��3�3�G�!�(�(� '�)����e�M�,��2�3�3�G�G�I�I�I�I�	>�
>�
>�
>�
>�
>�
>�
>�
>�
>�
>�
>����
>�
>�
>�
>����	�	�	��N�N�D�
�
�
�
�
�	����

���-�):�;�;�;���������
���-��3�3�3�3�3s[�A � A/�.A/�7C�A'E�E�G�$AG�G�G�G�G�G�$H�Hc�P�|jd}|�|ddd��dS)Nrrrr)r�
drop_not_null)r%r&r'r(r)s     r�rollbackr23s;���L�!8�9�M�����(�)�Y�����r
)F)�logging�	ipaddressr�	getLogger�__name__rr/r2rr
r�<module>r7se������ � � � � � �	��	�8�	$�	$��)4�)4�)4�)4�X�����r
defence360agent/migrations/__pycache__/088_add_malware_i360_clamd_scan_option.cpython-311.opt-1.pyc0000644000000000000000000000077500000000000027740 0ustar  �

�Y��l���dd�Zdd�ZdS)Fc��dS�N���migrator�database�fake�kwargss    �v/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/088_add_malware_i360_clamd_scan_option.py�migrater����D�c��dSrrrs    r
�rollbackrrr
N)F)rrrr
r
�<module>rs7��	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/088_add_malware_i360_clamd_scan_option.cpython-311.pyc0000644000000000000000000000077500000000000027001 0ustar  �

�Y��l���dd�Zdd�ZdS)Fc��dS�N���migrator�database�fake�kwargss    �v/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/088_add_malware_i360_clamd_scan_option.py�migrater����D�c��dSrrrs    r
�rollbackrrr
N)F)rrrr
r
�<module>rs7��	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/089_proactive_tables.cpython-311.opt-1.pyc0000644000000000000000000000663100000000000024527 0ustar  �

��~�	{���ddlZdd�Zdd�ZdS)�NFc�����|jd�G�fd�dtj���G�fd�dtj��}|����|�|��dS)N�countryc�&��eZdZGd�d��Zej��Zejd���Zej	d���Z
ejd���Zejd���Zej
�d���Zej	d���Zej	d���Zej	d���Zej	d���Zej	d���Zej	d���Zejd���Zejd���Zejd���ZdS)�migrate.<locals>.Proactivec��eZdZdZdS)�migrate.<locals>.Proactive.Meta�	proactiveN)�__name__�
__module__�__qualname__�db_table���d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/089_proactive_tables.py�Metars������"�H�H�HrrF��nullTN)r
rrr�pw�PrimaryKeyField�id�IntegerField�	timestamp�	TextField�ip�ip_int�
ip_version�ForeignKeyField�
ip_country�reason�description�action�host�path�url�count�uid�gid)�Countrys�r�	Proactiversd�������	#�	#�	#�	#�	#�	#�	#�	#� �R�
�
!�
!��#�B�O��/�/�/�	�
�R�\�t�
$�
$�
$�� ���d�+�+�+��$�R�_�$�/�/�/�
�'�R�'��d�;�;�;�
����5�)�)�)��"�b�l��-�-�-�����5�)�)�)���r�|��&�&�&���r�|��'�'�'���b�l��%�%�%�����U�+�+�+���b�o�5�)�)�)���b�o�5�)�)�)���rr)c���eZdZej�ddd���Zejd���Zejd���ZGd�d��Z	d	S)
�migrate.<locals>.ProactiveEnvF�CASCADE�env)r�	on_delete�related_namerTc�6�eZdZdZejddd��ZdS)�"migrate.<locals>.ProactiveEnv.Meta�
proactive_env�event�name�valueN)r
rrr
r�CompositeKey�primary_keyrrrrr1"s(������&�H�)�"�/�'�6�7�C�C�K�K�KrrN)
r
rrrrr3rr4r5r)r)s�r�ProactiveEnvr+s��������"��"��E�Y�U�
�
�
���r�|��'�'�'�����$�'�'�'��	D�	D�	D�	D�	D�	D�	D�	D�	D�	Drr8)�ormr�Model�create_model)�migrator�database�fake�kwargsr8r(r)s     @@r�migrater@s������l�9�%�G�*�*�*�*�*�*�*�B�H�*�*�*�(	D�	D�	D�	D�	D�	D�	D�r�x�	D�	D�	D�
���)�$�$�$����,�'�'�'�'�'rc��|jd}|jd}|�|��|�|��dS)Nr2r	)r9�remove_model)r<r=r>r?r8r)s      r�rollbackrC*sH���<��0�L���[�)�I����,�'�'�'����)�$�$�$�$�$r)F)�peeweerr@rCrrr�<module>rEsD������#(�#(�#(�#(�L%�%�%�%�%�%rdefence360agent/migrations/__pycache__/089_proactive_tables.cpython-311.pyc0000644000000000000000000000663100000000000023570 0ustar  �

��~�	{���ddlZdd�Zdd�ZdS)�NFc�����|jd�G�fd�dtj���G�fd�dtj��}|����|�|��dS)N�countryc�&��eZdZGd�d��Zej��Zejd���Zej	d���Z
ejd���Zejd���Zej
�d���Zej	d���Zej	d���Zej	d���Zej	d���Zej	d���Zej	d���Zejd���Zejd���Zejd���ZdS)�migrate.<locals>.Proactivec��eZdZdZdS)�migrate.<locals>.Proactive.Meta�	proactiveN)�__name__�
__module__�__qualname__�db_table���d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/089_proactive_tables.py�Metars������"�H�H�HrrF��nullTN)r
rrr�pw�PrimaryKeyField�id�IntegerField�	timestamp�	TextField�ip�ip_int�
ip_version�ForeignKeyField�
ip_country�reason�description�action�host�path�url�count�uid�gid)�Countrys�r�	Proactiversd�������	#�	#�	#�	#�	#�	#�	#�	#� �R�
�
!�
!��#�B�O��/�/�/�	�
�R�\�t�
$�
$�
$�� ���d�+�+�+��$�R�_�$�/�/�/�
�'�R�'��d�;�;�;�
����5�)�)�)��"�b�l��-�-�-�����5�)�)�)���r�|��&�&�&���r�|��'�'�'���b�l��%�%�%�����U�+�+�+���b�o�5�)�)�)���b�o�5�)�)�)���rr)c���eZdZej�ddd���Zejd���Zejd���ZGd�d��Z	d	S)
�migrate.<locals>.ProactiveEnvF�CASCADE�env)r�	on_delete�related_namerTc�6�eZdZdZejddd��ZdS)�"migrate.<locals>.ProactiveEnv.Meta�
proactive_env�event�name�valueN)r
rrr
r�CompositeKey�primary_keyrrrrr1"s(������&�H�)�"�/�'�6�7�C�C�K�K�KrrN)
r
rrrrr3rr4r5r)r)s�r�ProactiveEnvr+s��������"��"��E�Y�U�
�
�
���r�|��'�'�'�����$�'�'�'��	D�	D�	D�	D�	D�	D�	D�	D�	D�	Drr8)�ormr�Model�create_model)�migrator�database�fake�kwargsr8r(r)s     @@r�migrater@s������l�9�%�G�*�*�*�*�*�*�*�B�H�*�*�*�(	D�	D�	D�	D�	D�	D�	D�r�x�	D�	D�	D�
���)�$�$�$����,�'�'�'�'�'rc��|jd}|jd}|�|��|�|��dS)Nr2r	)r9�remove_model)r<r=r>r?r8r)s      r�rollbackrC*sH���<��0�L���[�)�I����,�'�'�'����)�$�$�$�$�$r)F)�peeweerr@rCrrr�<module>rEsD������#(�#(�#(�#(�L%�%�%�%�%�%rdefence360agent/migrations/__pycache__/090_safe_user_config.cpython-311.opt-1.pyc0000644000000000000000000000470600000000000024473 0ustar  �

Z�2����b�ddlZddlZddlZddlZddlmZeje��Zdd�Z	dd�Z
dS)�N)�CoreFc��	tj��D�]�}	tj�|jtj��}tj�|���rtj�	|��s�tj�tj
|j��}tj|��tj
|d|j��tj|d��tj�|tj��}t!j||��tj
|d|j��tj|d����V#t$$r3}t&�dt+|����Yd}~���d}~wwxYwdS#t,$rt&�d|��YdSwxYw)Nri�i�zSomething went wrong: %szFailed to migrate config for %s)�pwd�getpwall�os�path�join�pw_dirr�USER_CONFIG_FILE_NAME�isfile�islink�USER_CONFDIR�pw_name�mkdir�chown�pw_gid�chmod�shutil�move�OSError�logger�warning�str�	Exception�	exception)	�migrator�database�fake�kwargs�user�src�dst_dir�dst_file�es	         �d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/090_safe_user_config.py�migrater&s���B��L�N�N�	C�	C�D�
C��g�l�l�4�;��0J�K�K���7�>�>�#�&�&�
.�r�w�~�~�c�/B�/B�
.� �g�l�l�4�+<�d�l�K�K�G��H�W�%�%�%��H�W�a���5�5�5��H�W�e�,�,�,�!�w�|�|���!;� � �H��K��X�.�.�.��H�X�q�$�+�6�6�6��H�X�u�-�-�-����
C�
C�
C����9�3�q�6�6�B�B�B�B�B�B�B�B�����
C����	C�	C�� �B�B�B����:�D�A�A�A�A�A�A�B���s;�F-�EE+�)F-�+
F(�5(F#�F-�#F(�(F-�-%G�Gc��dS)N�)rrrrs    r%�rollbackr)!s���D�)F)rrr�logging� defence360agent.contracts.configr�	getLogger�__name__rr&r)r(r*r%�<module>r/s���
�
�
�
�
�
�
�
�	�	�	�	�����1�1�1�1�1�1�	��	�8�	$�	$��B�B�B�B�,	�	�	�	�	�	r*defence360agent/migrations/__pycache__/090_safe_user_config.cpython-311.pyc0000644000000000000000000000470600000000000023534 0ustar  �

Z�2����b�ddlZddlZddlZddlZddlmZeje��Zdd�Z	dd�Z
dS)�N)�CoreFc��	tj��D�]�}	tj�|jtj��}tj�|���rtj�	|��s�tj�tj
|j��}tj|��tj
|d|j��tj|d��tj�|tj��}t!j||��tj
|d|j��tj|d����V#t$$r3}t&�dt+|����Yd}~���d}~wwxYwdS#t,$rt&�d|��YdSwxYw)Nri�i�zSomething went wrong: %szFailed to migrate config for %s)�pwd�getpwall�os�path�join�pw_dirr�USER_CONFIG_FILE_NAME�isfile�islink�USER_CONFDIR�pw_name�mkdir�chown�pw_gid�chmod�shutil�move�OSError�logger�warning�str�	Exception�	exception)	�migrator�database�fake�kwargs�user�src�dst_dir�dst_file�es	         �d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/090_safe_user_config.py�migrater&s���B��L�N�N�	C�	C�D�
C��g�l�l�4�;��0J�K�K���7�>�>�#�&�&�
.�r�w�~�~�c�/B�/B�
.� �g�l�l�4�+<�d�l�K�K�G��H�W�%�%�%��H�W�a���5�5�5��H�W�e�,�,�,�!�w�|�|���!;� � �H��K��X�.�.�.��H�X�q�$�+�6�6�6��H�X�u�-�-�-����
C�
C�
C����9�3�q�6�6�B�B�B�B�B�B�B�B�����
C����	C�	C�� �B�B�B����:�D�A�A�A�A�A�A�B���s;�F-�EE+�)F-�+
F(�5(F#�F-�#F(�(F-�-%G�Gc��dS)N�)rrrrs    r%�rollbackr)!s���D�)F)rrr�logging� defence360agent.contracts.configr�	getLogger�__name__rr&r)r(r*r%�<module>r/s���
�
�
�
�
�
�
�
�	�	�	�	�����1�1�1�1�1�1�	��	�8�	$�	$��B�B�B�B�,	�	�	�	�	�	r*defence360agent/migrations/__pycache__/091_compress_old_logs.cpython-311.opt-1.pyc0000644000000000000000000000423700000000000024707 0ustar  �

�?t������n�ddlZddlZddlZddlZddlmZddlmZeje	��Z
dd�Zdd�ZdS)�N)�Logger)�get_log_file_namesFc	�&�t��D�]}tdtjdz��D]�}|�d|��}|�d�}	tj�|��r~t|d��5}tj|d��5}	tj
||	��ddd��n#1swxYwYddd��n#1swxYwYt	j|����#t$r&}
t�d||
��Yd}
~
��d}
~
wwxYw��dS)N��.z.gz�rb�wbz"Failed file %s compression with %s)r�ranger�BACKUP_COUNT�os�path�exists�open�gzip�shutil�copyfileobj�remove�	Exception�logger�	exception)�migrator�database�fake�kwargs�filename�i�source�dest�f_in�f_out�es           �e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/091_compress_old_logs.py�migrater#s���&�(�(�����q�&�-��1�2�2�
	�
	�A� �&�&�1�&�&�F��>�>�>�D�

��7�>�>�&�)�)�&��f�d�+�+�8�t�T�Y��d�6�6�8���*�4��7�7�7�8�8�8�8�8�8�8�8�8�8�8����8�8�8�8�8�8�8�8�8�8�8�8�8�8�8����8�8�8�8��I�f�%�%�%����
�
�
�� � �8�&�!�������������
����
	��s_�/C�-B<�B%	�B<�%B)
�)B<�,B)
�-B<�0C�<C�C�C�C�
D�&D�Dc��dS)z$Write your rollback migrations here.N�)rrrrs    r"�rollbackr&s���D�)F)
�loggingrrr� defence360agent.contracts.configr� defence360agent.internals.loggerr�	getLogger�__name__rr#r&r%r'r"�<module>r-s�������
�
�
�
�����	�	�	�	�3�3�3�3�3�3�?�?�?�?�?�?�	��	�8�	$�	$������$	�	�	�	�	�	r'defence360agent/migrations/__pycache__/091_compress_old_logs.cpython-311.pyc0000644000000000000000000000423700000000000023750 0ustar  �

�?t������n�ddlZddlZddlZddlZddlmZddlmZeje	��Z
dd�Zdd�ZdS)�N)�Logger)�get_log_file_namesFc	�&�t��D�]}tdtjdz��D]�}|�d|��}|�d�}	tj�|��r~t|d��5}tj|d��5}	tj
||	��ddd��n#1swxYwYddd��n#1swxYwYt	j|����#t$r&}
t�d||
��Yd}
~
��d}
~
wwxYw��dS)N��.z.gz�rb�wbz"Failed file %s compression with %s)r�ranger�BACKUP_COUNT�os�path�exists�open�gzip�shutil�copyfileobj�remove�	Exception�logger�	exception)�migrator�database�fake�kwargs�filename�i�source�dest�f_in�f_out�es           �e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/091_compress_old_logs.py�migrater#s���&�(�(�����q�&�-��1�2�2�
	�
	�A� �&�&�1�&�&�F��>�>�>�D�

��7�>�>�&�)�)�&��f�d�+�+�8�t�T�Y��d�6�6�8���*�4��7�7�7�8�8�8�8�8�8�8�8�8�8�8����8�8�8�8�8�8�8�8�8�8�8�8�8�8�8����8�8�8�8��I�f�%�%�%����
�
�
�� � �8�&�!�������������
����
	��s_�/C�-B<�B%	�B<�%B)
�)B<�,B)
�-B<�0C�<C�C�C�C�
D�&D�Dc��dS)z$Write your rollback migrations here.N�)rrrrs    r"�rollbackr&s���D�)F)
�loggingrrr� defence360agent.contracts.configr� defence360agent.internals.loggerr�	getLogger�__name__rr#r&r%r'r"�<module>r-s�������
�
�
�
�����	�	�	�	�3�3�3�3�3�3�?�?�?�?�?�?�	��	�8�	$�	$������$	�	�	�	�	�	r'defence360agent/migrations/__pycache__/092_ignore_proc_sys_dirs.cpython-311.opt-1.pyc0000644000000000000000000000154600000000000025420 0ustar  �

�Q�3�3(���dZdd�Zdd�ZdS)zC
This migration adds /proc and /sys to ignore for malware scanning
Fc�^�|s(dD]'}|jd}|�|����&dSdS)N)z/procz/sys�malware_ignore_path)�path)�orm�
get_or_create)�migrator�database�fake�kwargs�ignored_dir�MalwareIgnorePaths      �h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/092_ignore_proc_sys_dirs.py�migratersU���>�,�	>�	>�K� (��-B� C���+�+��+�=�=�=�=�>�>�	>�	>�c��dS)N�)rrr	r
s    r
�rollbackr
s���DrN)F)�__doc__rrrrr
�<module>rsA����
>�>�>�>�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/092_ignore_proc_sys_dirs.cpython-311.pyc0000644000000000000000000000154600000000000024461 0ustar  �

�Q�3�3(���dZdd�Zdd�ZdS)zC
This migration adds /proc and /sys to ignore for malware scanning
Fc�^�|s(dD]'}|jd}|�|����&dSdS)N)z/procz/sys�malware_ignore_path)�path)�orm�
get_or_create)�migrator�database�fake�kwargs�ignored_dir�MalwareIgnorePaths      �h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/092_ignore_proc_sys_dirs.py�migratersU���>�,�	>�	>�K� (��-B� C���+�+��+�=�=�=�=�>�>�	>�	>�c��dS)N�)rrr	r
s    r
�rollbackr
s���DrN)F)�__doc__rrrrr
�<module>rsA����
>�>�>�>�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/092_remove_old_disabled_rules.cpython-311.opt-1.pyc0000644000000000000000000000132000000000000026355 0ustar  �

�y��l���dZdd�Zdd�ZdS)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��dS�N���migrator�database�fake�kwargss    �m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/092_remove_old_disabled_rules.py�migrater����D�c��dSrrrs    r
�rollbackrrr
N)F)�__doc__rrrr
r
�<module>rsA����	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/092_remove_old_disabled_rules.cpython-311.pyc0000644000000000000000000000132000000000000025416 0ustar  �

�y��l���dZdd�Zdd�ZdS)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��dS�N���migrator�database�fake�kwargss    �m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/092_remove_old_disabled_rules.py�migrater����D�c��dSrrrs    r
�rollbackrrr
N)F)�__doc__rrrr
r
�<module>rsA����	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/093_make_quarantined_files_immutable.cpython-311.opt-1.pyc0000644000000000000000000000077300000000000027726 0ustar  �

�Y��l���dd�Zdd�ZdS)Fc��dS�N���migrator�database�fake�kwargss    �t/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/093_make_quarantined_files_immutable.py�migrater����D�c��dSrrrs    r
�rollbackrrr
N)F)rrrr
r
�<module>rs7��	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/093_make_quarantined_files_immutable.cpython-311.pyc0000644000000000000000000000077300000000000026767 0ustar  �

�Y��l���dd�Zdd�ZdS)Fc��dS�N���migrator�database�fake�kwargss    �t/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/093_make_quarantined_files_immutable.py�migrater����D�c��dSrrrs    r
�rollbackrrr
N)F)rrrr
r
�<module>rs7��	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/094_ignore_cagefs_proc.cpython-311.opt-1.pyc0000644000000000000000000000154200000000000025007 0ustar  �

�:�g=o����dZdd�Zdd�ZdS)zU
This migration adds /usr/share/cagefs-skeleton/proc/
to ignore for malware scanning
Fc�T�|s%|jd}|�d���dSdS)N�malware_ignore_pathz/usr/share/cagefs-skeleton/proc)�path)�orm�
get_or_create)�migrator�database�fake�kwargs�MalwareIgnorePaths     �f/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/094_ignore_cagefs_proc.py�migrater
sC���P�$�L�)>�?���'�'�-N�'�O�O�O�O�O�P�P�c��dS)N�)rrr	r
s    r�rollbackr
s���DrN)F)�__doc__r
rrrr�<module>rsE����P�P�P�P�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/094_ignore_cagefs_proc.cpython-311.pyc0000644000000000000000000000154200000000000024050 0ustar  �

�:�g=o����dZdd�Zdd�ZdS)zU
This migration adds /usr/share/cagefs-skeleton/proc/
to ignore for malware scanning
Fc�T�|s%|jd}|�d���dSdS)N�malware_ignore_pathz/usr/share/cagefs-skeleton/proc)�path)�orm�
get_or_create)�migrator�database�fake�kwargs�MalwareIgnorePaths     �f/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/094_ignore_cagefs_proc.py�migrater
sC���P�$�L�)>�?���'�'�-N�'�O�O�O�O�O�P�P�c��dS)N�)rrr	r
s    r�rollbackr
s���DrN)F)�__doc__r
rrrr�<module>rsE����P�P�P�P�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/095_add_total_malicious_field.cpython-311.opt-1.pyc0000644000000000000000000000167700000000000026346 0ustar  �

�5�~���f���ddlZdd�Zdd�ZdS)�NFc�v�|jd}|�|tjdd������dS)N�
malware_scansFr)�null�default)�total_malicious)�orm�
add_fields�pw�IntegerField��migrator�database�fake�kwargs�MalwareScans     �m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/095_add_total_malicious_field.py�migratersJ���,��/�K�������U�A�>�>�>�������c�L�|jd}|�|d��dS)Nrr)r�
remove_fieldsrs     r�rollbackrs+���,��/�K����;�(9�:�:�:�:�:r)F)�peeweer
rr�rr�<module>rsC����������;�;�;�;�;�;rdefence360agent/migrations/__pycache__/095_add_total_malicious_field.cpython-311.pyc0000644000000000000000000000167700000000000025407 0ustar  �

�5�~���f���ddlZdd�Zdd�ZdS)�NFc�v�|jd}|�|tjdd������dS)N�
malware_scansFr)�null�default)�total_malicious)�orm�
add_fields�pw�IntegerField��migrator�database�fake�kwargs�MalwareScans     �m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/095_add_total_malicious_field.py�migratersJ���,��/�K�������U�A�>�>�>�������c�L�|jd}|�|d��dS)Nrr)r�
remove_fieldsrs     r�rollbackrs+���,��/�K����;�(9�:�:�:�:�:r)F)�peeweer
rr�rr�<module>rsC����������;�;�;�;�;�;rdefence360agent/migrations/__pycache__/096_populate_total_malicious_field.cpython-311.opt-1.pyc0000644000000000000000000000204600000000000027437 0ustar  �

�G~,������dd�Zdd�ZdS)Fc��|rdS|jd}|jd}|D]`}|j����|j�����}||_|����adS)N�
malware_scans�malware_hits)�orm�malwarehit_set�select�where�	malicious�count�total_malicious�save)�migrator�database�fake�kwargs�MalwareScan�
MalwareHit�scanrs        �r/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/096_populate_total_malicious_field.py�migraters��������,��/�K���n�-�J�������&�&�(�(�.�.�z�/C�D�D�J�J�L�L�	� /����	�	�������c��dS)N�)r
rrrs    r�rollbackrs���DrN)F)rrrrr�<module>rs7������	�	�	�	�	�	rdefence360agent/migrations/__pycache__/096_populate_total_malicious_field.cpython-311.pyc0000644000000000000000000000204600000000000026500 0ustar  �

�G~,������dd�Zdd�ZdS)Fc��|rdS|jd}|jd}|D]`}|j����|j�����}||_|����adS)N�
malware_scans�malware_hits)�orm�malwarehit_set�select�where�	malicious�count�total_malicious�save)�migrator�database�fake�kwargs�MalwareScan�
MalwareHit�scanrs        �r/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/096_populate_total_malicious_field.py�migraters��������,��/�K���n�-�J�������&�&�(�(�.�.�z�/C�D�D�J�J�L�L�	� /����	�	�������c��dS)N�)r
rrrs    r�rollbackrs���DrN)F)rrrrr�<module>rs7������	�	�	�	�	�	rdefence360agent/migrations/__pycache__/097_remove_uid_and_gid.cpython-311.opt-1.pyc0000644000000000000000000000176700000000000025010 0ustar  �

��lI灸��>�ddlZeje��Zdd�Zdd�ZdS)�NFc��|jd}	|�|dd��dS#t$r%}t�|��Yd}~dSd}~wwxYw)N�malware_hits�uid�gid)�orm�
remove_fields�	Exception�logger�	exception)�migrator�database�fake�kwargs�
MalwareHit�es      �f/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/097_remove_uid_and_gid.py�migratersv����n�-�J�����z�5�%�8�8�8�8�8��������������������������s�(�
A�A�Ac��dS)N�)rr
rrs    r�rollbackrs���D�)F)�logging�	getLogger�__name__r
rrrrr�<module>rsR������	��	�8�	$�	$������	�	�	�	�	�	rdefence360agent/migrations/__pycache__/097_remove_uid_and_gid.cpython-311.pyc0000644000000000000000000000176700000000000024051 0ustar  �

��lI灸��>�ddlZeje��Zdd�Zdd�ZdS)�NFc��|jd}	|�|dd��dS#t$r%}t�|��Yd}~dSd}~wwxYw)N�malware_hits�uid�gid)�orm�
remove_fields�	Exception�logger�	exception)�migrator�database�fake�kwargs�
MalwareHit�es      �f/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/097_remove_uid_and_gid.py�migratersv����n�-�J�����z�5�%�8�8�8�8�8��������������������������s�(�
A�A�Ac��dS)N�)rr
rrs    r�rollbackrs���D�)F)�logging�	getLogger�__name__r
rrrrr�<module>rsR������	��	�8�	$�	$������	�	�	�	�	�	rdefence360agent/migrations/__pycache__/098_remote_proxy_tables.cpython-311.opt-1.pyc0000644000000000000000000000533000000000000025262 0ustar  �

{3=q�[8x���ddlZdd�Zdd�ZdS)�NFc���Gd�dtj���G�fd�dtj��}|����|�|��dS)Nc	���eZdZdZdZdZejd���Zejdej	d�
ee����g���Zejdd�	��Z
Gd
�d��ZdS)
�!migrate.<locals>.RemoteProxyGroupz9Groups multiple remote proxies together with common data.�manual�
imunify360F��nullzsource in ('{}', '{}'))r	�constraintsT)r	�defaultc��eZdZdZdZdS)�&migrate.<locals>.RemoteProxyGroup.Meta�remote_proxy_group)))�name�sourceTN)�__name__�
__module__�__qualname__�db_table�indexes���g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/098_remote_proxy_tables.py�Metar
s������+�H�3�G�G�GrrN)rrr�__doc__�MANUAL�
IMUNIFY360�pw�	CharFieldr�Check�formatr�BooleanField�enabledrrrr�RemoteProxyGrouprs�������G�G���!�
��r�|��'�'�'���������1�8�8���L�L�M�M��
�
�
��"�"�/�u�d�;�;�;��	4�	4�	4�	4�	4�	4�	4�	4�	4�	4rr#c�p��eZdZej�d���Zejd���ZGd�d��ZdS)�migrate.<locals>.RemoteProxyFrc��eZdZdZdS)�!migrate.<locals>.RemoteProxy.Meta�remote_proxyN)rrrrrrrrr's������%�H�H�HrrN)	rrrr�ForeignKeyField�group�	TextField�networkr)r#s�r�RemoteProxyr%si�������"��"�#3�%�@�@�@���"�,�E�*�*�*��	&�	&�	&�	&�	&�	&�	&�	&�	&�	&rr-)r�Model�create_model��migrator�database�fake�kwargsr-r#s     @r�migrater5s����4�4�4�4�4�2�8�4�4�4�$&�&�&�&�&�&�&�b�h�&�&�&�
���*�+�+�+����+�&�&�&�&�&rc��|jd}|jd}|�|��|�|��dS)Nr(r)�orm�remove_modelr0s      r�rollbackr9"sK���,�~�.�K��|�$8�9�����+�&�&�&����*�+�+�+�+�+r)F)�peeweerr5r9rrr�<module>r;sC������'�'�'�'�<,�,�,�,�,�,rdefence360agent/migrations/__pycache__/098_remote_proxy_tables.cpython-311.pyc0000644000000000000000000000533000000000000024323 0ustar  �

{3=q�[8x���ddlZdd�Zdd�ZdS)�NFc���Gd�dtj���G�fd�dtj��}|����|�|��dS)Nc	���eZdZdZdZdZejd���Zejdej	d�
ee����g���Zejdd�	��Z
Gd
�d��ZdS)
�!migrate.<locals>.RemoteProxyGroupz9Groups multiple remote proxies together with common data.�manual�
imunify360F��nullzsource in ('{}', '{}'))r	�constraintsT)r	�defaultc��eZdZdZdZdS)�&migrate.<locals>.RemoteProxyGroup.Meta�remote_proxy_group)))�name�sourceTN)�__name__�
__module__�__qualname__�db_table�indexes���g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/098_remote_proxy_tables.py�Metar
s������+�H�3�G�G�GrrN)rrr�__doc__�MANUAL�
IMUNIFY360�pw�	CharFieldr�Check�formatr�BooleanField�enabledrrrr�RemoteProxyGrouprs�������G�G���!�
��r�|��'�'�'���������1�8�8���L�L�M�M��
�
�
��"�"�/�u�d�;�;�;��	4�	4�	4�	4�	4�	4�	4�	4�	4�	4rr#c�p��eZdZej�d���Zejd���ZGd�d��ZdS)�migrate.<locals>.RemoteProxyFrc��eZdZdZdS)�!migrate.<locals>.RemoteProxy.Meta�remote_proxyN)rrrrrrrrr's������%�H�H�HrrN)	rrrr�ForeignKeyField�group�	TextField�networkr)r#s�r�RemoteProxyr%si�������"��"�#3�%�@�@�@���"�,�E�*�*�*��	&�	&�	&�	&�	&�	&�	&�	&�	&�	&rr-)r�Model�create_model��migrator�database�fake�kwargsr-r#s     @r�migrater5s����4�4�4�4�4�2�8�4�4�4�$&�&�&�&�&�&�&�b�h�&�&�&�
���*�+�+�+����+�&�&�&�&�&rc��|jd}|jd}|�|��|�|��dS)Nr(r)�orm�remove_modelr0s      r�rollbackr9"sK���,�~�.�K��|�$8�9�����+�&�&�&����*�+�+�+�+�+r)F)�peeweerr5r9rrr�<module>r;sC������'�'�'�'�<,�,�,�,�,�,rdefence360agent/migrations/__pycache__/099_remove_old_disabled_rules.cpython-311.opt-1.pyc0000644000000000000000000000423200000000000026371 0ustar  �

4�}*>=���ddlZddlZddlZddlZddlmZmZeje��Z	dZ
ejdd���Zdd�Z
dS)�N)�antivirus_mode�run_coroz,/etc/apache2/conf.d/i360_modsec_disable.confFc�,�|rdS	ddlm}n#t$rYdSwxYw	|���r!t	|�����sdS|��}tj�t��r]tjttj�|j
|j����tj|j��dSdS#t$$r&}t&�d|��Yd}~dSd}~wwxYw)Nr)�cPanelz)Failed to delete old rules config with %s)�im360.subsys.panels.cpanelr�ImportError�is_installedr�installed_modsec�os�path�exists�OLD_DISABLED_RULES_CONFIG�shutil�move�join�DISABLED_RULES_CONFIG_DIR�%GLOBAL_DISABLED_RULES_CONFIG_FILENAME�
subprocess�
check_call�REBUILD_HTTPDCONF_CMD�	Exception�logger�	exception)�migrator�database�fake�kwargsr�hp�es       �m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/099_remove_old_disabled_rules.py�migrater!
sX�������5�5�5�5�5�5�5�������������I��"�"�$�$�	�H��#�#�%�%�-
�-
�	�
�F�
�V�X�X��
�7�>�>�3�4�4�		<��K�)������0��<���
�
�
�
�!�"�":�;�;�;�;�;�		<�		<���I�I�I����D�a�H�H�H�H�H�H�H�H�H�����I���s*�
�
��5C#�B	C#�#
D�-D�Dc��dS)N�)rrrrs    r �rollbackr$,s���D�)F)�loggingrrr�defence360agent.utilsrr�	getLogger�__name__rr�skipr!r$r#r%r �<module>r+s�������	�	�	�	�
�
�
�
�����:�:�:�:�:�:�:�:�	��	�8�	$�	$��J����I�I�I���I�<	�	�	�	�	�	r%defence360agent/migrations/__pycache__/099_remove_old_disabled_rules.cpython-311.pyc0000644000000000000000000000423200000000000025432 0ustar  �

4�}*>=���ddlZddlZddlZddlZddlmZmZeje��Z	dZ
ejdd���Zdd�Z
dS)�N)�antivirus_mode�run_coroz,/etc/apache2/conf.d/i360_modsec_disable.confFc�,�|rdS	ddlm}n#t$rYdSwxYw	|���r!t	|�����sdS|��}tj�t��r]tjttj�|j
|j����tj|j��dSdS#t$$r&}t&�d|��Yd}~dSd}~wwxYw)Nr)�cPanelz)Failed to delete old rules config with %s)�im360.subsys.panels.cpanelr�ImportError�is_installedr�installed_modsec�os�path�exists�OLD_DISABLED_RULES_CONFIG�shutil�move�join�DISABLED_RULES_CONFIG_DIR�%GLOBAL_DISABLED_RULES_CONFIG_FILENAME�
subprocess�
check_call�REBUILD_HTTPDCONF_CMD�	Exception�logger�	exception)�migrator�database�fake�kwargsr�hp�es       �m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/099_remove_old_disabled_rules.py�migrater!
sX�������5�5�5�5�5�5�5�������������I��"�"�$�$�	�H��#�#�%�%�-
�-
�	�
�F�
�V�X�X��
�7�>�>�3�4�4�		<��K�)������0��<���
�
�
�
�!�"�":�;�;�;�;�;�		<�		<���I�I�I����D�a�H�H�H�H�H�H�H�H�H�����I���s*�
�
��5C#�B	C#�#
D�-D�Dc��dS)N�)rrrrs    r �rollbackr$,s���D�)F)�loggingrrr�defence360agent.utilsrr�	getLogger�__name__rr�skipr!r$r#r%r �<module>r+s�������	�	�	�	�
�
�
�
�����:�:�:�:�:�:�:�:�	��	�8�	$�	$��J����I�I�I���I�<	�	�	�	�	�	r%defence360agent/migrations/__pycache__/100_remove_captcha_ports_from_csf.cpython-311.opt-1.pyc0000644000000000000000000000264400000000000027246 0ustar  �

�rw��lR��F�ddlZddlZeje��Zdd�Zdd�ZdS)�NFc�(�|rdS	ddlm}ddlm}m}n#t
$rYdSwxYwtj�|j	��sdS	|�
||dddd��dS#t$rt�
d��YdSwxYw)	Nr)�csf)�IN�TCPi��i�i�i�z.Failed to remove captcha ports from csf config)�im360.subsysr�im360.utils.netrr�ImportError�os�path�isfile�
CSF_CONFIG�remove_ports�	Exception�logger�	exception)�migrator�database�fake�kwargsrrrs       �q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/100_remove_captcha_ports_from_csf.py�migraters��������$�$�$�$�$�$�+�+�+�+�+�+�+�+�+��������������7�>�>�#�.�)�)����K�����b�%���u�=�=�=�=�=���K�K�K����I�J�J�J�J�J�J�K���s��
#�#�
A)�)$B�Bc��dS)N�)rrrrs    r�rollbackrs���D�)F)r
�logging�	getLogger�__name__rrrrrr�<module>rs_��	�	�	�	�����	��	�8�	$�	$��K�K�K�K�"	�	�	�	�	�	rdefence360agent/migrations/__pycache__/100_remove_captcha_ports_from_csf.cpython-311.pyc0000644000000000000000000000264400000000000026307 0ustar  �

�rw��lR��F�ddlZddlZeje��Zdd�Zdd�ZdS)�NFc�(�|rdS	ddlm}ddlm}m}n#t
$rYdSwxYwtj�|j	��sdS	|�
||dddd��dS#t$rt�
d��YdSwxYw)	Nr)�csf)�IN�TCPi��i�i�i�z.Failed to remove captcha ports from csf config)�im360.subsysr�im360.utils.netrr�ImportError�os�path�isfile�
CSF_CONFIG�remove_ports�	Exception�logger�	exception)�migrator�database�fake�kwargsrrrs       �q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/100_remove_captcha_ports_from_csf.py�migraters��������$�$�$�$�$�$�+�+�+�+�+�+�+�+�+��������������7�>�>�#�.�)�)����K�����b�%���u�=�=�=�=�=���K�K�K����I�J�J�J�J�J�J�K���s��
#�#�
A)�)$B�Bc��dS)N�)rrrrs    r�rollbackrs���D�)F)r
�logging�	getLogger�__name__rrrrrr�<module>rs_��	�	�	�	�����	��	�8�	$�	$��K�K�K�K�"	�	�	�	�	�	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  �

};������F�ddlZddlZeje��Zdd�Zdd�ZdS)�NFc�j�|rdS	ddlm}ddlm}m}m}n#t$rYdSwxYwtj�	|j
��sdS	|�||dddddd	d
h��	�	|�||dddd	��dS#t$rt�d��YdSwxYw)
Nr)�csf)�IN�OUT�TCPi��i�i�i�ai�i�)iZix)�rangesz5Failed to remove unused Arconis ports from csf config)�im360.subsysr�im360.utils.netrrr�ImportError�os�path�isfile�
CSF_CONFIG�remove_ports�	Exception�logger�	exception)�migrator�database�fake�kwargsrrrrs        �z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/101_remove_unneeded_acronis_ports_from_csf.py�migraters2�������$�$�$�$�$�$�0�0�0�0�0�0�0�0�0�0�0��������������7�>�>�#�.�)�)����
������������ �>�	�
	
�
	
�
	
�	����c�4���T�:�:�:�:�:���
�
�
����C�	
�	
�	
�	
�	
�	
�
���s��
%�%�9B
�
$B2�1B2c��dS)N�)rrrrs    r�rollbackr&s���D�)F)r�logging�	getLogger�__name__rrrrrr�<module>r!s[��	�	�	�	�����	��	�8�	$�	$��
�
�
�
�>	�	�	�	�	�	rdefence360agent/migrations/__pycache__/101_remove_unneeded_acronis_ports_from_csf.cpython-311.pyc0000644000000000000000000000315300000000000030206 0ustar  �

};������F�ddlZddlZeje��Zdd�Zdd�ZdS)�NFc�j�|rdS	ddlm}ddlm}m}m}n#t$rYdSwxYwtj�	|j
��sdS	|�||dddddd	d
h��	�	|�||dddd	��dS#t$rt�d��YdSwxYw)
Nr)�csf)�IN�OUT�TCPi��i�i�i�ai�i�)iZix)�rangesz5Failed to remove unused Arconis ports from csf config)�im360.subsysr�im360.utils.netrrr�ImportError�os�path�isfile�
CSF_CONFIG�remove_ports�	Exception�logger�	exception)�migrator�database�fake�kwargsrrrrs        �z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/101_remove_unneeded_acronis_ports_from_csf.py�migraters2�������$�$�$�$�$�$�0�0�0�0�0�0�0�0�0�0�0��������������7�>�>�#�.�)�)����
������������ �>�	�
	
�
	
�
	
�	����c�4���T�:�:�:�:�:���
�
�
����C�	
�	
�	
�	
�	
�	
�
���s��
%�%�9B
�
$B2�1B2c��dS)N�)rrrrs    r�rollbackr&s���D�)F)r�logging�	getLogger�__name__rrrrrr�<module>r!s[��	�	�	�	�����	��	�8�	$�	$��
�
�
�
�>	�	�	�	�	�	rdefence360agent/migrations/__pycache__/102_proactive_ignore_list.cpython-311.opt-1.pyc0000644000000000000000000000645600000000000025562 0ustar  �

���c�q��*�ddlmZddlZdd�Zdd�ZdS)�)�timeNFc�\��Gd�dtj���G�fd�dtj��}|����|�|��|jd}|�|tjd������|�|d	d
��dS)Nc�t�eZdZdZejdd���Zejde���Z	Gd�d��Z
dS)	�%migrate.<locals>.ProactiveIgnoredPathz3
        Ignore list for proactive defence
        FT)�null�primary_key)r�defaultc��eZdZdZdS)�*migrate.<locals>.ProactiveIgnoredPath.Meta�proactive_ignored_pathN)�__name__�
__module__�__qualname__�db_table���i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/102_proactive_ignore_list.py�Metars������/�H�H�HrrN)r
rr�__doc__�pw�	TextField�path�IntegerFieldr�	timestamprrrr�ProactiveIgnoredPathrst������	�	��r�|��D�9�9�9��#�B�O���=�=�=�	�	0�	0�	0�	0�	0�	0�	0�	0�	0�	0rrc���eZdZdZej�ddd���Zejd���Zej	d���Z
Gd�d��Zd	S)
�%migrate.<locals>.ProactiveIgnoredRulez(
        Specific rules ignored
        F�CASCADE�rules)r�	on_delete�related_name�rc��eZdZdZdZdS)�*migrate.<locals>.ProactiveIgnoredRule.Meta�proactive_ignored_rule)))r�rule_idTN)r
rrr�indexesrrrrr$ s������/�H�4�G�G�GrrN)r
rrrr�ForeignKeyFieldrrr&r�	rule_namer)rs�r�ProactiveIgnoredRulers��������	�	�"�r�!� ��� �	
�
�
��"�"�/�u�-�-�-�� �B�L�e�,�,�,�	�	5�	5�	5�	5�	5�	5�	5�	5�	5�	5rr*�	proactiveTr")r&�reasonr))r�Model�create_model�orm�
add_fieldsr�rename_field)�migrator�database�fake�kwargsr*�	Proactivers      @r�migrater7s����	0�	0�	0�	0�	0�r�x�	0�	0�	0�5�5�5�5�5�5�5�r�x�5�5�5�$
���.�/�/�/����.�/�/�/���[�)�I�������T�*�*�*�����
���)�X�{�;�;�;�;�;rc��|jd}|jd}|�|��|�|��|jd}|�|d��|�|dd��dS)Nrr%r+r&r)r,)r/�remove_model�
remove_fieldsr1)r2r3r4r5rr*r6s       r�rollbackr;/s���#�<�(@�A��#�<�(@�A�����.�/�/�/����.�/�/�/���[�)�I����9�i�0�0�0����)�[�(�;�;�;�;�;r)F)r�peeweerr7r;rrr�<module>r=sV������������&<�&<�&<�&<�R<�<�<�<�<�<rdefence360agent/migrations/__pycache__/102_proactive_ignore_list.cpython-311.pyc0000644000000000000000000000645600000000000024623 0ustar  �

���c�q��*�ddlmZddlZdd�Zdd�ZdS)�)�timeNFc�\��Gd�dtj���G�fd�dtj��}|����|�|��|jd}|�|tjd������|�|d	d
��dS)Nc�t�eZdZdZejdd���Zejde���Z	Gd�d��Z
dS)	�%migrate.<locals>.ProactiveIgnoredPathz3
        Ignore list for proactive defence
        FT)�null�primary_key)r�defaultc��eZdZdZdS)�*migrate.<locals>.ProactiveIgnoredPath.Meta�proactive_ignored_pathN)�__name__�
__module__�__qualname__�db_table���i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/102_proactive_ignore_list.py�Metars������/�H�H�HrrN)r
rr�__doc__�pw�	TextField�path�IntegerFieldr�	timestamprrrr�ProactiveIgnoredPathrst������	�	��r�|��D�9�9�9��#�B�O���=�=�=�	�	0�	0�	0�	0�	0�	0�	0�	0�	0�	0rrc���eZdZdZej�ddd���Zejd���Zej	d���Z
Gd�d��Zd	S)
�%migrate.<locals>.ProactiveIgnoredRulez(
        Specific rules ignored
        F�CASCADE�rules)r�	on_delete�related_name�rc��eZdZdZdZdS)�*migrate.<locals>.ProactiveIgnoredRule.Meta�proactive_ignored_rule)))r�rule_idTN)r
rrr�indexesrrrrr$ s������/�H�4�G�G�GrrN)r
rrrr�ForeignKeyFieldrrr&r�	rule_namer)rs�r�ProactiveIgnoredRulers��������	�	�"�r�!� ��� �	
�
�
��"�"�/�u�-�-�-�� �B�L�e�,�,�,�	�	5�	5�	5�	5�	5�	5�	5�	5�	5�	5rr*�	proactiveTr")r&�reasonr))r�Model�create_model�orm�
add_fieldsr�rename_field)�migrator�database�fake�kwargsr*�	Proactivers      @r�migrater7s����	0�	0�	0�	0�	0�r�x�	0�	0�	0�5�5�5�5�5�5�5�r�x�5�5�5�$
���.�/�/�/����.�/�/�/���[�)�I�������T�*�*�*�����
���)�X�{�;�;�;�;�;rc��|jd}|jd}|�|��|�|��|jd}|�|d��|�|dd��dS)Nrr%r+r&r)r,)r/�remove_model�
remove_fieldsr1)r2r3r4r5rr*r6s       r�rollbackr;/s���#�<�(@�A��#�<�(@�A�����.�/�/�/����.�/�/�/���[�)�I����9�i�0�0�0����)�[�(�;�;�;�;�;r)F)r�peeweerr7r;rrr�<module>r=sV������������&<�&<�&<�&<�R<�<�<�<�<�<rdefence360agent/migrations/__pycache__/102_replace_comodo.cpython-311.opt-1.pyc0000644000000000000000000000151300000000000024130 0ustar  �

z��?�*T���dd�Zdd�ZdS)Fc�Z�|�d��|�d��dS)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'))�sql��migrator�database�fake�kwargss    �b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/102_replace_comodo.py�migrater
sE���L�L�	F����

�L�L�	<������c��dS)N�rs    r	�rollbackr
s���DrN)F)r
rr
rr	�<module>rs7��	�	�	�	�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/102_replace_comodo.cpython-311.pyc0000644000000000000000000000151300000000000023171 0ustar  �

z��?�*T���dd�Zdd�ZdS)Fc�Z�|�d��|�d��dS)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'))�sql��migrator�database�fake�kwargss    �b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/102_replace_comodo.py�migrater
sE���L�L�	F����

�L�L�	<������c��dS)N�rs    r	�rollbackr
s���DrN)F)r
rr
rr	�<module>rs7��	�	�	�	�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/103_remove_vd_license.cpython-311.opt-1.pyc0000644000000000000000000000124500000000000024650 0ustar  �

^sq����<�dZddlmZee��Zdd�Zdd�ZdS)z'
Remove Virusdie registration from CLN
�)�	getLoggerFc��dS�N���migrator�database�fake�kwargss    �e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/103_remove_vd_license.py�migrater

����D�c��dSrrrs    r�rollbackrrrN)F)�__doc__�loggingr�__name__�loggerr
rrrr�<module>rsf����������
��8�	�	��	�	�	�	�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/103_remove_vd_license.cpython-311.pyc0000644000000000000000000000124500000000000023711 0ustar  �

^sq����<�dZddlmZee��Zdd�Zdd�ZdS)z'
Remove Virusdie registration from CLN
�)�	getLoggerFc��dS�N���migrator�database�fake�kwargss    �e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/103_remove_vd_license.py�migrater

����D�c��dSrrrs    r�rollbackrrrN)F)�__doc__�loggingr�__name__�loggerr
rrrr�<module>rsf����������
��8�	�	��	�	�	�	�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/104_add_feature_management_permissions.cpython-311.opt-1.pyc0000644000000000000000000000274300000000000030257 0ustar  �

0c�����F�ddlmZmZmZGd�de��Zdd�Zdd�ZdS)	�)�BooleanField�	CharField�Modelc�X�eZdZGd�d��Zed���Zed���ZdS)�FeatureManagementPermsc��eZdZdZdS)�FeatureManagementPerms.Meta�feature_management_permissionsN)�__name__�
__module__�__qualname__�db_table���v/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/104_add_feature_management_permissions.py�Metar	s������3���rrT)�unique)�defaultN)rrr
rr�userr�	proactiverrrrrs\������4�4�4�4�4�4�4�4��9�D�!�!�!�D���T�*�*�*�I�I�IrrFc�:�|�t��dS)N)�create_modelr)�migrator�database�fake�kwargss    r�migraters�����0�1�1�1�1�1rc�J�|jd}|�|��dS)Nr
)�orm�remove_model)rrrrrs     r�rollbackr!s+��%�\�*J�K�����0�1�1�1�1�1rN)F)�peeweerrrrrr!rrr�<module>r#s��1�1�1�1�1�1�1�1�1�1�+�+�+�+�+�U�+�+�+�2�2�2�2�2�2�2�2�2�2rdefence360agent/migrations/__pycache__/104_add_feature_management_permissions.cpython-311.pyc0000644000000000000000000000274300000000000027320 0ustar  �

0c�����F�ddlmZmZmZGd�de��Zdd�Zdd�ZdS)	�)�BooleanField�	CharField�Modelc�X�eZdZGd�d��Zed���Zed���ZdS)�FeatureManagementPermsc��eZdZdZdS)�FeatureManagementPerms.Meta�feature_management_permissionsN)�__name__�
__module__�__qualname__�db_table���v/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/104_add_feature_management_permissions.py�Metar	s������3���rrT)�unique)�defaultN)rrr
rr�userr�	proactiverrrrrs\������4�4�4�4�4�4�4�4��9�D�!�!�!�D���T�*�*�*�I�I�IrrFc�:�|�t��dS)N)�create_modelr)�migrator�database�fake�kwargss    r�migraters�����0�1�1�1�1�1rc�J�|jd}|�|��dS)Nr
)�orm�remove_model)rrrrrs     r�rollbackr!s+��%�\�*J�K�����0�1�1�1�1�1rN)F)�peeweerrrrrr!rrr�<module>r#s��1�1�1�1�1�1�1�1�1�1�+�+�+�+�+�U�+�+�+�2�2�2�2�2�2�2�2�2�2r././@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  �

�\�&���dZdd�Zdd�ZdS)�Fc�^�|rdS|jd}|�t���dS)N�feature_management_permissions)�user)�orm�
get_or_create�DEFAULT)�migrator�database�fake�kwargs�FeatureManagementPermss     ��/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/105_populate_default_feature_management_permissions.py�migraters:������%�\�*J�K���(�(�g�(�6�6�6�6�6�c��dS)N�)r	r
rrs    r�rollbackr
s���DrN)F)rrrrrr�<module>rs<��
��7�7�7�7�	�	�	�	�	�	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  �

�\�&���dZdd�Zdd�ZdS)�Fc�^�|rdS|jd}|�t���dS)N�feature_management_permissions)�user)�orm�
get_or_create�DEFAULT)�migrator�database�fake�kwargs�FeatureManagementPermss     ��/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/105_populate_default_feature_management_permissions.py�migraters:������%�\�*J�K���(�(�g�(�6�6�6�6�6�c��dS)N�)r	r
rrs    r�rollbackr
s���DrN)F)rrrrrr�<module>rs<��
��7�7�7�7�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/106_add_malware_cleanup_in_config.cpython-311.opt-1.pyc0000644000000000000000000000225500000000000027147 0ustar  �


��#ād���"�ddlmZdd�Zdd�ZdS)�)�
ConfigFileFc��|rdSt��}|���}|sdS|�di��}|�dd��|�dd��|�|d���dS)N�MALWARE_CLEANUP�trim_file_instead_of_removalT�keep_original_files_days�F)�validate)r�config_to_dict�
setdefault�dict_to_config)�migrator�database�fake�kwargs�config_file�conf�malware_cleanups       �q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/106_add_malware_cleanup_in_config.py�migraters��������,�,�K��%�%�'�'�D������o�o�&7��<�<�O����=�t�D�D�D����9�2�>�>�>����t�e��4�4�4�4�4�c��dS)N�)r
rrrs    r�rollbackrs���DrN)F)� defence360agent.contracts.configrrrrrr�<module>rsI��7�7�7�7�7�7�5�5�5�5�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/106_add_malware_cleanup_in_config.cpython-311.pyc0000644000000000000000000000225500000000000026210 0ustar  �


��#ād���"�ddlmZdd�Zdd�ZdS)�)�
ConfigFileFc��|rdSt��}|���}|sdS|�di��}|�dd��|�dd��|�|d���dS)N�MALWARE_CLEANUP�trim_file_instead_of_removalT�keep_original_files_days�F)�validate)r�config_to_dict�
setdefault�dict_to_config)�migrator�database�fake�kwargs�config_file�conf�malware_cleanups       �q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/106_add_malware_cleanup_in_config.py�migraters��������,�,�K��%�%�'�'�D������o�o�&7��<�<�O����=�t�D�D�D����9�2�>�>�>����t�e��4�4�4�4�4�c��dS)N�)r
rrrs    r�rollbackrs���DrN)F)� defence360agent.contracts.configrrrrrr�<module>rsI��7�7�7�7�7�7�5�5�5�5�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/106_malware_hit_status_field_add.cpython-311.opt-1.pyc0000644000000000000000000000256300000000000027041 0ustar  �

�6/������ddlZddlmZmZddlmZejddd���Zeje	��Z
d
d�Zd
d	�ZdS)�N)�	CharField�
FloatField)�importerzimav.malwarelib.config�MalwareHitStatus)�module�name�defaultFc��|jd}|�|ttj���td������dS)N�malware_hits)r	T)�null)�status�
cleaned_at)�orm�
add_fieldsrr�FOUNDr��migrator�database�fake�kwargs�
MalwareHits     �p/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/106_malware_hit_status_field_add.py�migratersX����n�-�J������!1�!7�8�8�8��4�(�(�(�������c�N�|jd}|�|dd��dS)Nrr
r)r�
remove_fieldsrs     r�rollbackrs,����n�-�J����:�x��>�>�>�>�>r)F)
�logging�peeweerr�defence360agent.utilsr�getr�	getLogger�__name__�loggerrr�rr�<module>r&s�������(�(�(�(�(�(�(�(�*�*�*�*�*�*��8�<�#�*<�d�����

��	�8�	$�	$������?�?�?�?�?�?rdefence360agent/migrations/__pycache__/106_malware_hit_status_field_add.cpython-311.pyc0000644000000000000000000000256300000000000026102 0ustar  �

�6/������ddlZddlmZmZddlmZejddd���Zeje	��Z
d
d�Zd
d	�ZdS)�N)�	CharField�
FloatField)�importerzimav.malwarelib.config�MalwareHitStatus)�module�name�defaultFc��|jd}|�|ttj���td������dS)N�malware_hits)r	T)�null)�status�
cleaned_at)�orm�
add_fieldsrr�FOUNDr��migrator�database�fake�kwargs�
MalwareHits     �p/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/106_malware_hit_status_field_add.py�migratersX����n�-�J������!1�!7�8�8�8��4�(�(�(�������c�N�|jd}|�|dd��dS)Nrr
r)r�
remove_fieldsrs     r�rollbackrs,����n�-�J����:�x��>�>�>�>�>r)F)
�logging�peeweerr�defence360agent.utilsr�getr�	getLogger�__name__�loggerrr�rr�<module>r&s�������(�(�(�(�(�(�(�(�*�*�*�*�*�*��8�<�#�*<�d�����

��	�8�	$�	$������?�?�?�?�?�?rdefence360agent/migrations/__pycache__/107_add_bruteforce_rule_33339.cpython-311.opt-1.pyc0000644000000000000000000000267600000000000025740 0ustar  �

29��}����N�ddlZddlmZdZeje��Zdd�Zdd�ZdS)�N)�
ConfigFile�MOD_SEC_BLOCK_BY_CUSTOM_RULEFc�,�|rdS	t��}|�d���}|�ti��}ddd�|d<|�t|i��dS#t
$rt�d��YdSwxYw)NF)�	normalize�x�
)�check_period�
max_incidents�33339zFailed to create rule for 33339)r�config_to_dict�
setdefault�KEY�dict_to_config�	Exception�logger�	exception)�migrator�database�fake�kwargs�config_file�config�mod_sec_block_ruless       �m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/107_add_bruteforce_rule_33339.py�migrater
s�������<� �l�l���+�+�e�+�<�<��$�/�/��R�8�8����(
�(
��G�$�
	�"�"�C�)<�#=�>�>�>�>�>���<�<�<����:�;�;�;�;�;�;�<���s�A#A+�+$B�Bc��dS)N�)rrrrs    r�rollbackrs���D�)F)	�logging� defence360agent.contracts.configrr�	getLogger�__name__rrrrrr�<module>r$si������7�7�7�7�7�7�$��	��	�8�	$�	$��<�<�<�<�*	�	�	�	�	�	rdefence360agent/migrations/__pycache__/107_add_bruteforce_rule_33339.cpython-311.pyc0000644000000000000000000000267600000000000025001 0ustar  �

29��}����N�ddlZddlmZdZeje��Zdd�Zdd�ZdS)�N)�
ConfigFile�MOD_SEC_BLOCK_BY_CUSTOM_RULEFc�,�|rdS	t��}|�d���}|�ti��}ddd�|d<|�t|i��dS#t
$rt�d��YdSwxYw)NF)�	normalize�x�
)�check_period�
max_incidents�33339zFailed to create rule for 33339)r�config_to_dict�
setdefault�KEY�dict_to_config�	Exception�logger�	exception)�migrator�database�fake�kwargs�config_file�config�mod_sec_block_ruless       �m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/107_add_bruteforce_rule_33339.py�migrater
s�������<� �l�l���+�+�e�+�<�<��$�/�/��R�8�8����(
�(
��G�$�
	�"�"�C�)<�#=�>�>�>�>�>���<�<�<����:�;�;�;�;�;�;�<���s�A#A+�+$B�Bc��dS)N�)rrrrs    r�rollbackrs���D�)F)	�logging� defence360agent.contracts.configrr�	getLogger�__name__rrrrrr�<module>r$si������7�7�7�7�7�7�$��	��	�8�	$�	$��<�<�<�<�*	�	�	�	�	�	rdefence360agent/migrations/__pycache__/107_malware_hit_status_field_populate.cpython-311.opt-1.pyc0000644000000000000000000000205200000000000030134 0ustar  �

���sjy��J�ddlZddlmZeje��Zdd�Zdd�ZdS)�N)�BooleanFieldFc�T�|s%|jd}|�|d��dSdS)N�malware_hits�restored)�orm�
remove_fields��migrator�database�fake�kwargs�
MalwareHits     �u/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/107_malware_hit_status_field_populate.py�migraters<���7��\�.�1�
����z�:�6�6�6�6�6�7�7�c�j�|jd}|�|td������dS)NrF)�default)r)r�
add_fieldsrr	s     r�rollbackrs8����n�-�J����
�\�%�-H�-H�-H��I�I�I�I�Ir)F)�logging�peeweer�	getLogger�__name__�loggerrr�rr�<module>rsj������������	��	�8�	$�	$��7�7�7�7�J�J�J�J�J�Jrdefence360agent/migrations/__pycache__/107_malware_hit_status_field_populate.cpython-311.pyc0000644000000000000000000000205200000000000027175 0ustar  �

���sjy��J�ddlZddlmZeje��Zdd�Zdd�ZdS)�N)�BooleanFieldFc�T�|s%|jd}|�|d��dSdS)N�malware_hits�restored)�orm�
remove_fields��migrator�database�fake�kwargs�
MalwareHits     �u/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/107_malware_hit_status_field_populate.py�migraters<���7��\�.�1�
����z�:�6�6�6�6�6�7�7�c�j�|jd}|�|td������dS)NrF)�default)r)r�
add_fieldsrr	s     r�rollbackrs8����n�-�J����
�\�%�-H�-H�-H��I�I�I�I�Ir)F)�logging�peeweer�	getLogger�__name__�loggerrr�rr�<module>rsj������������	��	�8�	$�	$��7�7�7�7�J�J�J�J�J�Jrdefence360agent/migrations/__pycache__/108_feature_management_cleanup_add.cpython-311.opt-1.pyc0000644000000000000000000000206600000000000027335 0ustar  �

�
F�=���J�ddlZddlmZeje��Zdd�Zdd�ZdS)�N)�BooleanFieldFc�j�|jd}|�|td������dS)N�feature_management_permissionsF)�default)�cleanup)�orm�
add_fieldsr��migrator�database�fake�kwargs�FeatureManagementPermss     �r/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/108_feature_management_cleanup_add.py�migratersG��%�\�*J�K�������U�(C�(C�(C�������c�L�|jd}|�|d��dS)Nrr)r�
remove_fieldsr
s     r�rollbackrs-��%�\�*J�K�����1�9�=�=�=�=�=r)F)�logging�peeweer�	getLogger�__name__�loggerrr�rr�<module>rsd������������	��	�8�	$�	$������>�>�>�>�>�>rdefence360agent/migrations/__pycache__/108_feature_management_cleanup_add.cpython-311.pyc0000644000000000000000000000206600000000000026376 0ustar  �

�
F�=���J�ddlZddlmZeje��Zdd�Zdd�ZdS)�N)�BooleanFieldFc�j�|jd}|�|td������dS)N�feature_management_permissionsF)�default)�cleanup)�orm�
add_fieldsr��migrator�database�fake�kwargs�FeatureManagementPermss     �r/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/108_feature_management_cleanup_add.py�migratersG��%�\�*J�K�������U�(C�(C�(C�������c�L�|jd}|�|d��dS)Nrr)r�
remove_fieldsr
s     r�rollbackrs-��%�\�*J�K�����1�9�=�=�=�=�=r)F)�logging�peeweer�	getLogger�__name__�loggerrr�rr�<module>rsd������������	��	�8�	$�	$������>�>�>�>�>�>rdefence360agent/migrations/__pycache__/108_validate_config.cpython-311.opt-1.pyc0000644000000000000000000000343600000000000024307 0ustar  �

�D���g��Z�ddlZddlZddlmZmZmZeje��Zdd�Z	dd�Z
dS)�N)�ConfigsValidator�ConfigsValidatorError�LocalConfigFc��|rdS		tj��dS#t$rzt��}|jdz}tj|j|��|���}|�|��t�
d|��YdSwxYw#t$rt�d��YdSwxYw)Nz.invalidz?Invalid config replaced with default one. Old config save in %sz1Failed to replace invalid config with default one)
r�validate_system_configrr�path�os�rename�config_to_dict�dict_to_config�logger�warning�	Exception�	exception)�migrator�database�fake�kwargs�local_config�
backup_config�default_configs       �c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/108_validate_config.py�migraters������N�	��3�5�5�5�5�5��$�
	�
	�
	�&�=�=�L�(�-�
�:�M��I�l�'��7�7�7�)�8�8�:�:�N��'�'��7�7�7��N�N�)��
�
�
�
�
�
�
	������N�N�N����L�M�M�M�M�M�M�N���s(��BB �B#�B � B#�#$C�
Cc��dS)N�)rrrrs    r�rollbackr*s���D�)F)�loggingr	� defence360agent.contracts.configrrr�	getLogger�__name__r
rrrrr�<module>r"s�������	�	�	�	�����������
��	�8�	$�	$��N�N�N�N�0	�	�	�	�	�	rdefence360agent/migrations/__pycache__/108_validate_config.cpython-311.pyc0000644000000000000000000000343600000000000023350 0ustar  �

�D���g��Z�ddlZddlZddlmZmZmZeje��Zdd�Z	dd�Z
dS)�N)�ConfigsValidator�ConfigsValidatorError�LocalConfigFc��|rdS		tj��dS#t$rzt��}|jdz}tj|j|��|���}|�|��t�
d|��YdSwxYw#t$rt�d��YdSwxYw)Nz.invalidz?Invalid config replaced with default one. Old config save in %sz1Failed to replace invalid config with default one)
r�validate_system_configrr�path�os�rename�config_to_dict�dict_to_config�logger�warning�	Exception�	exception)�migrator�database�fake�kwargs�local_config�
backup_config�default_configs       �c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/108_validate_config.py�migraters������N�	��3�5�5�5�5�5��$�
	�
	�
	�&�=�=�L�(�-�
�:�M��I�l�'��7�7�7�)�8�8�:�:�N��'�'��7�7�7��N�N�)��
�
�
�
�
�
�
	������N�N�N����L�M�M�M�M�M�M�N���s(��BB �B#�B � B#�#$C�
Cc��dS)N�)rrrrs    r�rollbackr*s���D�)F)�loggingr	� defence360agent.contracts.configrrr�	getLogger�__name__r
rrrrr�<module>r"s�������	�	�	�	�����������
��	�8�	$�	$��N�N�N�N�0	�	�	�	�	�	rdefence360agent/migrations/__pycache__/109_dos_detector.cpython-311.opt-1.pyc0000644000000000000000000000337600000000000023653 0ustar  �

}��Go����N�ddlZddlmZmZeje��Zdd�Zdd�ZdS)�N)�_DOS_DETECTOR_MIN_LIMIT�
ConfigFileFc��|rdS	t��}|�d��}d|vrdSd|dvrTt|ddt��r3t	|ddt
��|dd<|dd=d|dvr |dd|dd<|dd=|�|d���dS#t$rt�	d	��YdSwxYw)
NF�DOS�max_connections�
default_limit�timeout�intervalT)�	overwritezFailed to replace DOS settings)
r�config_to_dict�
isinstance�int�maxr�dict_to_config�	Exception�logger�	exception)�migrator�database�fake�kwargs�config_file�configs      �`/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/109_dos_detector.py�migraters0������;� �l�l���+�+�E�2�2�������F���u�
�-�-�*��5�M�+�,�c�3
�3
�-�.1��u�
�/�0�2I�.�.�F�5�M�/�*��u�
�/�0���u�
�%�%�(.�u�
�i�(@�F�5�M�*�%��u�
�i�(��"�"�6�T�"�:�:�:�:�:���;�;�;����9�:�:�:�:�:�:�;���s�'C�BC�$C8�7C8c��dS)N�)rrrrs    r�rollbackr's���D�)F)	�logging� defence360agent.contracts.configrr�	getLogger�__name__rrrrrr�<module>r$s|��������������

��	�8�	$�	$��;�;�;�;�8	�	�	�	�	�	rdefence360agent/migrations/__pycache__/109_dos_detector.cpython-311.pyc0000644000000000000000000000337600000000000022714 0ustar  �

}��Go����N�ddlZddlmZmZeje��Zdd�Zdd�ZdS)�N)�_DOS_DETECTOR_MIN_LIMIT�
ConfigFileFc��|rdS	t��}|�d��}d|vrdSd|dvrTt|ddt��r3t	|ddt
��|dd<|dd=d|dvr |dd|dd<|dd=|�|d���dS#t$rt�	d	��YdSwxYw)
NF�DOS�max_connections�
default_limit�timeout�intervalT)�	overwritezFailed to replace DOS settings)
r�config_to_dict�
isinstance�int�maxr�dict_to_config�	Exception�logger�	exception)�migrator�database�fake�kwargs�config_file�configs      �`/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/109_dos_detector.py�migraters0������;� �l�l���+�+�E�2�2�������F���u�
�-�-�*��5�M�+�,�c�3
�3
�-�.1��u�
�/�0�2I�.�.�F�5�M�/�*��u�
�/�0���u�
�%�%�(.�u�
�i�(@�F�5�M�*�%��u�
�i�(��"�"�6�T�"�:�:�:�:�:���;�;�;����9�:�:�:�:�:�:�;���s�'C�BC�$C8�7C8c��dS)N�)rrrrs    r�rollbackr's���D�)F)	�logging� defence360agent.contracts.configrr�	getLogger�__name__rrrrrr�<module>r$s|��������������

��	�8�	$�	$��;�;�;�;�8	�	�	�	�	�	rdefence360agent/migrations/__pycache__/110_ignore_list_ip_as_int.cpython-311.opt-1.pyc0000644000000000000000000000311700000000000025521 0ustar  �

�"�I��M���ddlZdd�Zdd�ZdS)�NFc�h�|rdSGd�dtj��}|�|��dS)Nc��eZdZejd���Zejd���Zejd���Zejd���Z	Gd�d��Z
dS)�migrate.<locals>.IgnoreListNewF)�nullc�6�eZdZdZejddd��ZdS)�#migrate.<locals>.IgnoreListNew.Meta�ignore_list_new�network_address�netmask�versionN)�__name__�
__module__�__qualname__�db_table�pw�CompositeKey�primary_key���i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/110_ignore_list_ip_as_int.py�Metars-������(�H�)�"�/�!�9�i���K�K�KrrN)r
rrr�	CharField�ip�IntegerFieldr
rrrrrr�
IgnoreListNewrs�������
�R�\�u�
%�
%�
%��)�"�/�u�5�5�5��!�"�/�u�-�-�-��!�"�/�u�-�-�-��	�	�	�	�	�	�	�	�	�	rr)r�Model�create_model)�migrator�database�fake�kwargsrs     r�migrater"sW������
�
�
�
�
���
�
�
�
���-�(�(�(�(�(rc��dS)Nr)rrr r!s    r�rollbackr$s���Dr)F)�peeweerr"r$rrr�<module>r&sC������)�)�)�)�&	�	�	�	�	�	rdefence360agent/migrations/__pycache__/110_ignore_list_ip_as_int.cpython-311.pyc0000644000000000000000000000311700000000000024562 0ustar  �

�"�I��M���ddlZdd�Zdd�ZdS)�NFc�h�|rdSGd�dtj��}|�|��dS)Nc��eZdZejd���Zejd���Zejd���Zejd���Z	Gd�d��Z
dS)�migrate.<locals>.IgnoreListNewF)�nullc�6�eZdZdZejddd��ZdS)�#migrate.<locals>.IgnoreListNew.Meta�ignore_list_new�network_address�netmask�versionN)�__name__�
__module__�__qualname__�db_table�pw�CompositeKey�primary_key���i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/110_ignore_list_ip_as_int.py�Metars-������(�H�)�"�/�!�9�i���K�K�KrrN)r
rrr�	CharField�ip�IntegerFieldr
rrrrrr�
IgnoreListNewrs�������
�R�\�u�
%�
%�
%��)�"�/�u�5�5�5��!�"�/�u�-�-�-��!�"�/�u�-�-�-��	�	�	�	�	�	�	�	�	�	rr)r�Model�create_model)�migrator�database�fake�kwargsrs     r�migrater"sW������
�
�
�
�
���
�
�
�
���-�(�(�(�(�(rc��dS)Nr)rrr r!s    r�rollbackr$s���Dr)F)�peeweerr"r$rrr�<module>r&sC������)�)�)�)�&	�	�	�	�	�	rdefence360agent/migrations/__pycache__/111_ignore_list_ip_as_int.cpython-311.opt-1.pyc0000644000000000000000000000462600000000000025530 0ustar  �

c�����;���ddlZdd�Zdd�ZdS)�NFc��|rdS|jd}|jd}	ddlm}ddlm}|���5d�|������D��}t��}	|D]=}
	tj
|
��}n#t$rY�$wxYw|	�|���>|	D]=}||��\}}
}|�
|�|��||
|����>	ddd��n#1swxYwYn#t$rYnwxYw|�d��|�d	��dS)
N�ignore_list_new�ignore_listr)�IP)�pack_ip_networkc��g|]
}|d��S)�ip�)�.0�items  �i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/111_ignore_list_ip_as_int.py�
<listcomp>zmigrate.<locals>.<listcomp>s��M�M�M��$�t�*�M�M�M�)r	�network_address�netmask�versionzDROP TABLE ignore_listz1ALTER TABLE ignore_list_new RENAME TO ignore_list)�orm�defence360agent.utils.validater�im360.utils.netr�atomic�select�dicts�set�	ipaddress�
ip_network�
ValueError�add�create�ip_net_to_string�ImportError�sql)�migrator�database�fake�kwargs�
IgnoreListNew�
IgnoreListrr�
ip_strings�ipsrr	�net�maskrs               r
�migrater,s��������L�!2�3�M���m�,�J��5�5�5�5�5�5�3�3�3�3�3�3��_�_�
�
�	�	�M�M��1B�1B�1D�1D�1J�1J�1L�1L�M�M�M�J��%�%�C�"�
�
���"�-�d�3�3�B�B��!�����H��������������
�
��%4�_�R�%8�%8�"��T�7��$�$��*�*�2�.�.�$'� �#�	%�����
�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	����
�
�
���
����.
�L�L�)�*�*�*��L�L�D�E�E�E�E�EsI�D�AD�B�D�
B&�#D�%B&�&AD�D�D�
D%�$D%c��dS)z$Write your rollback migrations here.Nr
)r"r#r$r%s    r
�rollbackr.(s���Dr)F)rr,r.r
rr
�<module>r/sH������!F�!F�!F�!F�H	�	�	�	�	�	rdefence360agent/migrations/__pycache__/111_ignore_list_ip_as_int.cpython-311.pyc0000644000000000000000000000462600000000000024571 0ustar  �

c�����;���ddlZdd�Zdd�ZdS)�NFc��|rdS|jd}|jd}	ddlm}ddlm}|���5d�|������D��}t��}	|D]=}
	tj
|
��}n#t$rY�$wxYw|	�|���>|	D]=}||��\}}
}|�
|�|��||
|����>	ddd��n#1swxYwYn#t$rYnwxYw|�d��|�d	��dS)
N�ignore_list_new�ignore_listr)�IP)�pack_ip_networkc��g|]
}|d��S)�ip�)�.0�items  �i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/111_ignore_list_ip_as_int.py�
<listcomp>zmigrate.<locals>.<listcomp>s��M�M�M��$�t�*�M�M�M�)r	�network_address�netmask�versionzDROP TABLE ignore_listz1ALTER TABLE ignore_list_new RENAME TO ignore_list)�orm�defence360agent.utils.validater�im360.utils.netr�atomic�select�dicts�set�	ipaddress�
ip_network�
ValueError�add�create�ip_net_to_string�ImportError�sql)�migrator�database�fake�kwargs�
IgnoreListNew�
IgnoreListrr�
ip_strings�ipsrr	�net�maskrs               r
�migrater,s��������L�!2�3�M���m�,�J��5�5�5�5�5�5�3�3�3�3�3�3��_�_�
�
�	�	�M�M��1B�1B�1D�1D�1J�1J�1L�1L�M�M�M�J��%�%�C�"�
�
���"�-�d�3�3�B�B��!�����H��������������
�
��%4�_�R�%8�%8�"��T�7��$�$��*�*�2�.�.�$'� �#�	%�����
�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	����
�
�
���
����.
�L�L�)�*�*�*��L�L�D�E�E�E�E�EsI�D�AD�B�D�
B&�#D�%B&�&AD�D�D�
D%�$D%c��dS)z$Write your rollback migrations here.Nr
)r"r#r$r%s    r
�rollbackr.(s���Dr)F)rr,r.r
rr
�<module>r/sH������!F�!F�!F�!F�H	�	�	�	�	�	rdefence360agent/migrations/__pycache__/112_hardened_php.cpython-311.opt-1.pyc0000644000000000000000000000721200000000000023601 0ustar  �

UEn�c������ddlZddlZddlZddlZddlmZejddd���Zejddd���Zej	e
��ZdZdZ
d	Zd
�Zd�Zd�Zdd�Zdd�ZdS)�N)�importerzimav.malwarelib.utils.chattr�subtract_flags)�module�name�default�FS_IMMUTABLE_FLzimunify360-alt-php.repozimunify360-ea-php-hardened.repoz/etc/yum.repos.d/c��d|vrtthStj�d��rttg��Sttg��S)N�
cloudlinuxz/usr/local/cpanel/cpanel)�ALT_PHP�EA_PHP�os�path�exists�set)�releases �`/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/112_hardened_php.py�irrelevant_reposrsM���w����� � �	����2�	3�	3���G�9�~�~���F�8�}�}��c�x�t�dSttfD]�}t|z}tj�|��s�,t|��5}t|���t��t	j
|���d��ddd��n#1swxYwY��dS)Ni�)rrr�	REPOS_DIRr
rr�open�filenor�chmod)�	repo_namer�fs   r�fix_permissionsr"s��������v�&�(�(�	��9�$���w�~�~�d�#�#�	��
�$�Z�Z�	(�1��1�8�8�:�:��7�7�7��H�Q�X�X�Z�Z��'�'�'�	(�	(�	(�	(�	(�	(�	(�	(�	(�	(�	(����	(�	(�	(�	(��	(�(s�AB.�.B2	�5B2	c��tj�d��sdStd��5}|������}ddd��n#1swxYwYt
��t|��D]O}tj	t��5tjt|z��ddd��n#1swxYwY�PdS)Nz/etc/redhat-release)
r
rrr�read�lowerrr�
contextlib�suppress�FileNotFoundError�unlinkr)rrrs   r�
do_migrater$/s@��
�7�>�>�/�0�0����	
�#�	$�	$�#���&�&�(�(�.�.�"�"��#�#�#�#�#�#�#�#�#�#�#����#�#�#�#�����%�g�.�.�-�-�	�
�
 �!2�
3�
3�	-�	-��I�i�)�+�,�,�,�	-�	-�	-�	-�	-�	-�	-�	-�	-�	-�	-����	-�	-�	-�	-��-�-s#�'A$�$A(�+A(�'C�C	�C	Fc��|rdS	t��dS#t$rt�d��YdSwxYw)Nz+Failed to clean up HardenedPHP repositories)r$�	Exception�logger�	exception��migrator�database�fake�kwargss    r�migrater.;s_������H���������H�H�H����F�G�G�G�G�G�G�H���s��$>�>c��dS)N�r)s    r�rollbackr1Ds���Dr)F)r �loggingr
�os.path�defence360agent.utilsr�getrr�	getLogger�__name__r'rrrrrr$r.r1r0rr�<module>r8s����������	�	�	�	�����*�*�*�*�*�*����)�0@�$������(�,�)�0A�4�����
��	�8�	$�	$��
#��	*���	�	�	�	�
(�
(�
(�	-�	-�	-�H�H�H�H�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/112_hardened_php.cpython-311.pyc0000644000000000000000000000721200000000000022642 0ustar  �

UEn�c������ddlZddlZddlZddlZddlmZejddd���Zejddd���Zej	e
��ZdZdZ
d	Zd
�Zd�Zd�Zdd�Zdd�ZdS)�N)�importerzimav.malwarelib.utils.chattr�subtract_flags)�module�name�default�FS_IMMUTABLE_FLzimunify360-alt-php.repozimunify360-ea-php-hardened.repoz/etc/yum.repos.d/c��d|vrtthStj�d��rttg��Sttg��S)N�
cloudlinuxz/usr/local/cpanel/cpanel)�ALT_PHP�EA_PHP�os�path�exists�set)�releases �`/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/112_hardened_php.py�irrelevant_reposrsM���w����� � �	����2�	3�	3���G�9�~�~���F�8�}�}��c�x�t�dSttfD]�}t|z}tj�|��s�,t|��5}t|���t��t	j
|���d��ddd��n#1swxYwY��dS)Ni�)rrr�	REPOS_DIRr
rr�open�filenor�chmod)�	repo_namer�fs   r�fix_permissionsr"s��������v�&�(�(�	��9�$���w�~�~�d�#�#�	��
�$�Z�Z�	(�1��1�8�8�:�:��7�7�7��H�Q�X�X�Z�Z��'�'�'�	(�	(�	(�	(�	(�	(�	(�	(�	(�	(�	(����	(�	(�	(�	(��	(�(s�AB.�.B2	�5B2	c��tj�d��sdStd��5}|������}ddd��n#1swxYwYt
��t|��D]O}tj	t��5tjt|z��ddd��n#1swxYwY�PdS)Nz/etc/redhat-release)
r
rrr�read�lowerrr�
contextlib�suppress�FileNotFoundError�unlinkr)rrrs   r�
do_migrater$/s@��
�7�>�>�/�0�0����	
�#�	$�	$�#���&�&�(�(�.�.�"�"��#�#�#�#�#�#�#�#�#�#�#����#�#�#�#�����%�g�.�.�-�-�	�
�
 �!2�
3�
3�	-�	-��I�i�)�+�,�,�,�	-�	-�	-�	-�	-�	-�	-�	-�	-�	-�	-����	-�	-�	-�	-��-�-s#�'A$�$A(�+A(�'C�C	�C	Fc��|rdS	t��dS#t$rt�d��YdSwxYw)Nz+Failed to clean up HardenedPHP repositories)r$�	Exception�logger�	exception��migrator�database�fake�kwargss    r�migrater.;s_������H���������H�H�H����F�G�G�G�G�G�G�H���s��$>�>c��dS)N�r)s    r�rollbackr1Ds���Dr)F)r �loggingr
�os.path�defence360agent.utilsr�getrr�	getLogger�__name__r'rrrrrr$r.r1r0rr�<module>r8s����������	�	�	�	�����*�*�*�*�*�*����)�0@�$������(�,�)�0A�4�����
��	�8�	$�	$��
#��	*���	�	�	�	�
(�
(�
(�	-�	-�	-�H�H�H�H�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/113_move_quarantined_files.cpython-311.opt-1.pyc0000644000000000000000000000077000000000000025706 0ustar  �

�;x\=c�����dZd�Zd�ZdS)z# Quarantine is removed in DEF-15234c��dS�N���_�__s  �j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/113_move_quarantined_files.py�migrater	����D�c��dSrrrs  r�rollbackr
r
rN)�__doc__r	r
rrr�<module>rs3��)�)�	�	�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/113_move_quarantined_files.cpython-311.pyc0000644000000000000000000000077000000000000024747 0ustar  �

�;x\=c�����dZd�Zd�ZdS)z# Quarantine is removed in DEF-15234c��dS�N���_�__s  �j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/113_move_quarantined_files.py�migrater	����D�c��dSrrrs  r�rollbackr
r
rN)�__doc__r	r
rrr�<module>rs3��)�)�	�	�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/114_disable_auto-quarantine.cpython-311.opt-1.pyc0000644000000000000000000000345500000000000025767 0ustar  �

춲�x��A��V�ddlZddlZddlmZmZeje��Zdd�Zdd�Z	dS)�N)�
ConfigFile�CoreFc���|rdSdg}tj�tj��r1|�tjtj����|D]�}t|���}|���}|s�)|�	di���
d��}|dkr5d|dd<	|�|dd�	���}#t$rY��wxYw��dS)
zWrite your migrations here.N)�username�MALWARE_SCANNING�default_action�
quarantine�notifyTF)�	overwrite�validate)
�os�path�existsr�USER_CONFDIR�extend�listdirr�config_to_dict�
setdefault�get�dict_to_config�	Exception)	�migrator�database�fake�kwargs�	usernamesr�config_file�configrs	         �k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/114_disable_auto-quarantine.py�migrater 	s1��������I�	�w�~�~�d�'�(�(�8������D�$5�6�6�7�7�7����� �(�3�3�3���+�+�-�-���	���*�*�+=�r�B�B�F�F��
�
���\�)�)�;C�F�%�&�'7�8�
��*�*��d�U�+�������
�
�
���
����
*��s�C!�!
C.�-C.c��dS)N�)rrrrs    r�rollbackr#"s���D�)F)
�loggingr
� defence360agent.contracts.configrr�	getLogger�__name__�loggerr r#r"r$r�<module>r*ss������	�	�	�	�=�=�=�=�=�=�=�=�	��	�8�	$�	$������2	�	�	�	�	�	r$defence360agent/migrations/__pycache__/114_disable_auto-quarantine.cpython-311.pyc0000644000000000000000000000345500000000000025030 0ustar  �

춲�x��A��V�ddlZddlZddlmZmZeje��Zdd�Zdd�Z	dS)�N)�
ConfigFile�CoreFc���|rdSdg}tj�tj��r1|�tjtj����|D]�}t|���}|���}|s�)|�	di���
d��}|dkr5d|dd<	|�|dd�	���}#t$rY��wxYw��dS)
zWrite your migrations here.N)�username�MALWARE_SCANNING�default_action�
quarantine�notifyTF)�	overwrite�validate)
�os�path�existsr�USER_CONFDIR�extend�listdirr�config_to_dict�
setdefault�get�dict_to_config�	Exception)	�migrator�database�fake�kwargs�	usernamesr�config_file�configrs	         �k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/114_disable_auto-quarantine.py�migrater 	s1��������I�	�w�~�~�d�'�(�(�8������D�$5�6�6�7�7�7����� �(�3�3�3���+�+�-�-���	���*�*�+=�r�B�B�F�F��
�
���\�)�)�;C�F�%�&�'7�8�
��*�*��d�U�+�������
�
�
���
����
*��s�C!�!
C.�-C.c��dS)N�)rrrrs    r�rollbackr#"s���D�)F)
�loggingr
� defence360agent.contracts.configrr�	getLogger�__name__�loggerr r#r"r$r�<module>r*ss������	�	�	�	�=�=�=�=�=�=�=�=�	��	�8�	$�	$������2	�	�	�	�	�	r$defence360agent/migrations/__pycache__/115_feature_management_fields.cpython-311.opt-1.pyc0000644000000000000000000000426200000000000026342 0ustar  �

�(��-dҏ��2�ddlZddlmZmZmZdd�Zdd�ZdS)�N)�NA�FULL�	AV_REPORTFc��|jd}|�|tjtdtjd�tt����g���tjtdtjd�ttt����g������|�	dtf��|�	dtf��|�	d	tf��|�	d
tf��|�
|dd��dS)
N�feature_management_permissionsFzproactive_new in ('{}','{}'))�default�null�constraintszav in ('{}','{}','{}'))�
proactive_new�avz>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=0�cleanup�	proactive)�orm�
add_fields�pw�	TextFieldr�Check�formatrr�sql�
remove_fields)�migrator�database�fake�kwargs�permissions_models     �m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/115_feature_management_fields.py�migratersS�� ��%E�F�������l�����7�>�>�r�4�H�H�I�I��
�
�
��<�����1�8�8��Y��M�M�N�N��
�
�
�����$
�L�L�H�	
�����
�L�L�H�	�����

�L�L�	�	
�����

�L�L�	�	�����
���,�i��E�E�E�E�E�c��dS)N�)rrrrs    r�rollbackr!1s���Dr)F)�peeweer�,defence360agent.feature_management.constantsrrrrr!r rr�<module>r$sf������L�L�L�L�L�L�L�L�L�L�(F�(F�(F�(F�V	�	�	�	�	�	rdefence360agent/migrations/__pycache__/115_feature_management_fields.cpython-311.pyc0000644000000000000000000000426200000000000025403 0ustar  �

�(��-dҏ��2�ddlZddlmZmZmZdd�Zdd�ZdS)�N)�NA�FULL�	AV_REPORTFc��|jd}|�|tjtdtjd�tt����g���tjtdtjd�ttt����g������|�	dtf��|�	dtf��|�	d	tf��|�	d
tf��|�
|dd��dS)
N�feature_management_permissionsFzproactive_new in ('{}','{}'))�default�null�constraintszav in ('{}','{}','{}'))�
proactive_new�avz>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=0�cleanup�	proactive)�orm�
add_fields�pw�	TextFieldr�Check�formatrr�sql�
remove_fields)�migrator�database�fake�kwargs�permissions_models     �m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/115_feature_management_fields.py�migratersS�� ��%E�F�������l�����7�>�>�r�4�H�H�I�I��
�
�
��<�����1�8�8��Y��M�M�N�N��
�
�
�����$
�L�L�H�	
�����
�L�L�H�	�����

�L�L�	�	
�����

�L�L�	�	�����
���,�i��E�E�E�E�E�c��dS)N�)rrrrs    r�rollbackr!1s���Dr)F)�peeweer�,defence360agent.feature_management.constantsrrrrr!r rr�<module>r$sf������L�L�L�L�L�L�L�L�L�L�(F�(F�(F�(F�V	�	�	�	�	�	rdefence360agent/migrations/__pycache__/116_feature_management_fields.cpython-311.opt-1.pyc0000644000000000000000000000153300000000000026341 0ustar  �

�B���uUa���dd�Zdd�ZdS)Fc�N�|jd}|�|dd��dS)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)�migrator�database�fake�kwargs�permissions_models     �m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/116_feature_management_fields.py�migraters1��
!��%E�F�����+�_�k�J�J�J�J�J�c��dS)N�)rr	r
rs    r
�rollbackr
s���DrN)F)rrrrr
�<module>rs;��K�K�K�K�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/116_feature_management_fields.cpython-311.pyc0000644000000000000000000000153300000000000025402 0ustar  �

�B���uUa���dd�Zdd�ZdS)Fc�N�|jd}|�|dd��dS)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)�migrator�database�fake�kwargs�permissions_models     �m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/116_feature_management_fields.py�migraters1��
!��%E�F�����+�_�k�J�J�J�J�J�c��dS)N�)rr	r
rs    r
�rollbackr
s���DrN)F)rrrrr
�<module>rs;��K�K�K�K�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/117_remove_incorrect_fields.cpython-311.opt-1.pyc0000644000000000000000000000321700000000000026061 0ustar  �

���A����n�ddlZddlmZmZeje��Zd�Zde��fdefd�Zdd�Z	dS)	�N)�IConfig�LocalConfigc�6�	|�d���}d|vrdS|d�dd��|d�dd��|�|dd���dS#t$rt�d��YdSwxYw)	NF)�	normalize�DOS�timeout�max_connectionsT)�	overwrite�validatezFailed to remove fields)�config_to_dict�pop�dict_to_config�	Exception�logger�	exception)�config_file�configs  �k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/117_remove_incorrect_fields.py�_fix_configrs���4��+�+�e�+�<�<�������F��u�
���)�T�*�*�*��u�
���+�T�2�2�2��"�"�6�T�E�"�J�J�J�J�J���4�4�4����2�3�3�3�3�3�3�4���s�A0�AA0�0$B�BFrc�,�|rdSt|��dS�N)r)�migrator�database�faker�kwargss     r�migraters%�������������c��dSr�)rrrrs    r�rollbackr $s���Dr)F)
�logging� defence360agent.contracts.configrr�	getLogger�__name__rrrr rrr�<module>r%s�������A�A�A�A�A�A�A�A�	��	�8�	$�	$��4�4�4�$
�&�;�=�=�	
�
��	
�
�
�
�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/117_remove_incorrect_fields.cpython-311.pyc0000644000000000000000000000321700000000000025122 0ustar  �

���A����n�ddlZddlmZmZeje��Zd�Zde��fdefd�Zdd�Z	dS)	�N)�IConfig�LocalConfigc�6�	|�d���}d|vrdS|d�dd��|d�dd��|�|dd���dS#t$rt�d��YdSwxYw)	NF)�	normalize�DOS�timeout�max_connectionsT)�	overwrite�validatezFailed to remove fields)�config_to_dict�pop�dict_to_config�	Exception�logger�	exception)�config_file�configs  �k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/117_remove_incorrect_fields.py�_fix_configrs���4��+�+�e�+�<�<�������F��u�
���)�T�*�*�*��u�
���+�T�2�2�2��"�"�6�T�E�"�J�J�J�J�J���4�4�4����2�3�3�3�3�3�3�4���s�A0�AA0�0$B�BFrc�,�|rdSt|��dS�N)r)�migrator�database�faker�kwargss     r�migraters%�������������c��dSr�)rrrrs    r�rollbackr $s���Dr)F)
�logging� defence360agent.contracts.configrr�	getLogger�__name__rrrr rrr�<module>r%s�������A�A�A�A�A�A�A�A�	��	�8�	$�	$��4�4�4�$
�&�;�=�=�	
�
��	
�
�
�
�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/118_add_malware_user_infected.cpython-311.opt-1.pyc0000644000000000000000000000076400000000000026332 0ustar  �

�Y��l���dd�Zdd�ZdS)Fc��dS�N���migrator�database�fake�kwargss    �m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/118_add_malware_user_infected.py�migrater����D�c��dSrrrs    r
�rollbackrrr
N)F)rrrr
r
�<module>rs7��	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/118_add_malware_user_infected.cpython-311.pyc0000644000000000000000000000076400000000000025373 0ustar  �

�Y��l���dd�Zdd�ZdS)Fc��dS�N���migrator�database�fake�kwargss    �m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/118_add_malware_user_infected.py�migrater����D�c��dSrrrs    r
�rollbackrrr
N)F)rrrr
r
�<module>rs7��	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/118_remove_country_subnets.cpython-311.opt-1.pyc0000644000000000000000000000141500000000000026010 0ustar  �

�r�؉���>�ddlZeje��Zdd�Zdd�ZdS)�NFc�J�|jd}|�|��dS)N�country_subnets)�orm�remove_model)�migrator�database�fake�kwargs�CountrySubnetss     �j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/118_remove_country_subnets.py�migrater
s)���\�"3�4�N����.�)�)�)�)�)�c��dS)N�)rrr	r
s    r�rollbackrs���Dr)F)�logging�	getLogger�__name__�loggerr
rrrr�<module>rsR������	��	�8�	$�	$��*�*�*�*�
	�	�	�	�	�	rdefence360agent/migrations/__pycache__/118_remove_country_subnets.cpython-311.pyc0000644000000000000000000000141500000000000025051 0ustar  �

�r�؉���>�ddlZeje��Zdd�Zdd�ZdS)�NFc�J�|jd}|�|��dS)N�country_subnets)�orm�remove_model)�migrator�database�fake�kwargs�CountrySubnetss     �j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/118_remove_country_subnets.py�migrater
s)���\�"3�4�N����.�)�)�)�)�)�c��dS)N�)rrr	r
s    r�rollbackrs���Dr)F)�logging�	getLogger�__name__�loggerr
rrrr�<module>rsR������	��	�8�	$�	$��*�*�*�*�
	�	�	�	�	�	rdefence360agent/migrations/__pycache__/119_populate_malware_user_infected.cpython-311.opt-1.pyc0000644000000000000000000000077100000000000027432 0ustar  �

�Y��l���dd�Zdd�ZdS)Fc��dS�N���migrator�database�fake�kwargss    �r/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/119_populate_malware_user_infected.py�migrater����D�c��dSrrrs    r
�rollbackrrr
N)F)rrrr
r
�<module>rs7��	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/119_populate_malware_user_infected.cpython-311.pyc0000644000000000000000000000077100000000000026473 0ustar  �

�Y��l���dd�Zdd�ZdS)Fc��dS�N���migrator�database�fake�kwargss    �r/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/119_populate_malware_user_infected.py�migrater����D�c��dSrrrs    r
�rollbackrrr
N)F)rrrr
r
�<module>rs7��	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/120_scheduled_scan.cpython-311.opt-1.pyc0000644000000000000000000000460400000000000024125 0ustar  �

s�=�@%m����ddlZddlmZmZddlZddlmZddlm	Z	e	j
ddd���Zeje
��ZejejejejfZdd�Zd
d
�Zd
d�ZdS)�N)�date�	timedelta)�
ConfigFile)�importerzimav.malwarelib.config�MalwareScanType)�module�name�defaultc��tj��td���z}dd|jii}	t	|���}|�|��dS#t$rt�d��YdSwxYw)N�)�days�MALWARE_SCAN_SCHEDULE�day_of_month)�pathz*Failed to set malware scan schedule config)	r�todayr�dayr�dict_to_config�	Exception�logger�	exception)r�tomorrow�config�config_files    �b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/120_scheduled_scan.py�_update_configrs����z�|�|�i�Q�/�/�/�/�H�	 ��H�L�"
��F�G� �d�+�+�+���"�"�6�*�*�*�*�*���G�G�G����E�F�F�F�F�F�F�G���s�%A�$B�?BFc���|jd}|�|tjdtjd�t����g������|rdSt��dS)N�
malware_scansFz
type in {})�null�constraints)�type)�orm�
change_fields�pw�	CharField�Check�format�typesr)�migrator�database�fake�kwargs�MalwareScans     r�migrater-'s����,��/�K�����
�\��R�X�l�.A�.A�%�.H�.H�%I�%I�$J�
�
�
���������������c��dS�N�)r(r)r*r+s    r�rollbackr27s���Dr.r0)F)�logging�datetimerr�peeweer#� defence360agent.contracts.configr�defence360agent.utilsr�getr�	getLogger�__name__r�	ON_DEMAND�REALTIME�MALWARE_RESPONSE�
BACKGROUNDr'rr-r2r1r.r�<module>r?s�������$�$�$�$�$�$�$�$�����7�7�7�7�7�7�*�*�*�*�*�*��(�,�#�*;�T�����
��	�8�	$�	$�������$���		��
G�
G�
G�
G� 
�
�
�
� 	�	�	�	�	�	r.defence360agent/migrations/__pycache__/120_scheduled_scan.cpython-311.pyc0000644000000000000000000000460400000000000023166 0ustar  �

s�=�@%m����ddlZddlmZmZddlZddlmZddlm	Z	e	j
ddd���Zeje
��ZejejejejfZdd�Zd
d
�Zd
d�ZdS)�N)�date�	timedelta)�
ConfigFile)�importerzimav.malwarelib.config�MalwareScanType)�module�name�defaultc��tj��td���z}dd|jii}	t	|���}|�|��dS#t$rt�d��YdSwxYw)N�)�days�MALWARE_SCAN_SCHEDULE�day_of_month)�pathz*Failed to set malware scan schedule config)	r�todayr�dayr�dict_to_config�	Exception�logger�	exception)r�tomorrow�config�config_files    �b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/120_scheduled_scan.py�_update_configrs����z�|�|�i�Q�/�/�/�/�H�	 ��H�L�"
��F�G� �d�+�+�+���"�"�6�*�*�*�*�*���G�G�G����E�F�F�F�F�F�F�G���s�%A�$B�?BFc���|jd}|�|tjdtjd�t����g������|rdSt��dS)N�
malware_scansFz
type in {})�null�constraints)�type)�orm�
change_fields�pw�	CharField�Check�format�typesr)�migrator�database�fake�kwargs�MalwareScans     r�migrater-'s����,��/�K�����
�\��R�X�l�.A�.A�%�.H�.H�%I�%I�$J�
�
�
���������������c��dS�N�)r(r)r*r+s    r�rollbackr27s���Dr.r0)F)�logging�datetimerr�peeweer#� defence360agent.contracts.configr�defence360agent.utilsr�getr�	getLogger�__name__r�	ON_DEMAND�REALTIME�MALWARE_RESPONSE�
BACKGROUNDr'rr-r2r1r.r�<module>r?s�������$�$�$�$�$�$�$�$�����7�7�7�7�7�7�*�*�*�*�*�*��(�,�#�*;�T�����
��	�8�	$�	$�������$���		��
G�
G�
G�
G� 
�
�
�
� 	�	�	�	�	�	r.defence360agent/migrations/__pycache__/121_drop_captcha_stat.cpython-311.opt-1.pyc0000644000000000000000000000115300000000000024640 0ustar  �

f]:�� ���dd�Zdd�ZdS)Fc�F�|�|jd��dS)N�captcha_stat)�remove_model�orm��migrator�database�fake�kwargss    �e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/121_drop_captcha_stat.py�migraters#�����(�,�~�6�7�7�7�7�7�c��dS)N�rs    r�rollbackrs���Dr
N)F)rrrr
r�<module>rs7��8�8�8�8�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/121_drop_captcha_stat.cpython-311.pyc0000644000000000000000000000115300000000000023701 0ustar  �

f]:�� ���dd�Zdd�ZdS)Fc�F�|�|jd��dS)N�captcha_stat)�remove_model�orm��migrator�database�fake�kwargss    �e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/121_drop_captcha_stat.py�migraters#�����(�,�~�6�7�7�7�7�7�c��dS)N�rs    r�rollbackrs���Dr
N)F)rrrr
r�<module>rs7��8�8�8�8�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/122_cagefs_unmount.cpython-311.opt-1.pyc0000644000000000000000000000076200000000000024201 0ustar  �

O�%�;+m����dd�Zdd�ZdS)Fc��dS�N���migrator�database�fake�kwargss    �b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/122_cagefs_unmount.py�migraters	��	�D�c��dSrrrs    r
�rollbackrs���DrN)F)rrrrr
�<module>rs7��	�	�	�	�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/122_cagefs_unmount.cpython-311.pyc0000644000000000000000000000076200000000000023242 0ustar  �

O�%�;+m����dd�Zdd�ZdS)Fc��dS�N���migrator�database�fake�kwargss    �b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/122_cagefs_unmount.py�migraters	��	�D�c��dSrrrs    r
�rollbackrs���DrN)F)rrrrr
�<module>rs7��	�	�	�	�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/123_add_last_user_scan.cpython-311.opt-1.pyc0000644000000000000000000000236400000000000025002 0ustar  �

���P�4� ��D�ddlZGd�dej��Zdd�Zdd�ZdS)�Nc��eZdZGd�d��Zejd���Zejd���Zejd���Z	dS)�LastUserScanc��eZdZdZdS)�LastUserScan.Meta�last_user_scansN)�__name__�
__module__�__qualname__�db_table���f/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/123_add_last_user_scan.py�Metars������$���r
rT)�primary_keyF)�nullN)
rr	r
r�pw�	CharField�last_scanid�IntegerField�started�uidrr
rrrst������%�%�%�%�%�%�%�%��"�,�4�0�0�0�K��b�o�5�)�)�)�G�
�"�/�u�
%�
%�
%�C�C�Cr
rFc��dS�Nr��migrator�database�fake�kwargss    r�migrater
����Dr
c��dSrrrs    r�rollbackr"r r
)F)�peeweer�Modelrrr"rr
r�<module>r%so������&�&�&�&�&�2�8�&�&�&�	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/123_add_last_user_scan.cpython-311.pyc0000644000000000000000000000236400000000000024043 0ustar  �

���P�4� ��D�ddlZGd�dej��Zdd�Zdd�ZdS)�Nc��eZdZGd�d��Zejd���Zejd���Zejd���Z	dS)�LastUserScanc��eZdZdZdS)�LastUserScan.Meta�last_user_scansN)�__name__�
__module__�__qualname__�db_table���f/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/123_add_last_user_scan.py�Metars������$���r
rT)�primary_keyF)�nullN)
rr	r
r�pw�	CharField�last_scanid�IntegerField�started�uidrr
rrrst������%�%�%�%�%�%�%�%��"�,�4�0�0�0�K��b�o�5�)�)�)�G�
�"�/�u�
%�
%�
%�C�C�Cr
rFc��dS�Nr��migrator�database�fake�kwargss    r�migrater
����Dr
c��dSrrrs    r�rollbackr"r r
)F)�peeweer�Modelrrr"rr
r�<module>r%so������&�&�&�&�&�2�8�&�&�&�	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/123_disable_scheduled_scan.cpython-311.opt-1.pyc0000644000000000000000000000357400000000000025620 0ustar  �

�$���M���t�ddlZddlZddlZddlmZmZeje��ZdZ	d	d�Z
e	fd�Zd
d�Zd
d�Z
dS)�N)�
ConfigFile�NONEz!/etc/cron.d/imunify_scan_schedulec��ddtii}	t|���}|�|��dS#t$rt�d��YdSwxYw)N�MALWARE_SCAN_SCHEDULE�interval��pathz*Failed to set malware scan schedule config)rr�dict_to_config�	Exception�logger�	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�*�*�*�*�*���G�G�G����E�F�F�F�F�F�F�G���s�%4�$A�Ac��tjt��5tj|��ddd��dS#1swxYwYdS�N)�
contextlib�suppress�FileNotFoundError�os�unlinkrs r�_remove_cronrs���	�	�.�	/�	/���
�	�$������������������������s�<�A�AFc��dSr���migrator�database�fake�kwargss    r�migrater! s	��	�D�c��dSrrrs    r�rollbackr$*s���Dr"r)F)r�loggingr� defence360agent.contracts.configrr�	getLogger�__name__r�	CRON_PATHrrr!r$rr"r�<module>r*s�����������	�	�	�	�=�=�=�=�=�=�=�=�	��	�8�	$�	$��
0�	�G�G�G�G� �����
	�	�	�	�	�	�	�	�	�	r"defence360agent/migrations/__pycache__/123_disable_scheduled_scan.cpython-311.pyc0000644000000000000000000000357400000000000024661 0ustar  �

�$���M���t�ddlZddlZddlZddlmZmZeje��ZdZ	d	d�Z
e	fd�Zd
d�Zd
d�Z
dS)�N)�
ConfigFile�NONEz!/etc/cron.d/imunify_scan_schedulec��ddtii}	t|���}|�|��dS#t$rt�d��YdSwxYw)N�MALWARE_SCAN_SCHEDULE�interval��pathz*Failed to set malware scan schedule config)rr�dict_to_config�	Exception�logger�	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�*�*�*�*�*���G�G�G����E�F�F�F�F�F�F�G���s�%4�$A�Ac��tjt��5tj|��ddd��dS#1swxYwYdS�N)�
contextlib�suppress�FileNotFoundError�os�unlinkrs r�_remove_cronrs���	�	�.�	/�	/���
�	�$������������������������s�<�A�AFc��dSr���migrator�database�fake�kwargss    r�migrater! s	��	�D�c��dSrrrs    r�rollbackr$*s���Dr"r)F)r�loggingr� defence360agent.contracts.configrr�	getLogger�__name__r�	CRON_PATHrrr!r$rr"r�<module>r*s�����������	�	�	�	�=�=�=�=�=�=�=�=�	��	�8�	$�	$��
0�	�G�G�G�G� �����
	�	�	�	�	�	�	�	�	�	r"defence360agent/migrations/__pycache__/123_rename_plesk_vendor.cpython-311.opt-1.pyc0000644000000000000000000000365500000000000025213 0ustar  �

�L�M�����h�ddlZddlmZmZeje��Zejdd���Zdd�Z	dS)�N)�run_coro�antivirus_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_installedr�modsec_vendor_list�join�install_settings�	Exception�logger�warning)	�migrator�database�fake�kwargsrr�panel�installed_vendors�es	         �g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/123_rename_plesk_vendor.py�migrater	sc�������3�3�3�3�3�3�	
�	
�	
�	
�	
�	
�	
�������������M������	3�H�-J�-J�-L�-L�$M�$M�	3��E�G�G�E� (��)A�)A�)C�)C� D� D���s�x�x�(9�:�:�:�:���/�/�1�1�2�2�2�2�2�		3�	3�	3�	3�;�:���M�M�M����H�!�L�L�L�L�L�L�L�L�L�����M���s$��
!�!�BB;�;
C+�C&�&C+c��dS)N�)rrrrs    r�rollbackr s���D�)F)
�logging�defence360agent.utilsrr�	getLogger�__name__r�skiprrrr r�<module>r&s�������:�:�:�:�:�:�:�:�
��	�8�	$�	$����M�M�M���M�,	�	�	�	�	�	r defence360agent/migrations/__pycache__/123_rename_plesk_vendor.cpython-311.pyc0000644000000000000000000000365500000000000024254 0ustar  �

�L�M�����h�ddlZddlmZmZeje��Zejdd���Zdd�Z	dS)�N)�run_coro�antivirus_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_installedr�modsec_vendor_list�join�install_settings�	Exception�logger�warning)	�migrator�database�fake�kwargsrr�panel�installed_vendors�es	         �g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/123_rename_plesk_vendor.py�migrater	sc�������3�3�3�3�3�3�	
�	
�	
�	
�	
�	
�	
�������������M������	3�H�-J�-J�-L�-L�$M�$M�	3��E�G�G�E� (��)A�)A�)C�)C� D� D���s�x�x�(9�:�:�:�:���/�/�1�1�2�2�2�2�2�		3�	3�	3�	3�;�:���M�M�M����H�!�L�L�L�L�L�L�L�L�L�����M���s$��
!�!�BB;�;
C+�C&�&C+c��dS)N�)rrrrs    r�rollbackr s���D�)F)
�logging�defence360agent.utilsrr�	getLogger�__name__r�skiprrrr r�<module>r&s�������:�:�:�:�:�:�:�:�
��	�8�	$�	$����M�M�M���M�,	�	�	�	�	�	r defence360agent/migrations/__pycache__/124_add_hook_management_functionality.cpython-311.opt-1.pyc0000644000000000000000000000357700000000000030111 0ustar  �

�m}D?�����b�ddlmZddlmZmZmZmZddlmZGd�de��Zd
d�Z	d
d�Z
d	S)�)�time)�Model�	CharField�IntegerField�BooleanField)�
FilenameFieldc��eZdZGd�d��Zed���Zed���Zedd����Z	e
d���ZdS)	�	EventHookc��eZdZdZdS)�EventHook.Meta�
event_hookN)�__name__�
__module__�__qualname__�db_table���u/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/124_add_hook_management_functionality.py�Metar	s���������rrF)�nullc�8�tt����S�N)�intrrrr�<lambda>zEventHook.<lambda>s��s�4�6�6�{�{�r)r�default)rN)rrrrr�pathr�eventr�createdr�nativerrrr
r
s������� � � � � � � � ��=�e�$�$�$�D��I�5�!�!�!�E��l��/B�/B�C�C�C�G�
�\�%�
(�
(�
(�F�F�Frr
Fc�:�|�t��dSr)�create_modelr
)�migrator�database�fake�kwargss    r�migrater&s�����)�$�$�$�$�$rc�J�|jd}|�|��dS)Nr
)�orm�remove_model)r"r#r$r%r
s     r�rollbackr*s(����\�*�I����)�$�$�$�$�$rN)F)r�peeweerrrr�$defence360agent.model.simplificationrr
r&r*rrr�<module>r-s���������?�?�?�?�?�?�?�?�?�?�?�?�>�>�>�>�>�>�)�)�)�)�)��)�)�)�%�%�%�%�%�%�%�%�%�%rdefence360agent/migrations/__pycache__/124_add_hook_management_functionality.cpython-311.pyc0000644000000000000000000000357700000000000027152 0ustar  �

�m}D?�����b�ddlmZddlmZmZmZmZddlmZGd�de��Zd
d�Z	d
d�Z
d	S)�)�time)�Model�	CharField�IntegerField�BooleanField)�
FilenameFieldc��eZdZGd�d��Zed���Zed���Zedd����Z	e
d���ZdS)	�	EventHookc��eZdZdZdS)�EventHook.Meta�
event_hookN)�__name__�
__module__�__qualname__�db_table���u/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/124_add_hook_management_functionality.py�Metar	s���������rrF)�nullc�8�tt����S�N)�intrrrr�<lambda>zEventHook.<lambda>s��s�4�6�6�{�{�r)r�default)rN)rrrrr�pathr�eventr�createdr�nativerrrr
r
s������� � � � � � � � ��=�e�$�$�$�D��I�5�!�!�!�E��l��/B�/B�C�C�C�G�
�\�%�
(�
(�
(�F�F�Frr
Fc�:�|�t��dSr)�create_modelr
)�migrator�database�fake�kwargss    r�migrater&s�����)�$�$�$�$�$rc�J�|jd}|�|��dS)Nr
)�orm�remove_model)r"r#r$r%r
s     r�rollbackr*s(����\�*�I����)�$�$�$�$�$rN)F)r�peeweerrrr�$defence360agent.model.simplificationrr
r&r*rrr�<module>r-s���������?�?�?�?�?�?�?�?�?�?�?�?�>�>�>�>�>�>�)�)�)�)�)��)�)�)�%�%�%�%�%�%�%�%�%�%rdefence360agent/migrations/__pycache__/124_add_infected_domains_vendor.cpython-311.opt-1.pyc0000644000000000000000000000210000000000000026632 0ustar  �

hn� M�BX��F�ddlZddlZeje��Zdd�Zdd�ZdS)�NFc���|jd}|�|tjdd������|������dS)N�infected_domain_listFzgoogle-safe-browsing)�null�default)�vendor)�orm�
add_fields�pw�	TextField�delete�execute)�migrator�database�fake�kwargs�InfectedDomainss     �o/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/124_add_infected_domains_vendor.py�migratersh���l�#9�:�O������|��0F�G�G�G����������$�$�&�&�&�&�&�c��dS)N�)rrrrs    r�rollbackrs���Dr)F)�logging�peeweer
�	getLogger�__name__�loggerrrrrr�<module>rs^����������	��	�8�	$�	$��'�'�'�'�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/124_add_infected_domains_vendor.cpython-311.pyc0000644000000000000000000000210000000000000025673 0ustar  �

hn� M�BX��F�ddlZddlZeje��Zdd�Zdd�ZdS)�NFc���|jd}|�|tjdd������|������dS)N�infected_domain_listFzgoogle-safe-browsing)�null�default)�vendor)�orm�
add_fields�pw�	TextField�delete�execute)�migrator�database�fake�kwargs�InfectedDomainss     �o/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/124_add_infected_domains_vendor.py�migratersh���l�#9�:�O������|��0F�G�G�G����������$�$�&�&�&�&�&�c��dS)N�)rrrrs    r�rollbackrs���Dr)F)�logging�peeweer
�	getLogger�__name__�loggerrrrrr�<module>rs^����������	��	�8�	$�	$��'�'�'�'�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/125_rescan_scan_type.cpython-311.opt-1.pyc0000644000000000000000000000537100000000000024510 0ustar  �

����,�F����ddlZddlmZmZddlZddlmZddlmZejddd���Z	ej
e��Ze	j
e	je	je	je	jfZdd	�Zdd
�ZdS)�N)�datetime�	timedelta)�importer)�split_for_chunkzimav.malwarelib.config�MalwareScanType)�module�name�defaultFc��|jd}|jd}tj��td���z
���}t|�|j���|���	|j
|k����}t|��D]l}|����	|j�
|�������\}	}
|�|	|
���m|����	|j
|k�����\}	}
|�|	|
��|�|t!jdt!jd�t(����g������dS)	N�malware_hits�
malware_scans�)�daysFz
type in {})�null�constraints)�type)�ormr�nowr�	timestamp�list�select�id�join�where�startedr�delete�in_�sql�
change_fields�pw�	CharField�Check�format�types)�migrator�database�fake�kwargs�
MalwareHit�MalwareScan�date�hits_to_delete�chunkr�paramss           �d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/125_rescan_scan_type.py�migrater0s�����n�-�J��,��/�K��L�N�N�Y�B�/�/�/�/�:�:�<�<�D�����*�-�(�(�	
��k�	�	�	��{�"�T�)�	*�	*���N�!��0�0�"�"�� �'�'�)�)�/�/�
�
�0A�0A�%�0H�0H�I�I�M�M�O�O���V����S�&�!�!�!�!��$�$�&�&�,�,�[�-@�4�-G�H�H�L�L�N�N�K�C���L�L��f��������
�\��R�X�l�.A�.A�%�.H�.H�%I�%I�$J�
�
�
�������c��dS)N�)r%r&r'r(s    r/�rollbackr43s���Dr1)F)�loggingrr�peeweer �defence360agent.utilsrr�getr�	getLogger�__name__�logger�	ON_DEMAND�REALTIME�MALWARE_RESPONSE�
BACKGROUND�RESCANr$r0r4r3r1r/�<module>rAs�������(�(�(�(�(�(�(�(�����*�*�*�*�*�*�1�1�1�1�1�1��(�,�#�*;�T�����
��	�8�	$�	$�������$�����	������6	�	�	�	�	�	r1defence360agent/migrations/__pycache__/125_rescan_scan_type.cpython-311.pyc0000644000000000000000000000537100000000000023551 0ustar  �

����,�F����ddlZddlmZmZddlZddlmZddlmZejddd���Z	ej
e��Ze	j
e	je	je	je	jfZdd	�Zdd
�ZdS)�N)�datetime�	timedelta)�importer)�split_for_chunkzimav.malwarelib.config�MalwareScanType)�module�name�defaultFc��|jd}|jd}tj��td���z
���}t|�|j���|���	|j
|k����}t|��D]l}|����	|j�
|�������\}	}
|�|	|
���m|����	|j
|k�����\}	}
|�|	|
��|�|t!jdt!jd�t(����g������dS)	N�malware_hits�
malware_scans�)�daysFz
type in {})�null�constraints)�type)�ormr�nowr�	timestamp�list�select�id�join�where�startedr�delete�in_�sql�
change_fields�pw�	CharField�Check�format�types)�migrator�database�fake�kwargs�
MalwareHit�MalwareScan�date�hits_to_delete�chunkr�paramss           �d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/125_rescan_scan_type.py�migrater0s�����n�-�J��,��/�K��L�N�N�Y�B�/�/�/�/�:�:�<�<�D�����*�-�(�(�	
��k�	�	�	��{�"�T�)�	*�	*���N�!��0�0�"�"�� �'�'�)�)�/�/�
�
�0A�0A�%�0H�0H�I�I�M�M�O�O���V����S�&�!�!�!�!��$�$�&�&�,�,�[�-@�4�-G�H�H�L�L�N�N�K�C���L�L��f��������
�\��R�X�l�.A�.A�%�.H�.H�%I�%I�$J�
�
�
�������c��dS)N�)r%r&r'r(s    r/�rollbackr43s���Dr1)F)�loggingrr�peeweer �defence360agent.utilsrr�getr�	getLogger�__name__�logger�	ON_DEMAND�REALTIME�MALWARE_RESPONSE�
BACKGROUND�RESCANr$r0r4r3r1r/�<module>rAs�������(�(�(�(�(�(�(�(�����*�*�*�*�*�*�1�1�1�1�1�1��(�,�#�*;�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  �

[iG���|�ddlZddlZddlmZmZddlmZe��de��fdefd���Zdd�ZdS)	�N)�IConfigFile�LocalConfig)�log_error_and_ignoreF�config_filec�j�|rdStj�|j��sdSt|j��5}t	j|��}ddd��n#1swxYwY|�di��}|�dd��}||d<|�|d���dS)N�MALWARE_SCANNING�scan_modified_filesF)�validate)	�os�path�exists�open�yaml�	safe_load�
setdefault�pop�dict_to_config)	�migrator�database�faker�kwargs�f�conf�malware_settings�values	         �z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/126_add_malware_scan_modified_files_option.py�migrater	s�������
�7�>�>�+�*�+�+����	
�k��	�	�!�1��~�a� � ��!�!�!�!�!�!�!�!�!�!�!����!�!�!�!����'9�2�>�>��� � �!6��=�=�E�.3��*�+����t�e��4�4�4�4�4s�A � A$�'A$c��dS)N�)rrrrs    r�rollbackr !s���D�)F)	rr� defence360agent.contracts.configrr�defence360agent.utilsrrr rr!r�<module>r$s���	�	�	�	�����E�E�E�E�E�E�E�E�6�6�6�6�6�6�����
�*�{�}�}�	5�5��	5�5�5���5�.	�	�	�	�	�	r!defence360agent/migrations/__pycache__/126_add_malware_scan_modified_files_option.cpython-311.pyc0000644000000000000000000000322200000000000030101 0ustar  �

[iG���|�ddlZddlZddlmZmZddlmZe��de��fdefd���Zdd�ZdS)	�N)�IConfigFile�LocalConfig)�log_error_and_ignoreF�config_filec�j�|rdStj�|j��sdSt|j��5}t	j|��}ddd��n#1swxYwY|�di��}|�dd��}||d<|�|d���dS)N�MALWARE_SCANNING�scan_modified_filesF)�validate)	�os�path�exists�open�yaml�	safe_load�
setdefault�pop�dict_to_config)	�migrator�database�faker�kwargs�f�conf�malware_settings�values	         �z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/126_add_malware_scan_modified_files_option.py�migrater	s�������
�7�>�>�+�*�+�+����	
�k��	�	�!�1��~�a� � ��!�!�!�!�!�!�!�!�!�!�!����!�!�!�!����'9�2�>�>��� � �!6��=�=�E�.3��*�+����t�e��4�4�4�4�4s�A � A$�'A$c��dS)N�)rrrrs    r�rollbackr !s���D�)F)	rr� defence360agent.contracts.configrr�defence360agent.utilsrrr rr!r�<module>r$s���	�	�	�	�����E�E�E�E�E�E�E�E�6�6�6�6�6�6�����
�*�{�}�}�	5�5��	5�5�5���5�.	�	�	�	�	�	r!defence360agent/migrations/__pycache__/126_move_malware_hits_list.cpython-311.opt-1.pyc0000644000000000000000000000426000000000000025725 0ustar  �


��A�$R����ddlZddlZddlmZddlmZmZeje��Z	d�Z
ejdd���Zdd�Z
dS)	�N)�	FILES_DIR)�importer�antivirus_modec��	tj||��dS#t$rYdSt$r&}t�d|��Yd}~dSd}~wwxYw)Nz6Failed to move HackerTrap list to the new location: %r)�shutil�move�FileNotFoundError�	Exception�logger�error)�src�dst�errs   �j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/126_move_malware_hits_list.py�_mover
s���
���C���������
�
�
�����
�
�
����D�c�	
�	
�	
�	
�	
�	
�	
�	
�	
�����
���s��
A�	A�A�AFc�B�|rdS	ddlm}tjddd���}n#t$rYdSwxYwtt��|_|���}|�	��}||fD]$}tt|��|j���%dS)Nr)�
HackerTrapzimav.malwarelib.subsys.malware�HackerTrapHitsSaver)�module�name�default)� defence360agent.contracts.configrr�get�ImportError�strr�BASE_DIR�	_filepath�_clean_filepathr�DIR)	�migrator�database�fake�kwargsrr�src1�src2r
s	         r�migrater&s�������	�?�?�?�?�?�?�&�l�3�&��
�
�
����
�����������$'�y�>�>�� ��(�(�*�*�D��.�.�0�0�D��T�z�(�(��
�c�#�h�h�
��'�'�'�'�(�(s�$�
2�2c��dS)N�)r r!r"r#s    r�rollbackr)-s���D�)F)�loggingr�defence360agent.filesr�defence360agent.utilsrr�	getLogger�__name__rr�skipr&r)r(r*r�<module>r1s�������
�
�
�
�+�+�+�+�+�+�:�:�:�:�:�:�:�:�	��	�8�	$�	$��
�
�
���(�(�(���(�.	�	�	�	�	�	r*defence360agent/migrations/__pycache__/126_move_malware_hits_list.cpython-311.pyc0000644000000000000000000000426000000000000024766 0ustar  �


��A�$R����ddlZddlZddlmZddlmZmZeje��Z	d�Z
ejdd���Zdd�Z
dS)	�N)�	FILES_DIR)�importer�antivirus_modec��	tj||��dS#t$rYdSt$r&}t�d|��Yd}~dSd}~wwxYw)Nz6Failed to move HackerTrap list to the new location: %r)�shutil�move�FileNotFoundError�	Exception�logger�error)�src�dst�errs   �j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/126_move_malware_hits_list.py�_mover
s���
���C���������
�
�
�����
�
�
����D�c�	
�	
�	
�	
�	
�	
�	
�	
�	
�����
���s��
A�	A�A�AFc�B�|rdS	ddlm}tjddd���}n#t$rYdSwxYwtt��|_|���}|�	��}||fD]$}tt|��|j���%dS)Nr)�
HackerTrapzimav.malwarelib.subsys.malware�HackerTrapHitsSaver)�module�name�default)� defence360agent.contracts.configrr�get�ImportError�strr�BASE_DIR�	_filepath�_clean_filepathr�DIR)	�migrator�database�fake�kwargsrr�src1�src2r
s	         r�migrater&s�������	�?�?�?�?�?�?�&�l�3�&��
�
�
����
�����������$'�y�>�>�� ��(�(�*�*�D��.�.�0�0�D��T�z�(�(��
�c�#�h�h�
��'�'�'�'�(�(s�$�
2�2c��dS)N�)r r!r"r#s    r�rollbackr)-s���D�)F)�loggingr�defence360agent.filesr�defence360agent.utilsrr�	getLogger�__name__rr�skipr&r)r(r*r�<module>r1s�������
�
�
�
�+�+�+�+�+�+�:�:�:�:�:�:�:�:�	��	�8�	$�	$��
�
�
���(�(�(���(�.	�	�	�	�	�	r*defence360agent/migrations/__pycache__/127_remove_malware_hit_mode.cpython-311.opt-1.pyc0000644000000000000000000000124300000000000026041 0ustar  �

�E쟲����dd�Zdd�ZdS)Fc�L�|jd}|�|d��dS)N�malware_hits�mode)�orm�
remove_fields)�migrator�database�fake�kwargs�
MalwareHits     �k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/127_remove_malware_hit_mode.py�migrater
s*����n�-�J����:�v�.�.�.�.�.�c��dS)N�)rrr	r
s    r�rollbackrs���DrN)F)r
rrrr�<module>rs7��/�/�/�/�
	�	�	�	�	�	rdefence360agent/migrations/__pycache__/127_remove_malware_hit_mode.cpython-311.pyc0000644000000000000000000000124300000000000025102 0ustar  �

�E쟲����dd�Zdd�ZdS)Fc�L�|jd}|�|d��dS)N�malware_hits�mode)�orm�
remove_fields)�migrator�database�fake�kwargs�
MalwareHits     �k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/127_remove_malware_hit_mode.py�migrater
s*����n�-�J����:�v�.�.�.�.�.�c��dS)N�)rrr	r
s    r�rollbackrs���DrN)F)r
rrrr�<module>rs7��/�/�/�/�
	�	�	�	�	�	rdefence360agent/migrations/__pycache__/128_move_cleanup_storage_files.cpython-311.opt-1.pyc0000644000000000000000000000727600000000000026564 0ustar  �

��^�[e�����ddlZddlZddlZddlmZmZddlmZddlm	Z	ej
ddd���Zeje
��Zd�Zd	�Zd
d�Zd
d�ZdS)�N)�	CharField�Model)�importer)�
FilenameFieldzimav.malwarelib.cleanup.storage�CleanupStorage)�module�name�defaultc�2��G�fd�dt��}|S)zl
    Model stub for migration because we can't use migrator.orm[] due to
    custom field FilenameField
    c���eZdZG�fd�d��Zed���Zed���Zed���Zed���Z	e
defd���ZdS)	�get_model.<locals>.MalwareHitc���eZdZdZ�ZdS)�"get_model.<locals>.MalwareHit.Meta�malware_hitsN)�__name__�
__module__�__qualname__�db_table�database��dbs��n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/128_move_cleanup_storage_files.py�Metars�������%�H��H�H�H�rF)�nullT�returnc��	tjj�|j|j|jg��S#t$rYdSwxYw)zZ
            Get file name for cleanup storage
            :return: file name
            N)�os�path�extsep�join�user�hash�size�	TypeError)�selfs r�storage_namez*get_model.<locals>.MalwareHit.storage_name#sN��
��w�~�*�*�D�I�t�y�$�)�+L�M�M�M���
�
�
��t�t�
���s�58�
A�AN)
rrrrrr"r�	orig_filer#r$�property�strr'rs�r�
MalwareHitr
s��������	�	�	�	�	�	�	�	�	�	��y�e�$�$�$��!�M�u�-�-�-�	��y�d�#�#�#���y�d�#�#�#��	�	�#�	�	�	�
��	�	�	rr+)r)rr+s` r�	get_modelr,s?����������U����*�rc�,�ttjj||f��\}}tt||f��\}}	tj||��dS#t$rYdSt$r&}t�
d|��Yd}~dSd}~wwxYw)Nz2Failed to move stored file to the new location: %r)�maprr�joinpathr*�shutil�move�FileNotFoundError�	Exception�logger�error)�src�dst�errs   r�_mover91s����>�&�/�#�s��<�<�H�C���3��c�
�#�#�H�C��P���C���������
�
�
�����P�P�P����I�3�O�O�O�O�O�O�O�O�O�����P���s�A�
B�$	B�-B�BFc��|rdSt|��}|D];}|j}|��t�|j��}t	||���<dS�N)r,r'rr(r9)�migratorr�fake�kwargsr+�hitr6r7s        r�migrater@<si�������8�$�$�J���������;���)�)�#�-�8�8��
�c�3�����
�rc��dSr;�)r<rr=r>s    r�rollbackrCKs���Dr)F)�loggingrr0�peeweerr�defence360agent.utilsr�$defence360agent.model.simplificationr�getr�	getLoggerrr4r,r9r@rCrBrr�<module>rJs�������	�	�	�	�
�
�
�
�#�#�#�#�#�#�#�#�*�*�*�*�*�*�>�>�>�>�>�>����,�	������
��	�8�	$�	$�����<P�P�P�����	�	�	�	�	�	rdefence360agent/migrations/__pycache__/128_move_cleanup_storage_files.cpython-311.pyc0000644000000000000000000000727600000000000025625 0ustar  �

��^�[e�����ddlZddlZddlZddlmZmZddlmZddlm	Z	ej
ddd���Zeje
��Zd�Zd	�Zd
d�Zd
d�ZdS)�N)�	CharField�Model)�importer)�
FilenameFieldzimav.malwarelib.cleanup.storage�CleanupStorage)�module�name�defaultc�2��G�fd�dt��}|S)zl
    Model stub for migration because we can't use migrator.orm[] due to
    custom field FilenameField
    c���eZdZG�fd�d��Zed���Zed���Zed���Zed���Z	e
defd���ZdS)	�get_model.<locals>.MalwareHitc���eZdZdZ�ZdS)�"get_model.<locals>.MalwareHit.Meta�malware_hitsN)�__name__�
__module__�__qualname__�db_table�database��dbs��n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/128_move_cleanup_storage_files.py�Metars�������%�H��H�H�H�rF)�nullT�returnc��	tjj�|j|j|jg��S#t$rYdSwxYw)zZ
            Get file name for cleanup storage
            :return: file name
            N)�os�path�extsep�join�user�hash�size�	TypeError)�selfs r�storage_namez*get_model.<locals>.MalwareHit.storage_name#sN��
��w�~�*�*�D�I�t�y�$�)�+L�M�M�M���
�
�
��t�t�
���s�58�
A�AN)
rrrrrr"r�	orig_filer#r$�property�strr'rs�r�
MalwareHitr
s��������	�	�	�	�	�	�	�	�	�	��y�e�$�$�$��!�M�u�-�-�-�	��y�d�#�#�#���y�d�#�#�#��	�	�#�	�	�	�
��	�	�	rr+)r)rr+s` r�	get_modelr,s?����������U����*�rc�,�ttjj||f��\}}tt||f��\}}	tj||��dS#t$rYdSt$r&}t�
d|��Yd}~dSd}~wwxYw)Nz2Failed to move stored file to the new location: %r)�maprr�joinpathr*�shutil�move�FileNotFoundError�	Exception�logger�error)�src�dst�errs   r�_mover91s����>�&�/�#�s��<�<�H�C���3��c�
�#�#�H�C��P���C���������
�
�
�����P�P�P����I�3�O�O�O�O�O�O�O�O�O�����P���s�A�
B�$	B�-B�BFc��|rdSt|��}|D];}|j}|��t�|j��}t	||���<dS�N)r,r'rr(r9)�migratorr�fake�kwargsr+�hitr6r7s        r�migrater@<si�������8�$�$�J���������;���)�)�#�-�8�8��
�c�3�����
�rc��dSr;�)r<rr=r>s    r�rollbackrCKs���Dr)F)�loggingrr0�peeweerr�defence360agent.utilsr�$defence360agent.model.simplificationr�getr�	getLoggerrr4r,r9r@rCrBrr�<module>rJs�������	�	�	�	�
�
�
�
�#�#�#�#�#�#�#�#�*�*�*�*�*�*�>�>�>�>�>�>����,�	������
��	�8�	$�	$�����<P�P�P�����	�	�	�	�	�	rdefence360agent/migrations/__pycache__/129_fixed_cagefs_unmount.cpython-311.opt-1.pyc0000644000000000000000000000614600000000000025371 0ustar  �

��D�����ddlZddlZddlZddlmZddlmZmZdZdZ	dZ
dZed��d	d
gfd���Zd�Z
eejd
e
d���d���Zdd�Zdd�ZdS)�N)�	lru_cache)�retry_on�run_with_umask�cagefs�restartz/usr/sbin/cagefsctlz--wait-lock�z/usr/binz/binc�4�td�|D����S)z6Return whether we can find systemctl in given *paths*.c3�K�|]A}tj�tj�|d����V��BdS)�	systemctlN)�os�path�isfile�join)�.0�ps  �h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/129_fixed_cagefs_unmount.py�	<genexpr>z$systemctl_present.<locals>.<genexpr>s@����K�K��r�w�~�~�b�g�l�l�1�k�:�:�;�;�K�K�K�K�K�K�)�any)�pathss r�systemctl_presentr
s!���K�K�U�K�K�K�K�K�Krc���t��rdttg}ndttg}	tj|��n#t
$rYnwxYwt
jd��dS)Nr�service�)r�_COMMAND�
_SERVICE_NAME�
subprocess�
check_call�	Exception�time�sleep)�exc�i�cmds   r�_restart_cagefsr%st�����3��H�m�4����-��2��
���c�"�"�"�"���
�
�
���
�����J�q�M�M�M�M�Ms�A�
A�A�T)�	max_tries�on_error�silentc�H�tj|dtj���dS)NF)�shell�stderr)r�check_output�STDOUT)r$s r�_execute_commandr/s%����C�u�Z�5F�G�G�G�G�G�GrF�c�
�|rdSttdgttdgg}t|��5tj�t��r|D]}t
|���ddd��dS#1swxYwYdS)Nz--force-update-etcz
--remount-all)�_CAGEFSCTL_TOOL�
_WAIT_LOCKrrr
�existsr/)�migrator�database�fake�umask�kwargs�cmd_listr$s       r�migrater;)s�������	�*�&:�;�	�*�o�6��H�
��	�	�&�&�
�7�>�>�/�*�*�	&��
&�
&�� ��%�%�%�%�&�&�&�&�&�&�&�&�&�&�&�&����&�&�&�&�&�&s�9A8�8A<�?A<c��dS)N�)r5r6r7r9s    r�rollbackr>6s���Dr)Fr0)F)rr r�	functoolsr�defence360agent.utilsrrrrr2r3rr%�CalledProcessErrorr/r;r>r=rr�<module>rBs����������	�	�	�	�������:�:�:�:�:�:�:�:��
���'��
�
���1���'��0�L�L�L���L�
	�	�	�
���!��
��	���H�H�
��H�
&�
&�
&�
&�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/129_fixed_cagefs_unmount.cpython-311.pyc0000644000000000000000000000614600000000000024432 0ustar  �

��D�����ddlZddlZddlZddlmZddlmZmZdZdZ	dZ
dZed��d	d
gfd���Zd�Z
eejd
e
d���d���Zdd�Zdd�ZdS)�N)�	lru_cache)�retry_on�run_with_umask�cagefs�restartz/usr/sbin/cagefsctlz--wait-lock�z/usr/binz/binc�4�td�|D����S)z6Return whether we can find systemctl in given *paths*.c3�K�|]A}tj�tj�|d����V��BdS)�	systemctlN)�os�path�isfile�join)�.0�ps  �h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/129_fixed_cagefs_unmount.py�	<genexpr>z$systemctl_present.<locals>.<genexpr>s@����K�K��r�w�~�~�b�g�l�l�1�k�:�:�;�;�K�K�K�K�K�K�)�any)�pathss r�systemctl_presentr
s!���K�K�U�K�K�K�K�K�Krc���t��rdttg}ndttg}	tj|��n#t
$rYnwxYwt
jd��dS)Nr�service�)r�_COMMAND�
_SERVICE_NAME�
subprocess�
check_call�	Exception�time�sleep)�exc�i�cmds   r�_restart_cagefsr%st�����3��H�m�4����-��2��
���c�"�"�"�"���
�
�
���
�����J�q�M�M�M�M�Ms�A�
A�A�T)�	max_tries�on_error�silentc�H�tj|dtj���dS)NF)�shell�stderr)r�check_output�STDOUT)r$s r�_execute_commandr/s%����C�u�Z�5F�G�G�G�G�G�GrF�c�
�|rdSttdgttdgg}t|��5tj�t��r|D]}t
|���ddd��dS#1swxYwYdS)Nz--force-update-etcz
--remount-all)�_CAGEFSCTL_TOOL�
_WAIT_LOCKrrr
�existsr/)�migrator�database�fake�umask�kwargs�cmd_listr$s       r�migrater;)s�������	�*�&:�;�	�*�o�6��H�
��	�	�&�&�
�7�>�>�/�*�*�	&��
&�
&�� ��%�%�%�%�&�&�&�&�&�&�&�&�&�&�&�&����&�&�&�&�&�&s�9A8�8A<�?A<c��dS)N�)r5r6r7r9s    r�rollbackr>6s���Dr)Fr0)F)rr r�	functoolsr�defence360agent.utilsrrrrr2r3rr%�CalledProcessErrorr/r;r>r=rr�<module>rBs����������	�	�	�	�������:�:�:�:�:�:�:�:��
���'��
�
���1���'��0�L�L�L���L�
	�	�	�
���!��
��	���H�H�
��H�
&�
&�
&�
&�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/130_add_messages_to_send.cpython-311.opt-1.pyc0000644000000000000000000000264400000000000025316 0ustar  �

F�b��e��F�ddlmZmZmZGd�de��Zdd�Zdd�ZdS)	�)�
FloatField�Model�	BlobFieldc�X�eZdZGd�d��Zed���Zed���ZdS)�
MessageToSendc��eZdZdZdS)�MessageToSend.Meta�messages_to_sendN)�__name__�
__module__�__qualname__�db_table���h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/130_add_messages_to_send.py�Metar	s������%���rrF)�nullN)rrr
rr�	timestampr�messagerrrrrs\������&�&�&�&�&�&�&�&��
��&�&�&�I��i�U�#�#�#�G�G�GrrFc�:�|�t��dS)N)�create_modelr)�migrator�database�fake�kwargss    r�migraters�����-�(�(�(�(�(rc�J�|jd}|�|��dS)Nr
)�orm�
drop_model)rrrrrs     r�rollbackr s)���L�!3�4�M����
�&�&�&�&�&rN)F)�peeweerrrrrr rrr�<module>r"s��/�/�/�/�/�/�/�/�/�/�$�$�$�$�$�E�$�$�$�)�)�)�)�'�'�'�'�'�'rdefence360agent/migrations/__pycache__/130_add_messages_to_send.cpython-311.pyc0000644000000000000000000000264400000000000024357 0ustar  �

F�b��e��F�ddlmZmZmZGd�de��Zdd�Zdd�ZdS)	�)�
FloatField�Model�	BlobFieldc�X�eZdZGd�d��Zed���Zed���ZdS)�
MessageToSendc��eZdZdZdS)�MessageToSend.Meta�messages_to_sendN)�__name__�
__module__�__qualname__�db_table���h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/130_add_messages_to_send.py�Metar	s������%���rrF)�nullN)rrr
rr�	timestampr�messagerrrrrs\������&�&�&�&�&�&�&�&��
��&�&�&�I��i�U�#�#�#�G�G�GrrFc�:�|�t��dS)N)�create_modelr)�migrator�database�fake�kwargss    r�migraters�����-�(�(�(�(�(rc�J�|jd}|�|��dS)Nr
)�orm�
drop_model)rrrrrs     r�rollbackr s)���L�!3�4�M����
�&�&�&�&�&rN)F)�peeweerrrrrr rrr�<module>r"s��/�/�/�/�/�/�/�/�/�/�$�$�$�$�$�E�$�$�$�)�)�)�)�'�'�'�'�'�'rdefence360agent/migrations/__pycache__/131_incident_timestamp_index.cpython-311.opt-1.pyc0000644000000000000000000000121100000000000026221 0ustar  �

��ȕ�{����dd�Zdd�ZdS)Fc�0�|�d��dS)NzECREATE INDEX IF NOT EXISTS incident_timestamp ON incident (timestamp))�sql��migrator�database�fake�kwargss    �l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/131_incident_timestamp_index.py�migrater
s%���L�L�O������c��dS)N�rs    r	�rollbackr
s���DrN)F)r
rr
rr	�<module>rs7������	�	�	�	�	�	rdefence360agent/migrations/__pycache__/131_incident_timestamp_index.cpython-311.pyc0000644000000000000000000000121100000000000025262 0ustar  �

��ȕ�{����dd�Zdd�ZdS)Fc�0�|�d��dS)NzECREATE INDEX IF NOT EXISTS incident_timestamp ON incident (timestamp))�sql��migrator�database�fake�kwargss    �l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/131_incident_timestamp_index.py�migrater
s%���L�L�O������c��dS)N�rs    r	�rollbackr
s���DrN)F)r
rr
rr	�<module>rs7������	�	�	�	�	�	rdefence360agent/migrations/__pycache__/132_add_timestamp_field.cpython-311.opt-1.pyc0000644000000000000000000000177600000000000025151 0ustar  �

?<�H����F�ddlZddlZeje��Zdd�Zdd�ZdS)�NFc�t�|jd}|�|tjd������dS)N�malware_hitsT)�null)�	timestamp)�orm�
add_fields�pw�
FloatField��migrator�database�fake�kwargs�MalwareHitss     �g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/132_add_timestamp_field.py�migrater	s:���,�~�.�K�����r�}�$�/G�/G�/G��H�H�H�H�H�c�L�|jd}|�|d��dS)Nrr)r�
remove_fieldsrs     r�rollbackrs*���,�~�.�K����;��4�4�4�4�4r)F)�logging�peeweer	�	getLogger�__name__�loggerrr�rr�<module>rsd����������
��	�8�	$�	$��I�I�I�I�
5�5�5�5�5�5rdefence360agent/migrations/__pycache__/132_add_timestamp_field.cpython-311.pyc0000644000000000000000000000177600000000000024212 0ustar  �

?<�H����F�ddlZddlZeje��Zdd�Zdd�ZdS)�NFc�t�|jd}|�|tjd������dS)N�malware_hitsT)�null)�	timestamp)�orm�
add_fields�pw�
FloatField��migrator�database�fake�kwargs�MalwareHitss     �g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/132_add_timestamp_field.py�migrater	s:���,�~�.�K�����r�}�$�/G�/G�/G��H�H�H�H�H�c�L�|jd}|�|d��dS)Nrr)r�
remove_fieldsrs     r�rollbackrs*���,�~�.�K����;��4�4�4�4�4r)F)�logging�peeweer	�	getLogger�__name__�loggerrr�rr�<module>rsd����������
��	�8�	$�	$��I�I�I�I�
5�5�5�5�5�5rdefence360agent/migrations/__pycache__/133_add_scope_field_to_iplist.cpython-311.opt-1.pyc0000644000000000000000000000234300000000000026335 0ustar  �

�Ua�U��P�ddlZddlZeje��Zd\ZZdd�Zdd�Z	dS)�N)�local�groupFc
��|jd}|�|tjdtjdt
�dt�d���g������dS)N�iplistTzscope in ('z','z'))�null�constraints)�scope)�orm�
add_fields�pw�	CharField�Check�SCOPE_LOCAL�SCOPE_GROUP��migrator�database�fake�kwargs�ip_lists     �m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/133_add_scope_field_to_iplist.py�migratersp���l�8�$�G������l�����;�;�;����L�M�M��
�
�
�������c�L�|jd}|�|d��dS)Nrr	)r
�
remove_fieldsrs     r�rollbackrs*���l�8�$�G����7�G�,�,�,�,�,r)F)
�logging�peeweer�	getLogger�__name__�loggerrrrr�rr�<module>r#sj����������
��	�8�	$�	$��+���[�
�
�
�
�-�-�-�-�-�-rdefence360agent/migrations/__pycache__/133_add_scope_field_to_iplist.cpython-311.pyc0000644000000000000000000000234300000000000025376 0ustar  �

�Ua�U��P�ddlZddlZeje��Zd\ZZdd�Zdd�Z	dS)�N)�local�groupFc
��|jd}|�|tjdtjdt
�dt�d���g������dS)N�iplistTzscope in ('z','z'))�null�constraints)�scope)�orm�
add_fields�pw�	CharField�Check�SCOPE_LOCAL�SCOPE_GROUP��migrator�database�fake�kwargs�ip_lists     �m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/133_add_scope_field_to_iplist.py�migratersp���l�8�$�G������l�����;�;�;����L�M�M��
�
�
�������c�L�|jd}|�|d��dS)Nrr	)r
�
remove_fieldsrs     r�rollbackrs*���l�8�$�G����7�G�,�,�,�,�,r)F)
�logging�peeweer�	getLogger�__name__�loggerrrrr�rr�<module>r#sj����������
��	�8�	$�	$��+���[�
�
�
�
�-�-�-�-�-�-rdefence360agent/migrations/__pycache__/134_change_default_of_intensity_ram.cpython-311.opt-1.pyc0000644000000000000000000000254300000000000027550 0ustar  �

����pQ]���R�ddlZddlmZeje��Zdd�Zdd�Zdd�ZdS)	�N)�
ConfigFilec��dddii}	t|���}|�|��dS#t$rt�d��YdSwxYw)N�MALWARE_SCAN_INTENSITY�rami)�pathz*Failed to set malware scan schedule config)r�dict_to_config�	Exception�logger�	exception)r�config�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�*�*�*�*�*���G�G�G����E�F�F�F�F�F�F�G���s�%/�$A�AFc�*�|rdSt��dS�N)r��migrator�database�fake�kwargss    r�migraters!������������c��dSr�rs    r�rollbackrs���Drr)F)	�logging� defence360agent.contracts.configr�	getLogger�__name__r
rrrrrr�<module>r s|������7�7�7�7�7�7�	��	�8�	$�	$��G�G�G�G�����	�	�	�	�	�	rdefence360agent/migrations/__pycache__/134_change_default_of_intensity_ram.cpython-311.pyc0000644000000000000000000000254300000000000026611 0ustar  �

����pQ]���R�ddlZddlmZeje��Zdd�Zdd�Zdd�ZdS)	�N)�
ConfigFilec��dddii}	t|���}|�|��dS#t$rt�d��YdSwxYw)N�MALWARE_SCAN_INTENSITY�rami)�pathz*Failed to set malware scan schedule config)r�dict_to_config�	Exception�logger�	exception)r�config�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�*�*�*�*�*���G�G�G����E�F�F�F�F�F�F�G���s�%/�$A�AFc�*�|rdSt��dS�N)r��migrator�database�fake�kwargss    r�migraters!������������c��dSr�rs    r�rollbackrs���Drr)F)	�logging� defence360agent.contracts.configr�	getLogger�__name__r
rrrrrr�<module>r s|������7�7�7�7�7�7�	��	�8�	$�	$��G�G�G�G�����	�	�	�	�	�	rdefence360agent/migrations/__pycache__/135_export_proactive.cpython-311.opt-1.pyc0000644000000000000000000000504700000000000024566 0ustar  �

���l)d���ddlZddlZddlZeje��Zd\ZZdZd�	e��Z
dZd�Zd
d�Z
d
d	�ZdS)�N)z
proactive.csvzproactive_env.csvz�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 ?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��}ttj�||��ddd���5}tj
|��}|�|��ddd��n#1swxYwY��dS)N�w�zutf-8)�newline�encoding)�
PROACTIVE_CSV�
PROACTIVE_SQL�PROACTIVE_ENV_CSV�PROACTIVE_ENV_SQL�execute_sql�open�os�path�join�csv�writer�	writerows)�database�
target_dir�
events_num�filename�query�cur�csvfile�
csv_writers        �d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/135_export_proactive.py�exportrs���	�
�&�	�-�.��&�&���%��"�"�5�:�-�8�8��
��G�L�L��X�.�.����	
�
�
�	&�
���G�,�,�J�� � ��%�%�%�	&�	&�	&�	&�	&�	&�	&�	&�	&�	&�	&����	&�	&�	&�	&��&�&s�**B � B$	�'B$	Fc���|rdS	tjtd���t|td��dS#t$rt
�d��YdSwxYw)NT)�exist_oki�z'Failed to export proactive defence data)r�makedirs�
EXPORT_DIRr�	Exception�logger�	exception��migratorr�fake�kwargss    r�migrater)%s������D�
��J��.�.�.�.��x��T�*�*�*�*�*���D�D�D����B�C�C�C�C�C�C�D���s�19�$A!� A!c��dS)N�r%s    r�rollbackr,0s���D�)F)rr�logging�	getLogger�__name__r#rr
r	�formatrr!rr)r,r+r-r�<module>r2s���
�
�
�
�	�	�	�	�����	��	�8�	$�	$��#G� �
� �2�
���F�����5�
�
&�
&�
&� D�D�D�D�	�	�	�	�	�	r-defence360agent/migrations/__pycache__/135_export_proactive.cpython-311.pyc0000644000000000000000000000504700000000000023627 0ustar  �

���l)d���ddlZddlZddlZeje��Zd\ZZdZd�	e��Z
dZd�Zd
d�Z
d
d	�ZdS)�N)z
proactive.csvzproactive_env.csvz�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 ?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��}ttj�||��ddd���5}tj
|��}|�|��ddd��n#1swxYwY��dS)N�w�zutf-8)�newline�encoding)�
PROACTIVE_CSV�
PROACTIVE_SQL�PROACTIVE_ENV_CSV�PROACTIVE_ENV_SQL�execute_sql�open�os�path�join�csv�writer�	writerows)�database�
target_dir�
events_num�filename�query�cur�csvfile�
csv_writers        �d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/135_export_proactive.py�exportrs���	�
�&�	�-�.��&�&���%��"�"�5�:�-�8�8��
��G�L�L��X�.�.����	
�
�
�	&�
���G�,�,�J�� � ��%�%�%�	&�	&�	&�	&�	&�	&�	&�	&�	&�	&�	&����	&�	&�	&�	&��&�&s�**B � B$	�'B$	Fc���|rdS	tjtd���t|td��dS#t$rt
�d��YdSwxYw)NT)�exist_oki�z'Failed to export proactive defence data)r�makedirs�
EXPORT_DIRr�	Exception�logger�	exception��migratorr�fake�kwargss    r�migrater)%s������D�
��J��.�.�.�.��x��T�*�*�*�*�*���D�D�D����B�C�C�C�C�C�C�D���s�19�$A!� A!c��dS)N�r%s    r�rollbackr,0s���D�)F)rr�logging�	getLogger�__name__r#rr
r	�formatrr!rr)r,r+r-r�<module>r2s���
�
�
�
�	�	�	�	�����	��	�8�	$�	$��#G� �
� �2�
���F�����5�
�
&�
&�
&� D�D�D�D�	�	�	�	�	�	r-defence360agent/migrations/__pycache__/135_make_completed_nullable.cpython-311.opt-1.pyc0000644000000000000000000000142500000000000026014 0ustar  �

Fb.T@�����dd�Zdd�ZdS)Fc�L�|jd}|�|d��dS�N�
malware_scans�	completed)�orm�
drop_not_null��migrator�database�fake�kwargs�MalwareScans     �k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/135_make_completed_nullable.py�migraters*���,��/�K����;��4�4�4�4�4�c�L�|jd}|�|d��dSr)r�add_not_nullrs     r�rollbackrs*���,��/�K����+�{�3�3�3�3�3rN)F)rr�rr�<module>rs7��5�5�5�5�
4�4�4�4�4�4rdefence360agent/migrations/__pycache__/135_make_completed_nullable.cpython-311.pyc0000644000000000000000000000142500000000000025055 0ustar  �

Fb.T@�����dd�Zdd�ZdS)Fc�L�|jd}|�|d��dS�N�
malware_scans�	completed)�orm�
drop_not_null��migrator�database�fake�kwargs�MalwareScans     �k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/135_make_completed_nullable.py�migraters*���,��/�K����;��4�4�4�4�4�c�L�|jd}|�|d��dSr)r�add_not_nullrs     r�rollbackrs*���,��/�K����+�{�3�3�3�3�3rN)F)rr�rr�<module>rs7��5�5�5�5�
4�4�4�4�4�4rdefence360agent/migrations/__pycache__/136_drop_proactive.cpython-311.opt-1.pyc0000644000000000000000000000131700000000000024206 0ustar  �

ì�u�����dd�Zdd�ZdS)Fc��|�|jd��|�|jd��dS)N�	proactive�
proactive_env)�remove_model�orm��migrator�database�fake�kwargss    �b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/136_drop_proactive.py�migrater
s>�����(�,�{�3�4�4�4����(�,��7�8�8�8�8�8�c��dS)N�rs    r�rollbackrs���DrN)F)r
rrrr�<module>rs7��9�9�9�9�
	�	�	�	�	�	rdefence360agent/migrations/__pycache__/136_drop_proactive.cpython-311.pyc0000644000000000000000000000131700000000000023247 0ustar  �

ì�u�����dd�Zdd�ZdS)Fc��|�|jd��|�|jd��dS)N�	proactive�
proactive_env)�remove_model�orm��migrator�database�fake�kwargss    �b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/136_drop_proactive.py�migrater
s>�����(�,�{�3�4�4�4����(�,��7�8�8�8�8�8�c��dS)N�rs    r�rollbackrs���DrN)F)r
rrrr�<module>rs7��9�9�9�9�
	�	�	�	�	�	rdefence360agent/migrations/__pycache__/137_swap_initiator_and_cause.cpython-311.opt-1.pyc0000644000000000000000000000233500000000000026226 0ustar  �

+� �;�"\��>�ddlZeje��Zdd�Zdd�ZdS)�NFc� �|jd}	|���D]8}|jdvr|j|jc|_|_|����9dS#t
$r%}t�|��Yd}~dSd}~wwxYw)N�malware_history)�manualz	on-demand�realtime)�orm�select�	initiator�cause�save�	Exception�logger�	exception)�migrator�database�fake�kwargs�MalwareHistory�entry�es       �l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/137_swap_initiator_and_cause.py�migraters����\�"3�4�N��#�*�*�,�,�	�	�E���"E�E�E�/4����,���U�_��J�J�L�L�L�L�	�	��������������������������s�A
A�
B
�(B�B
c��dS)N�)rrrrs    r�rollbackrs���D�)F)�logging�	getLogger�__name__r
rrrrr�<module>rsR������	��	�8�	$�	$������	�	�	�	�	�	rdefence360agent/migrations/__pycache__/137_swap_initiator_and_cause.cpython-311.pyc0000644000000000000000000000233500000000000025267 0ustar  �

+� �;�"\��>�ddlZeje��Zdd�Zdd�ZdS)�NFc� �|jd}	|���D]8}|jdvr|j|jc|_|_|����9dS#t
$r%}t�|��Yd}~dSd}~wwxYw)N�malware_history)�manualz	on-demand�realtime)�orm�select�	initiator�cause�save�	Exception�logger�	exception)�migrator�database�fake�kwargs�MalwareHistory�entry�es       �l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/137_swap_initiator_and_cause.py�migraters����\�"3�4�N��#�*�*�,�,�	�	�E���"E�E�E�/4����,���U�_��J�J�L�L�L�L�	�	��������������������������s�A
A�
B
�(B�B
c��dS)N�)rrrrs    r�rollbackrs���D�)F)�logging�	getLogger�__name__r
rrrrr�<module>rsR������	��	�8�	$�	$������	�	�	�	�	�	rdefence360agent/migrations/__pycache__/138_move_rapid_scan_dir.cpython-311.opt-1.pyc0000644000000000000000000000526000000000000025160 0ustar  �

_DX�����l�ddlZddlZddlZddlmZddlmZejddd���Zd
d�Z	d
d	�Z
dS)�N)�
hosting_panel)�importerzimav.malwarelib.utils.user_list�panel_users)�module�name�defaultFc���|rdStj��}tj|��|�t	����}|D]�}tj|d��}t|jdz|j	z��}	tj���|d��}	n#t$rY�uwxYw|	�||	kr��	tj||	����#t$rY��wxYwdS�N�homez.rapid-scan-db��asyncio�new_event_loop�set_event_loop�run_until_completer�pathlib�Path�str�parentrr�HostingPanel�get_rapid_scan_db_dir�OSError�shutil�move�
�migrator�database�fake�kwargs�loop�users�user�path_obj�old_path�new_paths
          �g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/138_move_rapid_scan_dir.py�migrater&
s(�������!�#�#�D���4� � � ��#�#�K�M�M�2�2�E������<��V��-�-���x��)9�9�H�M�I�J�J��	�$�1�3�3�I�I��V����H�H���	�	�	��H�	������x�8�3�3��	��K��(�+�+�+�+���	�	�	��D�	�����s$�,B8�8
C�C�C(�(
C5�4C5c���tj��}tj|��|�t	����}|D]�}tj|d��}t|jdz|j	z��}	tj���|d��}	n#t$rY�uwxYw|	�||	kr��	tj|	|����#t$rY��wxYwdSr
rrs
          r%�rollbackr(%s���!�#�#�D���4� � � ��#�#�K�M�M�2�2�E������<��V��-�-���x��)9�9�H�M�I�J�J��	�$�1�3�3�I�I��V����H�H���	�	�	��H�	������x�8�3�3��	��K��(�+�+�+�+���	�	�	��D�	�����s$�,B4�4
C�C�C$�$
C1�0C1)F)r
rr�defence360agent.subsys.panelsr�defence360agent.utilsr�getrr&r(��r%�<module>r.s�����������
�
�
�
�7�7�7�7�7�7�*�*�*�*�*�*��h�l�,�=�$�����
����0�����r-defence360agent/migrations/__pycache__/138_move_rapid_scan_dir.cpython-311.pyc0000644000000000000000000000526000000000000024221 0ustar  �

_DX�����l�ddlZddlZddlZddlmZddlmZejddd���Zd
d�Z	d
d	�Z
dS)�N)�
hosting_panel)�importerzimav.malwarelib.utils.user_list�panel_users)�module�name�defaultFc���|rdStj��}tj|��|�t	����}|D]�}tj|d��}t|jdz|j	z��}	tj���|d��}	n#t$rY�uwxYw|	�||	kr��	tj||	����#t$rY��wxYwdS�N�homez.rapid-scan-db��asyncio�new_event_loop�set_event_loop�run_until_completer�pathlib�Path�str�parentrr�HostingPanel�get_rapid_scan_db_dir�OSError�shutil�move�
�migrator�database�fake�kwargs�loop�users�user�path_obj�old_path�new_paths
          �g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/138_move_rapid_scan_dir.py�migrater&
s(�������!�#�#�D���4� � � ��#�#�K�M�M�2�2�E������<��V��-�-���x��)9�9�H�M�I�J�J��	�$�1�3�3�I�I��V����H�H���	�	�	��H�	������x�8�3�3��	��K��(�+�+�+�+���	�	�	��D�	�����s$�,B8�8
C�C�C(�(
C5�4C5c���tj��}tj|��|�t	����}|D]�}tj|d��}t|jdz|j	z��}	tj���|d��}	n#t$rY�uwxYw|	�||	kr��	tj|	|����#t$rY��wxYwdSr
rrs
          r%�rollbackr(%s���!�#�#�D���4� � � ��#�#�K�M�M�2�2�E������<��V��-�-���x��)9�9�H�M�I�J�J��	�$�1�3�3�I�I��V����H�H���	�	�	��H�	������x�8�3�3��	��K��(�+�+�+�+���	�	�	��D�	�����s$�,B4�4
C�C�C$�$
C1�0C1)F)r
rr�defence360agent.subsys.panelsr�defence360agent.utilsr�getrr&r(��r%�<module>r.s�����������
�
�
�
�7�7�7�7�7�7�*�*�*�*�*�*��h�l�,�=�$�����
����0�����r-defence360agent/migrations/__pycache__/139_generic_modsec_config.cpython-311.opt-1.pyc0000644000000000000000000000121600000000000025462 0ustar  �

c���Z[���dd�Zdd�ZdS)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/
    N���migrator�database�fake�kwargss    �i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/139_generic_modsec_config.py�migrater
s����c��dS)Nrrs    r	�rollbackr
s���DrN)F)r
r
rrr	�<module>rs7������	�	�	�	�	�	rdefence360agent/migrations/__pycache__/139_generic_modsec_config.cpython-311.pyc0000644000000000000000000000121600000000000024523 0ustar  �

c���Z[���dd�Zdd�ZdS)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/
    N���migrator�database�fake�kwargss    �i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/139_generic_modsec_config.py�migrater
s����c��dS)Nrrs    r	�rollbackr
s���DrN)F)r
r
rrr	�<module>rs7������	�	�	�	�	�	rdefence360agent/migrations/__pycache__/140_cast_malware_hit_orig_file_as_blob.cpython-311.opt-1.pyc0000644000000000000000000000130300000000000030162 0ustar  �

�n�.\
���dd�Zdd�ZdS)Fc�8�|rdS|�d��dS)Nz^UPDATE malware_hits SET orig_file = CAST(orig_file AS BLOB) WHERE typeof(orig_file) != "blob";)�sql��migrator�database�fake�kwargss    �v/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/140_cast_malware_hit_orig_file_as_blob.py�migrater
s4�������L�L�	-������c��dS)N�rs    r	�rollbackrs���DrN)F)r
rr
rr	�<module>rs7������	�	�	�	�	�	rdefence360agent/migrations/__pycache__/140_cast_malware_hit_orig_file_as_blob.cpython-311.pyc0000644000000000000000000000130300000000000027223 0ustar  �

�n�.\
���dd�Zdd�ZdS)Fc�8�|rdS|�d��dS)Nz^UPDATE malware_hits SET orig_file = CAST(orig_file AS BLOB) WHERE typeof(orig_file) != "blob";)�sql��migrator�database�fake�kwargss    �v/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/140_cast_malware_hit_orig_file_as_blob.py�migrater
s4�������L�L�	-������c��dS)N�rs    r	�rollbackrs���DrN)F)r
rr
rr	�<module>rs7������	�	�	�	�	�	rdefence360agent/migrations/__pycache__/141_drop_last_user_scans.cpython-311.opt-1.pyc0000644000000000000000000000123500000000000025375 0ustar  �

W�{���i���dd�Zdd�ZdS)Fc�\�d|jvr"|�|jd��dSdS)N�last_user_scans)�orm�remove_model��migrator�database�fake�kwargss    �h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/141_drop_last_user_scans.py�migraters9���H�L�(�(����h�l�+<�=�>�>�>�>�>�)�(�c��dS)N�rs    r�rollbackrs���Dr
N)F)rrrr
r�<module>rs7��?�?�?�?�
	�	�	�	�	�	r
defence360agent/migrations/__pycache__/141_drop_last_user_scans.cpython-311.pyc0000644000000000000000000000123500000000000024436 0ustar  �

W�{���i���dd�Zdd�ZdS)Fc�\�d|jvr"|�|jd��dSdS)N�last_user_scans)�orm�remove_model��migrator�database�fake�kwargss    �h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/141_drop_last_user_scans.py�migraters9���H�L�(�(����h�l�+<�=�>�>�>�>�>�)�(�c��dS)N�rs    r�rollbackrs���Dr
N)F)rrrr
r�<module>rs7��?�?�?�?�
	�	�	�	�	�	r
defence360agent/migrations/__pycache__/143_malware_hit_cascade_delete.cpython-311.opt-1.pyc0000644000000000000000000000707200000000000026451 0ustar  �

�0���B�v��j�ddlZGd�dej��ZGd�dej��Zd	d�Zd	d�ZdS)
�Nc�J�eZdZGd�d��Zejd���ZdS)�MalwareScanc��eZdZdZdS)�MalwareScan.Meta�
malware_scansN��__name__�
__module__�__qualname__�db_table���n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/143_malware_hit_cascade_delete.py�Metars������"���rrT)�primary_keyN)r	r
rr�peewee�	CharField�scanidr
rrrrsM������#�#�#�#�#�#�#�#��V�
�$�
/�
/�
/�F�F�Frrc���eZdZGd�d��Zej��Zejeddd���Z	ej
d���Zejd���Z
ej
d���Zejdd���Zej
dd	���Zej
d
���Zej
d
���Zejd
���Zej
d���Zejd
���Zed
���ZdS)�
MalwareHitc��eZdZdZdS)�MalwareHit.Meta�malware_hitsNrr
rrrrs������!���rrF�hits�CASCADE)�null�related_name�	on_delete)r)r�defaultzai-bolitT�found)rc�8�td�|jj��S)Nc��|jS�N)�column_name)�fields r�<lambda>z,MalwareHit.get_field_names.<locals>.<lambda> s	���!2�r)�map�_meta�
sorted_fields)�clss r�get_field_nameszMalwareHit.get_field_namess���2�2�C�I�4K�L�L�LrN)r	r
rrr�PrimaryKeyField�id�ForeignKeyFieldrrr�user�	BlobField�	orig_file�type�BooleanField�	malicious�vendor�hash�size�
FloatField�	timestamp�status�
cleaned_at�classmethodr+r
rrrrsf������"�"�"�"�"�"�"�"�
 ��	�	!�	!�B�
#�V�
#��%�f�	����F��6���'�'�'�D� �� �e�,�,�,�I��6���'�'�'�D�#��#���>�>�>�I�
�V�
�5�*�
=�
=�
=�F��6���&�&�&�D��6���&�&�&�D�!��!�t�,�,�,�I�
�V�
�g�
.�
.�
.�F�"��"��-�-�-�J��M�M��[�M�M�MrrFc�6�|�d��|�t��d�t�����}|�d�|����|�d��dS)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_modelr�joinr+�format)�migrator�database�fake�kwargs�malware_hit_fieldss     r�migraterH#s����L�L�G�H�H�H����*�%�%�%����*�"<�"<�">�">�?�?���L�L�J�	��"�	#�	#����
�L�L�/�0�0�0�0�0rc��dSr#r
)rCrDrErFs    r�rollbackrJ/s���Dr)F)r�ModelrrrHrJr
rr�<module>rLs���
�
�
�
�0�0�0�0�0�&�,�0�0�0�M�M�M�M�M���M�M�M�0	1�	1�	1�	1�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/143_malware_hit_cascade_delete.cpython-311.pyc0000644000000000000000000000707200000000000025512 0ustar  �

�0���B�v��j�ddlZGd�dej��ZGd�dej��Zd	d�Zd	d�ZdS)
�Nc�J�eZdZGd�d��Zejd���ZdS)�MalwareScanc��eZdZdZdS)�MalwareScan.Meta�
malware_scansN��__name__�
__module__�__qualname__�db_table���n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/143_malware_hit_cascade_delete.py�Metars������"���rrT)�primary_keyN)r	r
rr�peewee�	CharField�scanidr
rrrrsM������#�#�#�#�#�#�#�#��V�
�$�
/�
/�
/�F�F�Frrc���eZdZGd�d��Zej��Zejeddd���Z	ej
d���Zejd���Z
ej
d���Zejdd���Zej
dd	���Zej
d
���Zej
d
���Zejd
���Zej
d���Zejd
���Zed
���ZdS)�
MalwareHitc��eZdZdZdS)�MalwareHit.Meta�malware_hitsNrr
rrrrs������!���rrF�hits�CASCADE)�null�related_name�	on_delete)r)r�defaultzai-bolitT�found)rc�8�td�|jj��S)Nc��|jS�N)�column_name)�fields r�<lambda>z,MalwareHit.get_field_names.<locals>.<lambda> s	���!2�r)�map�_meta�
sorted_fields)�clss r�get_field_nameszMalwareHit.get_field_namess���2�2�C�I�4K�L�L�LrN)r	r
rrr�PrimaryKeyField�id�ForeignKeyFieldrrr�user�	BlobField�	orig_file�type�BooleanField�	malicious�vendor�hash�size�
FloatField�	timestamp�status�
cleaned_at�classmethodr+r
rrrrsf������"�"�"�"�"�"�"�"�
 ��	�	!�	!�B�
#�V�
#��%�f�	����F��6���'�'�'�D� �� �e�,�,�,�I��6���'�'�'�D�#��#���>�>�>�I�
�V�
�5�*�
=�
=�
=�F��6���&�&�&�D��6���&�&�&�D�!��!�t�,�,�,�I�
�V�
�g�
.�
.�
.�F�"��"��-�-�-�J��M�M��[�M�M�MrrFc�6�|�d��|�t��d�t�����}|�d�|����|�d��dS)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_modelr�joinr+�format)�migrator�database�fake�kwargs�malware_hit_fieldss     r�migraterH#s����L�L�G�H�H�H����*�%�%�%����*�"<�"<�">�">�?�?���L�L�J�	��"�	#�	#����
�L�L�/�0�0�0�0�0rc��dSr#r
)rCrDrErFs    r�rollbackrJ/s���Dr)F)r�ModelrrrHrJr
rr�<module>rLs���
�
�
�
�0�0�0�0�0�&�,�0�0�0�M�M�M�M�M���M�M�M�0	1�	1�	1�	1�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/144_remove_clamav_config_options.cpython-311.opt-1.pyc0000644000000000000000000000317400000000000027110 0ustar  �

��|�DX���h�ddlZddlmZmZeje��Zde��fdefd�Zdd�ZdS)�N)�IConfig�
ConfigFileF�config_filec�v�|rdS	|�d���}d|vrdS|d�dd��|d�dd��|d�dd��|�|dd���dS#t$rt�d	��YdSwxYw)
NF)�	normalize�MALWARE_SCANNING�
i360_clamd�show_clamav_results�
clamav_binaryT)�	overwrite�validatez&Failed to remove clamav config options)�config_to_dict�pop�dict_to_config�	Exception�logger�	exception)�migrator�database�faker�kwargs�configs      �p/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/144_remove_clamav_config_options.py�migraters�������C��+�+�e�+�<�<���V�+�+��F��!�"�&�&�|�T�:�:�:��!�"�&�&�'<�d�C�C�C��!�"�&�&���=�=�=��"�"�6�T�E�"�J�J�J�J�J���C�C�C����A�B�B�B�B�B�B�C���s�B�A,B�$B8�7B8c��dS)N�)rrrrs    r�rollbackr!s���D�)F)	�logging� defence360agent.contracts.configrr�	getLogger�__name__rrrrrr�<module>r#s�������@�@�@�@�@�@�@�@�	��	�8�	$�	$��
�%�:�<�<�	C�C��	C�C�C�C�2	�	�	�	�	�	rdefence360agent/migrations/__pycache__/144_remove_clamav_config_options.cpython-311.pyc0000644000000000000000000000317400000000000026151 0ustar  �

��|�DX���h�ddlZddlmZmZeje��Zde��fdefd�Zdd�ZdS)�N)�IConfig�
ConfigFileF�config_filec�v�|rdS	|�d���}d|vrdS|d�dd��|d�dd��|d�dd��|�|dd���dS#t$rt�d	��YdSwxYw)
NF)�	normalize�MALWARE_SCANNING�
i360_clamd�show_clamav_results�
clamav_binaryT)�	overwrite�validatez&Failed to remove clamav config options)�config_to_dict�pop�dict_to_config�	Exception�logger�	exception)�migrator�database�faker�kwargs�configs      �p/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/144_remove_clamav_config_options.py�migraters�������C��+�+�e�+�<�<���V�+�+��F��!�"�&�&�|�T�:�:�:��!�"�&�&�'<�d�C�C�C��!�"�&�&���=�=�=��"�"�6�T�E�"�J�J�J�J�J���C�C�C����A�B�B�B�B�B�B�C���s�B�A,B�$B8�7B8c��dS)N�)rrrrs    r�rollbackr!s���D�)F)	�logging� defence360agent.contracts.configrr�	getLogger�__name__rrrrrr�<module>r#s�������@�@�@�@�@�@�@�@�	��	�8�	$�	$��
�%�:�<�<�	C�C��	C�C�C�C�2	�	�	�	�	�	rdefence360agent/migrations/__pycache__/144_remove_hash_table.cpython-311.opt-1.pyc0000644000000000000000000000112500000000000024631 0ustar  �

X����N����dd�Zdd�ZdS)Fc�0�|�d��dS)Nz"DROP TABLE IF EXISTS malware_hash;)�sql��migrator�database�fake�kwargss    �e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/144_remove_hash_table.py�migrater
s���L�L�5�6�6�6�6�6�c��dS)N�rs    r	�rollbackrs���DrN)F)r
rr
rr	�<module>rs7��7�7�7�7�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/144_remove_hash_table.cpython-311.pyc0000644000000000000000000000112500000000000023672 0ustar  �

X����N����dd�Zdd�ZdS)Fc�0�|�d��dS)Nz"DROP TABLE IF EXISTS malware_hash;)�sql��migrator�database�fake�kwargss    �e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/144_remove_hash_table.py�migrater
s���L�L�5�6�6�6�6�6�c��dS)N�rs    r	�rollbackrs���DrN)F)r
rr
rr	�<module>rs7��7�7�7�7�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/145_move_quarantine.cpython-311.opt-1.pyc0000644000000000000000000000076100000000000024365 0ustar  �

�;x\=c�����dZd�Zd�ZdS)z# Quarantine is removed in DEF-15234c��dS�N���_�__s  �c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/145_move_quarantine.py�migrater	����D�c��dSrrrs  r�rollbackr
r
rN)�__doc__r	r
rrr�<module>rs3��)�)�	�	�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/145_move_quarantine.cpython-311.pyc0000644000000000000000000000076100000000000023426 0ustar  �

�;x\=c�����dZd�Zd�ZdS)z# Quarantine is removed in DEF-15234c��dS�N���_�__s  �c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/145_move_quarantine.py�migrater	����D�c��dSrrrs  r�rollbackr
r
rN)�__doc__r	r
rrr�<module>rs3��)�)�	�	�	�	�	�	�	�	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  �

�Y��l���dd�Zdd�ZdS)Fc��dS�N���migrator�database�fake�kwargss    �x/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/146_malware_user_infected_cascade_delete.py�migrater����D�c��dSrrrs    r
�rollbackrrr
N)F)rrrr
r
�<module>rs7��	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/146_malware_user_infected_cascade_delete.cpython-311.pyc0000644000000000000000000000077700000000000027555 0ustar  �

�Y��l���dd�Zdd�ZdS)Fc��dS�N���migrator�database�fake�kwargss    �x/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/146_malware_user_infected_cascade_delete.py�migrater����D�c��dSrrrs    r
�rollbackrrr
N)F)rrrr
r
�<module>rs7��	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/147_remove_vendor_field.cpython-311.opt-1.pyc0000644000000000000000000000164100000000000025205 0ustar  �

�'#�:����ddlZdd�Zdd�ZdS)�NFc�L�|jd}|�|d��dS)N�malware_hits�vendor)�orm�
remove_fields��migrator�database�fake�kwargsrs     �g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/147_remove_vendor_field.py�migraters*���<��/�L����<��2�2�2�2�2�c�v�|jd}|�|tjdd������dS)NrFzai-bolit)�null�default)r)r�
add_fields�peewee�	CharFieldrs     r
�rollbackr	sJ���<��/�L�����V�-�5�*�M�M�M������r)F)rrr�rr
�<module>rs@��
�
�
�
�3�3�3�3�
�����rdefence360agent/migrations/__pycache__/147_remove_vendor_field.cpython-311.pyc0000644000000000000000000000164100000000000024246 0ustar  �

�'#�:����ddlZdd�Zdd�ZdS)�NFc�L�|jd}|�|d��dS)N�malware_hits�vendor)�orm�
remove_fields��migrator�database�fake�kwargsrs     �g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/147_remove_vendor_field.py�migraters*���<��/�L����<��2�2�2�2�2�c�v�|jd}|�|tjdd������dS)NrFzai-bolit)�null�default)r)r�
add_fields�peewee�	CharFieldrs     r
�rollbackr	sJ���<��/�L�����V�-�5�*�M�M�M������r)F)rrr�rr
�<module>rs@��
�
�
�
�3�3�3�3�
�����rdefence360agent/migrations/__pycache__/147_user_scan_type.cpython-311.opt-1.pyc0000644000000000000000000000254400000000000024216 0ustar  �

�i��������ddlZddlmZejddd���Zejejejej	ej
ejfZd	d�Z
d	d�ZdS)
�N)�importerzimav.malwarelib.config�MalwareScanType)�module�name�defaultFc���|jd}|�|tjdtjd�t����g������dS)N�
malware_scansFz
type in {})�null�constraints)�type)�orm�
change_fields�pw�	CharField�Check�format�types)�migrator�database�fake�kwargs�MalwareScans     �b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/147_user_scan_type.py�migratersl���,��/�K�����
�\��R�X�l�.A�.A�%�.H�.H�%I�%I�$J�
�
�
�������c��dS)N�)rrrrs    r�rollbackrs���Dr)F)�peeweer�defence360agent.utilsr�getr�	ON_DEMAND�REALTIME�MALWARE_RESPONSE�
BACKGROUND�RESCAN�USERrrrrrr�<module>r(s�������*�*�*�*�*�*��(�,�#�*;�T�����
�����$�������
	������	�	�	�	�	�	rdefence360agent/migrations/__pycache__/147_user_scan_type.cpython-311.pyc0000644000000000000000000000254400000000000023257 0ustar  �

�i��������ddlZddlmZejddd���Zejejejej	ej
ejfZd	d�Z
d	d�ZdS)
�N)�importerzimav.malwarelib.config�MalwareScanType)�module�name�defaultFc���|jd}|�|tjdtjd�t����g������dS)N�
malware_scansFz
type in {})�null�constraints)�type)�orm�
change_fields�pw�	CharField�Check�format�types)�migrator�database�fake�kwargs�MalwareScans     �b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/147_user_scan_type.py�migratersl���,��/�K�����
�\��R�X�l�.A�.A�%�.H�.H�%I�%I�$J�
�
�
�������c��dS)N�)rrrrs    r�rollbackrs���Dr)F)�peeweer�defence360agent.utilsr�getr�	ON_DEMAND�REALTIME�MALWARE_RESPONSE�
BACKGROUND�RESCAN�USERrrrrrr�<module>r(s�������*�*�*�*�*�*��(�,�#�*;�T�����
�����$�������
	������	�	�	�	�	�	rdefence360agent/migrations/__pycache__/148_reconstruct_pickled_scan_queue.cpython-311.opt-1.pyc0000644000000000000000000000121000000000000027437 0ustar  �

w��'�4%���d�Zd�ZdS)c��dS)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.py�migrater�����c��dS)zDowngrade is not supportedNrrs  r�rollbackr	r	r
N)rrrr
r�<module>r
s-�����%�%�%�%�%r
defence360agent/migrations/__pycache__/148_reconstruct_pickled_scan_queue.cpython-311.pyc0000644000000000000000000000121000000000000026500 0ustar  �

w��'�4%���d�Zd�ZdS)c��dS)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.py�migrater�����c��dS)zDowngrade is not supportedNrrs  r�rollbackr	r	r
N)rrrr
r�<module>r
s-�����%�%�%�%�%r
defence360agent/migrations/__pycache__/148_remove_malware_user_infected.cpython-311.opt-1.pyc0000644000000000000000000000115000000000000027070 0ustar  �

�+Et�b���dd�Zdd�ZdS)Fc�0�|�d��dS)Nz*DROP TABLE IF EXISTS malware_user_infected)�sql��migrator�database�fake�kwargss    �p/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/148_remove_malware_user_infected.py�migrater
s���L�L�=�>�>�>�>�>�c��dS)N�rs    r	�rollbackrs���DrN)F)r
rr
rr	�<module>rs7��?�?�?�?�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/148_remove_malware_user_infected.cpython-311.pyc0000644000000000000000000000115000000000000026131 0ustar  �

�+Et�b���dd�Zdd�ZdS)Fc�0�|�d��dS)Nz*DROP TABLE IF EXISTS malware_user_infected)�sql��migrator�database�fake�kwargss    �p/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/148_remove_malware_user_infected.py�migrater
s���L�L�=�>�>�>�>�>�c��dS)N�rs    r	�rollbackrs���DrN)F)r
rr
rr	�<module>rs7��?�?�?�?�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/149_add_captcha_passed_field_to_iplist.cpython-311.opt-1.pyc0000644000000000000000000000166500000000000030203 0ustar  �

Zr�>T��7���ddlZdd�Zdd�ZdS)�NFc�v�|jd}|�|tjdd������dS)N�iplistF)�null�default)�captcha_passed)�orm�
add_fields�pw�BooleanField��migrator�database�fake�kwargs�IPLists     �v/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/149_add_captcha_passed_field_to_iplist.py�migratersI��
�\�(�
#�F�����r��E�5�I�I�I�������c�L�|jd}|�|d��dS)Nrr)r�
remove_fieldsrs     r�rollbackrs+��
�\�(�
#�F����6�#3�4�4�4�4�4r)F)�peeweer
rr�rr�<module>rsC����������5�5�5�5�5�5rdefence360agent/migrations/__pycache__/149_add_captcha_passed_field_to_iplist.cpython-311.pyc0000644000000000000000000000166500000000000027244 0ustar  �

Zr�>T��7���ddlZdd�Zdd�ZdS)�NFc�v�|jd}|�|tjdd������dS)N�iplistF)�null�default)�captcha_passed)�orm�
add_fields�pw�BooleanField��migrator�database�fake�kwargs�IPLists     �v/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/149_add_captcha_passed_field_to_iplist.py�migratersI��
�\�(�
#�F�����r��E�5�I�I�I�������c�L�|jd}|�|d��dS)Nrr)r�
remove_fieldsrs     r�rollbackrs+��
�\�(�
#�F����6�#3�4�4�4�4�4r)F)�peeweer
rr�rr�<module>rsC����������5�5�5�5�5�5rdefence360agent/migrations/__pycache__/149_make_config_inactive.cpython-311.opt-1.pyc0000644000000000000000000000300300000000000025310 0ustar  �

�Kf
�W��J�dZddlZddlZeje��Zdd�Zdd�ZdS)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�|��gd����0#t$rY�<t$rt
�d|��Y�bwxYwdS)N)zincludes/modsec2.imunify.confzmodsec2.imunify.confz/usr/sbin/whmapi1�modsec_make_config_inactivez	config={}T)�checkzFailed to make %s inactive)�
subprocess�run�format�FileNotFoundError�	Exception�logger�	exception)�migrator�database�fake�kwargs�confs     �h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/149_make_config_inactive.py�migrater
s�������I�
A�
A��	A��N�'�1��&�&�t�,�,��
�

�
�
�
�
��!�	�	�	��D��	A�	A�	A����9�4�@�@�@�@�@�	A����
A�
As�,7�
A*�$A*�)A*c��dS)N�)r
rrrs    r�rollbackr!s���D�)F)�__doc__�loggingr�	getLogger�__name__rrrrrr�<module>rsn������������	��	�8�	$�	$��A�A�A�A�(	�	�	�	�	�	rdefence360agent/migrations/__pycache__/149_make_config_inactive.cpython-311.pyc0000644000000000000000000000300300000000000024351 0ustar  �

�Kf
�W��J�dZddlZddlZeje��Zdd�Zdd�ZdS)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�|��gd����0#t$rY�<t$rt
�d|��Y�bwxYwdS)N)zincludes/modsec2.imunify.confzmodsec2.imunify.confz/usr/sbin/whmapi1�modsec_make_config_inactivez	config={}T)�checkzFailed to make %s inactive)�
subprocess�run�format�FileNotFoundError�	Exception�logger�	exception)�migrator�database�fake�kwargs�confs     �h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/149_make_config_inactive.py�migrater
s�������I�
A�
A��	A��N�'�1��&�&�t�,�,��
�

�
�
�
�
��!�	�	�	��D��	A�	A�	A����9�4�@�@�@�@�@�	A����
A�
As�,7�
A*�$A*�)A*c��dS)N�)r
rrrs    r�rollbackr!s���D�)F)�__doc__�loggingr�	getLogger�__name__rrrrrr�<module>rsn������������	��	�8�	$�	$��A�A�A�A�(	�	�	�	�	�	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  �

�Gw�����>�ddlZeje��Zdd�Zdd�ZdS)�NFc�n�|jd}|jdk|jz|jz|j�d��z}	|�|jdi���|��}|�	��dS#t$rt�d��YdSwxYw)N�iplist�WHITEzdue to successful captcha passTz%Failed update to captcha_passed field)
�orm�listname�full_access�manual�comment�contains�update�captcha_passed�where�execute�	Exception�logger�	exception)�migrator�database�fake�kwargs�IPList�captcha_pass_condition�qs       ��/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/150_update_captcha_passed_field_for_iplist_entries.py�migraters���
�\�(�
#�F�	��G�	#����	 ��M�>�	��>�"�"�#C�D�D�	F��B��M�M�6�0�$�7�8�8�>�>�"�
�
��	
�	�	��������B�B�B����@�A�A�A�A�A�A�B���s�AB�$B4�3B4c��dS)N�)rrrrs    r�rollbackrs���D�)F)�logging�	getLogger�__name__rrrrrr�<module>r#sV������	��	�8�	$�	$��B�B�B�B�"	�	�	�	�	�	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  �

�Gw�����>�ddlZeje��Zdd�Zdd�ZdS)�NFc�n�|jd}|jdk|jz|jz|j�d��z}	|�|jdi���|��}|�	��dS#t$rt�d��YdSwxYw)N�iplist�WHITEzdue to successful captcha passTz%Failed update to captcha_passed field)
�orm�listname�full_access�manual�comment�contains�update�captcha_passed�where�execute�	Exception�logger�	exception)�migrator�database�fake�kwargs�IPList�captcha_pass_condition�qs       ��/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/150_update_captcha_passed_field_for_iplist_entries.py�migraters���
�\�(�
#�F�	��G�	#����	 ��M�>�	��>�"�"�#C�D�D�	F��B��M�M�6�0�$�7�8�8�>�>�"�
�
��	
�	�	��������B�B�B����@�A�A�A�A�A�A�B���s�AB�$B4�3B4c��dS)N�)rrrrs    r�rollbackrs���D�)F)�logging�	getLogger�__name__rrrrrr�<module>r#sV������	��	�8�	$�	$��B�B�B�B�"	�	�	�	�	�	rdefence360agent/migrations/__pycache__/151_change_constraint_for_iplist.cpython-311.opt-1.pyc0000644000000000000000000000211400000000000027102 0ustar  �

�p�ػ����ddlZdd�Zdd�ZdS)�NFc
���|jd}d}|�|tjdtjd�d�|������g������dS)N�iplist)�WHITE�BLACK�GRAY�GRAY_SPLASHSCREENFzlistname in ('{}')z',')�null�constraints)�listname)�orm�
change_fields�pw�	CharField�Check�format�join)�migrator�database�fake�kwargs�
orm_IPList�IP_LISTSs      �p/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/151_change_constraint_for_iplist.py�migraters�����h�'�J�>�H����������-�4�4�U�Z�Z��5I�5I�J�J�K�K��
�
�
�������c��dS)N�)rrrrs    r�rollbackrs���Dr)F)�peeweerrrrrr�<module>r sC����������	�	�	�	�	�	rdefence360agent/migrations/__pycache__/151_change_constraint_for_iplist.cpython-311.pyc0000644000000000000000000000211400000000000026143 0ustar  �

�p�ػ����ddlZdd�Zdd�ZdS)�NFc
���|jd}d}|�|tjdtjd�d�|������g������dS)N�iplist)�WHITE�BLACK�GRAY�GRAY_SPLASHSCREENFzlistname in ('{}')z',')�null�constraints)�listname)�orm�
change_fields�pw�	CharField�Check�format�join)�migrator�database�fake�kwargs�
orm_IPList�IP_LISTSs      �p/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/151_change_constraint_for_iplist.py�migraters�����h�'�J�>�H����������-�4�4�U�Z�Z��5I�5I�J�J�K�K��
�
�
�������c��dS)N�)rrrrs    r�rollbackrs���Dr)F)�peeweerrrrrr�<module>r sC����������	�	�	�	�	�	rdefence360agent/migrations/__pycache__/152_add_listname_to_primary_key.cpython-311.opt-1.pyc0000644000000000000000000001045200000000000026725 0ustar  �

�E@�[.`k��B�ddlmZmZmZmZmZmZmZddlZdd�Z	dd�Z
dS)�)�BooleanField�	CharField�Check�CompositeKey�ForeignKeyField�IntegerField�ModelNFc���|jd}|jd�G�fd�dt��}|�|��d�|jjD��dgz}|�d�d�|���	����|�d
��|�d��|�|d��|�|d
��|�|d��dS)N�iplist�countryc
�b��eZdZdZdZdxZ\ZZZZ	d\Z
Zed���Z
eded�d�e������g�	��Zed
d���Zed���Zedd
����Zed���Zed���Ze�d���Zedd���Zedd���Zed���Zedd���Zed���Zed���Z ed���Z!edede
�de�d���g�	��Z"Gd�d��Z#dS)�migrate.<locals>.TMP_IPListz'iplist' db table.�action_type)�WHITE�BLACK�GRAY�GRAY_SPLASHSCREEN)�local�groupF)�nullzlistname in ('{}')z',')r�constraintsrT)�defaultrc�B�ttj����S�N)�int�time���o/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/152_add_listname_to_primary_key.py�<lambda>z$migrate.<locals>.TMP_IPList.<lambda>.s��s�4�9�;�;�'7�'7�r)rrzscope in ('z')c�.�eZdZdZedddd��ZdS)� migrate.<locals>.TMP_IPList.Meta�	tmpiplist�network_address�netmask�version�listnameN)�__name__�
__module__�__qualname__�db_tabler�primary_keyrrr�Metar"Ks-������"�H�&�,�!�9�i����K�K�Krr-N)$r(r)r*�__doc__�ACTION_TYPE�IP_LISTSrrrr�SCOPE_LOCAL�SCOPE_GROUPr�ipr�format�joinr'r�
expiration�
imported_from�ctime�deep�commentrrr�captcha_passed�manual�full_access�auto_whitelistedr$r%r&�scoper-)�Countrys�r�
TMP_IPListrs������� � �$��>
�	
��:�E�5�$�(9�$4� ��[�
�Y�E�
"�
"�
"���9����*�1�1�%�*�*�X�2F�2F�G�G�H�H��
�
�
��"�\��D�
�
�
�
�"�	�t�,�,�,�
����7�7�
�
�
���|��&�&�&���)��&�&�&��!�/�'��5�5�5��&��5�%�@�@�@����5�$�7�7�7��#�l��-�-�-��'�<�T�5�A�A�A��&�,�E�2�2�2���,�E�*�*�*���,�E�*�*�*���	��������[�[�[�I�J�J��
�
�
��	�	�	�	�	�	�	�	�	�	rrAc��g|]
}|dk�|��S)rr)�.0�names  r�
<listcomp>zmigrate.<locals>.<listcomp>Zs-�������9���	
���r�
country_idz<INSERT INTO tmpiplist ({fields}) SELECT {fields} FROM iplist�,)�fieldszDROP TABLE iplistz&ALTER TABLE tmpiplist RENAME TO iplistr'r6r3)	�ormr	�create_model�_meta�sorted_field_names�sqlr4r5�	add_index)�migrator�database�fake�kwargs�
orm_IPListrArHr@s       @r�migraterT
sF�����h�'�J��l�9�%�G�>�>�>�>�>�>�>�U�>�>�>�@
���*�%�%�%����$�7����
��	�F�

�L�L�F�M�M��8�8�F�#�#�	N�	
�	
����

�L�L�$�%�%�%��L�L�9�:�:�:����z�:�.�.�.����z�<�0�0�0����z�4�(�(�(�(�(rc��dSrr)rOrPrQrRs    r�rollbackrVks���Dr)F)�peeweerrrrrrr	rrTrVrrr�<module>rXs�������������������������[)�[)�[)�[)�|	�	�	�	�	�	rdefence360agent/migrations/__pycache__/152_add_listname_to_primary_key.cpython-311.pyc0000644000000000000000000001045200000000000025766 0ustar  �

�E@�[.`k��B�ddlmZmZmZmZmZmZmZddlZdd�Z	dd�Z
dS)�)�BooleanField�	CharField�Check�CompositeKey�ForeignKeyField�IntegerField�ModelNFc���|jd}|jd�G�fd�dt��}|�|��d�|jjD��dgz}|�d�d�|���	����|�d
��|�d��|�|d��|�|d
��|�|d��dS)N�iplist�countryc
�b��eZdZdZdZdxZ\ZZZZ	d\Z
Zed���Z
eded�d�e������g�	��Zed
d���Zed���Zedd
����Zed���Zed���Ze�d���Zedd���Zedd���Zed���Zedd���Zed���Zed���Z ed���Z!edede
�de�d���g�	��Z"Gd�d��Z#dS)�migrate.<locals>.TMP_IPListz'iplist' db table.�action_type)�WHITE�BLACK�GRAY�GRAY_SPLASHSCREEN)�local�groupF)�nullzlistname in ('{}')z',')r�constraintsrT)�defaultrc�B�ttj����S�N)�int�time���o/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/152_add_listname_to_primary_key.py�<lambda>z$migrate.<locals>.TMP_IPList.<lambda>.s��s�4�9�;�;�'7�'7�r)rrzscope in ('z')c�.�eZdZdZedddd��ZdS)� migrate.<locals>.TMP_IPList.Meta�	tmpiplist�network_address�netmask�version�listnameN)�__name__�
__module__�__qualname__�db_tabler�primary_keyrrr�Metar"Ks-������"�H�&�,�!�9�i����K�K�Krr-N)$r(r)r*�__doc__�ACTION_TYPE�IP_LISTSrrrr�SCOPE_LOCAL�SCOPE_GROUPr�ipr�format�joinr'r�
expiration�
imported_from�ctime�deep�commentrrr�captcha_passed�manual�full_access�auto_whitelistedr$r%r&�scoper-)�Countrys�r�
TMP_IPListrs������� � �$��>
�	
��:�E�5�$�(9�$4� ��[�
�Y�E�
"�
"�
"���9����*�1�1�%�*�*�X�2F�2F�G�G�H�H��
�
�
��"�\��D�
�
�
�
�"�	�t�,�,�,�
����7�7�
�
�
���|��&�&�&���)��&�&�&��!�/�'��5�5�5��&��5�%�@�@�@����5�$�7�7�7��#�l��-�-�-��'�<�T�5�A�A�A��&�,�E�2�2�2���,�E�*�*�*���,�E�*�*�*���	��������[�[�[�I�J�J��
�
�
��	�	�	�	�	�	�	�	�	�	rrAc��g|]
}|dk�|��S)rr)�.0�names  r�
<listcomp>zmigrate.<locals>.<listcomp>Zs-�������9���	
���r�
country_idz<INSERT INTO tmpiplist ({fields}) SELECT {fields} FROM iplist�,)�fieldszDROP TABLE iplistz&ALTER TABLE tmpiplist RENAME TO iplistr'r6r3)	�ormr	�create_model�_meta�sorted_field_names�sqlr4r5�	add_index)�migrator�database�fake�kwargs�
orm_IPListrArHr@s       @r�migraterT
sF�����h�'�J��l�9�%�G�>�>�>�>�>�>�>�U�>�>�>�@
���*�%�%�%����$�7����
��	�F�

�L�L�F�M�M��8�8�F�#�#�	N�	
�	
����

�L�L�$�%�%�%��L�L�9�:�:�:����z�:�.�.�.����z�<�0�0�0����z�4�(�(�(�(�(rc��dSrr)rOrPrQrRs    r�rollbackrVks���Dr)F)�peeweerrrrrrr	rrTrVrrr�<module>rXs�������������������������[)�[)�[)�[)�|	�	�	�	�	�	rdefence360agent/migrations/__pycache__/153_migrate_config_default_action.cpython-311.opt-1.pyc0000644000000000000000000000461700000000000027211 0ustar  �

���˴+_d���ddlZddlZddlmZmZddlmZeje��Z	de��dfdefd�Z
e��defd���Zd
d	�ZdS)�N)�
ConfigFile�IConfig)�log_error_and_ignorez/etc/imunify360/user_configF�config_filec���|rdSt|��tj�|��sdStj|��D]}tt|������ dS)N)�username)�migrate_config�os�path�exists�listdirr)�migrator�database�user_config_dirr�fake�kwargsrs       �q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/153_migrate_config_default_action.py�migrater
s}�������;����
�7�>�>�/�*�*�����J��/�/�6�6���z�8�4�4�4�5�5�5�5�6�6�c�b�|�d���}|sdS|�di��}|�d��}|dkr>d|d<|�di��}|�d��}|�|d	krd	|d<n|d
vrd|d<ndS|�|ddd���dS)
NF)�	normalize�MALWARE_SCANNING�default_action�
quarantine�cleanup�MALWARE_CLEANUP�keep_original_files_days�)�cleanup_or_quarantine�deleteT)�	overwrite�validater)�config_to_dict�
setdefault�get�dict_to_config)r�config�malware_settingsr�cleanup_settings�keep_original_filess      rr	r	 s��
�
'�
'�%�
'�
8�
8�F������(�(�);�R�@�@��%�)�)�*:�;�;�N���%�%�-6��)�*�!�,�,�->��C�C��.�2�2�3M�N�N���*�/B�S�/H�/H�;>��7�8��	�>�	>�	>�-6��)�*�*�������$��%������rc��dS)N�)rrrrs    r�rollbackr-8s���Dr)F)
�loggingr
� defence360agent.contracts.configrr�defence360agent.utilsr�	getLogger�__name__�loggerrr	r-r,rr�<module>r4s�������	�	�	�	�@�@�@�@�@�@�@�@�6�6�6�6�6�6�	��	�8�	$�	$��2�%�:�<�<�	�6�6��	6�6�6�6�,������������.	�	�	�	�	�	rdefence360agent/migrations/__pycache__/153_migrate_config_default_action.cpython-311.pyc0000644000000000000000000000461700000000000026252 0ustar  �

���˴+_d���ddlZddlZddlmZmZddlmZeje��Z	de��dfdefd�Z
e��defd���Zd
d	�ZdS)�N)�
ConfigFile�IConfig)�log_error_and_ignorez/etc/imunify360/user_configF�config_filec���|rdSt|��tj�|��sdStj|��D]}tt|������ dS)N)�username)�migrate_config�os�path�exists�listdirr)�migrator�database�user_config_dirr�fake�kwargsrs       �q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/153_migrate_config_default_action.py�migrater
s}�������;����
�7�>�>�/�*�*�����J��/�/�6�6���z�8�4�4�4�5�5�5�5�6�6�c�b�|�d���}|sdS|�di��}|�d��}|dkr>d|d<|�di��}|�d��}|�|d	krd	|d<n|d
vrd|d<ndS|�|ddd���dS)
NF)�	normalize�MALWARE_SCANNING�default_action�
quarantine�cleanup�MALWARE_CLEANUP�keep_original_files_days�)�cleanup_or_quarantine�deleteT)�	overwrite�validater)�config_to_dict�
setdefault�get�dict_to_config)r�config�malware_settingsr�cleanup_settings�keep_original_filess      rr	r	 s��
�
'�
'�%�
'�
8�
8�F������(�(�);�R�@�@��%�)�)�*:�;�;�N���%�%�-6��)�*�!�,�,�->��C�C��.�2�2�3M�N�N���*�/B�S�/H�/H�;>��7�8��	�>�	>�	>�-6��)�*�*�������$��%������rc��dS)N�)rrrrs    r�rollbackr-8s���Dr)F)
�loggingr
� defence360agent.contracts.configrr�defence360agent.utilsr�	getLogger�__name__�loggerrr	r-r,rr�<module>r4s�������	�	�	�	�@�@�@�@�@�@�@�@�6�6�6�6�6�6�	��	�8�	$�	$��2�%�:�<�<�	�6�6��	6�6�6�6�,������������.	�	�	�	�	�	rdefence360agent/migrations/__pycache__/153_update_incident_name.cpython-311.opt-1.pyc0000644000000000000000000000122600000000000025323 0ustar  �

9%��f����dd�Zdd�ZdS)Fc�0�|�d��dS)NzTUPDATE incident SET name='Login Blocked by cpHulk' where plugin='cphulk' and name='')�sql��migrator�database�fake�kwargss    �h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/153_update_incident_name.py�migrater
s'���L�L�	-������c��dS)N�rs    r	�rollbackrs���DrN)F)r
rr
rr	�<module>rs7������	�	�	�	�	�	rdefence360agent/migrations/__pycache__/153_update_incident_name.cpython-311.pyc0000644000000000000000000000122600000000000024364 0ustar  �

9%��f����dd�Zdd�ZdS)Fc�0�|�d��dS)NzTUPDATE incident SET name='Login Blocked by cpHulk' where plugin='cphulk' and name='')�sql��migrator�database�fake�kwargss    �h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/153_update_incident_name.py�migrater
s'���L�L�	-������c��dS)N�rs    r	�rollbackrs���DrN)F)r
rr
rr	�<module>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  �

h
�������D�ddlmZmZmZe��dd�defd�Zd�ZdS)�)�IConfig�LocalConfig�
NonBaseMergerF)�config_file�fakerc��|rdSttj������d���}|�di��}|�d��}|�!d|d<|�d|idd���dSdS)N)�namesT)�
force_read�PERMISSIONS�user_override_malware_actionsF)�validate�without_defaults)r�get_layer_names�configs_to_dict�
setdefault�get�dict_to_config)rr�_�__�config�permission_settingsrs       ��/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/154_migrate_config_user_override_malware_actions.py�migraters�������
��,�.�.�����o��o�&�&��!�+�+�M�2�>�>��$7�$;�$;�'�%�%�!�%�,�?C��;�<��"�"�
�/�0��!�	#�	
�	
�	
�	
�	
�-�,�c��dS)N�)rrs  r�rollbackrs���DrN)� defence360agent.contracts.configrrrrrrrr�<module>rs�������������(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  �

h
�������D�ddlmZmZmZe��dd�defd�Zd�ZdS)�)�IConfig�LocalConfig�
NonBaseMergerF)�config_file�fakerc��|rdSttj������d���}|�di��}|�d��}|�!d|d<|�d|idd���dSdS)N)�namesT)�
force_read�PERMISSIONS�user_override_malware_actionsF)�validate�without_defaults)r�get_layer_names�configs_to_dict�
setdefault�get�dict_to_config)rr�_�__�config�permission_settingsrs       ��/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/154_migrate_config_user_override_malware_actions.py�migraters�������
��,�.�.�����o��o�&�&��!�+�+�M�2�>�>��$7�$;�$;�'�%�%�!�%�,�?C��;�<��"�"�
�/�0��!�	#�	
�	
�	
�	
�	
�-�,�c��dS)N�)rrs  r�rollbackrs���DrN)� defence360agent.contracts.configrrrrrrrr�<module>rs�������������(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  �

$M���1��D�ddlmZmZmZe��dd�defd�Zd�ZdS)�)�IConfig�LocalConfig�
NonBaseMergerF)�config_file�fakerc��|rdSttj������d���}|�di��}|�d��}|�!d|d<|�d|idd���dSdS)N)�namesT)�
force_read�PERMISSIONS�user_override_proactive_defenseF)�validate�without_defaults)r�get_layer_names�configs_to_dict�
setdefault�get�dict_to_config)rr�_�__�config�permission_settings�user_override_malware_actionss       ��/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/155_migrate_config_user_override_proactive_defense.py�migraters�������
��,�.�.�����o��o�&�&��!�+�+�M�2�>�>��$7�$;�$;�)�%�%�!�%�,�AE��=�>��"�"�
�/�0��!�	#�	
�	
�	
�	
�	
�-�,�c��dS)N�)rrs  r�rollbackrs���DrN)� defence360agent.contracts.configrrrrrrrr�<module>r 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  �

$M���1��D�ddlmZmZmZe��dd�defd�Zd�ZdS)�)�IConfig�LocalConfig�
NonBaseMergerF)�config_file�fakerc��|rdSttj������d���}|�di��}|�d��}|�!d|d<|�d|idd���dSdS)N)�namesT)�
force_read�PERMISSIONS�user_override_proactive_defenseF)�validate�without_defaults)r�get_layer_names�configs_to_dict�
setdefault�get�dict_to_config)rr�_�__�config�permission_settings�user_override_malware_actionss       ��/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/155_migrate_config_user_override_proactive_defense.py�migraters�������
��,�.�.�����o��o�&�&��!�+�+�M�2�>�>��$7�$;�$;�)�%�%�!�%�,�AE��=�>��"�"�
�/�0��!�	#�	
�	
�	
�	
�	
�-�,�c��dS)N�)rrs  r�rollbackrs���DrN)� defence360agent.contracts.configrrrrrrrr�<module>r s�������������(3�{�}�}�5�
�
�
�W�
�
�
�
�,	�	�	�	�	rdefence360agent/migrations/__pycache__/156_remove_default_values_from_config.cpython-311.opt-1.pyc0000644000000000000000000000174500000000000030125 0ustar  �

V����	����dZdd�Zdd�ZdS)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��dS�N���migrator�database�fake�kwargss    �u/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/156_remove_default_values_from_config.py�migrater����D�c��dSrrrs    r
�rollbackrrr
N)F)�__doc__rrrr
r
�<module>rsA��
�
�	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/156_remove_default_values_from_config.cpython-311.pyc0000644000000000000000000000174500000000000027166 0ustar  �

V����	����dZdd�Zdd�ZdS)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��dS�N���migrator�database�fake�kwargss    �u/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/156_remove_default_values_from_config.py�migrater����D�c��dSrrrs    r
�rollbackrrr
N)F)�__doc__rrrr
r
�<module>rsA��
�
�	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/157_move_i360_modsec_disable_conf.cpython-311.opt-1.pyc0000644000000000000000000000347400000000000026730 0ustar  �

�n��'��j�ddlZddlZddlZddlmZeje��ZdZdZ	dZ
dZd
d�Z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</etc/httpd/conf/plesk.conf.d/vhosts/i360_modsec_disable.confFc�N�|rdStj��tjzrt}t}nt
}t}tj�	|��rD	tj||��dS#t$rt�d|��YdSwxYwdS)NzFailed move %s)r�id_like�DEBIAN�#_DEBIAN_OLD_MODSEC_DISABLE_FILENAME�#_DEBIAN_NEW_MODSEC_DISABLE_FILENAME�_OLD_MODSEC_DISABLE_FILENAME�_NEW_MODSEC_DISABLE_FILENAME�os�path�exists�shutil�move�	Exception�logger�	exception)�migrator�database�fake�kwargs�
old_file_name�
new_file_names      �q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/157_move_i360_modsec_disable_conf.py�migraters������������!5�5�5�;�
�;�
�
�4�
�4�
�	�w�~�~�m�$�$�>�	>��K�
�}�5�5�5�5�5���	>�	>�	>����-�}�=�=�=�=�=�=�	>����>�>s�"A9�9%B"�!B"c��dS)N�)rrrrs    r�rollbackr)s���D�)F)�loggingrr�defence360agent.utilsr�	getLogger�__name__rrrr
r	rrrrr�<module>r#s�������	�	�	�	�
�
�
�
�/�/�/�/�/�/�	��	�8�	$�	$��>�$�E�$�<��C��
>�>�>�>�"	�	�	�	�	�	rdefence360agent/migrations/__pycache__/157_move_i360_modsec_disable_conf.cpython-311.pyc0000644000000000000000000000347400000000000025771 0ustar  �

�n��'��j�ddlZddlZddlZddlmZeje��ZdZdZ	dZ
dZd
d�Z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</etc/httpd/conf/plesk.conf.d/vhosts/i360_modsec_disable.confFc�N�|rdStj��tjzrt}t}nt
}t}tj�	|��rD	tj||��dS#t$rt�d|��YdSwxYwdS)NzFailed move %s)r�id_like�DEBIAN�#_DEBIAN_OLD_MODSEC_DISABLE_FILENAME�#_DEBIAN_NEW_MODSEC_DISABLE_FILENAME�_OLD_MODSEC_DISABLE_FILENAME�_NEW_MODSEC_DISABLE_FILENAME�os�path�exists�shutil�move�	Exception�logger�	exception)�migrator�database�fake�kwargs�
old_file_name�
new_file_names      �q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/157_move_i360_modsec_disable_conf.py�migraters������������!5�5�5�;�
�;�
�
�4�
�4�
�	�w�~�~�m�$�$�>�	>��K�
�}�5�5�5�5�5���	>�	>�	>����-�}�=�=�=�=�=�=�	>����>�>s�"A9�9%B"�!B"c��dS)N�)rrrrs    r�rollbackr)s���D�)F)�loggingrr�defence360agent.utilsr�	getLogger�__name__rrrr
r	rrrrr�<module>r#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  �

�Gl�U�����r�ddlZddlZddlZddlmZeje��ZdZdZ	dZ
dZdZdZ
dd	�Zdd
�ZdS)�N)�
OsReleaseInfoz2/etc/apache2/plesk.conf.d/i360_modsec_disable.confz</etc/apache2/conf-enabled/zz999_modsec2.imunify_disable.confz5/etc/httpd/conf/plesk.conf.d/i360_modsec_disable.confz4/etc/httpd/conf.d/zz999_modsec2.imunify_disable.confz7/etc/apache2/conf/plesk.conf.d/i360_modsec_disable.confFc�`�|rdSd}d}tj��tjzrt}t}t
}t}nt}t}|ratj
�|��rB	tj
||��n+#t$rt�d|��YnwxYwtj
�|��rX	tj|��tj||��dS#t$rt�d|��YdSwxYwdS)NzFailed move %szFailed change symlink %s)r�id_like�DEBIAN�#_DEBIAN_OLD_MODSEC_DISABLE_FILENAME�#_DEBIAN_NEW_MODSEC_DISABLE_FILENAME�#_DEBIAN_MODSEC_DISABLE_SYMLINK_PATH�_DEBIAN_MODSEC_DISABLE_SYMLINK�_MODSEC_DISABLE_SYMLINK_PATH�_MODSEC_DISABLE_SYMLINK�os�path�exists�shutil�move�	Exception�logger�	exception�islink�unlink�symlink)�migrator�database�fake�kwargs�
old_file_name�
new_file_name�symlink_pathrs        �y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/158_move_i360_modsec_disable_conf_symlink.py�migrater sQ�������M��M������!5�5�*�;�
�;�
�:��0���3��)���>�����
�6�6�>�	>��K�
�}�5�5�5�5���	>�	>�	>����-�}�=�=�=�=�=�	>����	�w�~�~�g���B�	B��I�g�����J�|�W�-�-�-�-�-���	B�	B�	B����7��A�A�A�A�A�A�	B����	B�Bs$�6B�%B4�3B4�)D�%D+�*D+c��dS)N�)rrrrs    r�rollbackr#:s���D�)F)�loggingr
r�defence360agent.utilsr�	getLogger�__name__rr	r
rrrrr r#r"r$r�<module>r)s�������	�	�	�	�
�
�
�
�/�/�/�/�/�/�	��	�8�	$�	$��9�$�C��<��;��
9�$�>�$�
B�B�B�B�6	�	�	�	�	�	r$defence360agent/migrations/__pycache__/158_move_i360_modsec_disable_conf_symlink.cpython-311.pyc0000644000000000000000000000473600000000000027542 0ustar  �

�Gl�U�����r�ddlZddlZddlZddlmZeje��ZdZdZ	dZ
dZdZdZ
dd	�Zdd
�ZdS)�N)�
OsReleaseInfoz2/etc/apache2/plesk.conf.d/i360_modsec_disable.confz</etc/apache2/conf-enabled/zz999_modsec2.imunify_disable.confz5/etc/httpd/conf/plesk.conf.d/i360_modsec_disable.confz4/etc/httpd/conf.d/zz999_modsec2.imunify_disable.confz7/etc/apache2/conf/plesk.conf.d/i360_modsec_disable.confFc�`�|rdSd}d}tj��tjzrt}t}t
}t}nt}t}|ratj
�|��rB	tj
||��n+#t$rt�d|��YnwxYwtj
�|��rX	tj|��tj||��dS#t$rt�d|��YdSwxYwdS)NzFailed move %szFailed change symlink %s)r�id_like�DEBIAN�#_DEBIAN_OLD_MODSEC_DISABLE_FILENAME�#_DEBIAN_NEW_MODSEC_DISABLE_FILENAME�#_DEBIAN_MODSEC_DISABLE_SYMLINK_PATH�_DEBIAN_MODSEC_DISABLE_SYMLINK�_MODSEC_DISABLE_SYMLINK_PATH�_MODSEC_DISABLE_SYMLINK�os�path�exists�shutil�move�	Exception�logger�	exception�islink�unlink�symlink)�migrator�database�fake�kwargs�
old_file_name�
new_file_name�symlink_pathrs        �y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/158_move_i360_modsec_disable_conf_symlink.py�migrater sQ�������M��M������!5�5�*�;�
�;�
�:��0���3��)���>�����
�6�6�>�	>��K�
�}�5�5�5�5���	>�	>�	>����-�}�=�=�=�=�=�	>����	�w�~�~�g���B�	B��I�g�����J�|�W�-�-�-�-�-���	B�	B�	B����7��A�A�A�A�A�A�	B����	B�Bs$�6B�%B4�3B4�)D�%D+�*D+c��dS)N�)rrrrs    r�rollbackr#:s���D�)F)�loggingr
r�defence360agent.utilsr�	getLogger�__name__rr	r
rrrrr r#r"r$r�<module>r)s�������	�	�	�	�
�
�
�
�/�/�/�/�/�/�	��	�8�	$�	$��9�$�C��<��;��
9�$�>�$�
B�B�B�B�6	�	�	�	�	�	r$defence360agent/migrations/__pycache__/159_remove_defaults_from_local_config.cpython-311.opt-1.pyc0000644000000000000000000000333600000000000030104 0ustar  �

.�.�	���Z�dZddlZddlmZddlmZeje��Zdd�Z	dd�Z
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||���}|�|d���dS#t
$r&}t�d|��Yd}~dSd}~wwxYw)NT)�
force_readF)�without_defaults)�	main_conf�	base_conf)�	overwritez(Can't overwrite local config, reason: %s)r�config_to_dict�	normalizer�dict_to_config�	Exception�logger�error)	�migrator�database�fake�kwargs�local_config�
local_conf�defaults�non_default_conf�excs	         �u/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/159_remove_defaults_from_local_config.py�migraters�������F�"�}�}��!�0�0�D�0�A�A�
��)�)�"�u�)�E�E��)� �H�
�
�
��	�#�#�$4��#�E�E�E�E�E���F�F�F����?��E�E�E�E�E�E�E�E�E�����F���s�A#A+�+
B�5B�Bc��dS)N�)rrrrs    r�rollbackr s���D�)F)�__doc__�logging� defence360agent.contracts.configr�)defence360agent.contracts.config_providerr�	getLogger�__name__rrrrrr�<module>r&s�����
����8�8�8�8�8�8�D�D�D�D�D�D�	��	�8�	$�	$��F�F�F�F�$	�	�	�	�	�	rdefence360agent/migrations/__pycache__/159_remove_defaults_from_local_config.cpython-311.pyc0000644000000000000000000000333600000000000027145 0ustar  �

.�.�	���Z�dZddlZddlmZddlmZeje��Zdd�Z	dd�Z
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||���}|�|d���dS#t
$r&}t�d|��Yd}~dSd}~wwxYw)NT)�
force_readF)�without_defaults)�	main_conf�	base_conf)�	overwritez(Can't overwrite local config, reason: %s)r�config_to_dict�	normalizer�dict_to_config�	Exception�logger�error)	�migrator�database�fake�kwargs�local_config�
local_conf�defaults�non_default_conf�excs	         �u/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/159_remove_defaults_from_local_config.py�migraters�������F�"�}�}��!�0�0�D�0�A�A�
��)�)�"�u�)�E�E��)� �H�
�
�
��	�#�#�$4��#�E�E�E�E�E���F�F�F����?��E�E�E�E�E�E�E�E�E�����F���s�A#A+�+
B�5B�Bc��dS)N�)rrrrs    r�rollbackr s���D�)F)�__doc__�logging� defence360agent.contracts.configr�)defence360agent.contracts.config_providerr�	getLogger�__name__rrrrrr�<module>r&s�����
����8�8�8�8�8�8�D�D�D�D�D�D�	��	�8�	$�	$��F�F�F�F�$	�	�	�	�	�	rdefence360agent/migrations/__pycache__/160_remove_quarantine.cpython-311.opt-1.pyc0000644000000000000000000001235300000000000024711 0ustar  �

��O��n�C���ddlZddlZddlZddlZddlmZddlmZddlmZm	Z	ddl
mZmZddl
mZddlmZeje��ZdZd	Zd
ZeddgZd
�Zdd�Zd�Zde	eeffd�Zdedeeeffd�ZdS)�N)�glob)�Path)�Tuple�Union)�	CharField�Model)�
FilenameField)�HostingPanelz.imunify.quarantinedz/var/imunify360�quarantinedz/var/wwwz/home*c�2��G�fd�dt��}|S)zl
    Model stub for migration because we can't use migrator.orm[] due to
    custom field FilenameField
    c�Z��eZdZG�fd�d��Zed���Ze��ZdS)�get_model.<locals>.MalwareHitc���eZdZdZ�ZdS)�"get_model.<locals>.MalwareHit.Meta�malware_hitsN)�__name__�
__module__�__qualname__�db_table�database��dbs��e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/160_remove_quarantine.py�Metars�������%�H��H�H�H�rF)�nullN)rrrrr	�	orig_filer�statusrs�r�
MalwareHitrs`�������	�	�	�	�	�	�	�	�	�	�"�M�u�-�-�-�	�������rr)r)rrs` r�	get_modelr s?����������U�����rFc��|rdS|pt}t|��}|����|jt
k��}|D]8}t
|j��\}	}||	��|����9tD]B}
ttj�
|
t����D]
}	||	����CdS�N)�delete_quarantine_folderr �select�wherer�QUARANTINED�	find_quarr�delete_instance�QUARANTINE_PARENTSr�os�path�join�	QUAR_NAME)�	_migratorr�fake�delete_function�_�__�modelr�hit�path_to_delete�parents           r�migrater7)s�������&�A�)A�O��h���E��,�,�.�.�&�&�u�|�{�'B�C�C�K�����%�c�m�4�4�������'�'�'��������%�,�,��"�2�7�<�<��	�#B�#B�C�C�	,�	,�N��O�N�+�+�+�+�	,�,�,rc��dSr"�)r1r2s  r�rollbackr:?s���Dr�quarantine_pathc���t|��}|jtkrK||���kr5t�d|��t
j|d���dSdSdS)NzDeleting quarantine folder %sT)�
ignore_errors)r�namer-�resolve�logger�info�shutil�rmtree)r;s rr#r#Csr���?�+�+�O���	�)�)���6�6�8�8�8�8����3�_�E�E�E��
�o�T�:�:�:�:�:�:�		*�)�8�8r�source�returnc��t|��}tt��tz|�td����f}d}d}|jD]P}	tj|���j��}|}n#t$rY�>t$r|cYcSwxYw|�|jdkr|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.
    �/N�root)r�DEF_QUARr-�relative_to�parents�pwd�getpwuid�stat�st_uid�FileNotFoundError�KeyError�pw_namer?r
�
base_home_dir�pw_dir�RuntimeError�
ValueError)	rD�file�default_result�userr6r+�resolved_place�base_dir�relatives	         rr'r'Ms�����<�<�D��(�^�^�i�/��1A�1A�$�s�)�)�1L�1L�L�N��D�
�F���	�	��	��<��	�	��� 2�3�3�D��F��E��
!�	�	�	��H��	"�	"�	"�!�!�!�!�!�!�	"�����|�t�|�v�-�-����^�^�%�%��(8�(8��(@�(@�@�N���>�>�/�/���<�<�����|�,�������������!�-�-�h�7�7�����������������i���)�)s<�+B�
B'�B'�&B'�$&D�D!� D!�%D;�;E
�	E
)FN)�loggingr*rLrBr�pathlibr�typingrr�peeweerr�$defence360agent.model.simplificationr	�+defence360agent.subsys.panels.hosting_panelr
�	getLoggerrr@r-rIr&r)r r7r:�strr#r'r9rr�<module>res\������	�	�	�	�
�
�
�
�
�
�
�
���������������������#�#�#�#�#�#�#�#�?�>�>�>�>�>�D�D�D�D�D�D�	��	�8�	$�	$��"�	������
�H�5�����",�,�,�,�,	�	�	�;�e�C��I�.>�;�;�;�;�'*�c�'*�e�D�$�J�/�'*�'*�'*�'*�'*�'*rdefence360agent/migrations/__pycache__/160_remove_quarantine.cpython-311.pyc0000644000000000000000000001235300000000000023752 0ustar  �

��O��n�C���ddlZddlZddlZddlZddlmZddlmZddlmZm	Z	ddl
mZmZddl
mZddlmZeje��ZdZd	Zd
ZeddgZd
�Zdd�Zd�Zde	eeffd�Zdedeeeffd�ZdS)�N)�glob)�Path)�Tuple�Union)�	CharField�Model)�
FilenameField)�HostingPanelz.imunify.quarantinedz/var/imunify360�quarantinedz/var/wwwz/home*c�2��G�fd�dt��}|S)zl
    Model stub for migration because we can't use migrator.orm[] due to
    custom field FilenameField
    c�Z��eZdZG�fd�d��Zed���Ze��ZdS)�get_model.<locals>.MalwareHitc���eZdZdZ�ZdS)�"get_model.<locals>.MalwareHit.Meta�malware_hitsN)�__name__�
__module__�__qualname__�db_table�database��dbs��e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/160_remove_quarantine.py�Metars�������%�H��H�H�H�rF)�nullN)rrrrr	�	orig_filer�statusrs�r�
MalwareHitrs`�������	�	�	�	�	�	�	�	�	�	�"�M�u�-�-�-�	�������rr)r)rrs` r�	get_modelr s?����������U�����rFc��|rdS|pt}t|��}|����|jt
k��}|D]8}t
|j��\}	}||	��|����9tD]B}
ttj�
|
t����D]
}	||	����CdS�N)�delete_quarantine_folderr �select�wherer�QUARANTINED�	find_quarr�delete_instance�QUARANTINE_PARENTSr�os�path�join�	QUAR_NAME)�	_migratorr�fake�delete_function�_�__�modelr�hit�path_to_delete�parents           r�migrater7)s�������&�A�)A�O��h���E��,�,�.�.�&�&�u�|�{�'B�C�C�K�����%�c�m�4�4�������'�'�'��������%�,�,��"�2�7�<�<��	�#B�#B�C�C�	,�	,�N��O�N�+�+�+�+�	,�,�,rc��dSr"�)r1r2s  r�rollbackr:?s���Dr�quarantine_pathc���t|��}|jtkrK||���kr5t�d|��t
j|d���dSdSdS)NzDeleting quarantine folder %sT)�
ignore_errors)r�namer-�resolve�logger�info�shutil�rmtree)r;s rr#r#Csr���?�+�+�O���	�)�)���6�6�8�8�8�8����3�_�E�E�E��
�o�T�:�:�:�:�:�:�		*�)�8�8r�source�returnc��t|��}tt��tz|�td����f}d}d}|jD]P}	tj|���j��}|}n#t$rY�>t$r|cYcSwxYw|�|jdkr|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.
    �/N�root)r�DEF_QUARr-�relative_to�parents�pwd�getpwuid�stat�st_uid�FileNotFoundError�KeyError�pw_namer?r
�
base_home_dir�pw_dir�RuntimeError�
ValueError)	rD�file�default_result�userr6r+�resolved_place�base_dir�relatives	         rr'r'Ms�����<�<�D��(�^�^�i�/��1A�1A�$�s�)�)�1L�1L�L�N��D�
�F���	�	��	��<��	�	��� 2�3�3�D��F��E��
!�	�	�	��H��	"�	"�	"�!�!�!�!�!�!�	"�����|�t�|�v�-�-����^�^�%�%��(8�(8��(@�(@�@�N���>�>�/�/���<�<�����|�,�������������!�-�-�h�7�7�����������������i���)�)s<�+B�
B'�B'�&B'�$&D�D!� D!�%D;�;E
�	E
)FN)�loggingr*rLrBr�pathlibr�typingrr�peeweerr�$defence360agent.model.simplificationr	�+defence360agent.subsys.panels.hosting_panelr
�	getLoggerrr@r-rIr&r)r r7r:�strr#r'r9rr�<module>res\������	�	�	�	�
�
�
�
�
�
�
�
���������������������#�#�#�#�#�#�#�#�?�>�>�>�>�>�D�D�D�D�D�D�	��	�8�	$�	$��"�	������
�H�5�����",�,�,�,�,	�	�	�;�e�C��I�.>�;�;�;�;�'*�c�'*�e�D�$�J�/�'*�'*�'*�'*�'*�'*rdefence360agent/migrations/__pycache__/160_unmount_sigs_v1.cpython-311.opt-1.pyc0000644000000000000000000000434000000000000024322 0ustar  �

	^���<��V�dZddlZddlZddlmZeje��Zdd�Zdd�Z	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	tjd|�d�dd	�
��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.subsysr�CAGEFS_MP_FILENAME�ImportErrorr�	read_text�FileNotFoundError�	Exception�logger�	exception�
subprocess�
check_call)�migrator�database�fake�kwargsr�filename�text�es        �c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/160_unmount_sigs_v1.py�migrater	s{������+�3�3�3�3�3�3��.�����+�+�+�*����+����	��H�~�~�'�'�)�)��+�$�6�6��F�7��
��������������4�h��B�B�B���������������A����DL�8�8�
N��"�		
�		
�		
�		
�		
�		
���A�A�A����<�a�@�@�@�@�@�@�@�@�@�����A���sA�
�#�#�!A�
B�	B�%B�B�B-�-
C�7C�Cc��dS)N�)rrrrs    r�rollbackr.s���D�)F)
�__doc__�loggingr�pathlibr�	getLogger�__name__rrrrrr�<module>r$s{��"�"���������������	��	�8�	$�	$��"A�"A�"A�"A�J	�	�	�	�	�	rdefence360agent/migrations/__pycache__/160_unmount_sigs_v1.cpython-311.pyc0000644000000000000000000000434000000000000023363 0ustar  �

	^���<��V�dZddlZddlZddlmZeje��Zdd�Zdd�Z	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	tjd|�d�dd	�
��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.subsysr�CAGEFS_MP_FILENAME�ImportErrorr�	read_text�FileNotFoundError�	Exception�logger�	exception�
subprocess�
check_call)�migrator�database�fake�kwargsr�filename�text�es        �c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/160_unmount_sigs_v1.py�migrater	s{������+�3�3�3�3�3�3��.�����+�+�+�*����+����	��H�~�~�'�'�)�)��+�$�6�6��F�7��
��������������4�h��B�B�B���������������A����DL�8�8�
N��"�		
�		
�		
�		
�		
�		
���A�A�A����<�a�@�@�@�@�@�@�@�@�@�����A���sA�
�#�#�!A�
B�	B�%B�B�B-�-
C�7C�Cc��dS)N�)rrrrs    r�rollbackr.s���D�)F)
�__doc__�loggingr�pathlibr�	getLogger�__name__rrrrrr�<module>r$s{��"�"���������������	��	�8�	$�	$��"A�"A�"A�"A�J	�	�	�	�	�	rdefence360agent/migrations/__pycache__/161_remove_ea4_main_local_conf.cpython-311.opt-1.pyc0000644000000000000000000000517400000000000026402 0ustar  �

�C��G���dZddlZddlmZddlZddlmZeje��Z	ed��Z
ed��ZdZdZ
ejd	ee
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'}	t�d||	��Yd}	~	dSd}	~	wwxYw)Nr)�cPanelzCan't remove %s, reason: %s)�im360.subsys.panels.cpanelr�ImportError�is_installed�exists�	read_text�replace�NEW�OLD�unlink�
subprocess�
check_call�REBUILD_HTTPDCONF_CMD�	Exception�logger�error)
�migrator�database�fake�default_conf_path�local_conf_path�kwargsr�origin_text�
restored_text�excs
          �n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/161_remove_ea4_main_local_conf.py�migrater sS�������5�5�5�5�5�5�5�������������
J���� � �	D�_�%;�%;�%=�%=�	D�+�5�5�7�7�K�+�5�5�7�7�?�?��S�I�I�M���+�+��&�&�(�(�(��%�f�&B�C�C�C�C�C�	D�	D�	D�	D�,�+���J�J�J����2�O�S�I�I�I�I�I�I�I�I�I�����J���s$�
�
��B!C�
C9�C4�4C9c��dS)N�)rrrrs    r�rollbackr#:s���D�)F)�__doc__�logging�pathlibrr�defence360agent.utilsr�	getLogger�__name__r�EA4_MAIN_LOCAL_PATH�EA4_MAIN_DEFAULT_PATHr
r�skipr r#r"r$r�<module>r.s���	�	���������������0�0�0�0�0�0�	��	�8�	$�	$���d�K�L�L����6����������
�+�'�J�J�J���J�:	�	�	�	�	�	r$defence360agent/migrations/__pycache__/161_remove_ea4_main_local_conf.cpython-311.pyc0000644000000000000000000000517400000000000025443 0ustar  �

�C��G���dZddlZddlmZddlZddlmZeje��Z	ed��Z
ed��ZdZdZ
ejd	ee
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'}	t�d||	��Yd}	~	dSd}	~	wwxYw)Nr)�cPanelzCan't remove %s, reason: %s)�im360.subsys.panels.cpanelr�ImportError�is_installed�exists�	read_text�replace�NEW�OLD�unlink�
subprocess�
check_call�REBUILD_HTTPDCONF_CMD�	Exception�logger�error)
�migrator�database�fake�default_conf_path�local_conf_path�kwargsr�origin_text�
restored_text�excs
          �n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/161_remove_ea4_main_local_conf.py�migrater sS�������5�5�5�5�5�5�5�������������
J���� � �	D�_�%;�%;�%=�%=�	D�+�5�5�7�7�K�+�5�5�7�7�?�?��S�I�I�M���+�+��&�&�(�(�(��%�f�&B�C�C�C�C�C�	D�	D�	D�	D�,�+���J�J�J����2�O�S�I�I�I�I�I�I�I�I�I�����J���s$�
�
��B!C�
C9�C4�4C9c��dS)N�)rrrrs    r�rollbackr#:s���D�)F)�__doc__�logging�pathlibrr�defence360agent.utilsr�	getLogger�__name__r�EA4_MAIN_LOCAL_PATH�EA4_MAIN_DEFAULT_PATHr
r�skipr r#r"r$r�<module>r.s���	�	���������������0�0�0�0�0�0�	��	�8�	$�	$���d�K�L�L����6����������
�+�'�J�J�J���J�:	�	�	�	�	�	r$defence360agent/migrations/__pycache__/162_add_resource_type.cpython-311.opt-1.pyc0000644000000000000000000000463200000000000024670 0ustar  �

b4�<G���x�ddlZddlZddlmZejddd���Zeje��Z	d	d�Z
d	d�ZdS)
�N)�importerzimav.malwarelib.config�MalwareScanResourceType)�module�name�defaultFc
��|jd}|�|tjdtjjtjd�tj	jtjjf����g���tjd���tjd���tjd���tjd������|jd}|�|tjdtjjtjd�tj	jtjjf����g����	��|�
|d
d��dS)N�malware_hitsFzresource_type in {})�nullr�constraintsT)r
)�
resource_type�app_name�db_host�db_port�db_name�
malware_scans)r�total_files�total_resources)�orm�
add_fields�pw�	CharFieldr�FILE�value�Check�format�DB�rename_field)�migrator�database�fake�kwargs�MalwareHits�MalwareScans      �e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/162_add_resource_type.py�migrater%s{���,�~�.�K������l��+�0�6���)�0�0�3�6�<�3�8�>������	�

�

�

���4�(�(�(���$�'�'�'���$�'�'�'���$�'�'�'�'����*�,��/�K������l��+�0�6���)�0�0�3�6�<�3�8�>������	�

�

�

�����"
���+�}�6G�H�H�H�H�H�c�L�|jd}|�|d��dS)Nr	r)r�
remove_fields)rrr r!r"s     r$�rollbackr)<s*���,�~�.�K����;��8�8�8�8�8r&)F)�logging�peeweer�defence360agent.utilsr�getr�	getLogger�__name__�loggerr%r)�r&r$�<module>r2s�����������*�*�*�*�*�*�&�(�,�#�	"������
��	�8�	$�	$��)I�)I�)I�)I�X9�9�9�9�9�9r&defence360agent/migrations/__pycache__/162_add_resource_type.cpython-311.pyc0000644000000000000000000000463200000000000023731 0ustar  �

b4�<G���x�ddlZddlZddlmZejddd���Zeje��Z	d	d�Z
d	d�ZdS)
�N)�importerzimav.malwarelib.config�MalwareScanResourceType)�module�name�defaultFc
��|jd}|�|tjdtjjtjd�tj	jtjjf����g���tjd���tjd���tjd���tjd������|jd}|�|tjdtjjtjd�tj	jtjjf����g����	��|�
|d
d��dS)N�malware_hitsFzresource_type in {})�nullr�constraintsT)r
)�
resource_type�app_name�db_host�db_port�db_name�
malware_scans)r�total_files�total_resources)�orm�
add_fields�pw�	CharFieldr�FILE�value�Check�format�DB�rename_field)�migrator�database�fake�kwargs�MalwareHits�MalwareScans      �e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/162_add_resource_type.py�migrater%s{���,�~�.�K������l��+�0�6���)�0�0�3�6�<�3�8�>������	�

�

�

���4�(�(�(���$�'�'�'���$�'�'�'���$�'�'�'�'����*�,��/�K������l��+�0�6���)�0�0�3�6�<�3�8�>������	�

�

�

�����"
���+�}�6G�H�H�H�H�H�c�L�|jd}|�|d��dS)Nr	r)r�
remove_fields)rrr r!r"s     r$�rollbackr)<s*���,�~�.�K����;��8�8�8�8�8r&)F)�logging�peeweer�defence360agent.utilsr�getr�	getLogger�__name__�loggerr%r)�r&r$�<module>r2s�����������*�*�*�*�*�*�&�(�,�#�	"������
��	�8�	$�	$��)I�)I�)I�)I�X9�9�9�9�9�9r&defence360agent/migrations/__pycache__/163_drop_malware_scanned_stat.cpython-311.opt-1.pyc0000644000000000000000000000137700000000000026376 0ustar  �

�����<�����dd�Zdd�ZdS)Fc�n�	|jd}|�|��dS#t$rYdSwxYw)N�malware_scanned_stat)�orm�remove_model�KeyError)�migrator�database�fake�kwargs�models     �m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/163_drop_malware_scanned_stat.py�migrater
sP��
���3�4�����e�$�$�$�$�$���
�
�
����
���s�"&�
4�4c��dS)N�)rrr	r
s    r�rollbackr	s���D�N)F)r
rrrr�<module>rs7��
�
�
�
�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/163_drop_malware_scanned_stat.cpython-311.pyc0000644000000000000000000000137700000000000025437 0ustar  �

�����<�����dd�Zdd�ZdS)Fc�n�	|jd}|�|��dS#t$rYdSwxYw)N�malware_scanned_stat)�orm�remove_model�KeyError)�migrator�database�fake�kwargs�models     �m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/163_drop_malware_scanned_stat.py�migrater
sP��
���3�4�����e�$�$�$�$�$���
�
�
����
���s�"&�
4�4c��dS)N�)rrr	r
s    r�rollbackr	s���D�N)F)r
rrrr�<module>rs7��
�
�
�
�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/164_add_resource_type_to_ignore.cpython-311.opt-1.pyc0000644000000000000000000000513300000000000026734 0ustar  �

��.�k��^�ddlZddlmZmZmZmZmZGd�de��Zdd�d�Zd�Z	d	�Z
dS)
�N)�	CharField�Check�CompositeKey�IntegerField�Modelc��eZdZGd�d��ZdZe��Zeded��g���Ze	dd����Z
dS)	�TMPMalwareIgnorePathc�*�eZdZdZedd��ZdS)�TMPMalwareIgnorePath.Meta�tmp_malware_ignore_path�path�
resource_typeN)�__name__�
__module__�__qualname__�db_tabler�primary_key���o/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/164_add_resource_type_to_ignore.py�Metars$������,��"�l�6�?�;�;���rrNFzresource_type in ('file','db'))�null�constraintsc�B�ttj����S�N)�int�timerrr�<lambda>zTMPMalwareIgnorePath.<lambda>s��#�d�i�k�k�:J�:J�r)r�default)rrrr�CACHErr
rrr�
added_daterrrr	r	s�������<�<�<�<�<�<�<�<�
�E��9�;�;�D��I�
���'G�!H�!H� I����M���5�2J�2J�K�K�K�J�J�Jrr	F)�fakec�$�t|��dSr)� change_malware_ignore_path_model)�migratorr"�_�__s    r�migrater(s��$�X�.�.�.�.�.rc���|�t��|�d��|�d��|�d��|�d��dS)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������.�/�/�/��L�L�	A����
�L�L�1�2�2�2��L�L�K����
�L�L�	1�����rc��dSrr)r&r's  r�rollbackr-(s���Dr)r�peeweerrrrrr	r(r$r-rrr�<module>r/s�������F�F�F�F�F�F�F�F�F�F�F�F�F�F�L�L�L�L�L�5�L�L�L� %�/�/�/�/�/�
�
�
� 	�	�	�	�	rdefence360agent/migrations/__pycache__/164_add_resource_type_to_ignore.cpython-311.pyc0000644000000000000000000000513300000000000025775 0ustar  �

��.�k��^�ddlZddlmZmZmZmZmZGd�de��Zdd�d�Zd�Z	d	�Z
dS)
�N)�	CharField�Check�CompositeKey�IntegerField�Modelc��eZdZGd�d��ZdZe��Zeded��g���Ze	dd����Z
dS)	�TMPMalwareIgnorePathc�*�eZdZdZedd��ZdS)�TMPMalwareIgnorePath.Meta�tmp_malware_ignore_path�path�
resource_typeN)�__name__�
__module__�__qualname__�db_tabler�primary_key���o/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/164_add_resource_type_to_ignore.py�Metars$������,��"�l�6�?�;�;���rrNFzresource_type in ('file','db'))�null�constraintsc�B�ttj����S�N)�int�timerrr�<lambda>zTMPMalwareIgnorePath.<lambda>s��#�d�i�k�k�:J�:J�r)r�default)rrrr�CACHErr
rrr�
added_daterrrr	r	s�������<�<�<�<�<�<�<�<�
�E��9�;�;�D��I�
���'G�!H�!H� I����M���5�2J�2J�K�K�K�J�J�Jrr	F)�fakec�$�t|��dSr)� change_malware_ignore_path_model)�migratorr"�_�__s    r�migrater(s��$�X�.�.�.�.�.rc���|�t��|�d��|�d��|�d��|�d��dS)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������.�/�/�/��L�L�	A����
�L�L�1�2�2�2��L�L�K����
�L�L�	1�����rc��dSrr)r&r's  r�rollbackr-(s���Dr)r�peeweerrrrrr	r(r$r-rrr�<module>r/s�������F�F�F�F�F�F�F�F�F�F�F�F�F�F�L�L�L�L�L�5�L�L�L� %�/�/�/�/�/�
�
�
� 	�	�	�	�	rdefence360agent/migrations/__pycache__/165_add_db_fields_to_malware_history.cpython-311.opt-1.pyc0000644000000000000000000000310200000000000027700 0ustar  �

��g@�����`�ddlmZmZddlmZejddd���Zdd�d	�Zdd�d
�ZdS)�)�	CharField�Check)�importerzimav.malwarelib.config�MalwareScanResourceTypeN)�module�name�defaultF)�fakec
�,�|jd}|�|td���tdtd�t
jjt
jjf����gt
jj������dS)N�malware_historyT)�nullFzresource_type in {})r
�constraintsr	)�app_name�
resource_type)	�orm�
add_fieldsrr�formatr�DB�value�FILE��migratorr
�_�__rs     �t/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/165_add_db_fields_to_malware_history.py�migraters����l�#4�5�O�������%�%�%����)�0�0�3�6�<�3�8�>������	�,�0�6�

�

�

�������c�N�|jd}|�|dd��dS)Nrrr)r�
remove_fieldsrs     r�rollbackr "s-���l�#4�5�O����?�J��H�H�H�H�Hr)	�peeweerr�defence360agent.utilsr�getrrr �rr�<module>r%s���#�#�#�#�#�#�#�#�*�*�*�*�*�*�&�(�,�#�	"������ %������,!&�I�I�I�I�I�I�Irdefence360agent/migrations/__pycache__/165_add_db_fields_to_malware_history.cpython-311.pyc0000644000000000000000000000310200000000000026741 0ustar  �

��g@�����`�ddlmZmZddlmZejddd���Zdd�d	�Zdd�d
�ZdS)�)�	CharField�Check)�importerzimav.malwarelib.config�MalwareScanResourceTypeN)�module�name�defaultF)�fakec
�,�|jd}|�|td���tdtd�t
jjt
jjf����gt
jj������dS)N�malware_historyT)�nullFzresource_type in {})r
�constraintsr	)�app_name�
resource_type)	�orm�
add_fieldsrr�formatr�DB�value�FILE��migratorr
�_�__rs     �t/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/165_add_db_fields_to_malware_history.py�migraters����l�#4�5�O�������%�%�%����)�0�0�3�6�<�3�8�>������	�,�0�6�

�

�

�������c�N�|jd}|�|dd��dS)Nrrr)r�
remove_fieldsrs     r�rollbackr "s-���l�#4�5�O����?�J��H�H�H�H�Hr)	�peeweerr�defence360agent.utilsr�getrrr �rr�<module>r%s���#�#�#�#�#�#�#�#�*�*�*�*�*�*�&�(�,�#�	"������ %������,!&�I�I�I�I�I�I�Irdefence360agent/migrations/__pycache__/166_add_id_field_to_malware_ignore_path.cpython-311.opt-1.pyc0000644000000000000000000000442300000000000030332 0ustar  �

qǡ�_�U���\�ddlmZddlmZmZmZmZmZGd�de��Zdd�d�Zd�Z	d	S)
�)�time)�	CharField�Check�IntegerField�Model�PrimaryKeyFieldc��eZdZGd�d��ZdZe��Ze��Zede	d��g���Z
edd����ZdS)	�MalwareIgnorePathc��eZdZdZdZdS)�MalwareIgnorePath.Meta�malware_ignore_path)))�path�
resource_typeTN)�__name__�
__module__�__qualname__�db_table�indexes���w/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/166_add_id_field_to_malware_ignore_path.py�Metars������(��6���rrNFzresource_type in ('file','db'))�null�constraintsc�8�tt����S�N)�intrrrr�<lambda>zMalwareIgnorePath.<lambda>s��#�d�f�f�+�+�r)r�default)
rrrr�CACHEr�idrrrrr�
added_daterrrr
r
s�������7�7�7�7�7�7�7�7�
�E�	��	�	�B��9�;�;�D��I�
���'G�!H�!H� I����M���5�2E�2E�F�F�F�J�J�Jrr
F)�fakec��|�d��|�t��|�d��|�d��dS)NzBALTER TABLE malware_ignore_path RENAME TO malware_ignore_path_old;z�INSERT 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    r�migrater*si���L�L�L����
���+�,�,�,��L�L�	L����
�L�L�6�7�7�7�7�7rc��dSrr)r(r)s  r�rollbackr,!s���DrN)
r�peeweerrrrrr
r*r,rrr�<module>r.s���������I�I�I�I�I�I�I�I�I�I�I�I�I�I�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  �

qǡ�_�U���\�ddlmZddlmZmZmZmZmZGd�de��Zdd�d�Zd�Z	d	S)
�)�time)�	CharField�Check�IntegerField�Model�PrimaryKeyFieldc��eZdZGd�d��ZdZe��Ze��Zede	d��g���Z
edd����ZdS)	�MalwareIgnorePathc��eZdZdZdZdS)�MalwareIgnorePath.Meta�malware_ignore_path)))�path�
resource_typeTN)�__name__�
__module__�__qualname__�db_table�indexes���w/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/166_add_id_field_to_malware_ignore_path.py�Metars������(��6���rrNFzresource_type in ('file','db'))�null�constraintsc�8�tt����S�N)�intrrrr�<lambda>zMalwareIgnorePath.<lambda>s��#�d�f�f�+�+�r)r�default)
rrrr�CACHEr�idrrrrr�
added_daterrrr
r
s�������7�7�7�7�7�7�7�7�
�E�	��	�	�B��9�;�;�D��I�
���'G�!H�!H� I����M���5�2E�2E�F�F�F�J�J�Jrr
F)�fakec��|�d��|�t��|�d��|�d��dS)NzBALTER TABLE malware_ignore_path RENAME TO malware_ignore_path_old;z�INSERT 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    r�migrater*si���L�L�L����
���+�,�,�,��L�L�	L����
�L�L�6�7�7�7�7�7rc��dSrr)r(r)s  r�rollbackr,!s���DrN)
r�peeweerrrrrr
r*r,rrr�<module>r.s���������I�I�I�I�I�I�I�I�I�I�I�I�I�I�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  �

}&��_W ���f�ddlmZmZmZmZGd�de��ZGd�de��Zd
d�Zd
d�Zd	S)�)�	CharField�Model�IntegerField�CompositeKeyc��eZdZed���Zed���Zed���Zed���ZGd�d��ZdS)�IPListRecordF��nullc�.�eZdZdZedddd��ZdS)�IPListRecord.Meta�iplistrecord�network_address�netmask�version�	iplist_idN��__name__�
__module__�__qualname__�db_tabler�primary_key���a/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/167_remote_iplist.py�Metar
s-������!��"�l��y�)�[�
�
���rrN)	rrrrrrrrrrrrrrs�������"�l��.�.�.�O��l��&�&�&�G��l��&�&�&�G���%�(�(�(�I�
�
�
�
�
�
�
�
�
�
rrc�X�eZdZed���Zed���ZGd�d��ZdS)�
IPListPurposeFr	c�*�eZdZdZedd��ZdS)�IPListPurpose.Meta�
iplistpurpose�purposerNrrrrrrs$������"��"�l�9�k�:�:���rrN)rrrrr!rrrrrrrrs`�������i�U�#�#�#�G���%�(�(�(�I�;�;�;�;�;�;�;�;�;�;rrFc�n�|�t��|�t��dS)N)�create_modelrr)�migrator�database�fake�kwargss    r�migrater(s0�����,�'�'�'����-�(�(�(�(�(rc��|jd}|�|��|jd}|�|��dS)Nr
r )�orm�remove_model)r$r%r&r'rrs      r�rollbackr,sH���<��/�L����,�'�'�'��L��1�M����-�(�(�(�(�(rN)F)	�peeweerrrrrrr(r,rrr�<module>r.s���?�?�?�?�?�?�?�?�?�?�?�?�

�

�

�

�

�5�

�

�

�;�;�;�;�;�E�;�;�;�)�)�)�)�
)�)�)�)�)�)rdefence360agent/migrations/__pycache__/167_remote_iplist.cpython-311.pyc0000644000000000000000000000462400000000000023116 0ustar  �

}&��_W ���f�ddlmZmZmZmZGd�de��ZGd�de��Zd
d�Zd
d�Zd	S)�)�	CharField�Model�IntegerField�CompositeKeyc��eZdZed���Zed���Zed���Zed���ZGd�d��ZdS)�IPListRecordF��nullc�.�eZdZdZedddd��ZdS)�IPListRecord.Meta�iplistrecord�network_address�netmask�version�	iplist_idN��__name__�
__module__�__qualname__�db_tabler�primary_key���a/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/167_remote_iplist.py�Metar
s-������!��"�l��y�)�[�
�
���rrN)	rrrrrrrrrrrrrrs�������"�l��.�.�.�O��l��&�&�&�G��l��&�&�&�G���%�(�(�(�I�
�
�
�
�
�
�
�
�
�
rrc�X�eZdZed���Zed���ZGd�d��ZdS)�
IPListPurposeFr	c�*�eZdZdZedd��ZdS)�IPListPurpose.Meta�
iplistpurpose�purposerNrrrrrrs$������"��"�l�9�k�:�:���rrN)rrrrr!rrrrrrrrs`�������i�U�#�#�#�G���%�(�(�(�I�;�;�;�;�;�;�;�;�;�;rrFc�n�|�t��|�t��dS)N)�create_modelrr)�migrator�database�fake�kwargss    r�migrater(s0�����,�'�'�'����-�(�(�(�(�(rc��|jd}|�|��|jd}|�|��dS)Nr
r )�orm�remove_model)r$r%r&r'rrs      r�rollbackr,sH���<��/�L����,�'�'�'��L��1�M����-�(�(�(�(�(rN)F)	�peeweerrrrrrr(r,rrr�<module>r.s���?�?�?�?�?�?�?�?�?�?�?�?�

�

�

�

�

�5�

�

�

�;�;�;�;�;�E�;�;�;�)�)�)�)�
)�)�)�)�)�)rdefence360agent/migrations/__pycache__/168_add_icontact_throttle.cpython-311.opt-1.pyc0000644000000000000000000000355300000000000025540 0ustar  �

э!D��Z��V�ddlmZmZmZmZddlmZGd�de��Zd	d�Zd	d�Z	dS)
�)�	CharField�Check�IntegerField�Model)�IContactMessageTypec���eZdZGd�d��Zeded�eej	��eej
��f����g���Zed���Z
dS)	�IContactThrottlec��eZdZdZdS)�IContactThrottle.Meta�icontact_throttleN)�__name__�
__module__�__qualname__�db_table���i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/168_add_icontact_throttle.py�Metars������&���rrTzmessage_type in {})�primary_key�constraintsr)�defaultN)r
rrrrr�format�strr�
MALWARE_FOUND�SCAN_NOT_SCHEDULED�message_typer�	timestamprrrr	r	s�������'�'�'�'�'�'�'�'��9���E�$�+�+���/�=�>�>���/�B�C�C����
�
�	
����L���Q�'�'�'�I�I�Irr	Fc�:�|�t��dS)N)�create_modelr	)�migrator�database�fake�kwargss    r�migrater$s�����*�+�+�+�+�+rc�J�|jd}|�|��dS)Nr)�orm�remove_model)r r!r"r#r	s     r�rollbackr(s+���|�$7�8�����*�+�+�+�+�+rN)F)
�peeweerrrr� defence360agent.contracts.configrr	r$r(rrr�<module>r+s���8�8�8�8�8�8�8�8�8�8�8�8�@�@�@�@�@�@�(�(�(�(�(�u�(�(�(�(,�,�,�,�,�,�,�,�,�,rdefence360agent/migrations/__pycache__/168_add_icontact_throttle.cpython-311.pyc0000644000000000000000000000355300000000000024601 0ustar  �

э!D��Z��V�ddlmZmZmZmZddlmZGd�de��Zd	d�Zd	d�Z	dS)
�)�	CharField�Check�IntegerField�Model)�IContactMessageTypec���eZdZGd�d��Zeded�eej	��eej
��f����g���Zed���Z
dS)	�IContactThrottlec��eZdZdZdS)�IContactThrottle.Meta�icontact_throttleN)�__name__�
__module__�__qualname__�db_table���i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/168_add_icontact_throttle.py�Metars������&���rrTzmessage_type in {})�primary_key�constraintsr)�defaultN)r
rrrrr�format�strr�
MALWARE_FOUND�SCAN_NOT_SCHEDULED�message_typer�	timestamprrrr	r	s�������'�'�'�'�'�'�'�'��9���E�$�+�+���/�=�>�>���/�B�C�C����
�
�	
����L���Q�'�'�'�I�I�Irr	Fc�:�|�t��dS)N)�create_modelr	)�migrator�database�fake�kwargss    r�migrater$s�����*�+�+�+�+�+rc�J�|jd}|�|��dS)Nr)�orm�remove_model)r r!r"r#r	s     r�rollbackr(s+���|�$7�8�����*�+�+�+�+�+rN)F)
�peeweerrrr� defence360agent.contracts.configrr	r$r(rrr�<module>r+s���8�8�8�8�8�8�8�8�8�8�8�8�@�@�@�@�@�@�(�(�(�(�(�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  �

�jm�n�ߢ��*�ddlZddlmZdd�Zdd�ZdS)�N)�IContactMessageTypeFc��|rdS|jd}|�tjt	j��dz���dS)N�icontact_throttlei�:	)�message_type�	timestamp)�orm�creater�SCAN_NOT_SCHEDULED�time)�migrator�database�fake�kwargs�IContactThrotles     ��/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/169_add_record_to_throttle_scan_not_schedule_events.py�migratersW�������l�#6�7�O����(�;��)�+�+��+�������c��|rdS|jd}|����|jtjk�����dS)Nr)r�delete�whererrr
�execute)rr
rr�IContactThrottles     r�rollbackrsZ�������|�$7�8�������#�#��%�)<�)O�O���
�g�i�i�i�i�ir)F)r� defence360agent.contracts.configrrr�rr�<module>rsR������@�@�@�@�@�@����������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  �

�jm�n�ߢ��*�ddlZddlmZdd�Zdd�ZdS)�N)�IContactMessageTypeFc��|rdS|jd}|�tjt	j��dz���dS)N�icontact_throttlei�:	)�message_type�	timestamp)�orm�creater�SCAN_NOT_SCHEDULED�time)�migrator�database�fake�kwargs�IContactThrotles     ��/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/169_add_record_to_throttle_scan_not_schedule_events.py�migratersW�������l�#6�7�O����(�;��)�+�+��+�������c��|rdS|jd}|����|jtjk�����dS)Nr)r�delete�whererrr
�execute)rr
rr�IContactThrottles     r�rollbackrsZ�������|�$7�8�������#�#��%�)<�)O�O���
�g�i�i�i�i�ir)F)r� defence360agent.contracts.configrrr�rr�<module>rsR������@�@�@�@�@�@����������rdefence360agent/migrations/__pycache__/170_add_db_fields_to_malware_history.cpython-311.opt-1.pyc0000644000000000000000000000207500000000000027704 0ustar  �

�8�����*�ddlmZdd�d�Zdd�d�ZdS)�)�	CharFieldF)�fakec��|jd}|�|td���td���td������dS)N�malware_historyT)�null)�db_host�db_port�db_name)�orm�
add_fieldsr��migratorr�_�__rs     �t/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/170_add_db_fields_to_malware_history.py�migratersc���l�#4�5�O������t�$�$�$��t�$�$�$��t�$�$�$�	������c�P�|jd}|�|ddd��dS)Nrrr	r
)r�
remove_fieldsr
s     r�rollbackrs/���l�#4�5�O����?�I�y�)�L�L�L�L�LrN)�peeweerrr�rr�<module>rsd�������� %������!&�M�M�M�M�M�M�Mrdefence360agent/migrations/__pycache__/170_add_db_fields_to_malware_history.cpython-311.pyc0000644000000000000000000000207500000000000026745 0ustar  �

�8�����*�ddlmZdd�d�Zdd�d�ZdS)�)�	CharFieldF)�fakec��|jd}|�|td���td���td������dS)N�malware_historyT)�null)�db_host�db_port�db_name)�orm�
add_fieldsr��migratorr�_�__rs     �t/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/170_add_db_fields_to_malware_history.py�migratersc���l�#4�5�O������t�$�$�$��t�$�$�$��t�$�$�$�	������c�P�|jd}|�|ddd��dS)Nrrr	r
)r�
remove_fieldsr
s     r�rollbackrs/���l�#4�5�O����?�I�y�)�L�L�L�L�LrN)�peeweerrr�rr�<module>rsd�������� %������!&�M�M�M�M�M�M�Mrdefence360agent/migrations/__pycache__/180_move_captcha_configs.cpython-311.opt-1.pyc0000644000000000000000000000122400000000000025323 0ustar  �

�x�>2����dZdd�Zdd�ZdS)z�
No need to rollback captcha keys config because WebshieldCaptchaKeys plugin
recreates it on the agent start so just stubbing the migration
Fc��dS�N���migrator�database�fake�kwargss    �h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/180_move_captcha_configs.py�migrater����D�c��dSrrrs    r
�rollbackrrr
N)F)�__doc__rrrr
r
�<module>rsA����	�	�	�	�	�	�	�	�	�	r
defence360agent/migrations/__pycache__/180_move_captcha_configs.cpython-311.pyc0000644000000000000000000000122400000000000024364 0ustar  �

�x�>2����dZdd�Zdd�ZdS)z�
No need to rollback captcha keys config because WebshieldCaptchaKeys plugin
recreates it on the agent start so just stubbing the migration
Fc��dS�N���migrator�database�fake�kwargss    �h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/180_move_captcha_configs.py�migrater����D�c��dSrrrs    r
�rollbackrrr
N)F)�__doc__rrrr
r
�<module>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  �

�v�-S���J�dZddlmZmZmZGd�de��Zd	d�Zd	d�ZdS)
zl
Drop constrains for icontact_throttle.message_type, since correlation server
can set any type (DEF-19971).
�)�	CharField�IntegerField�Modelc�X�eZdZGd�d��Zed���Zed���ZdS)�IContactThrottlec��eZdZdZdS)�IContactThrottle.Meta�icontact_throttleN)�__name__�
__module__�__qualname__�db_table���}/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/182_remove_constraints_from_icontact_throttle.py�Metar		s������&���rrT)�primary_keyr)�defaultN)rrr
rr�message_typer�	timestamprrrrrs\������'�'�'�'�'�'�'�'��9��.�.�.�L���Q�'�'�'�I�I�IrrFc��|rdS|�d��|�t��|�d��|�d��dS)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_modelr��migrator�database�fake�kwargss    r�migratersv�������L�L�G����
���*�+�+�+��L�L�	C����
�L�L�3�4�4�4�4�4rc��dS)Nrrs    r�rollbackr!s���DrN)F)�__doc__�peeweerrrrrr!rrr�<module>r$s�����2�1�1�1�1�1�1�1�1�1�(�(�(�(�(�u�(�(�(�5�5�5�5�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/182_remove_constraints_from_icontact_throttle.cpython-311.pyc0000644000000000000000000000361300000000000031011 0ustar  �

�v�-S���J�dZddlmZmZmZGd�de��Zd	d�Zd	d�ZdS)
zl
Drop constrains for icontact_throttle.message_type, since correlation server
can set any type (DEF-19971).
�)�	CharField�IntegerField�Modelc�X�eZdZGd�d��Zed���Zed���ZdS)�IContactThrottlec��eZdZdZdS)�IContactThrottle.Meta�icontact_throttleN)�__name__�
__module__�__qualname__�db_table���}/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/182_remove_constraints_from_icontact_throttle.py�Metar		s������&���rrT)�primary_keyr)�defaultN)rrr
rr�message_typer�	timestamprrrrrs\������'�'�'�'�'�'�'�'��9��.�.�.�L���Q�'�'�'�I�I�IrrFc��|rdS|�d��|�t��|�d��|�d��dS)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_modelr��migrator�database�fake�kwargss    r�migratersv�������L�L�G����
���*�+�+�+��L�L�	C����
�L�L�3�4�4�4�4�4rc��dS)Nrrs    r�rollbackr!s���DrN)F)�__doc__�peeweerrrrrr!rrr�<module>r$s�����2�1�1�1�1�1�1�1�1�1�(�(�(�(�(�u�(�(�(�5�5�5�5�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/183_add_user_field_to_malware_scans.cpython-311.opt-1.pyc0000644000000000000000000000174100000000000027523 0ustar  �

�+L�R���*�ddlmZdd�d�Zdd�d�ZdS)�)�	CharFieldF)�fakec�r�|rdS|jd}|�|td������dS)N�
malware_scansT)�null)�	initiator)�orm�
add_fieldsr��migratorr�_�__rs     �s/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/183_add_user_field_to_malware_scans.py�migratersS�������L��1�M�������&�&�&�������c�T�|rdS|jd}|�|d��dS)Nrr)r	�
remove_fieldsrs     r�rollbackrs7�������L��1�M����=�+�6�6�6�6�6rN)�peeweerrr�rr�<module>rs]�������� %������!&�7�7�7�7�7�7�7rdefence360agent/migrations/__pycache__/183_add_user_field_to_malware_scans.cpython-311.pyc0000644000000000000000000000174100000000000026564 0ustar  �

�+L�R���*�ddlmZdd�d�Zdd�d�ZdS)�)�	CharFieldF)�fakec�r�|rdS|jd}|�|td������dS)N�
malware_scansT)�null)�	initiator)�orm�
add_fieldsr��migratorr�_�__rs     �s/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/183_add_user_field_to_malware_scans.py�migratersS�������L��1�M�������&�&�&�������c�T�|rdS|jd}|�|d��dS)Nrr)r	�
remove_fieldsrs     r�rollbackrs7�������L��1�M����=�+�6�6�6�6�6rN)�peeweerrr�rr�<module>rs]�������� %������!&�7�7�7�7�7�7�7r././@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  �

��؁����B�ddlZGd�dej��Zdd�Zd�ZdS)�Nc��eZdZGd�d��Zejd���Zejdejd��gd���Z	d	S)
�
SecureSitec��eZdZdZdS)�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.py�Metars������ ���r
rT)�uniqueFz$subscription_type in ('basic','pro')�basic)�null�constraints�defaultN)
rr	r
r�pw�	CharField�user�	TextField�Check�subscription_typerr
rrrs�������!�!�!�!�!�!�!�!��2�<�t�$�$�$�D�$���
��R�X�D�E�E�F�������r
rFc�B�|rdS|�t��dS)N)�create_modelr)�migrator�_db�fake�__s    r�migrater!s)���������*�%�%�%�%�%r
c��dS)z
Not supportedNr)�_r s  r�rollbackr$s���r
)F)�peeweer�Modelrr!r$rr
r�<module>r'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  �

��؁����B�ddlZGd�dej��Zdd�Zd�ZdS)�Nc��eZdZGd�d��Zejd���Zejdejd��gd���Z	d	S)
�
SecureSitec��eZdZdZdS)�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.py�Metars������ ���r
rT)�uniqueFz$subscription_type in ('basic','pro')�basic)�null�constraints�defaultN)
rr	r
r�pw�	CharField�user�	TextField�Check�subscription_typerr
rrrs�������!�!�!�!�!�!�!�!��2�<�t�$�$�$�D�$���
��R�X�D�E�E�F�������r
rFc�B�|rdS|�t��dS)N)�create_modelr)�migrator�_db�fake�__s    r�migrater!s)���������*�%�%�%�%�%r
c��dS)z
Not supportedNr)�_r s  r�rollbackr$s���r
)F)�peeweer�Modelrr!r$rr
r�<module>r'sj������	�	�	�	�	���	�	�	�&�&�&�&�����r
defence360agent/migrations/__pycache__/185_delete_all_secure_site_id.cpython-311.opt-1.pyc0000644000000000000000000000242100000000000026327 0ustar  �

2�(� ��H�ddlZddlmZeje��Zdd�Zd�ZdS)�N)�PathFc��|rdS	td���d��}|D],}|���s|�d����-dS#t$rt
�d��YdSwxYw)N�/zhome*/*/.secure_site_idT)�
missing_okz:An exception occurred while deleting .secure_site_id files)r�glob�
is_symlink�unlink�	Exception�logger�	exception)�migrator�_db�fake�__�id_files�id_files      �m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/185_delete_all_secure_site_id.py�migraters�������
���9�9�>�>�";�<�<���	0�	0�G��%�%�'�'�
0����$��/�/�/��	0�	0���
�
�
����H�	
�	
�	
�	
�	
�	
�
���s�AA�$B�Bc��dS)z
Not supportedN�)�_rs  r�rollbackrs����)F)�logging�pathlibr�	getLogger�__name__rrrrrr�<module>rs_������������	��	�8�	$�	$��
�
�
�
�����rdefence360agent/migrations/__pycache__/185_delete_all_secure_site_id.cpython-311.pyc0000644000000000000000000000242100000000000025370 0ustar  �

2�(� ��H�ddlZddlmZeje��Zdd�Zd�ZdS)�N)�PathFc��|rdS	td���d��}|D],}|���s|�d����-dS#t$rt
�d��YdSwxYw)N�/zhome*/*/.secure_site_idT)�
missing_okz:An exception occurred while deleting .secure_site_id files)r�glob�
is_symlink�unlink�	Exception�logger�	exception)�migrator�_db�fake�__�id_files�id_files      �m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/185_delete_all_secure_site_id.py�migraters�������
���9�9�>�>�";�<�<���	0�	0�G��%�%�'�'�
0����$��/�/�/��	0�	0���
�
�
����H�	
�	
�	
�	
�	
�	
�
���s�AA�$B�Bc��dS)z
Not supportedN�)�_rs  r�rollbackrs����)F)�logging�pathlibr�	getLogger�__name__rrrrrr�<module>rs_������������	��	�8�	$�	$��
�
�
�
�����rdefence360agent/migrations/__pycache__/186_add_user_field_to_icontact_throttle.cpython-311.opt-1.pyc0000644000000000000000000000634600000000000030446 0ustar  �

��?y����6�ddlmZmZmZmZdd�d�Zdd�d�ZdS)�)�CompositeKey�Model�	CharField�IntegerFieldF)�fakec�@�|rdS|jd}Gd�dt��}|�|td������|�|��|�d��|�d��|�d	��dS)
N�icontact_throttlec�l�eZdZGd�d��Ze��Zed���Zed���ZdS)�$migrate.<locals>.TmpIContactThrottlec�*�eZdZdZedd��ZdS)�)migrate.<locals>.TmpIContactThrottle.Meta�tmp_icontact_throttle�message_type�userN)�__name__�
__module__�__qualname__�db_tabler�primary_key���w/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/186_add_user_field_to_icontact_throttle.py�Metar
s$������.�H�&�,�~�v�>�>�K�K�KrrT��nullr��defaultN)	rrrrrrrr�	timestamprrr�TmpIContactThrottler
sg������	?�	?�	?�	?�	?�	?�	?�	?�!�y�{�{���y�d�#�#�#�� �L��+�+�+�	�	�	rrTr)rz}INSERT INTO tmp_icontact_throttle (message_type, user, timestamp) SELECT message_type, user, timestamp FROM icontact_throttle�DROP TABLE icontact_throttle�=ALTER TABLE tmp_icontact_throttle RENAME TO icontact_throttle)�ormr�
add_fieldsr�create_model�sql)�migratorr�_�__r	rs      r�migrater)s������� ��%8�9��,�,�,�,�,�e�,�,�,�
����
�D�
!�
!�
!�����
���-�.�.�.��L�L�	F����
�L�L�/�0�0�0��L�L�G�����rc���|rdSGd�dt��}|�|��|�d��|�d��|�d��dS)Nc�X�eZdZGd�d��Zed���Zed���ZdS)�%rollback.<locals>.TmpIContactThrottlec��eZdZdZdS)�*rollback.<locals>.TmpIContactThrottle.Metar	N)rrrrrrrrr.)s������*�H�H�HrrT)rrrN)rrrrrrrrrrrrr,(s\������	+�	+�	+�	+�	+�	+�	+�	+�!�y�T�2�2�2�� �L��+�+�+�	�	�	rrzqINSERT INTO tmp_icontact_throttle (message_type, timestamp) SELECT message_type, timestamp FROM icontact_throttler r!)rr$r%)r&rr'r(rs     r�rollbackr/$s�������,�,�,�,�,�e�,�,�,�
���-�.�.�.��L�L�	@����
�L�L�/�0�0�0��L�L�G�����rN)�peeweerrrrr)r/rrr�<module>r1sp��?�?�?�?�?�?�?�?�?�?�?�?� %������@!&�������rdefence360agent/migrations/__pycache__/186_add_user_field_to_icontact_throttle.cpython-311.pyc0000644000000000000000000000634600000000000027507 0ustar  �

��?y����6�ddlmZmZmZmZdd�d�Zdd�d�ZdS)�)�CompositeKey�Model�	CharField�IntegerFieldF)�fakec�@�|rdS|jd}Gd�dt��}|�|td������|�|��|�d��|�d��|�d	��dS)
N�icontact_throttlec�l�eZdZGd�d��Ze��Zed���Zed���ZdS)�$migrate.<locals>.TmpIContactThrottlec�*�eZdZdZedd��ZdS)�)migrate.<locals>.TmpIContactThrottle.Meta�tmp_icontact_throttle�message_type�userN)�__name__�
__module__�__qualname__�db_tabler�primary_key���w/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/186_add_user_field_to_icontact_throttle.py�Metar
s$������.�H�&�,�~�v�>�>�K�K�KrrT��nullr��defaultN)	rrrrrrrr�	timestamprrr�TmpIContactThrottler
sg������	?�	?�	?�	?�	?�	?�	?�	?�!�y�{�{���y�d�#�#�#�� �L��+�+�+�	�	�	rrTr)rz}INSERT INTO tmp_icontact_throttle (message_type, user, timestamp) SELECT message_type, user, timestamp FROM icontact_throttle�DROP TABLE icontact_throttle�=ALTER TABLE tmp_icontact_throttle RENAME TO icontact_throttle)�ormr�
add_fieldsr�create_model�sql)�migratorr�_�__r	rs      r�migrater)s������� ��%8�9��,�,�,�,�,�e�,�,�,�
����
�D�
!�
!�
!�����
���-�.�.�.��L�L�	F����
�L�L�/�0�0�0��L�L�G�����rc���|rdSGd�dt��}|�|��|�d��|�d��|�d��dS)Nc�X�eZdZGd�d��Zed���Zed���ZdS)�%rollback.<locals>.TmpIContactThrottlec��eZdZdZdS)�*rollback.<locals>.TmpIContactThrottle.Metar	N)rrrrrrrrr.)s������*�H�H�HrrT)rrrN)rrrrrrrrrrrrr,(s\������	+�	+�	+�	+�	+�	+�	+�	+�!�y�T�2�2�2�� �L��+�+�+�	�	�	rrzqINSERT INTO tmp_icontact_throttle (message_type, timestamp) SELECT message_type, timestamp FROM icontact_throttler r!)rr$r%)r&rr'r(rs     r�rollbackr/$s�������,�,�,�,�,�e�,�,�,�
���-�.�.�.��L�L�	@����
�L�L�/�0�0�0��L�L�G�����rN)�peeweerrrrr)r/rrr�<module>r1sp��?�?�?�?�?�?�?�?�?�?�?�?� %������@!&�������rdefence360agent/migrations/__pycache__/187_fix_scan_unserialization.cpython-311.opt-1.pyc0000644000000000000000000000712500000000000026271 0ustar  �

(:5 
��r���dZddlZddlZddlZddlmZeje��Zed��Z	dZ
dZGd�dej��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.malwarelibc���eZdZ�fd�Z�xZS)�AVUnpicklerc�$��	t���||��S#t$r`|�t��rDtj|�tt����}t||��cYS�wxYw�N)
�super�
find_class�ModuleNotFoundError�
startswith�IM360_MALWARELIB�	importlib�
import_module�replace�
AV_MALWARELIB�getattr)�self�module�name�	av_module�	__class__s    ��l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/187_fix_scan_unserialization.pyr	zAVUnpickler.find_classs����	��7�7�%�%�f�d�3�3�3��"�	�	�	�� � �!1�2�2�
0�%�3��N�N�#3�]�C�C���	��y�$�/�/�/�/�/��
	���s�!%�A&B�
B)�__name__�
__module__�__qualname__r	�
__classcell__)rs@rrrs8�������	�	�	�	�	�	�	�	�	�rc���|�|jdz��}|�d��5}tj||��ddd��n#1swxYwY|�|��dS)Nz.temp�wb)�	with_namer�open�pickle�dumpr)�obj�path�	temp_path�fs    rr"r"s������t�y�7�2�3�3�I�	����	�	�����C��������������������������d�����s�A�A�AF)�fakec���|st���sdSt���t���vr�	t�d��5}t
|�����}ddd��n#1swxYwYt|t��dS#t$r,}t�dt|��Yd}~dSd}~wwxYwdS)N�rbz"Failed to load pickle scans %s: %s)�
SCANS_PATH�existsr�encode�
read_bytesr r�loadr"�	Exception�logger�	exception)�migratorr'�_�__r&r#�excs       r�migrater6'sC����:�$�$�&�&������� � �J�$9�$9�$;�$;�;�;�	"�����&�&�
,�!�!�!�n�n�)�)�+�+��
,�
,�
,�
,�
,�
,�
,�
,�
,�
,�
,����
,�
,�
,�
,�
��j�!�!�!�!�!���	�	�	����4�j�#�
�
�
�
�
�
�
�
�
�����	����	<�;s<�B;�+"B�
B;�B�B;� B�!B;�;
C1�!C,�,C1c��dSr�)r2r'r3r4s    r�rollbackr97s���Dr)�__doc__r
�loggingr!�pathlibr�	getLoggerrr0r*rr�	Unpicklerrr"r6r9r8rr�<module>r?s�������������
�
�
�
�������	��	�8�	$�	$��
�T�8�
9�
9�
�%��!�
�
�
�
�
�
�&�"�
�
�
���� %�
"�
"�
"�
"�
"� !&�	�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/187_fix_scan_unserialization.cpython-311.pyc0000644000000000000000000000712500000000000025332 0ustar  �

(:5 
��r���dZddlZddlZddlZddlmZeje��Zed��Z	dZ
dZGd�dej��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.malwarelibc���eZdZ�fd�Z�xZS)�AVUnpicklerc�$��	t���||��S#t$r`|�t��rDtj|�tt����}t||��cYS�wxYw�N)
�super�
find_class�ModuleNotFoundError�
startswith�IM360_MALWARELIB�	importlib�
import_module�replace�
AV_MALWARELIB�getattr)�self�module�name�	av_module�	__class__s    ��l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/187_fix_scan_unserialization.pyr	zAVUnpickler.find_classs����	��7�7�%�%�f�d�3�3�3��"�	�	�	�� � �!1�2�2�
0�%�3��N�N�#3�]�C�C���	��y�$�/�/�/�/�/��
	���s�!%�A&B�
B)�__name__�
__module__�__qualname__r	�
__classcell__)rs@rrrs8�������	�	�	�	�	�	�	�	�	�rc���|�|jdz��}|�d��5}tj||��ddd��n#1swxYwY|�|��dS)Nz.temp�wb)�	with_namer�open�pickle�dumpr)�obj�path�	temp_path�fs    rr"r"s������t�y�7�2�3�3�I�	����	�	�����C��������������������������d�����s�A�A�AF)�fakec���|st���sdSt���t���vr�	t�d��5}t
|�����}ddd��n#1swxYwYt|t��dS#t$r,}t�dt|��Yd}~dSd}~wwxYwdS)N�rbz"Failed to load pickle scans %s: %s)�
SCANS_PATH�existsr�encode�
read_bytesr r�loadr"�	Exception�logger�	exception)�migratorr'�_�__r&r#�excs       r�migrater6'sC����:�$�$�&�&������� � �J�$9�$9�$;�$;�;�;�	"�����&�&�
,�!�!�!�n�n�)�)�+�+��
,�
,�
,�
,�
,�
,�
,�
,�
,�
,�
,����
,�
,�
,�
,�
��j�!�!�!�!�!���	�	�	����4�j�#�
�
�
�
�
�
�
�
�
�����	����	<�;s<�B;�+"B�
B;�B�B;� B�!B;�;
C1�!C,�,C1c��dSr�)r2r'r3r4s    r�rollbackr97s���Dr)�__doc__r
�loggingr!�pathlibr�	getLoggerrr0r*rr�	Unpicklerrr"r6r9r8rr�<module>r?s�������������
�
�
�
�������	��	�8�	$�	$��
�T�8�
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  �

V�e-��F�ddlmZmZmZGd�de��Zdd�Zdd�ZdS)	�)�BooleanField�	CharField�Modelc�Z�eZdZGd�d��Zed���Zedd���ZdS)�	MyImunifyc��eZdZdZdS)�MyImunify.Meta�	myimunifyN)�__name__�
__module__�__qualname__�db_table���y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/188_add_protection_status_field_myimunify.py�Metar	s���������rrT)�uniqueF)�null�defaultN)rrr
rr�userr�
protectionrrrrrs^���������������9�D�!�!�!�D���5�%�8�8�8�J�J�JrrFc�B�|rdS|�t��dS�N)�create_modelr��migrator�_db�fake�__s    r�migrater �)���������)�$�$�$�$�$rc�B�|rdS|�t��dSr)�remove_modelrrs    r�rollbackr$r!rN)F)�peeweerrrrr r$rrr�<module>r&s��1�1�1�1�1�1�1�1�1�1�9�9�9�9�9��9�9�9�%�%�%�%�%�%�%�%�%�%rdefence360agent/migrations/__pycache__/188_add_protection_status_field_myimunify.cpython-311.pyc0000644000000000000000000000260100000000000030105 0ustar  �

V�e-��F�ddlmZmZmZGd�de��Zdd�Zdd�ZdS)	�)�BooleanField�	CharField�Modelc�Z�eZdZGd�d��Zed���Zedd���ZdS)�	MyImunifyc��eZdZdZdS)�MyImunify.Meta�	myimunifyN)�__name__�
__module__�__qualname__�db_table���y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/188_add_protection_status_field_myimunify.py�Metar	s���������rrT)�uniqueF)�null�defaultN)rrr
rr�userr�
protectionrrrrrs^���������������9�D�!�!�!�D���5�%�8�8�8�J�J�JrrFc�B�|rdS|�t��dS�N)�create_modelr��migrator�_db�fake�__s    r�migrater �)���������)�$�$�$�$�$rc�B�|rdS|�t��dSr)�remove_modelrrs    r�rollbackr$r!rN)F)�peeweerrrrr r$rrr�<module>r&s��1�1�1�1�1�1�1�1�1�1�9�9�9�9�9��9�9�9�%�%�%�%�%�%�%�%�%�%rdefence360agent/migrations/__pycache__/189_add_messages_to_send_nr.cpython-311.opt-1.pyc0000644000000000000000000000266700000000000026040 0ustar  �

�qE��560��F�ddlmZmZmZGd�de��Zdd�Zdd�ZdS)	�)�
FloatField�Model�	BlobFieldc�X�eZdZGd�d��Zed���Zed���ZdS)�
MessageToSendc��eZdZdZdS)�MessageToSend.Meta�messages_to_send_nrN)�__name__�
__module__�__qualname__�db_table���k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/189_add_messages_to_send_nr.py�Metar	s������(���rrF)�nullN)rrr
rr�	timestampr�messagerrrrrs\������)�)�)�)�)�)�)�)��
��&�&�&�I��i�U�#�#�#�G�G�GrrFc�:�|�t��dS)N)�create_modelr)�migrator�database�fake�kwargss    r�migraters�����-�(�(�(�(�(rc�J�|jd}|�|��dS)N�messages_to_send)�orm�
drop_model)rrrrrs     r�rollbackr!s)���L�!3�4�M����
�&�&�&�&�&rN)F)�peeweerrrrrr!rrr�<module>r#s��/�/�/�/�/�/�/�/�/�/�$�$�$�$�$�E�$�$�$�)�)�)�)�'�'�'�'�'�'rdefence360agent/migrations/__pycache__/189_add_messages_to_send_nr.cpython-311.pyc0000644000000000000000000000266700000000000025101 0ustar  �

�qE��560��F�ddlmZmZmZGd�de��Zdd�Zdd�ZdS)	�)�
FloatField�Model�	BlobFieldc�X�eZdZGd�d��Zed���Zed���ZdS)�
MessageToSendc��eZdZdZdS)�MessageToSend.Meta�messages_to_send_nrN)�__name__�
__module__�__qualname__�db_table���k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/189_add_messages_to_send_nr.py�Metar	s������(���rrF)�nullN)rrr
rr�	timestampr�messagerrrrrs\������)�)�)�)�)�)�)�)��
��&�&�&�I��i�U�#�#�#�G�G�GrrFc�:�|�t��dS)N)�create_modelr)�migrator�database�fake�kwargss    r�migraters�����-�(�(�(�(�(rc�J�|jd}|�|��dS)N�messages_to_send)�orm�
drop_model)rrrrrs     r�rollbackr!s)���L�!3�4�M����
�&�&�&�&�&rN)F)�peeweerrrrrr!rrr�<module>r#s��/�/�/�/�/�/�/�/�/�/�$�$�$�$�$�E�$�$�$�)�)�)�)�'�'�'�'�'�'rdefence360agent/migrations/__pycache__/190_add_analyst_cleanup_request_table.cpython-311.opt-1.pyc0000644000000000000000000000453600000000000030105 0ustar  �

ߤ�(���p��b�ddlmZmZmZmZmZmZddlmZmZGd�de��Z	d	d�Z
d	d�ZdS)
�)�Model�	AutoField�	CharField�	TextField�TimestampField�Check)�datetime�timezonec�8�eZdZdZGd�d��Ze��Zed���Zed���Z	e
d���Zede
jej�����Zedded��g�	��Zede
jej�����Zd
S)�AnalystCleanupRequestz
    Model for storing analyst cleanup requests.
    Tracks request details and status for each cleanup request submitted.
    c��eZdZdZdS)�AnalystCleanupRequest.Meta�analyst_cleanup_requestsN)�__name__�
__module__�__qualname__�db_table���u/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/190_add_analyst_cleanup_request_table.py�Metars������-���rrF)�null)r�default�pendingz/status in ('pending','in_progress','completed'))rr�constraintsN)rrr�__doc__rr�idr�username�
zendesk_idr�ticket_linkrr	�nowr
�utc�
created_atr�status�last_updatedrrrrrs���������
.�.�.�.�.�.�.�.�
����B��y�e�$�$�$�H����&�&�&�J��)��'�'�'�K���U�L�H�L���4N�4N�O�O�O�J�
�Y�
���U�L�M�M�N����F�
"�>�
�L�H�L���6�6����L�L�LrrFc�:�|�t��dS)N)�create_modelr)�migrator�database�fake�kwargss    r�migrater,$s�����/�0�0�0�0�0rc�J�|jd}|�|��dS)Nr)�orm�
drop_model)r(r)r*r+�analyst_cleanup_requests     r�rollbackr1(s+��&�l�+E�F�����/�0�0�0�0�0rN)F)�peeweerrrrrrr	r
rr,r1rrr�<module>r3s�������������������(�'�'�'�'�'�'�'������E����01�1�1�1�1�1�1�1�1�1rdefence360agent/migrations/__pycache__/190_add_analyst_cleanup_request_table.cpython-311.pyc0000644000000000000000000000453600000000000027146 0ustar  �

ߤ�(���p��b�ddlmZmZmZmZmZmZddlmZmZGd�de��Z	d	d�Z
d	d�ZdS)
�)�Model�	AutoField�	CharField�	TextField�TimestampField�Check)�datetime�timezonec�8�eZdZdZGd�d��Ze��Zed���Zed���Z	e
d���Zede
jej�����Zedded��g�	��Zede
jej�����Zd
S)�AnalystCleanupRequestz
    Model for storing analyst cleanup requests.
    Tracks request details and status for each cleanup request submitted.
    c��eZdZdZdS)�AnalystCleanupRequest.Meta�analyst_cleanup_requestsN)�__name__�
__module__�__qualname__�db_table���u/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/190_add_analyst_cleanup_request_table.py�Metars������-���rrF)�null)r�default�pendingz/status in ('pending','in_progress','completed'))rr�constraintsN)rrr�__doc__rr�idr�username�
zendesk_idr�ticket_linkrr	�nowr
�utc�
created_atr�status�last_updatedrrrrrs���������
.�.�.�.�.�.�.�.�
����B��y�e�$�$�$�H����&�&�&�J��)��'�'�'�K���U�L�H�L���4N�4N�O�O�O�J�
�Y�
���U�L�M�M�N����F�
"�>�
�L�H�L���6�6����L�L�LrrFc�:�|�t��dS)N)�create_modelr)�migrator�database�fake�kwargss    r�migrater,$s�����/�0�0�0�0�0rc�J�|jd}|�|��dS)Nr)�orm�
drop_model)r(r)r*r+�analyst_cleanup_requests     r�rollbackr1(s+��&�l�+E�F�����/�0�0�0�0�0rN)F)�peeweerrrrrrr	r
rr,r1rrr�<module>r3s�������������������(�'�'�'�'�'�'�'������E����01�1�1�1�1�1�1�1�1�1rdefence360agent/migrations/__pycache__/191_create_wordpress_incident_table.cpython-311.opt-1.pyc0000644000000000000000000000522200000000000027565 0ustar  �

���G�U4��T�dZddlZddlmZGd�dej��Zd	d�Zd	d�ZdS)
z�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.
�N)�	JSONFieldc���eZdZejdd���Zejd���Zejd���Zej	d���Z
ejd���Zejd���Zejd���Z
ejd���Zejd���Zejdd���Zejdd���Zed���Zejdd���ZGd	�d
��ZdS)�WordpressIncidentT)�primary_key�null)r�
country_id)r�column_nameN)r�defaultFc��eZdZdZdS)�WordpressIncident.Meta�wordpress_incidentN)�__name__�
__module__�__qualname__�db_table���s/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/191_create_wordpress_incident_table.py�Metars������'���rr)rrr�pw�IntegerField�id�	CharField�plugin�rule�
FloatField�	timestamp�retries�severity�name�	TextField�description�abuser�country�domainr�
extra_info�BooleanField�sent_to_serverrrrrrrsF������	���T��	5�	5�	5�B�
�R�\�t�
$�
$�
$�F��2�<�T�"�"�"�D���
�4�(�(�(�I��b�o�4�(�(�(�G��r��D�)�)�)�H��2�<�T�"�"�"�D��"�,�D�)�)�)�K�
�R�\�t�
$�
$�
$�F��b�l��,�?�?�?�G�
�R�\�t�T�
2�
2�
2�F����%�%�%�J�$�R�_�%��?�?�?�N�(�(�(�(�(�(�(�(�(�(rrFc�t�|�t��|�tdd���dS)NrF)�unique)�create_modelr�	add_index��migrator�database�fake�kwargss    r�migrater2s;�����+�,�,�,�
���(�+�e��D�D�D�D�Drc�>�|�td���dS)NT)�cascade)�remove_modelrr-s    r�rollbackr6&s"�����+�T��:�:�:�:�:r)F)	�__doc__�peeweer�playhouse.sqlite_extr�Modelrr2r6rrr�<module>r;s���������*�*�*�*�*�*�(�(�(�(�(���(�(�(�&E�E�E�E�;�;�;�;�;�;rdefence360agent/migrations/__pycache__/191_create_wordpress_incident_table.cpython-311.pyc0000644000000000000000000000522200000000000026626 0ustar  �

���G�U4��T�dZddlZddlmZGd�dej��Zd	d�Zd	d�ZdS)
z�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.
�N)�	JSONFieldc���eZdZejdd���Zejd���Zejd���Zej	d���Z
ejd���Zejd���Zejd���Z
ejd���Zejd���Zejdd���Zejdd���Zed���Zejdd���ZGd	�d
��ZdS)�WordpressIncidentT)�primary_key�null)r�
country_id)r�column_nameN)r�defaultFc��eZdZdZdS)�WordpressIncident.Meta�wordpress_incidentN)�__name__�
__module__�__qualname__�db_table���s/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/191_create_wordpress_incident_table.py�Metars������'���rr)rrr�pw�IntegerField�id�	CharField�plugin�rule�
FloatField�	timestamp�retries�severity�name�	TextField�description�abuser�country�domainr�
extra_info�BooleanField�sent_to_serverrrrrrrsF������	���T��	5�	5�	5�B�
�R�\�t�
$�
$�
$�F��2�<�T�"�"�"�D���
�4�(�(�(�I��b�o�4�(�(�(�G��r��D�)�)�)�H��2�<�T�"�"�"�D��"�,�D�)�)�)�K�
�R�\�t�
$�
$�
$�F��b�l��,�?�?�?�G�
�R�\�t�T�
2�
2�
2�F����%�%�%�J�$�R�_�%��?�?�?�N�(�(�(�(�(�(�(�(�(�(rrFc�t�|�t��|�tdd���dS)NrF)�unique)�create_modelr�	add_index��migrator�database�fake�kwargss    r�migrater2s;�����+�,�,�,�
���(�+�e��D�D�D�D�Drc�>�|�td���dS)NT)�cascade)�remove_modelrr-s    r�rollbackr6&s"�����+�T��:�:�:�:�:r)F)	�__doc__�peeweer�playhouse.sqlite_extr�Modelrr2r6rrr�<module>r;s���������*�*�*�*�*�*�(�(�(�(�(���(�(�(�&E�E�E�E�;�;�;�;�;�;rdefence360agent/migrations/__pycache__/192_add_wordpress_incident_unique_index.cpython-311.opt-1.pyc0000644000000000000000000000255000000000000030462 0ustar  �

5lk
�`>���dZdd�Zdd�ZdS)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_incident�abuser�name�plugin�rule�severity�domainT)�uniqueN)�orm�	add_index��migrator�database�fake�kwargs�WordpressIncidents     �w/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/192_add_wordpress_incident_unique_index.py�migrater	sQ�� ��%9�:��
������������	�	�	�	�	�c	�V�|jd}|�|dddddd��dS)	z"Remove the unique composite index.rrrrrrr	N)r�
drop_indexr
s     r�rollbackrsG�� ��%9�:����������������rN)F)�__doc__rr�rr�<module>rsA��������$�����rdefence360agent/migrations/__pycache__/192_add_wordpress_incident_unique_index.cpython-311.pyc0000644000000000000000000000255000000000000027523 0ustar  �

5lk
�`>���dZdd�Zdd�ZdS)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_incident�abuser�name�plugin�rule�severity�domainT)�uniqueN)�orm�	add_index��migrator�database�fake�kwargs�WordpressIncidents     �w/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/192_add_wordpress_incident_unique_index.py�migrater	sQ�� ��%9�:��
������������	�	�	�	�	�c	�V�|jd}|�|dddddd��dS)	z"Remove the unique composite index.rrrrrrr	N)r�
drop_indexr
s     r�rollbackrsG�� ��%9�:����������������rN)F)�__doc__rr�rr�<module>rsA��������$�����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  �

�����9V��"�dZddlZdd�Zdd�ZdS)z�Remove sent_to_server column from wordpress_incident table.

The sent_to_server field is no longer needed for WordPress incident tracking.
�NFc�L�|jd}|�|d��dS)N�wordpress_incident�sent_to_server)�orm�
remove_fields��migrator�database�fake�kwargs�WordpressIncidents     ��/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/193_remove_sent_to_server_from_wordpress_incident.py�migrater	s.�� ��%9�:�����,�.>�?�?�?�?�?�c�v�|jd}|�|tjdd������dS)NrF)�null�default)r)r�
add_fields�pw�BooleanFieldrs     r�rollbackrsL�� ��%9�:��������E�5�A�A�A������r)F)�__doc__�peeweerrr�rr�<module>rsS����
����@�@�@�@�
�����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  �

�����9V��"�dZddlZdd�Zdd�ZdS)z�Remove sent_to_server column from wordpress_incident table.

The sent_to_server field is no longer needed for WordPress incident tracking.
�NFc�L�|jd}|�|d��dS)N�wordpress_incident�sent_to_server)�orm�
remove_fields��migrator�database�fake�kwargs�WordpressIncidents     ��/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/193_remove_sent_to_server_from_wordpress_incident.py�migrater	s.�� ��%9�:�����,�.>�?�?�?�?�?�c�v�|jd}|�|tjdd������dS)NrF)�null�default)r)r�
add_fields�pw�BooleanFieldrs     r�rollbackrsL�� ��%9�:��������E�5�A�A�A������r)F)�__doc__�peeweerrr�rr�<module>rsS����
����@�@�@�@�
�����rdefence360agent/migrations/__pycache__/194_add_wp_disabled_rules.cpython-311.opt-1.pyc0000644000000000000000000000404300000000000025470 0ustar  �

u�L����"�dZddlZdd�Zdd�ZdS)z�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.
�NFc�`�Gd�dtj��}|�|��dS)Nc��eZdZGd�d��Zej��Zejd���Zejd���Z	ejd���Z
ejd���Zejd���Z
ejd���ZdS)�migrate.<locals>.WPDisabledRulec��eZdZdZdZdS)�$migrate.<locals>.WPDisabledRule.Meta�wp_disabled_rules)))�rule_id�scope�scope_valueTN)�__name__�
__module__�__qualname__�db_table�indexes���i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/194_add_wp_disabled_rules.py�Metars������*�H�D�G�G�GrrF)�nullTN)rr
rr�pw�PrimaryKeyField�id�	CharFieldr	r
r�
FloatField�disabled_at�source�IntegerField�created_by_user_idrrr�WPDisabledRulers�������	E�	E�	E�	E�	E�	E�	E�	E� �R�
�
!�
!���"�,�E�*�*�*�����%�(�(�(��"�b�l��-�-�-��#�b�m��/�/�/�����5�)�)�)��,�R�_�%�8�8�8���rr)r�Model�create_model��migrator�database�fake�kwargsrs     r�migrater'
sJ��9�9�9�9�9���9�9�9�
���.�)�)�)�)�)rc�J�|jd}|�|��dS)Nr)�orm�remove_modelr"s     r�rollbackr+s)���\�"5�6�N����.�)�)�)�)�)r)F)�__doc__�peeweerr'r+rrr�<module>r.sO��������*�*�*�*�"*�*�*�*�*�*rdefence360agent/migrations/__pycache__/194_add_wp_disabled_rules.cpython-311.pyc0000644000000000000000000000404300000000000024531 0ustar  �

u�L����"�dZddlZdd�Zdd�ZdS)z�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.
�NFc�`�Gd�dtj��}|�|��dS)Nc��eZdZGd�d��Zej��Zejd���Zejd���Z	ejd���Z
ejd���Zejd���Z
ejd���ZdS)�migrate.<locals>.WPDisabledRulec��eZdZdZdZdS)�$migrate.<locals>.WPDisabledRule.Meta�wp_disabled_rules)))�rule_id�scope�scope_valueTN)�__name__�
__module__�__qualname__�db_table�indexes���i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/194_add_wp_disabled_rules.py�Metars������*�H�D�G�G�GrrF)�nullTN)rr
rr�pw�PrimaryKeyField�id�	CharFieldr	r
r�
FloatField�disabled_at�source�IntegerField�created_by_user_idrrr�WPDisabledRulers�������	E�	E�	E�	E�	E�	E�	E�	E� �R�
�
!�
!���"�,�E�*�*�*�����%�(�(�(��"�b�l��-�-�-��#�b�m��/�/�/�����5�)�)�)��,�R�_�%�8�8�8���rr)r�Model�create_model��migrator�database�fake�kwargsrs     r�migrater'
sJ��9�9�9�9�9���9�9�9�
���.�)�)�)�)�)rc�J�|jd}|�|��dS)Nr)�orm�remove_modelr"s     r�rollbackr+s)���\�"5�6�N����.�)�)�)�)�)r)F)�__doc__�peeweerr'r+rrr�<module>r.sO��������*�*�*�*�"*�*�*�*�*�*rdefence360agent/migrations/__pycache__/194_create_nonprivileged_config.cpython-311.opt-1.pyc0000644000000000000000000000247600000000000026716 0ustar  �

;gV&����N�dZddlZddlmZeje��Zdd�Zdd�ZdS)z[
Create imunify360-merged-nonprivileged.config with settings needed by non-root processes.
�N)�MergerFc���|rdS	tj��t�d��dS#t$r&}t�d|��Yd}~dSd}~wwxYw)Nz)Successfully created nonprivileged configz)Failed to create nonprivileged config: %s)r�update_merged_config�logger�info�	Exception�error)�migrator�database�fake�kwargs�excs     �o/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/194_create_nonprivileged_config.py�migraters�������

�	�#�%�%�%����?�@�@�@�@�@���
�
�
����7��	
�	
�	
�	
�	
�	
�	
�	
�	
�����
���s�-5�
A%�A � A%c��dS)N�)r
rrr
s    r�rollbackrs���D�)F)	�__doc__�logging� defence360agent.contracts.configr�	getLogger�__name__rrrrrr�<module>rsp��������3�3�3�3�3�3�	��	�8�	$�	$��
�
�
�
�"	�	�	�	�	�	rdefence360agent/migrations/__pycache__/194_create_nonprivileged_config.cpython-311.pyc0000644000000000000000000000247600000000000025757 0ustar  �

;gV&����N�dZddlZddlmZeje��Zdd�Zdd�ZdS)z[
Create imunify360-merged-nonprivileged.config with settings needed by non-root processes.
�N)�MergerFc���|rdS	tj��t�d��dS#t$r&}t�d|��Yd}~dSd}~wwxYw)Nz)Successfully created nonprivileged configz)Failed to create nonprivileged config: %s)r�update_merged_config�logger�info�	Exception�error)�migrator�database�fake�kwargs�excs     �o/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/194_create_nonprivileged_config.py�migraters�������

�	�#�%�%�%����?�@�@�@�@�@���
�
�
����7��	
�	
�	
�	
�	
�	
�	
�	
�	
�����
���s�-5�
A%�A � A%c��dS)N�)r
rrr
s    r�rollbackrs���D�)F)	�__doc__�logging� defence360agent.contracts.configr�	getLogger�__name__rrrrrr�<module>rsp��������3�3�3�3�3�3�	��	�8�	$�	$��
�
�
�
�"	�	�	�	�	�	rdefence360agent/migrations/__pycache__/195_create_wordpress_site.cpython-311.opt-1.pyc0000644000000000000000000000306600000000000025575 0ustar  �

��j�C�=��J�dZddlmZmZmZGd�de��Zd	d�Zd	d�ZdS)
z�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.
�)�IntegerField�	CharField�Modelc�r�eZdZGd�d��Zedd���Zed���Zed���ZdS)�
WordpressSitec��eZdZdZdS)�WordpressSite.Meta�wordpress_siteN)�__name__�
__module__�__qualname__�db_table���i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/195_create_wordpress_site.py�Metar	s������#���rrTF)�primary_key�null)rN)	rrr
rr�docroot�domainr�uidrrrrrsp������$�$�$�$�$�$�$�$��i�D�u�5�5�5�G�
�Y�E�
"�
"�
"�F�
�,�E�
"�
"�
"�C�C�CrrFc�:�|�t��dS�N)�create_modelr��migrator�database�fake�kwargss    r�migrater s�����-�(�(�(�(�(rc��dSrrrs    r�rollbackr"s���DrN)F)�__doc__�peeweerrrrr r"rrr�<module>r%s�����2�1�1�1�1�1�1�1�1�1�#�#�#�#�#�E�#�#�#�)�)�)�)�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/195_create_wordpress_site.cpython-311.pyc0000644000000000000000000000306600000000000024636 0ustar  �

��j�C�=��J�dZddlmZmZmZGd�de��Zd	d�Zd	d�ZdS)
z�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.
�)�IntegerField�	CharField�Modelc�r�eZdZGd�d��Zedd���Zed���Zed���ZdS)�
WordpressSitec��eZdZdZdS)�WordpressSite.Meta�wordpress_siteN)�__name__�
__module__�__qualname__�db_table���i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/195_create_wordpress_site.py�Metar	s������#���rrTF)�primary_key�null)rN)	rrr
rr�docroot�domainr�uidrrrrrsp������$�$�$�$�$�$�$�$��i�D�u�5�5�5�G�
�Y�E�
"�
"�
"�F�
�,�E�
"�
"�
"�C�C�CrrFc�:�|�t��dS�N)�create_modelr��migrator�database�fake�kwargss    r�migrater s�����-�(�(�(�(�(rc��dSrrrs    r�rollbackr"s���DrN)F)�__doc__�peeweerrrrr r"rrr�<module>r%s�����2�1�1�1�1�1�1�1�1�1�#�#�#�#�#�E�#�#�#�)�)�)�)�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/196_add_disabled_rules_sync_ts.cpython-311.opt-1.pyc0000644000000000000000000000214100000000000026523 0ustar  �

�T�ϳ��2��&�dZddlmZdd�Zdd�ZdS)zzAdd disabled_rules_sync_ts field to wordpress_site table.

Tracks when disabled-rules.php was last written for each site.
�)�
FloatFieldFc�l�|jd}|�|tdd������dS)N�wordpress_siteT)�null�default)�disabled_rules_sync_ts)�orm�
add_fieldsr��migrator�database�fake�kwargs�
WordpressSites     �n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/196_add_disabled_rules_sync_ts.py�migrater	sI���L�!1�2�M�����)�t�T�B�B�B�������c�L�|jd}|�|d��dS)Nrr)r	�
remove_fieldsrs     r�rollbackrs,���L�!1�2�M����=�*B�C�C�C�C�CrN)F)�__doc__�peeweerrr�rr�<module>rs[����
����������D�D�D�D�D�Drdefence360agent/migrations/__pycache__/196_add_disabled_rules_sync_ts.cpython-311.pyc0000644000000000000000000000214100000000000025564 0ustar  �

�T�ϳ��2��&�dZddlmZdd�Zdd�ZdS)zzAdd disabled_rules_sync_ts field to wordpress_site table.

Tracks when disabled-rules.php was last written for each site.
�)�
FloatFieldFc�l�|jd}|�|tdd������dS)N�wordpress_siteT)�null�default)�disabled_rules_sync_ts)�orm�
add_fieldsr��migrator�database�fake�kwargs�
WordpressSites     �n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/196_add_disabled_rules_sync_ts.py�migrater	sI���L�!1�2�M�����)�t�T�B�B�B�������c�L�|jd}|�|d��dS)Nrr)r	�
remove_fieldsrs     r�rollbackrs,���L�!1�2�M����=�*B�C�C�C�C�CrN)F)�__doc__�peeweerrr�rr�<module>rs[����
����������D�D�D�D�D�Dr././@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  �

3;�~�F���&�dZddlmZdd�Zdd�ZdS)z�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.
�)�TimestampFieldFc��|rdSd�|�d��D��}d|vr4|jd}|�|td������dSdS)Nc��g|]	}|j��
S�)�name)�.0�cols  �z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/197_add_wordpress_site_manually_deleted_at.py�
<listcomp>zmigrate.<locals>.<listcomp>
s��J�J�J�C�s�x�J�J�J��wordpress_site�manually_deleted_atT)�null)r)�get_columns�orm�add_columnsr)�migrator�database�fake�kwargs�columns�
WordpressSites      r
�migrater
s�������J�J�8�#7�#7�8H�#I�#I�J�J�J�G��G�+�+� ��%5�6�
�����~�4�/H�/H�/H�	�	
�	
�	
�	
�	
�,�+rc��dS)Nr)rrrrs    r
�rollbackrs���DrN)F)�__doc__�peeweerrrrrr
�<module>rsU����"�!�!�!�!�!�
�
�
�
�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/197_add_wordpress_site_manually_deleted_at.cpython-311.pyc0000644000000000000000000000261100000000000030174 0ustar  �

3;�~�F���&�dZddlmZdd�Zdd�ZdS)z�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.
�)�TimestampFieldFc��|rdSd�|�d��D��}d|vr4|jd}|�|td������dSdS)Nc��g|]	}|j��
S�)�name)�.0�cols  �z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/197_add_wordpress_site_manually_deleted_at.py�
<listcomp>zmigrate.<locals>.<listcomp>
s��J�J�J�C�s�x�J�J�J��wordpress_site�manually_deleted_atT)�null)r)�get_columns�orm�add_columnsr)�migrator�database�fake�kwargs�columns�
WordpressSites      r
�migrater
s�������J�J�8�#7�#7�8H�#I�#I�J�J�J�G��G�+�+� ��%5�6�
�����~�4�/H�/H�/H�	�	
�	
�	
�	
�	
�,�+rc��dS)Nr)rrrrs    r
�rollbackrs���DrN)F)�__doc__�peeweerrrrrr
�<module>rsU����"�!�!�!�!�!�
�
�
�
�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/198_add_wordpress_site_version.cpython-311.opt-1.pyc0000644000000000000000000000256400000000000026634 0ustar  �

�eA�Q�9��&�dZddlmZdd�Zdd�ZdS)z�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.
�)�	CharFieldFc��|rdSd�|�d��D��}d|vr5|jd}|�|tdd������dSdS)Nc��g|]	}|j��
S�)�name)�.0�cols  �n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/198_add_wordpress_site_version.py�
<listcomp>zmigrate.<locals>.<listcomp>
s��J�J�J�C�s�x�J�J�J��wordpress_site�versionz1.0.0F)�default�null)r)�get_columns�orm�add_columnsr)�migrator�database�fake�kwargs�columns�
WordpressSites      r
�migrater
s�������J�J�8�#7�#7�8H�#I�#I�J�J�J�G����� ��%5�6�
�����9�W�5�#I�#I�#I�	�	
�	
�	
�	
�	
� �rc��dS)Nr)rrrrs    r
�rollbackrs���DrN)F)�__doc__�peeweerrrrrr
�<module>rsU����������
�
�
�
�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/198_add_wordpress_site_version.cpython-311.pyc0000644000000000000000000000256400000000000025675 0ustar  �

�eA�Q�9��&�dZddlmZdd�Zdd�ZdS)z�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.
�)�	CharFieldFc��|rdSd�|�d��D��}d|vr5|jd}|�|tdd������dSdS)Nc��g|]	}|j��
S�)�name)�.0�cols  �n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/198_add_wordpress_site_version.py�
<listcomp>zmigrate.<locals>.<listcomp>
s��J�J�J�C�s�x�J�J�J��wordpress_site�versionz1.0.0F)�default�null)r)�get_columns�orm�add_columnsr)�migrator�database�fake�kwargs�columns�
WordpressSites      r
�migrater
s�������J�J�8�#7�#7�8H�#I�#I�J�J�J�G����� ��%5�6�
�����9�W�5�#I�#I�#I�	�	
�	
�	
�	
�	
� �rc��dS)Nr)rrrrs    r
�rollbackrs���DrN)F)�__doc__�peeweerrrrrr
�<module>rsU����������
�
�
�
�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/199_proactive_log_permission.cpython-311.opt-1.pyc0000644000000000000000000000526100000000000026306 0ustar  �

s@�����f�dZddlmZmZmZmZddlmZmZm	Z	m
Z
Gd�de��Zd
d�Zd
d�Z
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.
�)�	CharField�Check�Model�	TextField)�	AV_REPORT�FULL�LOG�NAc
���eZdZGd�d��Zed���Zeded�e	e
e����ge���Zeded�e	e
e����ge
���Zd	S)
�FeatureManagementPermsc��eZdZdZdS)�FeatureManagementPerms.Meta�feature_management_permissionsN)�__name__�
__module__�__qualname__�db_table���l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/199_proactive_log_permission.py�Metars������3���rrT)�uniqueFzproactive in ('{}','{}','{}'))�null�constraints�defaultzav in ('{}','{}','{}')N)rrrrr�userrr�formatr
r	r�	proactiver�avrrrrrs�������4�4�4�4�4�4�4�4��9�D�!�!�!�D��	�
��E�1�8�8��S�$�G�G�H�H�
�����I�
��
��E�*�1�1�"�i��F�F�G�G�
��
�
�
�B�B�BrrFc��|rdS|�d��|�t��|�d��|�d��dS)NzWALTER TABLE feature_management_permissions RENAME TO feature_management_permissions_oldz�INSERT 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��migrator�database�fake�kwargss    r�migrater()sx�������L�L�	7����
���0�1�1�1��L�L�	M����
�L�L�@�A�A�A�A�Arc��dS)Nrr#s    r�rollbackr*8s���DrN)F)�__doc__�peeweerrrr�,defence360agent.feature_management.constantsrrr	r
rr(r*rrr�<module>r.s�����6�5�5�5�5�5�5�5�5�5�5�5������������������U����*B�B�B�B�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/199_proactive_log_permission.cpython-311.pyc0000644000000000000000000000526100000000000025347 0ustar  �

s@�����f�dZddlmZmZmZmZddlmZmZm	Z	m
Z
Gd�de��Zd
d�Zd
d�Z
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.
�)�	CharField�Check�Model�	TextField)�	AV_REPORT�FULL�LOG�NAc
���eZdZGd�d��Zed���Zeded�e	e
e����ge���Zeded�e	e
e����ge
���Zd	S)
�FeatureManagementPermsc��eZdZdZdS)�FeatureManagementPerms.Meta�feature_management_permissionsN)�__name__�
__module__�__qualname__�db_table���l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/199_proactive_log_permission.py�Metars������3���rrT)�uniqueFzproactive in ('{}','{}','{}'))�null�constraints�defaultzav in ('{}','{}','{}')N)rrrrr�userrr�formatr
r	r�	proactiver�avrrrrrs�������4�4�4�4�4�4�4�4��9�D�!�!�!�D��	�
��E�1�8�8��S�$�G�G�H�H�
�����I�
��
��E�*�1�1�"�i��F�F�G�G�
��
�
�
�B�B�BrrFc��|rdS|�d��|�t��|�d��|�d��dS)NzWALTER TABLE feature_management_permissions RENAME TO feature_management_permissions_oldz�INSERT 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��migrator�database�fake�kwargss    r�migrater()sx�������L�L�	7����
���0�1�1�1��L�L�	M����
�L�L�@�A�A�A�A�Arc��dS)Nrr#s    r�rollbackr*8s���DrN)F)�__doc__�peeweerrrr�,defence360agent.feature_management.constantsrrr	r
rr(r*rrr�<module>r.s�����6�5�5�5�5�5�5�5�5�5�5�5������������������U����*B�B�B�B�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/200_seed_per_user_waf_enabled.cpython-311.opt-1.pyc0000644000000000000000000000623700000000000026317 0ustar  �

��-����ddlZddlZddlmZmZmZddlmZejddd���Z	ej
e��Zd
d�Z
d
d	�ZdS)�N)�
UserConfig�UserType�choose_value_from_config)�importerzimav.malwarelib.utils.user_list�panel_users)�module�name�defaultFc��|st�dStj��}tj|��		|�t����}n?#t
$r2t�d��Y|���dSwxYw|D]�}	|d}n:#ttf$r&}t�d||��Yd}~�>d}~wwxYw	tdd|���\}	}
|
tjkr�mn3#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 seed�userz=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)r�asyncio�new_event_loop�set_event_loop�run_until_complete�	Exception�logger�	exception�close�KeyError�	TypeError�warningrr�ROOTr�dict_to_config)�migrator�database�fake�kwargs�loop�users�entryr�e�_�sources           �m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/200_seed_per_user_waf_enabled.py�migrater)sg����{�"����!�#�#�D���4� � � �/�	��+�+�K�M�M�:�:�E�E���	�	�	����F�
�
�
�
�P	
�
�
������Y	�����$	�$	�E�	
� ��=�����i�(�
�
�
���� ���	������������
����
�4��!�%����	��6�
�X�]�*�*��+���
�
�
����N������
���������

����

��H�-�-�-�<�<� �=�$�"7�8�%)�=�������
�
�
����J���������������
����?$	�L	
�
�
��������
�
�������s��!A�F.�$B�;F.�B�F.�B$�#F.�$C�5C�F.�C�F.�%D�F.�
D6�D1�,F.�1D6�6F.�:)E$�#F.�$
F�.F�
F.�F�F.�.Gc��dS)N�)rrr r!s    r(�rollbackr,Ls���D�)F)r�logging� defence360agent.contracts.configrrr�defence360agent.utilsr�getr�	getLogger�__name__rr)r,r+r-r(�<module>r4s���������������������
+�*�*�*�*�*��h�l�,�	������
��	�8�	$�	$��5�5�5�5�p	�	�	�	�	�	r-defence360agent/migrations/__pycache__/200_seed_per_user_waf_enabled.cpython-311.pyc0000644000000000000000000000623700000000000025360 0ustar  �

��-����ddlZddlZddlmZmZmZddlmZejddd���Z	ej
e��Zd
d�Z
d
d	�ZdS)�N)�
UserConfig�UserType�choose_value_from_config)�importerzimav.malwarelib.utils.user_list�panel_users)�module�name�defaultFc��|st�dStj��}tj|��		|�t����}n?#t
$r2t�d��Y|���dSwxYw|D]�}	|d}n:#ttf$r&}t�d||��Yd}~�>d}~wwxYw	tdd|���\}	}
|
tjkr�mn3#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 seed�userz=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)r�asyncio�new_event_loop�set_event_loop�run_until_complete�	Exception�logger�	exception�close�KeyError�	TypeError�warningrr�ROOTr�dict_to_config)�migrator�database�fake�kwargs�loop�users�entryr�e�_�sources           �m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/200_seed_per_user_waf_enabled.py�migrater)sg����{�"����!�#�#�D���4� � � �/�	��+�+�K�M�M�:�:�E�E���	�	�	����F�
�
�
�
�P	
�
�
������Y	�����$	�$	�E�	
� ��=�����i�(�
�
�
���� ���	������������
����
�4��!�%����	��6�
�X�]�*�*��+���
�
�
����N������
���������

����

��H�-�-�-�<�<� �=�$�"7�8�%)�=�������
�
�
����J���������������
����?$	�L	
�
�
��������
�
�������s��!A�F.�$B�;F.�B�F.�B$�#F.�$C�5C�F.�C�F.�%D�F.�
D6�D1�,F.�1D6�6F.�:)E$�#F.�$
F�.F�
F.�F�F.�.Gc��dS)N�)rrr r!s    r(�rollbackr,Ls���D�)F)r�logging� defence360agent.contracts.configrrr�defence360agent.utilsr�getr�	getLogger�__name__rr)r,r+r-r(�<module>r4s���������������������
+�*�*�*�*�*��h�l�,�	������
��	�8�	$�	$��5�5�5�5�p	�	�	�	�	�	r-defence360agent/migrations/__pycache__/201_rerender_nonprivileged_config.cpython-311.opt-1.pyc0000644000000000000000000000235500000000000027242 0ustar  �

j�X���N�dZddlZddlmZeje��Zdd�Zdd�ZdS)z
Re-render imunify360-merged-nonprivileged.config so the newly-split
MALWARE_SCANNING.enable_scan_modsec key lands on upgrade.
�N)�MergerFc��|rdS	tj��dS#t$r&}t�d|��Yd}~dSd}~wwxYw)Nz,Failed to re-render nonprivileged config: %s)r�update_merged_config�	Exception�logger�error)�migrator�database�fake�kwargs�excs     �q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/201_rerender_nonprivileged_config.py�migraters�������
��#�%�%�%�%�%���
�
�
����:��	
�	
�	
�	
�	
�	
�	
�	
�	
�����
���s��
A�A�Ac��dS)N�)r	r
rrs    r�rollbackrs���D�)F)	�__doc__�logging� defence360agent.contracts.configr�	getLogger�__name__rrrrrr�<module>rsp��������3�3�3�3�3�3�	��	�8�	$�	$��

�

�

�

�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/201_rerender_nonprivileged_config.cpython-311.pyc0000644000000000000000000000235500000000000026303 0ustar  �

j�X���N�dZddlZddlmZeje��Zdd�Zdd�ZdS)z
Re-render imunify360-merged-nonprivileged.config so the newly-split
MALWARE_SCANNING.enable_scan_modsec key lands on upgrade.
�N)�MergerFc��|rdS	tj��dS#t$r&}t�d|��Yd}~dSd}~wwxYw)Nz,Failed to re-render nonprivileged config: %s)r�update_merged_config�	Exception�logger�error)�migrator�database�fake�kwargs�excs     �q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/201_rerender_nonprivileged_config.py�migraters�������
��#�%�%�%�%�%���
�
�
����:��	
�	
�	
�	
�	
�	
�	
�	
�	
�����
���s��
A�A�Ac��dS)N�)r	r
rrs    r�rollbackrs���D�)F)	�__doc__�logging� defence360agent.contracts.configr�	getLogger�__name__rrrrrr�<module>rsp��������3�3�3�3�3�3�	��	�8�	$�	$��

�

�

�

�	�	�	�	�	�	rdefence360agent/migrations/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000000030500000000000022330 0ustar  �

s�����s���dS)N�r��X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/__init__.py�<module>rs���rdefence360agent/migrations/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000030500000000000021371 0ustar  �

s�����s���dS)N�r��X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/__init__.py�<module>rs���rdefence360agent/migrations/__pycache__/conf.cpython-311.opt-1.pyc0000644000000000000000000000057200000000000021524 0ustar  �

���j�	��F�ddlmZd�ej��ZdS)�)�Modelzsqlite:///{}N)� defence360agent.contracts.configr�format�PATH�DATABASE���T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/conf.py�<module>rs0��2�2�2�2�2�2�� � ���,�,���r	defence360agent/migrations/__pycache__/conf.cpython-311.pyc0000644000000000000000000000057200000000000020565 0ustar  �

���j�	��F�ddlmZd�ej��ZdS)�)�Modelzsqlite:///{}N)� defence360agent.contracts.configr�format�PATH�DATABASE���T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/conf.py�<module>rs0��2�2�2�2�2�2�� � ���,�,���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  �

ք������2�ddlmZmZGd�de��ZdS)�)�IntegrityError�Modelc�(�eZdZdZed���ZdS)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��	|jj���5|jdi|��dfcddd��S#1swxYwYdS#t$r�g}|���D]b\}}t
||��}|jjr|j|jj	j
vn|j	}|js|r|�||k���c|j
|�dfcYSwxYw)NTF�)�_meta�database�atomic�creater�items�getattr�
composite_key�name�primary_key�field_names�unique�append�get)�cls�kwargs�query�
field_name�value�field�field_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�!�s�z�+�+�F�+�+�T�1�
2�
2�
2�
2�
2�
2�
2�
2�
2�
2�
2�
2����
2�
2�
2�
2�
2�
2���	*�	*�	*��E�%+�\�\�^�^�
1�
1�!�
�E���Z�0�0���y�.�+�E�J�#�)�"7�"C�C�C��*�%�
�<�1�#7�1��L�L��%��0�0�0���3�7�E�?�E�)�)�)�)�	*���s2�A	�<�A	�A�A	�A�A	�	BC�CN)�__name__�
__module__�__qualname__�__doc__�classmethodrr�rrrs9��������
�*�*��[�*�*�*r#rN)�peeweerr�	BaseModelrr#r�<module>r&sQ��5�5�5�5�5�5�5�5�*�*�*�*�*�I�*�*�*�*�*r#defence360agent/model/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000360200000000000020320 0ustar  �

ք������2�ddlmZmZGd�de��ZdS)�)�IntegrityError�Modelc�(�eZdZdZed���ZdS)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��	|jj���5|jdi|��dfcddd��S#1swxYwYdS#t$r�g}|���D]b\}}t
||��}|jjr|j|jj	j
vn|j	}|js|r|�||k���c|j
|�dfcYSwxYw)NTF�)�_meta�database�atomic�creater�items�getattr�
composite_key�name�primary_key�field_names�unique�append�get)�cls�kwargs�query�
field_name�value�field�field_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�!�s�z�+�+�F�+�+�T�1�
2�
2�
2�
2�
2�
2�
2�
2�
2�
2�
2�
2����
2�
2�
2�
2�
2�
2���	*�	*�	*��E�%+�\�\�^�^�
1�
1�!�
�E���Z�0�0���y�.�+�E�J�#�)�"7�"C�C�C��*�%�
�<�1�#7�1��L�L��%��0�0�0���3�7�E�?�E�)�)�)�)�	*���s2�A	�<�A	�A�A	�A�A	�	BC�CN)�__name__�
__module__�__qualname__�__doc__�classmethodrr�rrrs9��������
�*�*��[�*�*�*r#rN)�peeweerr�	BaseModelrr#r�<module>r&sQ��5�5�5�5�5�5�5�5�*�*�*�*�*�I�*�*�*�*�*r#defence360agent/model/__pycache__/analyst_cleanup.cpython-311.opt-1.pyc0000644000000000000000000001332100000000000022701 0ustar  �

4e��L���N�ddlZddlmZmZddlmZmZmZGd�de��ZdS)�N)�Model�instance)�datetime�timezone�	timedeltac��eZdZdZGd�d��Zej��Zejd���Z	ejd���Z
ejd���Zej
dejej�����Zejddejd��g�	��Zej
dejej�����Zed
���Zedd
���Zedd���Zededzfd���Zed���Zed���ZdS)�AnalystCleanupRequestz
    Model for storing analyst cleanup requests.
    Tracks request details and status for each cleanup request submitted.
    c� �eZdZejZdZdS)�AnalystCleanupRequest.Meta�analyst_cleanup_requestsN)�__name__�
__module__�__qualname__r�db�database�db_table���Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/analyst_cleanup.py�Metars�������;��-���rrF)�null)r�default�pendingz/status in ('pending','in_progress','completed'))rr�constraintsc�2�|�|||���S)zCreate a new cleanup request)�username�
zendesk_id�ticket_link)�create)�clsrrrs    r�create_requestz$AnalystCleanupRequest.create_request"s&���z�z��*�+��
�
�	
r�2rc��|����|j|k���|j������|���|��S)z$Get all requests for a specific user)�select�wherer�order_by�
created_at�desc�limit�offset)r rr)r*s    r�get_user_requestsz'AnalystCleanupRequest.get_user_requests)sY��
�J�J�L�L�
�U�3�<�8�+�
,�
,�
�X�c�n�)�)�+�+�
,�
,�
�U�5�\�\�
�V�F�^�^�	
rc���|����|j������|���|��S)zGet all requests for a sever)r$r&r'r(r)r*)r r)r*s   r�get_all_requestsz&AnalystCleanupRequest.get_all_requests4sE��
�J�J�L�L�
�X�c�n�)�)�+�+�
,�
,�
�U�5�\�\�
�V�F�^�^�		
r�returnNc��|����|j|k|j�ddg��z���d�����}|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_progress�N)r$r%r�status�in_r)�firstr)r r�active_requests   r�get_active_request_linkz-AnalystCleanupRequest.get_active_request_link>ss��
�J�J�L�L�
�U����)��:�>�>�9�m�"<�=�=�?����U�1�X�X�
�%�'�'�	�.<�E�~�)�)��Erc��|�||����|j|k�����S)zUpdate the status of a request)r2�last_updated)�updater%r�execute)r r�
new_statusr8s    r�
update_statusz#AnalystCleanupRequest.update_statusPs9��
�J�J�j�|�J�D�D�
�U�3�>�Z�/�
0�
0�
�W�Y�Y�	
rc��tjtj��t	d���z
}t
�t
jt
jt
j	t
j
���t
j	�ddg��t
j	dkt
j
|kzz��S)z�
        Returns a query to fetch active cleanup requests and recently completed
        requests for the specified users.
        �)�daysrr0�	completed)
r�nowr�utcrr	r$rrr2r8r%r3)r �three_days_agos  r�get_all_relevant_requestsz/AnalystCleanupRequest.get_all_relevant_requestsYs���"��h�l�3�3�i�Q�6G�6G�6G�G��$�+�+�!�*�!�,�!�(�!�.�	
�
�
�%�
"�
)�
-�
-�y�-�.H�
I�
I�&�-��<�(�5��G�I�
�
�
�	
r)r"r)r
rr�__doc__r�pw�	AutoField�id�	CharFieldrr�	TextFieldr�TimestampFieldrrArrBr'�Checkr2r8�classmethodr!r+r-�strr6r<rDrrrr	r	s���������
.�.�.�.�.�.�.�.�
�����B��r�|��'�'�'�H����5�)�)�)�J��"�,�E�*�*�*�K�"��"�
�L�H�L���6�6����J��R�\�
���B�H�F�G�G�
����F�%�2�$�
�L�H�L���6�6����L��
�
��[�
��
�
�
��[�
��
�
�
��[�
��F�#��*�F�F�F��[�F�"�
�
��[�
��
�
��[�
�
�
rr	)	�peeweerF�defence360agent.modelrrrrrr	rrr�<module>rQs�������1�1�1�1�1�1�1�1�2�2�2�2�2�2�2�2�2�2�f
�f
�f
�f
�f
�E�f
�f
�f
�f
�f
rdefence360agent/model/__pycache__/analyst_cleanup.cpython-311.pyc0000644000000000000000000001332100000000000021742 0ustar  �

4e��L���N�ddlZddlmZmZddlmZmZmZGd�de��ZdS)�N)�Model�instance)�datetime�timezone�	timedeltac��eZdZdZGd�d��Zej��Zejd���Z	ejd���Z
ejd���Zej
dejej�����Zejddejd��g�	��Zej
dejej�����Zed
���Zedd
���Zedd���Zededzfd���Zed���Zed���ZdS)�AnalystCleanupRequestz
    Model for storing analyst cleanup requests.
    Tracks request details and status for each cleanup request submitted.
    c� �eZdZejZdZdS)�AnalystCleanupRequest.Meta�analyst_cleanup_requestsN)�__name__�
__module__�__qualname__r�db�database�db_table���Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/analyst_cleanup.py�Metars�������;��-���rrF)�null)r�default�pendingz/status in ('pending','in_progress','completed'))rr�constraintsc�2�|�|||���S)zCreate a new cleanup request)�username�
zendesk_id�ticket_link)�create)�clsrrrs    r�create_requestz$AnalystCleanupRequest.create_request"s&���z�z��*�+��
�
�	
r�2rc��|����|j|k���|j������|���|��S)z$Get all requests for a specific user)�select�wherer�order_by�
created_at�desc�limit�offset)r rr)r*s    r�get_user_requestsz'AnalystCleanupRequest.get_user_requests)sY��
�J�J�L�L�
�U�3�<�8�+�
,�
,�
�X�c�n�)�)�+�+�
,�
,�
�U�5�\�\�
�V�F�^�^�	
rc���|����|j������|���|��S)zGet all requests for a sever)r$r&r'r(r)r*)r r)r*s   r�get_all_requestsz&AnalystCleanupRequest.get_all_requests4sE��
�J�J�L�L�
�X�c�n�)�)�+�+�
,�
,�
�U�5�\�\�
�V�F�^�^�		
r�returnNc��|����|j|k|j�ddg��z���d�����}|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_progress�N)r$r%r�status�in_r)�firstr)r r�active_requests   r�get_active_request_linkz-AnalystCleanupRequest.get_active_request_link>ss��
�J�J�L�L�
�U����)��:�>�>�9�m�"<�=�=�?����U�1�X�X�
�%�'�'�	�.<�E�~�)�)��Erc��|�||����|j|k�����S)zUpdate the status of a request)r2�last_updated)�updater%r�execute)r r�
new_statusr8s    r�
update_statusz#AnalystCleanupRequest.update_statusPs9��
�J�J�j�|�J�D�D�
�U�3�>�Z�/�
0�
0�
�W�Y�Y�	
rc��tjtj��t	d���z
}t
�t
jt
jt
j	t
j
���t
j	�ddg��t
j	dkt
j
|kzz��S)z�
        Returns a query to fetch active cleanup requests and recently completed
        requests for the specified users.
        �)�daysrr0�	completed)
r�nowr�utcrr	r$rrr2r8r%r3)r �three_days_agos  r�get_all_relevant_requestsz/AnalystCleanupRequest.get_all_relevant_requestsYs���"��h�l�3�3�i�Q�6G�6G�6G�G��$�+�+�!�*�!�,�!�(�!�.�	
�
�
�%�
"�
)�
-�
-�y�-�.H�
I�
I�&�-��<�(�5��G�I�
�
�
�	
r)r"r)r
rr�__doc__r�pw�	AutoField�id�	CharFieldrr�	TextFieldr�TimestampFieldrrArrBr'�Checkr2r8�classmethodr!r+r-�strr6r<rDrrrr	r	s���������
.�.�.�.�.�.�.�.�
�����B��r�|��'�'�'�H����5�)�)�)�J��"�,�E�*�*�*�K�"��"�
�L�H�L���6�6����J��R�\�
���B�H�F�G�G�
����F�%�2�$�
�L�H�L���6�6����L��
�
��[�
��
�
�
��[�
��
�
�
��[�
��F�#��*�F�F�F��[�F�"�
�
��[�
��
�
��[�
�
�
rr	)	�peeweerF�defence360agent.modelrrrrrr	rrr�<module>rQs�������1�1�1�1�1�1�1�1�2�2�2�2�2�2�2�2�2�2�f
�f
�f
�f
�f
�E�f
�f
�f
�f
�f
rdefence360agent/model/__pycache__/event_hook.cpython-311.opt-1.pyc0000644000000000000000000000676300000000000021674 0ustar  �

��$��^,^��^�ddlmZddlmZmZmZddlmZmZddlm	Z	Gd�de��Z
dS)�)�time)�	CharField�IntegerField�BooleanField)�instance�Model)�
FilenameFieldc���eZdZdZGd�d��Zed���Zed���Ze	dd����Z
ed���Z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__r�db�database�db_table���U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/event_hook.py�Metar
s�������;�����rrF)�nullc�8�tt����S�N)�intrrrr�<lambda>zEventHook.<lambda>s��s�4�6�6�{�{�r)r�default)rc��|���}|dkr|�|j|k��}t|�����S)N�all)�select�where�event�list�dicts)�clsr#�qs   r�list_eventszEventHook.list_eventssD���J�J�L�L���E�>�>�����	�U�*�+�+�A��A�G�G�I�I���rc��|����|j|k|j|kz��}|���rdS|�|||���}|���S)N)r#�path�native)r!r"r#r*�exists�create�as_dict)r&r#r*r+r'�hooks      r�add_hookzEventHook.add_hook$sj���J�J�L�L����	�U� 2�s�x�4�7G�H�I�I���8�8�:�:�	��4��z�z��D��z�@�@���|�|�~�~�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���J�J�L�L����	�U� 2�s�x�4�7G�H�I�I���x�x�z�z�	��4��u�u�w�w���|�|�~�~���������rc�8�|j|j|j|jd�S)N�r*r#�createdr+r7)�selfs rr.zEventHook.as_dict6s%���I��Z��|��k�	
�
�	
rN)F)rrr�__doc__rr	r*rr#rr8rr+�classmethodr(r0r5r.rrrrr	s���������
 � � � � � � � �
�=�e�$�$�$�D��I�5�!�!�!�E��l��/B�/B�C�C�C�G��\�%�
(�
(�
(�F�����[�������[������[��
�
�
�
�
rrN)r�peeweerrr�defence360agent.modelrr�$defence360agent.model.simplificationr	rrrr�<module>r?s���������8�8�8�8�8�8�8�8�8�8�1�1�1�1�1�1�1�1�>�>�>�>�>�>�3
�3
�3
�3
�3
��3
�3
�3
�3
�3
rdefence360agent/model/__pycache__/event_hook.cpython-311.pyc0000644000000000000000000000676300000000000020735 0ustar  �

��$��^,^��^�ddlmZddlmZmZmZddlmZmZddlm	Z	Gd�de��Z
dS)�)�time)�	CharField�IntegerField�BooleanField)�instance�Model)�
FilenameFieldc���eZdZdZGd�d��Zed���Zed���Ze	dd����Z
ed���Z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__r�db�database�db_table���U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/event_hook.py�Metar
s�������;�����rrF)�nullc�8�tt����S�N)�intrrrr�<lambda>zEventHook.<lambda>s��s�4�6�6�{�{�r)r�default)rc��|���}|dkr|�|j|k��}t|�����S)N�all)�select�where�event�list�dicts)�clsr#�qs   r�list_eventszEventHook.list_eventssD���J�J�L�L���E�>�>�����	�U�*�+�+�A��A�G�G�I�I���rc��|����|j|k|j|kz��}|���rdS|�|||���}|���S)N)r#�path�native)r!r"r#r*�exists�create�as_dict)r&r#r*r+r'�hooks      r�add_hookzEventHook.add_hook$sj���J�J�L�L����	�U� 2�s�x�4�7G�H�I�I���8�8�:�:�	��4��z�z��D��z�@�@���|�|�~�~�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���J�J�L�L����	�U� 2�s�x�4�7G�H�I�I���x�x�z�z�	��4��u�u�w�w���|�|�~�~���������rc�8�|j|j|j|jd�S)N�r*r#�createdr+r7)�selfs rr.zEventHook.as_dict6s%���I��Z��|��k�	
�
�	
rN)F)rrr�__doc__rr	r*rr#rr8rr+�classmethodr(r0r5r.rrrrr	s���������
 � � � � � � � �
�=�e�$�$�$�D��I�5�!�!�!�E��l��/B�/B�C�C�C�G��\�%�
(�
(�
(�F�����[�������[������[��
�
�
�
�
rrN)r�peeweerrr�defence360agent.modelrr�$defence360agent.model.simplificationr	rrrr�<module>r?s���������8�8�8�8�8�8�8�8�8�8�1�1�1�1�1�1�1�1�>�>�>�>�>�>�3
�3
�3
�3
�3
��3
�3
�3
�3
�3
rdefence360agent/model/__pycache__/icontact.cpython-311.opt-1.pyc0000644000000000000000000000472500000000000021333 0ustar  �

v!I�l������ddlZddlmZmZmZddlmZddlmZm	Z	ddl
mZmZej
eejeiZGd�de��ZdS)�N)�	CharField�IntegerField�CompositeKey)�IContactMessageType)�Model�instance)�DAY�WEEKc��eZdZGd�d��Ze��Zed���Zed���Ze	d
d���Z
e	d
d	���ZdS)�IContactThrottlec�8�eZdZejZdZedd��ZdS)�IContactThrottle.Meta�icontact_throttle�message_type�userN)	�__name__�
__module__�__qualname__r�db�database�db_tabler�primary_key���S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/icontact.py�Metars+�������;��&��"�l�>�6�:�:���rrT)�nullr)�defaultNc�t�|�||���\}}tj��|jz
|kS)N)rr)�
get_or_create�time�	timestamp)�clsr�period_limitr�obj�_s      r�may_be_notifiedz IContactThrottle.may_be_notifieds6���"�"��4�"�H�H���Q��	���c�m�+�|�;�;rc��|�tj������|j|k|�|j�d��n
|j|k�����dS)N)r"T)�updater!�whererr�is_null�execute)r#rrs   r�refreshzIContactThrottle.refreshsh���
�
�T�Y�[�[�
�)�)�/�/����,�&*�l�C�H���T�"�"�"���D�8H�	
�	
��'�)�)�)�)�)r)N)rrrrrrrrr"�classmethodr'r-rrrrrs�������;�;�;�;�;�;�;�;�
�9�;�;�L��9�$����D���Q�'�'�'�I��<�<�<��[�<������[���rr)r!�peeweerrr� defence360agent.contracts.configr�defence360agent.modelrr�defence360agent.utils.commonr	r
�
MALWARE_FOUND�SCAN_NOT_SCHEDULED�THROTTLING_PERIODrrrr�<module>r6s�������8�8�8�8�8�8�8�8�8�8�@�@�@�@�@�@�1�1�1�1�1�1�1�1�2�2�2�2�2�2�2�2��%�s��*�D��������u�����rdefence360agent/model/__pycache__/icontact.cpython-311.pyc0000644000000000000000000000472500000000000020374 0ustar  �

v!I�l������ddlZddlmZmZmZddlmZddlmZm	Z	ddl
mZmZej
eejeiZGd�de��ZdS)�N)�	CharField�IntegerField�CompositeKey)�IContactMessageType)�Model�instance)�DAY�WEEKc��eZdZGd�d��Ze��Zed���Zed���Ze	d
d���Z
e	d
d	���ZdS)�IContactThrottlec�8�eZdZejZdZedd��ZdS)�IContactThrottle.Meta�icontact_throttle�message_type�userN)	�__name__�
__module__�__qualname__r�db�database�db_tabler�primary_key���S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/icontact.py�Metars+�������;��&��"�l�>�6�:�:���rrT)�nullr)�defaultNc�t�|�||���\}}tj��|jz
|kS)N)rr)�
get_or_create�time�	timestamp)�clsr�period_limitr�obj�_s      r�may_be_notifiedz IContactThrottle.may_be_notifieds6���"�"��4�"�H�H���Q��	���c�m�+�|�;�;rc��|�tj������|j|k|�|j�d��n
|j|k�����dS)N)r"T)�updater!�whererr�is_null�execute)r#rrs   r�refreshzIContactThrottle.refreshsh���
�
�T�Y�[�[�
�)�)�/�/����,�&*�l�C�H���T�"�"�"���D�8H�	
�	
��'�)�)�)�)�)r)N)rrrrrrrrr"�classmethodr'r-rrrrrs�������;�;�;�;�;�;�;�;�
�9�;�;�L��9�$����D���Q�'�'�'�I��<�<�<��[�<������[���rr)r!�peeweerrr� defence360agent.contracts.configr�defence360agent.modelrr�defence360agent.utils.commonr	r
�
MALWARE_FOUND�SCAN_NOT_SCHEDULED�THROTTLING_PERIODrrrr�<module>r6s�������8�8�8�8�8�8�8�8�8�8�@�@�@�@�@�@�1�1�1�1�1�1�1�1�2�2�2�2�2�2�2�2��%�s��*�D��������u�����rdefence360agent/model/__pycache__/infected_domain.cpython-311.opt-1.pyc0000644000000000000000000001325700000000000022637 0ustar  �

-<���)����ddlZddlZddlZddlmZmZmZmZddlm	Z	m
Z
eje��Z
Gd�de
��ZdS)�N)�	CharField�
FloatField�IntegerField�	TextField)�instance�Modelc���eZdZdZed���Zed���Zed���Zed���Z	e
��Zed���Z
Gd�d��Zed
d
���Zed���ZdS)�InfectedDomainListzDDomains with bad reputation, used for Reputation Management feature.T)�primary_key)�nullFc� �eZdZejZdZdS)�InfectedDomainList.Meta�infected_domain_listN)�__name__�
__module__�__qualname__r�db�database�db_table���Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/infected_domain.py�Metar!s�������;��)���rrr�2c����|����|j|j|j�����}�fd�|���D��}tj|d����}d}g}t|��D]P\}	}
|dz
}t|��|kr3|	|kr-|
\}}|\}
}|�|
|d�|D��d����Q||fS)Nc3�0�K�|]}|d�v�|V��dS)�usernameNr)�.0�row�existing_userss  �r�	<genexpr>z1InfectedDomainList.get_by_user.<locals>.<genexpr>,s:�����
�
��C�
�O�~�,M�,M�C�,M�,M�,M�,M�
�
rc�"�|d|dfS)Nr�namer)rs r�<lambda>z0InfectedDomainList.get_by_user.<locals>.<lambda>0s��s�:���F��.L�r)�keyr�c�>�g|]}|d|d|dd���S)�threat_type�vendor�	timestamp)�typer)r*r)r�ts  r�
<listcomp>z2InfectedDomainList.get_by_user.<locals>.<listcomp>>sG��$�$�$�!"�	)*�-�(8�*+�H�+�-.�{�^���$�$�$r)r�domain�threats)�select�order_byrr#r*�desc�dicts�	itertools�groupby�	enumerate�len�append)�clsr �offset�limit�query�filtered_by_user�grouped�	max_count�result�i�value�groupr/rr#s `             r�get_by_userzInfectedDomainList.get_by_user%s;����
�
���%�%��L�#�(�C�M�$6�$6�$8�$8�
�
��
�
�
�
� �;�;�=�=�
�
�
���#��"L�"L�
�
�
���	���!�'�*�*�	�	�H�A�u���N�I��F���e�#�#�!�v�+�+�!&���w�!&���$��
�
�$,�"&�$�$�&-�
$�$�$���
�
�
���y� � rc
��d�|������D��}tj���5|������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:
        c�J�i|] }|d|d|df|d��!S)r#r(r)r*r)r�rs  r�
<dictcomp>z6InfectedDomainList.refresh_domains.<locals>.<dictcomp>TsB��
�
�
���v�Y��-�(�!�H�+�6��+��
�
�
rr<zUsers for domain %s not found.r))zgoogle-safe-browsingzyandex-safe-browsing�detailsr(�spamhaus)�	phishtank�	openphishzspam domain�THREAT_TYPE_UNSPECIFIED)rr#r(r)r*N)r0r3rr�atomic�delete�execute�time�logger�warning�get�create)r9�domains�domains_to_users�existing�now�domain_infor.�userr)r(r*s           r�refresh_domainsz"InfectedDomainList.refresh_domainsJs���
�
��Z�Z�\�\�'�'�)�)�
�
�
���[�
�
�
!�
!�	�	��J�J�L�L� � �"�"�"��)�+�+�C�&�
�
��$�W�-���!1�1�1��N�N�#C�V�L�L�L��,�V�4���D�(��2�F��"���'2�)�&<�]�&K����:�-�-�&1�)�&<����#=�=�=�&3���&?�� (�����f�5�s�!�!�I��J�J�!%�#�$/�%�"+������#�
�	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	�	s�CD5�5D9�<D9N)rr)rrr�__doc__r�idrrr#r(rr*rr)r�classmethodrDr\rrrr
r
s�������N�N�	��$�	'�	'�	'�B��y�d�#�#�#�H��9�%� � � �D��)��'�'�'�K��
���I�
�Y�D�
!�
!�
!�F�*�*�*�*�*�*�*�*��"!�"!�"!��[�"!�H�,�,��[�,�,�,rr
)r4�loggingrQ�peeweerrrr�defence360agent.modelrr�	getLoggerrrRr
rrr�<module>rds���������������������������2�1�1�1�1�1�1�1�	��	�8�	$�	$��f�f�f�f�f��f�f�f�f�frdefence360agent/model/__pycache__/infected_domain.cpython-311.pyc0000644000000000000000000001325700000000000021700 0ustar  �

-<���)����ddlZddlZddlZddlmZmZmZmZddlm	Z	m
Z
eje��Z
Gd�de
��ZdS)�N)�	CharField�
FloatField�IntegerField�	TextField)�instance�Modelc���eZdZdZed���Zed���Zed���Zed���Z	e
��Zed���Z
Gd�d��Zed
d
���Zed���ZdS)�InfectedDomainListzDDomains with bad reputation, used for Reputation Management feature.T)�primary_key)�nullFc� �eZdZejZdZdS)�InfectedDomainList.Meta�infected_domain_listN)�__name__�
__module__�__qualname__r�db�database�db_table���Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/infected_domain.py�Metar!s�������;��)���rrr�2c����|����|j|j|j�����}�fd�|���D��}tj|d����}d}g}t|��D]P\}	}
|dz
}t|��|kr3|	|kr-|
\}}|\}
}|�|
|d�|D��d����Q||fS)Nc3�0�K�|]}|d�v�|V��dS)�usernameNr)�.0�row�existing_userss  �r�	<genexpr>z1InfectedDomainList.get_by_user.<locals>.<genexpr>,s:�����
�
��C�
�O�~�,M�,M�C�,M�,M�,M�,M�
�
rc�"�|d|dfS)Nr�namer)rs r�<lambda>z0InfectedDomainList.get_by_user.<locals>.<lambda>0s��s�:���F��.L�r)�keyr�c�>�g|]}|d|d|dd���S)�threat_type�vendor�	timestamp)�typer)r*r)r�ts  r�
<listcomp>z2InfectedDomainList.get_by_user.<locals>.<listcomp>>sG��$�$�$�!"�	)*�-�(8�*+�H�+�-.�{�^���$�$�$r)r�domain�threats)�select�order_byrr#r*�desc�dicts�	itertools�groupby�	enumerate�len�append)�clsr �offset�limit�query�filtered_by_user�grouped�	max_count�result�i�value�groupr/rr#s `             r�get_by_userzInfectedDomainList.get_by_user%s;����
�
���%�%��L�#�(�C�M�$6�$6�$8�$8�
�
��
�
�
�
� �;�;�=�=�
�
�
���#��"L�"L�
�
�
���	���!�'�*�*�	�	�H�A�u���N�I��F���e�#�#�!�v�+�+�!&���w�!&���$��
�
�$,�"&�$�$�&-�
$�$�$���
�
�
���y� � rc
��d�|������D��}tj���5|������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:
        c�J�i|] }|d|d|df|d��!S)r#r(r)r*r)r�rs  r�
<dictcomp>z6InfectedDomainList.refresh_domains.<locals>.<dictcomp>TsB��
�
�
���v�Y��-�(�!�H�+�6��+��
�
�
rr<zUsers for domain %s not found.r))zgoogle-safe-browsingzyandex-safe-browsing�detailsr(�spamhaus)�	phishtank�	openphishzspam domain�THREAT_TYPE_UNSPECIFIED)rr#r(r)r*N)r0r3rr�atomic�delete�execute�time�logger�warning�get�create)r9�domains�domains_to_users�existing�now�domain_infor.�userr)r(r*s           r�refresh_domainsz"InfectedDomainList.refresh_domainsJs���
�
��Z�Z�\�\�'�'�)�)�
�
�
���[�
�
�
!�
!�	�	��J�J�L�L� � �"�"�"��)�+�+�C�&�
�
��$�W�-���!1�1�1��N�N�#C�V�L�L�L��,�V�4���D�(��2�F��"���'2�)�&<�]�&K����:�-�-�&1�)�&<����#=�=�=�&3���&?�� (�����f�5�s�!�!�I��J�J�!%�#�$/�%�"+������#�
�	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	�	s�CD5�5D9�<D9N)rr)rrr�__doc__r�idrrr#r(rr*rr)r�classmethodrDr\rrrr
r
s�������N�N�	��$�	'�	'�	'�B��y�d�#�#�#�H��9�%� � � �D��)��'�'�'�K��
���I�
�Y�D�
!�
!�
!�F�*�*�*�*�*�*�*�*��"!�"!�"!��[�"!�H�,�,��[�,�,�,rr
)r4�loggingrQ�peeweerrrr�defence360agent.modelrr�	getLoggerrrRr
rrr�<module>rds���������������������������2�1�1�1�1�1�1�1�	��	�8�	$�	$��f�f�f�f�f��f�f�f�f�frdefence360agent/model/__pycache__/instance.cpython-311.opt-1.pyc0000644000000000000000000000076000000000000021326 0ustar  �

�������B�ddlmcmZejdgd�d���ZdS)�N))�journal_mode�wal)�foreign_keys�ON)�busy_timeouti'T)�pragmas�regexp_function)�defence360agent.model.tls_check�model�	tls_check�SqliteDatabaseWrapper�db���S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/instance.py�<module>rsV��3�3�3�3�3�3�3�3�3�%�Y�$��
�
�
�
������rdefence360agent/model/__pycache__/instance.cpython-311.pyc0000644000000000000000000000076000000000000020367 0ustar  �

�������B�ddlmcmZejdgd�d���ZdS)�N))�journal_mode�wal)�foreign_keys�ON)�busy_timeouti'T)�pragmas�regexp_function)�defence360agent.model.tls_check�model�	tls_check�SqliteDatabaseWrapper�db���S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/instance.py�<module>rsV��3�3�3�3�3�3�3�3�3�%�Y�$��
�
�
�
������rdefence360agent/model/__pycache__/messages_to_send.cpython-311.opt-1.pyc0000644000000000000000000000717300000000000023051 0ustar  �

T�Xry�5��N�ddlmZddlmZmZddlmZmZGd�de��ZdS)�)�
namedtuple)�
FloatField�	BlobField)�instance�Modelc����eZdZdZGd�d��Zed���Zed���Ze	dd��Z
edd	���Zed
���Z
edd���Zed�fd���Z�xZS)�
MessageToSendzc
    Storage for messages to be sent to server
    while connection to server is not available
    c� �eZdZejZdZdS)�MessageToSend.Meta�messages_to_send_nrN)�__name__�
__module__�__qualname__r�db�database�db_table���[/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/messages_to_send.py�Metars�������;��(���rrF)�null�MessageToSendTztimestamp message�c��|����|j���|��}|S�N)�select�order_by�	timestamp�limit)�clsr�olds   r�
get_oldestzMessageToSend.get_oldests2���j�j�l�l�#�#�C�M�2�2�8�8��?�?���
rc��|����|j�|����}|���Sr)�delete�where�id�in_�execute)r �query�qs   r�	delete_inzMessageToSend.delete_ins9���J�J�L�L���s�v�z�z�%�0�0�1�1���y�y�{�{�rc�&�|����|j���|��}|����|j�|����}|���Sr)	rrrrr$r%r&r'r()r rr!r*s    r�
delete_oldzMessageToSend.delete_old"sb���j�j�l�l�#�#�C�M�2�2�8�8��?�?���J�J�L�L���s�v�z�z�#���/�/���y�y�{�{�r�returnNc�����tdt|��d��D]G}�fd�|||dz�D��}t��j|fi|������HdS)Nr�dc�H��g|]}�j|������Sr)r�_asdict)�.0�rowr s  �r�
<listcomp>z-MessageToSend.insert_many.<locals>.<listcomp>,s=������7:�"��"�C�(�0�0�2�2���r)�range�len�super�insert_manyr()r �rows�kwargs�i�data�	__class__s`    �rr9zMessageToSend.insert_many(s������q�#�d�)�)�S�)�)�	:�	:�A�����>B�1�q�3�w�;�>O����D�
 �E�G�G���/�/��/�/�7�7�9�9�9�9�		:�	:r)r)r.N)r
rr�__doc__rrrr�messagerr�classmethodr"r+r-r9�
__classcell__)r>s@rr	r	s
���������
)�)�)�)�)�)�)�)�
�
��&�&�&�I��i�U�#�#�#�G��Z� 0�2E�F�F�N������[������[�������[��
�:�:�:�:�:��[�:�:�:�:�:rr	N)	�collectionsr�peeweerr�defence360agent.modelrrr	rrr�<module>rFs{��"�"�"�"�"�"�(�(�(�(�(�(�(�(�1�1�1�1�1�1�1�1�':�':�':�':�':�E�':�':�':�':�':rdefence360agent/model/__pycache__/messages_to_send.cpython-311.pyc0000644000000000000000000000717300000000000022112 0ustar  �

T�Xry�5��N�ddlmZddlmZmZddlmZmZGd�de��ZdS)�)�
namedtuple)�
FloatField�	BlobField)�instance�Modelc����eZdZdZGd�d��Zed���Zed���Ze	dd��Z
edd	���Zed
���Z
edd���Zed�fd���Z�xZS)�
MessageToSendzc
    Storage for messages to be sent to server
    while connection to server is not available
    c� �eZdZejZdZdS)�MessageToSend.Meta�messages_to_send_nrN)�__name__�
__module__�__qualname__r�db�database�db_table���[/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/messages_to_send.py�Metars�������;��(���rrF)�null�MessageToSendTztimestamp message�c��|����|j���|��}|S�N)�select�order_by�	timestamp�limit)�clsr�olds   r�
get_oldestzMessageToSend.get_oldests2���j�j�l�l�#�#�C�M�2�2�8�8��?�?���
rc��|����|j�|����}|���Sr)�delete�where�id�in_�execute)r �query�qs   r�	delete_inzMessageToSend.delete_ins9���J�J�L�L���s�v�z�z�%�0�0�1�1���y�y�{�{�rc�&�|����|j���|��}|����|j�|����}|���Sr)	rrrrr$r%r&r'r()r rr!r*s    r�
delete_oldzMessageToSend.delete_old"sb���j�j�l�l�#�#�C�M�2�2�8�8��?�?���J�J�L�L���s�v�z�z�#���/�/���y�y�{�{�r�returnNc�����tdt|��d��D]G}�fd�|||dz�D��}t��j|fi|������HdS)Nr�dc�H��g|]}�j|������Sr)r�_asdict)�.0�rowr s  �r�
<listcomp>z-MessageToSend.insert_many.<locals>.<listcomp>,s=������7:�"��"�C�(�0�0�2�2���r)�range�len�super�insert_manyr()r �rows�kwargs�i�data�	__class__s`    �rr9zMessageToSend.insert_many(s������q�#�d�)�)�S�)�)�	:�	:�A�����>B�1�q�3�w�;�>O����D�
 �E�G�G���/�/��/�/�7�7�9�9�9�9�		:�	:r)r)r.N)r
rr�__doc__rrrr�messagerr�classmethodr"r+r-r9�
__classcell__)r>s@rr	r	s
���������
)�)�)�)�)�)�)�)�
�
��&�&�&�I��i�U�#�#�#�G��Z� 0�2E�F�F�N������[������[�������[��
�:�:�:�:�:��[�:�:�:�:�:rr	N)	�collectionsr�peeweerr�defence360agent.modelrrr	rrr�<module>rFs{��"�"�"�"�"�"�(�(�(�(�(�(�(�(�1�1�1�1�1�1�1�1�':�':�':�':�':�E�':�':�':�':�':rdefence360agent/model/__pycache__/simplification.cpython-311.opt-1.pyc0000644000000000000000000002304100000000000022531 0ustar  �

�R�ツ����B�ddlZddlZddlZddlZddlmZmZmZmZm	Z	m
Z
ddlmZm
Z
dZeje��ZGd�de��ZGd�de��ZGd	�d
e
��Zd�Zde
d
ededefd�ZGd�de
��Zd�Zd�ZGd�d��Ze��ZdS)�N)�	BlobField�	CharField�	DateField�ForeignKeyField�IntegerField�PeeweeException)�instance�Modeli�Qc��eZdZdZd�Zd�ZdS)�
FilenameFieldz/
    Class to store file names in database
    c�*�tj|��S�N)�os�fsencode��self�values  �Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/simplification.py�db_valuezFilenameField.db_value����{�5�!�!�!�c�*�tj|��Sr)r�fsdecoders  r�python_valuezFilenameField.python_valuerrN)�__name__�
__module__�__qualname__�__doc__rr�rrrrs<��������"�"�"�"�"�"�"�"rrc��eZdZdZd�ZdS)�
ScanPathField�
list_of_filesc�>�t|t��r|jS|Sr)�
isinstance�list�REALTIME_SCAN_PATH_STUBrs  rrzScanPathField.db_value&s"���e�T�"�"�	0��/�/��rN)rrrr&rrrrr!r!#s)������-������rr!c��eZdZdZdS)�
ModelErrorzf
    Model exception. Please use this one from other modules instead
    PeeweeException directly
    N)rrrrrrrr(r(,s��������
	�Drr(c��K�||�S)z0
    Fake run_in_executor() test (DEF-4541)
    r)�loop�cb�argss   r�run_in_executorr-5s�����2�t�9�r�table�num_days�	max_count�returnc�4�t|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�
ValueError�format�time�	POSIX_DAY�selectr3�order_by�desc�limit�where�delete�not_in�execute)r.r/r0�
has_timestamp�
end_save_time�to_keep�
deleted_counts       r�remove_old_and_truncaterE<s����E�;��6�6�M��N��>�E�E�e�L�L�M�M�M��I�K�K�(�Y�"6�6�M�
���U�_�%�%�	��%�/�&�&�(�(�	)�	)�	��y�	�	�	��u���.�	/�	/�	�	�������U�_�3�3�G�<�<�=�=�E�E�G�G���rc��eZdZdZGd�d��Zed���Zedd���Ze	de
fd	���Ze	dd
���ZdS)�Eulaz�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.
    c� �eZdZejZdZdS)�	Eula.Meta�eulaN)rrrr	�db�database�db_tablerrr�MetarI^s�������;�����rrNT)�primary_keyN)�null�defaultr1c��tt|����|j������|j���d����d��}|duS)N�)	�next�iterr9r=�accepted�is_nullr:�updatedr<)�cls�
unaccepteds  r�is_acceptedzEula.is_acceptedgsp�����
�
�����s�|�+�+�-�-�.�.���#�+�&�&���q���	
�
�
�
�
�
��T�!�!rc���|�tj������|j��������dS)N)rV)�updater7r=rVrWr@)rYs r�acceptzEula.accepttsI���
�
�D�I�K�K�
�(�(�.�.��L� � �"�"�	
�	
�
�'�)�)�)�)�)r)r1N)
rrrrrNrrXrrV�classmethod�boolr[r^rrrrGrGWs�����������������
�i�D�)�)�)�G��|��t�4�4�4�H��
"�D�
"�
"�
"��[�
"������[���rrGc�B�d�tj|d���D��S)Nc��g|]\}}|��Srr)�.0�_�objs   r�
<listcomp>zget_models.<locals>.<listcomp>|s,������A�s�	���rc�j�tj|��ot|t��o
|tkSr)�inspect�isclass�
issubclassr
)res r�<lambda>zget_models.<locals>.<lambda>�s1�����,�,���3��&�&���u��r)rh�
getmembers��modules r�
get_modelsro{s=�����(��
�
�
�
����rc��tj���tj�t	|��d���dS)NT)�safe)r	rK�connect�
create_tablesrorms rrsrs�s?���K�������K���j��0�0�t��<�<�<�<�<rc�T�eZdZededefd���Zededefd���Zd�Z	dS)�ApplyOrderBy�column_namer1c����t|t��r|jn|}d�t|dd��}|�t||�fd�������st||d��}|�|f��S)z�
        :param _model: peewee.Model or peewee.ForeignKeyField
        :param column_name: str
        :return: tuple<peewee.Node>
        r�OrderByNc����Srr)�nodess�rrkz,ApplyOrderBy.resolve_nodes.<locals>.<lambda>�s���%�r)r$r�	rel_modelr4)�_modelrv�model�custom_order_by�noderzs     @r�
resolve_nodeszApplyOrderBy.resolve_nodes�s����!+�6�?� C� C�O�F����	���!�%��D�9�9���&�H�G�O�[�-�-�-�-�H�H�J�J�E��	 ��5�+�t�4�4�D�������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<str>
        :return: list<peewee.Node>
        rrSN)rur��	get_nodes�append)r}r�rv�restrz�result�
node_or_modelrs        rr�zApplyOrderBy.get_nodes�s���)��O�\�!�"�"�-=�T���*�*�5�+�>�>����"�	-�	-�M��
-�(�2�2�=�$�G�G�(�(�D��M�M�$�'�'�'�'�(��
�
�m�,�,�,�,��
rc���g}|D]j}t�||j�d����}|D]2}|�|jr|���n|���3�k|j|�S)z�
        :param order_by: list<OrderBy>
        :param model: peewee.Model or peewee.ForeignKeyField
        :param query_builder: peewee.Query
        :return: peewee.Query with applied order_by
        �.)rur�rv�splitr�r;r:)rr:r}�
query_builder�orders�orderrzrs        r�__call__zApplyOrderBy.__call__�s������	C�	C�E� �*�*�5�%�2C�2I�2I�#�2N�2N�O�O�E��
C�
C���
�
�U�Z�A�d�i�i�k�k�k�T�B�B�B�B�
C�&�}�%�v�.�.rN)
rrr�staticmethod�str�tupler�r%r�r�rrrruru�s}��������3��5�����\��&��t�������\��$
/�
/�
/�
/�
/rru)rh�loggingrr7�peeweerrrrrr�defence360agent.modelr	r
r8�	getLoggerr�loggerrr!r(r-�intrErGrorsru�apply_order_byrrr�<module>r�s�����������	�	�	�	���������������������2�1�1�1�1�1�1�1�
�	�	��	�8�	$�	$��	"�	"�	"�	"�	"�I�	"�	"�	"������I����	�	�	�	�	��	�	�	���������,/�������6!�!�!�!�!�5�!�!�!�H	�	�	�=�=�=�
5/�5/�5/�5/�5/�5/�5/�5/�p������rdefence360agent/model/__pycache__/simplification.cpython-311.pyc0000644000000000000000000002304100000000000021572 0ustar  �

�R�ツ����B�ddlZddlZddlZddlZddlmZmZmZmZm	Z	m
Z
ddlmZm
Z
dZeje��ZGd�de��ZGd�de��ZGd	�d
e
��Zd�Zde
d
ededefd�ZGd�de
��Zd�Zd�ZGd�d��Ze��ZdS)�N)�	BlobField�	CharField�	DateField�ForeignKeyField�IntegerField�PeeweeException)�instance�Modeli�Qc��eZdZdZd�Zd�ZdS)�
FilenameFieldz/
    Class to store file names in database
    c�*�tj|��S�N)�os�fsencode��self�values  �Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/simplification.py�db_valuezFilenameField.db_value����{�5�!�!�!�c�*�tj|��Sr)r�fsdecoders  r�python_valuezFilenameField.python_valuerrN)�__name__�
__module__�__qualname__�__doc__rr�rrrrs<��������"�"�"�"�"�"�"�"rrc��eZdZdZd�ZdS)�
ScanPathField�
list_of_filesc�>�t|t��r|jS|Sr)�
isinstance�list�REALTIME_SCAN_PATH_STUBrs  rrzScanPathField.db_value&s"���e�T�"�"�	0��/�/��rN)rrrr&rrrrr!r!#s)������-������rr!c��eZdZdZdS)�
ModelErrorzf
    Model exception. Please use this one from other modules instead
    PeeweeException directly
    N)rrrrrrrr(r(,s��������
	�Drr(c��K�||�S)z0
    Fake run_in_executor() test (DEF-4541)
    r)�loop�cb�argss   r�run_in_executorr-5s�����2�t�9�r�table�num_days�	max_count�returnc�4�t|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�
ValueError�format�time�	POSIX_DAY�selectr3�order_by�desc�limit�where�delete�not_in�execute)r.r/r0�
has_timestamp�
end_save_time�to_keep�
deleted_counts       r�remove_old_and_truncaterE<s����E�;��6�6�M��N��>�E�E�e�L�L�M�M�M��I�K�K�(�Y�"6�6�M�
���U�_�%�%�	��%�/�&�&�(�(�	)�	)�	��y�	�	�	��u���.�	/�	/�	�	�������U�_�3�3�G�<�<�=�=�E�E�G�G���rc��eZdZdZGd�d��Zed���Zedd���Ze	de
fd	���Ze	dd
���ZdS)�Eulaz�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.
    c� �eZdZejZdZdS)�	Eula.Meta�eulaN)rrrr	�db�database�db_tablerrr�MetarI^s�������;�����rrNT)�primary_keyN)�null�defaultr1c��tt|����|j������|j���d����d��}|duS)N�)	�next�iterr9r=�accepted�is_nullr:�updatedr<)�cls�
unaccepteds  r�is_acceptedzEula.is_acceptedgsp�����
�
�����s�|�+�+�-�-�.�.���#�+�&�&���q���	
�
�
�
�
�
��T�!�!rc���|�tj������|j��������dS)N)rV)�updater7r=rVrWr@)rYs r�acceptzEula.accepttsI���
�
�D�I�K�K�
�(�(�.�.��L� � �"�"�	
�	
�
�'�)�)�)�)�)r)r1N)
rrrrrNrrXrrV�classmethod�boolr[r^rrrrGrGWs�����������������
�i�D�)�)�)�G��|��t�4�4�4�H��
"�D�
"�
"�
"��[�
"������[���rrGc�B�d�tj|d���D��S)Nc��g|]\}}|��Srr)�.0�_�objs   r�
<listcomp>zget_models.<locals>.<listcomp>|s,������A�s�	���rc�j�tj|��ot|t��o
|tkSr)�inspect�isclass�
issubclassr
)res r�<lambda>zget_models.<locals>.<lambda>�s1�����,�,���3��&�&���u��r)rh�
getmembers��modules r�
get_modelsro{s=�����(��
�
�
�
����rc��tj���tj�t	|��d���dS)NT)�safe)r	rK�connect�
create_tablesrorms rrsrs�s?���K�������K���j��0�0�t��<�<�<�<�<rc�T�eZdZededefd���Zededefd���Zd�Z	dS)�ApplyOrderBy�column_namer1c����t|t��r|jn|}d�t|dd��}|�t||�fd�������st||d��}|�|f��S)z�
        :param _model: peewee.Model or peewee.ForeignKeyField
        :param column_name: str
        :return: tuple<peewee.Node>
        r�OrderByNc����Srr)�nodess�rrkz,ApplyOrderBy.resolve_nodes.<locals>.<lambda>�s���%�r)r$r�	rel_modelr4)�_modelrv�model�custom_order_by�noderzs     @r�
resolve_nodeszApplyOrderBy.resolve_nodes�s����!+�6�?� C� C�O�F����	���!�%��D�9�9���&�H�G�O�[�-�-�-�-�H�H�J�J�E��	 ��5�+�t�4�4�D�������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<str>
        :return: list<peewee.Node>
        rrSN)rur��	get_nodes�append)r}r�rv�restrz�result�
node_or_modelrs        rr�zApplyOrderBy.get_nodes�s���)��O�\�!�"�"�-=�T���*�*�5�+�>�>����"�	-�	-�M��
-�(�2�2�=�$�G�G�(�(�D��M�M�$�'�'�'�'�(��
�
�m�,�,�,�,��
rc���g}|D]j}t�||j�d����}|D]2}|�|jr|���n|���3�k|j|�S)z�
        :param order_by: list<OrderBy>
        :param model: peewee.Model or peewee.ForeignKeyField
        :param query_builder: peewee.Query
        :return: peewee.Query with applied order_by
        �.)rur�rv�splitr�r;r:)rr:r}�
query_builder�orders�orderrzrs        r�__call__zApplyOrderBy.__call__�s������	C�	C�E� �*�*�5�%�2C�2I�2I�#�2N�2N�O�O�E��
C�
C���
�
�U�Z�A�d�i�i�k�k�k�T�B�B�B�B�
C�&�}�%�v�.�.rN)
rrr�staticmethod�str�tupler�r%r�r�rrrruru�s}��������3��5�����\��&��t�������\��$
/�
/�
/�
/�
/rru)rh�loggingrr7�peeweerrrrrr�defence360agent.modelr	r
r8�	getLoggerr�loggerrr!r(r-�intrErGrorsru�apply_order_byrrr�<module>r�s�����������	�	�	�	���������������������2�1�1�1�1�1�1�1�
�	�	��	�8�	$�	$��	"�	"�	"�	"�	"�I�	"�	"�	"������I����	�	�	�	�	��	�	�	���������,/�������6!�!�!�!�!�5�!�!�!�H	�	�	�=�=�=�
5/�5/�5/�5/�5/�5/�5/�5/�p������rdefence360agent/model/__pycache__/tls_check.cpython-311.opt-1.pyc0000644000000000000000000001107300000000000021460 0ustar  �

Bʢ�;�y�����ddlZddlZddlZddlZddlmZddlmZGd�de��Z	ej
e��Zej
��ZdZGd�d��ZGd	�d
e��Zd
d�Zd�ZdS)�N)�SqliteExtDatabase)�gc��eZdZdZdS)�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.pyrrs��������	�Drrg@c�&�eZdZdefd�Zd�Zd�ZdS)�_TimedAtomic�innerc�0�||_d|_d|_dS)Ng�)�_inner�_start�_caller)�selfrs  r
�__init__z_TimedAtomic.__init__s����� �������rc���tj��|_d�t	jd���dd���|_|j���S)Nr�)�limit���)	�time�	monotonicr�join�	traceback�format_stackrr�	__enter__)rs r
r!z_TimedAtomic.__enter__!sO���n�&�&����w�w�y�5�A�>�>�>�s��s�C�D�D����{�$�$�&�&�&rc��|jj|�}tj��|jz
}|t
kr!t�d||j��|S)Nz"Slow transaction held for %.2fs
%s)	r�__exit__rrr�_SLOW_TXN_THRESHOLD_S�logger�warningr)r�args�result�elapseds    r
r#z_TimedAtomic.__exit__&s[��%���%�t�,���.�"�"�T�[�0���*�*�*��N�N�5����
�
�
�
�
rN)rrr	�objectrr!r#rrr
rrsM�������f�����
'�'�'�
	�	�	�	�	rrc�0��eZdZ�fd�Zddef�fd�
Z�xZS)�SqliteDatabaseWrapperc�N��t|i|��t��j|i|��S�N)�	_validate�super�execute_sql)rr'�kwargs�	__class__s   �r
r1z!SqliteDatabaseWrapper.execute_sql3s2����4�"�6�"�"�"�"�u�w�w�"�D�3�F�3�3�3r�	IMMEDIATE�	lock_typec���t���|��}tjd��rt	|��S|S)N�DEBUG)r0�atomicr�getr)rr5rr3s   �r
r8zSqliteDatabaseWrapper.atomic7s;��������y�)�)���5��>�>�	'���&�&�&��r)r4)rrr	r1�strr8�
__classcell__)r3s@r
r,r,2sb�������4�4�4�4�4�������������rr,c��ttd��rt���|ptj��t_dS)N�thread_ident_memo)�hasattr�_thread_local_storager�	threading�	get_identr=)�	new_values r
�resetrC>sC���$�&9�:�:� �����	�*�Y�(�*�*��+�+�+rc��ttdd��}|�t�d��dS|t	j��kr1t�d|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/Gs�����2�D����� ����N�O�O�O�O�O�	�i�1�3�3�	3�	3����
-����!�!���
	
�	
�	
�	
�	
�
4�	3rr.)�loggingr@rr�playhouse.sqlite_extr�&defence360agent.internals.global_scoper�	Exceptionr�	getLoggerrr%�localr?r$rr,rCr/rrr
�<module>rMs'������������������2�2�2�2�2�2�4�4�4�4�4�4�	�	�	�	�	�i�	�	�	�
��	�8�	$�	$��'�	��)�)������������.	�	�	�	�	�-�	�	�	�����
�
�
�
�
rdefence360agent/model/__pycache__/tls_check.cpython-311.pyc0000644000000000000000000001107300000000000020521 0ustar  �

Bʢ�;�y�����ddlZddlZddlZddlZddlmZddlmZGd�de��Z	ej
e��Zej
��ZdZGd�d��ZGd	�d
e��Zd
d�Zd�ZdS)�N)�SqliteExtDatabase)�gc��eZdZdZdS)�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.pyrrs��������	�Drrg@c�&�eZdZdefd�Zd�Zd�ZdS)�_TimedAtomic�innerc�0�||_d|_d|_dS)Ng�)�_inner�_start�_caller)�selfrs  r
�__init__z_TimedAtomic.__init__s����� �������rc���tj��|_d�t	jd���dd���|_|j���S)Nr�)�limit���)	�time�	monotonicr�join�	traceback�format_stackrr�	__enter__)rs r
r!z_TimedAtomic.__enter__!sO���n�&�&����w�w�y�5�A�>�>�>�s��s�C�D�D����{�$�$�&�&�&rc��|jj|�}tj��|jz
}|t
kr!t�d||j��|S)Nz"Slow transaction held for %.2fs
%s)	r�__exit__rrr�_SLOW_TXN_THRESHOLD_S�logger�warningr)r�args�result�elapseds    r
r#z_TimedAtomic.__exit__&s[��%���%�t�,���.�"�"�T�[�0���*�*�*��N�N�5����
�
�
�
�
rN)rrr	�objectrr!r#rrr
rrsM�������f�����
'�'�'�
	�	�	�	�	rrc�0��eZdZ�fd�Zddef�fd�
Z�xZS)�SqliteDatabaseWrapperc�N��t|i|��t��j|i|��S�N)�	_validate�super�execute_sql)rr'�kwargs�	__class__s   �r
r1z!SqliteDatabaseWrapper.execute_sql3s2����4�"�6�"�"�"�"�u�w�w�"�D�3�F�3�3�3r�	IMMEDIATE�	lock_typec���t���|��}tjd��rt	|��S|S)N�DEBUG)r0�atomicr�getr)rr5rr3s   �r
r8zSqliteDatabaseWrapper.atomic7s;��������y�)�)���5��>�>�	'���&�&�&��r)r4)rrr	r1�strr8�
__classcell__)r3s@r
r,r,2sb�������4�4�4�4�4�������������rr,c��ttd��rt���|ptj��t_dS)N�thread_ident_memo)�hasattr�_thread_local_storager�	threading�	get_identr=)�	new_values r
�resetrC>sC���$�&9�:�:� �����	�*�Y�(�*�*��+�+�+rc��ttdd��}|�t�d��dS|t	j��kr1t�d|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/Gs�����2�D����� ����N�O�O�O�O�O�	�i�1�3�3�	3�	3����
-����!�!���
	
�	
�	
�	
�	
�
4�	3rr.)�loggingr@rr�playhouse.sqlite_extr�&defence360agent.internals.global_scoper�	Exceptionr�	getLoggerrr%�localr?r$rr,rCr/rrr
�<module>rMs'������������������2�2�2�2�2�2�4�4�4�4�4�4�	�	�	�	�	�i�	�	�	�
��	�8�	$�	$��'�	��)�)������������.	�	�	�	�	�-�	�	�	�����
�
�
�
�
rdefence360agent/model/__pycache__/wordpress.cpython-311.opt-1.pyc0000644000000000000000000000632000000000000021550 0ustar  �

"~�e7�v��~�ddlmZddlmZddlmZmZmZmZddl	m
Z
mZGd�de��ZGd�de��Z
d	S)
�)�annotations)�
NamedTuple)�	CharField�
FloatField�IntegerField�TimestampField)�instance�Modelc�h�eZdZUded<ded<ded<dZded<edd���Zdd�Zd
�Zd�Z	dS)�WPSite�str�docroot�domain�int�uid�1.0.0�version�site�
WordpressSite�returnc�H�||j|j|j|j���S)z7Create a WPSite instance from a WordpressSite instance.�rrrrr)�clsrs  �T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/wordpress.py�from_wordpress_sitezWPSite.from_wordpress_sites1���s��L��;����L�	
�
�
�	
�c�F�t|j|j|j|���S)z5Create a new WPSite instance with an updated version.r)rrrr)�selfrs  r�build_with_versionzWPSite.build_with_versions+����L��;����	
�
�
�	
rc��t|t��stS|j|j|jf|j|j|jfkS�N)�
isinstancer�NotImplementedrrr)r�others  r�__eq__z
WPSite.__eq__!sH���%��(�(�	"�!�!���d�k�4�8�4��M��L��I�9
�
�	
rc�D�t|j|j|jf��Sr!)�hashrrr)rs r�__hash__zWPSite.__hash__+s���T�\�4�;���9�:�:�:rN)rrrr)rr
rr)
�__name__�
__module__�__qualname__�__annotations__r�classmethodrrr%r(�rrrrs���������L�L�L��K�K�K��H�H�H��G������
�
�
��[�
�
�
�
�
�
�
�
�;�;�;�;�;rrc��eZdZGd�d��Zedd���Zed���Zed���Ze	dd���Z
ed	d���Zedd�
��Z
dS)rc� �eZdZejZdZdS)�WordpressSite.Meta�wordpress_siteN)r)r*r+r	�db�database�db_tabler.rr�Metar10s�������;��#���rr6TF)�primary_key�null)r8N)�defaultr8r)r8r9)r)r*r+r6rrrrrr�manually_deleted_atrr�disabled_rules_sync_tsr.rrrr/s�������$�$�$�$�$�$�$�$��i�D�u�5�5�5�G�
�Y�E�
"�
"�
"�F�
�,�E�
"�
"�
"�C�(�.��D�A�A�A���i��e�4�4�4�G�'�Z�T�4�@�@�@���rrN)�
__future__r�typingr�peeweerrrr�defence360agent.modelr	r
rrr.rr�<module>r@s���"�"�"�"�"�"�������F�F�F�F�F�F�F�F�F�F�F�F�1�1�1�1�1�1�1�1�$;�$;�$;�$;�$;�Z�$;�$;�$;�N
A�
A�
A�
A�
A�E�
A�
A�
A�
A�
Ardefence360agent/model/__pycache__/wordpress.cpython-311.pyc0000644000000000000000000000632000000000000020611 0ustar  �

"~�e7�v��~�ddlmZddlmZddlmZmZmZmZddl	m
Z
mZGd�de��ZGd�de��Z
d	S)
�)�annotations)�
NamedTuple)�	CharField�
FloatField�IntegerField�TimestampField)�instance�Modelc�h�eZdZUded<ded<ded<dZded<edd���Zdd�Zd
�Zd�Z	dS)�WPSite�str�docroot�domain�int�uid�1.0.0�version�site�
WordpressSite�returnc�H�||j|j|j|j���S)z7Create a WPSite instance from a WordpressSite instance.�rrrrr)�clsrs  �T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/wordpress.py�from_wordpress_sitezWPSite.from_wordpress_sites1���s��L��;����L�	
�
�
�	
�c�F�t|j|j|j|���S)z5Create a new WPSite instance with an updated version.r)rrrr)�selfrs  r�build_with_versionzWPSite.build_with_versions+����L��;����	
�
�
�	
rc��t|t��stS|j|j|jf|j|j|jfkS�N)�
isinstancer�NotImplementedrrr)r�others  r�__eq__z
WPSite.__eq__!sH���%��(�(�	"�!�!���d�k�4�8�4��M��L��I�9
�
�	
rc�D�t|j|j|jf��Sr!)�hashrrr)rs r�__hash__zWPSite.__hash__+s���T�\�4�;���9�:�:�:rN)rrrr)rr
rr)
�__name__�
__module__�__qualname__�__annotations__r�classmethodrrr%r(�rrrrs���������L�L�L��K�K�K��H�H�H��G������
�
�
��[�
�
�
�
�
�
�
�
�;�;�;�;�;rrc��eZdZGd�d��Zedd���Zed���Zed���Ze	dd���Z
ed	d���Zedd�
��Z
dS)rc� �eZdZejZdZdS)�WordpressSite.Meta�wordpress_siteN)r)r*r+r	�db�database�db_tabler.rr�Metar10s�������;��#���rr6TF)�primary_key�null)r8N)�defaultr8r)r8r9)r)r*r+r6rrrrrr�manually_deleted_atrr�disabled_rules_sync_tsr.rrrr/s�������$�$�$�$�$�$�$�$��i�D�u�5�5�5�G�
�Y�E�
"�
"�
"�F�
�,�E�
"�
"�
"�C�(�.��D�A�A�A���i��e�4�4�4�G�'�Z�T�4�@�@�@���rrN)�
__future__r�typingr�peeweerrrr�defence360agent.modelr	r
rrr.rr�<module>r@s���"�"�"�"�"�"�������F�F�F�F�F�F�F�F�F�F�F�F�1�1�1�1�1�1�1�1�$;�$;�$;�$;�$;�Z�$;�$;�$;�N
A�
A�
A�
A�
A�E�
A�
A�
A�
A�
Ardefence360agent/model/__pycache__/wordpress_incident.cpython-311.opt-1.pyc0000644000000000000000000005037700000000000023440 0ustar  �

LVgA#���t�dZddlZddlZddlZddlmZmZddlmZddl	m
Z
mZmZm
Z
ddlmZmZddlmZddlmZmZdd	lmZdd
lmZeje��Ze��ZGd�de��Zd
e de de fd�Z!efd
e de de fd�Z"ed���Z#de$dzde$dzfd�Z%d
e de defd�Z&d
e de defd�Z'dede fd�Z(												d0de)de)de)dzde$dzde$dzd e$dzd!e$dzd"e$dzd#e)dzd$e)dzd%e*dzd&e+fd'�Z,d(e*e de*e fd)�Z-d*e)fd+�Z.d
e de$fd,�Z/d-e$dzde)fd.�Z0de$dzfd/�Z1dS)1a0Helper 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)�	ExitStack�contextmanager)�	timedelta)�	CharField�
FloatField�IntegerField�	TextField)�	JSONField�fn)�geo)�Model�instance)�apply_order_by)�OrderByc�R�eZdZdZedd���Zed���Zed���Ze	d���Z
ed���Zed���Zed���Z
ed���Zed���Zedd���Zedd���Zed���ZGd	�d
��ZdS)�WordpressIncidentz�
    WordPress incident model for CVE protection.
    Uses dedicated wordpress_incident table created in migration 191.

    Unique constraint on (abuser, name, plugin, rule, severity, domain)
    allows deduplication similar to the aggregate plugin.
    T)�primary_key�null)r�
country_id)r�column_nameN)r�defaultc�$�eZdZejZdZdZdS)�WordpressIncident.Meta�wordpress_incident)))�abuser�name�plugin�rule�severity�domainTN)�__name__�
__module__�__qualname__r�db�database�db_table�indexes���]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/wordpress_incident.py�Metar8s!�������;��'��
���r)r+)r!r"r#�__doc__r�idrrrr�	timestamp�retriesrrr	�descriptionr�countryr r
�
extra_infor+r(r)r*rr"s&��������
��$�T�	2�	2�	2�B�
�Y�D�
!�
!�
!�F��9�$����D��
��%�%�%�I��l��%�%�%�G��|��&�&�&�H��9�$����D��)��&�&�&�K�
�Y�D�
!�
!�
!�F��i�T�|�<�<�<�G�
�Y�D�$�
/�
/�
/�F����%�%�%�J�
�
�
�
�
�
�
�
�
�
r)r�
incident_data�	site_info�returnc��t|�d����}t|�d����}t|�d����}id|�d���d|�d���d|�d���d|�d���d|�d���d	|�d	���d
|�d
���d|�d���d|�d���d
|�d���d|�d���d|�d���d|�d���d|�d���d|�d���d|�d���d|�d���|�d��||||�d��d��S) aI
    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
    �FILES�	GET_NAMES�
POST_NAMES�cve�mode�target�slug�version�user_logged_in�username�user_id�	site_path�request_method�REQUEST_METHOD�script_filename�SCRIPT_FILENAME�php_self�PHP_SELF�	path_info�	PATH_INFO�request_uri�REQUEST_URI�query_string�QUERY_STRING�http_x_forwarded_for�HTTP_X_FORWARDED_FOR�http_user_agent�HTTP_USER_AGENT�HTTP_REFERER�RAW_DATA)�http_referer�files�	get_names�
post_names�raw_data)�serialize_json_field�get)r3r4�
files_json�get_names_json�post_names_jsons     r*�build_extra_infor_AsR��&�m�&7�&7��&@�&@�A�A�J�)�-�*;�*;�K�*H�*H�I�I�N�*�=�+<�+<�\�+J�+J�K�K�O��
�}� � ��'�'��	�
�!�!�&�)�)��	�-�#�#�H�-�-�	�
	�
�!�!�&�)�)��	�=�$�$�Y�/�/�
�	�-�+�+�,<�=�=��	�I�M�M�*�-�-��	�9�=�=��+�+��	�Y�]�]�;�/�/��	�-�+�+�,<�=�=��	�=�,�,�->�?�?��	�M�%�%�j�1�1��	�]�&�&�{�3�3�� 	�}�(�(��7�7�!�"	�
�)�)�.�9�9�#�$	�
� 1� 1�2H� I� I�%�&	�=�,�,�->�?�?�'�(&�)�)�.�9�9��#�%�!�%�%�j�1�1�3���r)c�x�|�d��pt|��}t||��}|�d��p|�d��}|tur7t	��5}t||��}ddd��n#1swxYwYnt||��}d|�dd��t
|�dd	����d
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_ipN�	wordpress�rule_id�unknown�tsr�r;zWordPress CVE: r:�Unknownr )rrr.r/rrr0rr1r r2)r[�build_message_fallbackr_�_UNSET�country_reader�
_country_code�float�calculate_severity)r3r4�
geo_readerrar2�	abuser_ip�readerr1s        r*�build_incident_dictrsns���&���	�*�*��.D��/�/�G�"�-��;�;�J��!�!�-�0�0��M�4E�4E��5�5�I��V���
�
�
�	7��#�F�I�6�6�G�	7�	7�	7�	7�	7�	7�	7�	7�	7�	7�	7����	7�	7�	7�	7�� �
�I�6�6����!�!�)�Y�7�7��=�,�,�T�1�5�5�6�6��&�}�'8�'8��'@�'@�A�A�G�-�"3�"3�E�9�"E�"E�G�G�����-�-��)�)� ���s�6B�B�Bc#�4K�t��5}	|�tj����}nB#t$r5}t
�d|��dV�Yd}~ddd��dSd}~wwxYw|V�ddd��dS#1swxYwYdS)z�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.
    zGeoIP reader unavailable: %sN)r�
enter_contextrrr�	Exception�logger�debug)�stackrr�excs   r*rlrl�s����
�����	��(�(�����6�6�F�F���	�	�	��L�L�7��=�=�=��J�J�J��F�F�F�
������������	����������������������������s7�B
�&:�B
�
A9�A4�#B
�4A9�9B
�
B�B�ipc��|�|sdS	|�|��S#t$r'}t�d||��Yd}~dSd}~wwxYw)NzGeoIP lookup failed for %s: %s)�get_codervrwrx)rrr{rzs   r*rmrm�sj��
�~�R�~��t�����r�"�"�"���������5�r�3�?�?�?��t�t�t�t�t��������s��
A�A	�	Ac�D�t||��}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
    r()rsr�create)r3r4�
incident_dicts   r*�create_wordpress_incidentr��s*��(�
�y�A�A�M��#�4�4�m�4�4�4r)c���t||��}tjdi|���tjtjtjtjtjtj	gtj
tj
dztj|di����t���
��}t|��dS)ai
    Insert or update a WordPress incident in the wordpress_incident table.

    If an incident with the same aggregate key (abuser, name, plugin, rule,
    severity, domain) exists, increment its retries counter and update timestamp.
    Otherwise, create a new incident with retries=1.

    This implements similar deduplication logic as the aggregate plugin.

    Args:
        incident_data: Dict with incident fields from PHP incident file
        site_info: Dict with site information (domain, site_path, username, user_id)

    Returns:
        WordpressIncident instance (either newly created or updated)
    rhr.)�conflict_target�updaterr()rsr�insert�on_conflictrrrrrr r/r.�	returning�execute�list)r3r4r��results    r*�upsert_wordpress_incidentr��s���&(�
�y�A�A�M�	� �1�1�=�1�1�	��!�(�!�&�!�(�!�&�!�*�!�(�
�"�)�+<�+D�q�+H�!�+�]�;�-G��
�

�

�
��$�	%�	%�	����#�*��<�<��?�r)�incidentc
��|j|j|j|j|j|j|j|j|j|j	|j
|jd�S)z�
    Convert a WordpressIncident model instance to a dictionary.

    Args:
        incident: WordpressIncident model instance

    Returns:
        Dictionary representation of the incident
    �r-rrr.r/rrr0rr1r r2r�)r�s r*�wordpress_incident_to_dictr��sU���k��/��
��'��#��%��
��+��/��#��/��)�
�
�
r)��F�limit�offsetrA�by_abuser_ip�by_country_code�	by_domain�search�site_search�since�to�order_by�include_hiddenc��t�t���tjdk��}|sR|�tj���tj�d��z��}|�6|�tjtj	d��|k��}|�2|�tj
�|����}|�#|�tj|k��}|�2|�tj
�|����}|��|�tj�|��tj�|��ztj
�|��ztj
�|��z��}|�6|�tjtj	d��|k��}|�6|�tj�d��|k��}|	�6|�tj�d��|	k��}|
�pg}
|
D]T}t%|t&��r(|
�t+j|�����?|
�|���Ut/|
t|��}n1|�tj�����}|�|��}|�|��}d�|���D��S)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
    rdzTEST-Nz	$.user_idz$.site_path�REALc�,�g|]}t|����Sr(�r���.0�incs  r*�
<listcomp>z+get_wordpress_incidents.<locals>.<listcomp>zs!��G�G�G��&�s�+�+�G�G�Gr))r�select�whererr�is_null�
startswithr�json_extractr2r�containsr1r rr0r.�cast�
isinstance�str�appendr�
fromstringrr��descr�r�r�)r�r�rAr�r�r�r�r�r�r�r�r��query�converted_order_by�items               r*�get_wordpress_incidentsr�s��L
�$�$�%6�7�7�=�=�	�	!�[�	0�
�
�E��
�����"�*�*�,�,� �%�0�0��9�9�9�
:�
�
��
������O�-�8�+�F�F��
�
�
��
�����-�4�=�=�l�K�K�L�L���"����-�5��H�I�I�������-�4�=�=�i�H�H�I�I��
������"�+�+�F�3�3��+�4�4�V�<�<�
=��&�/�/��7�7�
8� �&�/�/��7�7�
8�
�
��������O�-�8�-�H�H��
�
�
��

�����-�7�<�<�V�D�D��M�N�N��	�~����-�7�<�<�V�D�D��J�K�K�������	0�	0�D��$��$�$�
0�"�)�)�'�*<�T�*B�*B�C�C�C�C�"�)�)�$�/�/�/�/��1�3D�e�L�L������0�:�?�?�A�A�B�B���K�K����E��L�L�� � �E�G�G�u�}�}���G�G�G�Gr)�incidents_datac��|sgSt�|���t�����}d�|D��S)z�
    Bulk create WordPress incidents in a single transaction.
    Args:
        incidents_data: List of dictionaries containing incident field data

    Returns:
        List of created incident dictionaries
    c�,�g|]}t|����Sr(r�r�s  r*r�z3bulk_create_wordpress_incidents.<locals>.<listcomp>�s!��>�>�>��&�s�+�+�>�>�>r))r�insert_manyr�r�)r�r�s  r*�bulk_create_wordpress_incidentsr�}sV�����	�	�%�%�n�5�5�	��$�	%�	%�	�����?�>�v�>�>�>�>r)�daysc�R�tj��t|������z
}t����tjdktj�d��|kz���	��}|S)N)r�rdr�)
�timer�
total_secondsr�deleter�rr.r�r�)r��cutoff_time�deleteds   r*�delete_old_wordpress_incidentsr��s����)�+�+�	�t� 4� 4� 4� B� B� D� D�D�K�� � �"�"�	��
�
%��
4� �*�/�/��7�7�+�E�
G�

�

�
����
��Nr)c��dg}|�d��r|�|d��|�d��r|�|d��|�d��r|�|d��|�d��r|�|d��|�d��r|�|d��d�|��S)z=Build message if plugin didn't provide one (per spec format).z
IM WP plugin:rer:r=r>r;� )r[r��join)r3�partss  r*rjrj�s���
��E�����#�#�/�
���]�9�-�.�.�.�������+�
���]�5�)�*�*�*����� � �,�
���]�6�*�+�+�+�����#�#�/�
���]�9�-�.�.�.����� � �,�
���]�6�*�+�+�+��8�8�E�?�?�r)r;c�&�|dkrdS|dkrdSdS)z!Calculate severity based on mode.�block��pass�r()r;s r*roro�s#���w����q�	
�����q��qr)c�`�|�dSt|t��r|Stj|��S)z>Serialize a value to JSON string if it's not already a string.N)r�r��json�dumps)�values r*rZrZ�s3���}��t��%��������:�e���r))r�rNNNNNNNNNF)2r,�loggingr�r��
contextlibrr�datetimer�peeweerrrr	�playhouse.sqlite_extr
r�defence360agent.internalsr�defence360agent.modelr
r�$defence360agent.model.simplificationr�"defence360agent.rpc_tools.validater�	getLoggerr!rw�objectrkr�dictr_rsrlr�rmr�r�r��intr��boolr�r�r�rjrorZr(r)r*�<module>r�s����������������0�0�0�0�0�0�0�0�������������������/�.�.�.�.�.�.�.�)�)�)�)�)�)�1�1�1�1�1�1�1�1�?�?�?�?�?�?�6�6�6�6�6�6�	��	�8�	$�	$��	�����
�
�
�
�
��
�
�
�>*�D�*�T�*�d�*�*�*�*�\6<�/�/��/�$(�/�	�/�/�/�/�d�
�
���
� �c�D�j��S�4�Z�����5��5�$(�5��5�5�5�5�"-��-�$(�-��-�-�-�-�`�):��t�����6���#�"&� ��"��� � �dH�dH��dH��dH��4�Z�dH���*�	dH�
�4�Z�dH��T�z�
dH�
�$�J�dH��t��dH���:�dH�	�d�
�dH��T�k�dH��dH�dH�dH�dH�N?���J�?�	�$�Z�?�?�?�?�0
��
�
�
�
��$��3�����$�S�4�Z��C������3��:������r)defence360agent/model/__pycache__/wordpress_incident.cpython-311.pyc0000644000000000000000000005037700000000000022501 0ustar  �

LVgA#���t�dZddlZddlZddlZddlmZmZddlmZddl	m
Z
mZmZm
Z
ddlmZmZddlmZddlmZmZdd	lmZdd
lmZeje��Ze��ZGd�de��Zd
e de de fd�Z!efd
e de de fd�Z"ed���Z#de$dzde$dzfd�Z%d
e de defd�Z&d
e de defd�Z'dede fd�Z(												d0de)de)de)dzde$dzde$dzd e$dzd!e$dzd"e$dzd#e)dzd$e)dzd%e*dzd&e+fd'�Z,d(e*e de*e fd)�Z-d*e)fd+�Z.d
e de$fd,�Z/d-e$dzde)fd.�Z0de$dzfd/�Z1dS)1a0Helper 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)�	ExitStack�contextmanager)�	timedelta)�	CharField�
FloatField�IntegerField�	TextField)�	JSONField�fn)�geo)�Model�instance)�apply_order_by)�OrderByc�R�eZdZdZedd���Zed���Zed���Ze	d���Z
ed���Zed���Zed���Z
ed���Zed���Zedd���Zedd���Zed���ZGd	�d
��ZdS)�WordpressIncidentz�
    WordPress incident model for CVE protection.
    Uses dedicated wordpress_incident table created in migration 191.

    Unique constraint on (abuser, name, plugin, rule, severity, domain)
    allows deduplication similar to the aggregate plugin.
    T)�primary_key�null)r�
country_id)r�column_nameN)r�defaultc�$�eZdZejZdZdZdS)�WordpressIncident.Meta�wordpress_incident)))�abuser�name�plugin�rule�severity�domainTN)�__name__�
__module__�__qualname__r�db�database�db_table�indexes���]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/wordpress_incident.py�Metar8s!�������;��'��
���r)r+)r!r"r#�__doc__r�idrrrr�	timestamp�retriesrrr	�descriptionr�countryr r
�
extra_infor+r(r)r*rr"s&��������
��$�T�	2�	2�	2�B�
�Y�D�
!�
!�
!�F��9�$����D��
��%�%�%�I��l��%�%�%�G��|��&�&�&�H��9�$����D��)��&�&�&�K�
�Y�D�
!�
!�
!�F��i�T�|�<�<�<�G�
�Y�D�$�
/�
/�
/�F����%�%�%�J�
�
�
�
�
�
�
�
�
�
r)r�
incident_data�	site_info�returnc��t|�d����}t|�d����}t|�d����}id|�d���d|�d���d|�d���d|�d���d|�d���d	|�d	���d
|�d
���d|�d���d|�d���d
|�d���d|�d���d|�d���d|�d���d|�d���d|�d���d|�d���d|�d���|�d��||||�d��d��S) aI
    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
    �FILES�	GET_NAMES�
POST_NAMES�cve�mode�target�slug�version�user_logged_in�username�user_id�	site_path�request_method�REQUEST_METHOD�script_filename�SCRIPT_FILENAME�php_self�PHP_SELF�	path_info�	PATH_INFO�request_uri�REQUEST_URI�query_string�QUERY_STRING�http_x_forwarded_for�HTTP_X_FORWARDED_FOR�http_user_agent�HTTP_USER_AGENT�HTTP_REFERER�RAW_DATA)�http_referer�files�	get_names�
post_names�raw_data)�serialize_json_field�get)r3r4�
files_json�get_names_json�post_names_jsons     r*�build_extra_infor_AsR��&�m�&7�&7��&@�&@�A�A�J�)�-�*;�*;�K�*H�*H�I�I�N�*�=�+<�+<�\�+J�+J�K�K�O��
�}� � ��'�'��	�
�!�!�&�)�)��	�-�#�#�H�-�-�	�
	�
�!�!�&�)�)��	�=�$�$�Y�/�/�
�	�-�+�+�,<�=�=��	�I�M�M�*�-�-��	�9�=�=��+�+��	�Y�]�]�;�/�/��	�-�+�+�,<�=�=��	�=�,�,�->�?�?��	�M�%�%�j�1�1��	�]�&�&�{�3�3�� 	�}�(�(��7�7�!�"	�
�)�)�.�9�9�#�$	�
� 1� 1�2H� I� I�%�&	�=�,�,�->�?�?�'�(&�)�)�.�9�9��#�%�!�%�%�j�1�1�3���r)c�x�|�d��pt|��}t||��}|�d��p|�d��}|tur7t	��5}t||��}ddd��n#1swxYwYnt||��}d|�dd��t
|�dd	����d
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_ipN�	wordpress�rule_id�unknown�tsr�r;zWordPress CVE: r:�Unknownr )rrr.r/rrr0rr1r r2)r[�build_message_fallbackr_�_UNSET�country_reader�
_country_code�float�calculate_severity)r3r4�
geo_readerrar2�	abuser_ip�readerr1s        r*�build_incident_dictrsns���&���	�*�*��.D��/�/�G�"�-��;�;�J��!�!�-�0�0��M�4E�4E��5�5�I��V���
�
�
�	7��#�F�I�6�6�G�	7�	7�	7�	7�	7�	7�	7�	7�	7�	7�	7����	7�	7�	7�	7�� �
�I�6�6����!�!�)�Y�7�7��=�,�,�T�1�5�5�6�6��&�}�'8�'8��'@�'@�A�A�G�-�"3�"3�E�9�"E�"E�G�G�����-�-��)�)� ���s�6B�B�Bc#�4K�t��5}	|�tj����}nB#t$r5}t
�d|��dV�Yd}~ddd��dSd}~wwxYw|V�ddd��dS#1swxYwYdS)z�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.
    zGeoIP reader unavailable: %sN)r�
enter_contextrrr�	Exception�logger�debug)�stackrr�excs   r*rlrl�s����
�����	��(�(�����6�6�F�F���	�	�	��L�L�7��=�=�=��J�J�J��F�F�F�
������������	����������������������������s7�B
�&:�B
�
A9�A4�#B
�4A9�9B
�
B�B�ipc��|�|sdS	|�|��S#t$r'}t�d||��Yd}~dSd}~wwxYw)NzGeoIP lookup failed for %s: %s)�get_codervrwrx)rrr{rzs   r*rmrm�sj��
�~�R�~��t�����r�"�"�"���������5�r�3�?�?�?��t�t�t�t�t��������s��
A�A	�	Ac�D�t||��}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
    r()rsr�create)r3r4�
incident_dicts   r*�create_wordpress_incidentr��s*��(�
�y�A�A�M��#�4�4�m�4�4�4r)c���t||��}tjdi|���tjtjtjtjtjtj	gtj
tj
dztj|di����t���
��}t|��dS)ai
    Insert or update a WordPress incident in the wordpress_incident table.

    If an incident with the same aggregate key (abuser, name, plugin, rule,
    severity, domain) exists, increment its retries counter and update timestamp.
    Otherwise, create a new incident with retries=1.

    This implements similar deduplication logic as the aggregate plugin.

    Args:
        incident_data: Dict with incident fields from PHP incident file
        site_info: Dict with site information (domain, site_path, username, user_id)

    Returns:
        WordpressIncident instance (either newly created or updated)
    rhr.)�conflict_target�updaterr()rsr�insert�on_conflictrrrrrr r/r.�	returning�execute�list)r3r4r��results    r*�upsert_wordpress_incidentr��s���&(�
�y�A�A�M�	� �1�1�=�1�1�	��!�(�!�&�!�(�!�&�!�*�!�(�
�"�)�+<�+D�q�+H�!�+�]�;�-G��
�

�

�
��$�	%�	%�	����#�*��<�<��?�r)�incidentc
��|j|j|j|j|j|j|j|j|j|j	|j
|jd�S)z�
    Convert a WordpressIncident model instance to a dictionary.

    Args:
        incident: WordpressIncident model instance

    Returns:
        Dictionary representation of the incident
    �r-rrr.r/rrr0rr1r r2r�)r�s r*�wordpress_incident_to_dictr��sU���k��/��
��'��#��%��
��+��/��#��/��)�
�
�
r)��F�limit�offsetrA�by_abuser_ip�by_country_code�	by_domain�search�site_search�since�to�order_by�include_hiddenc��t�t���tjdk��}|sR|�tj���tj�d��z��}|�6|�tjtj	d��|k��}|�2|�tj
�|����}|�#|�tj|k��}|�2|�tj
�|����}|��|�tj�|��tj�|��ztj
�|��ztj
�|��z��}|�6|�tjtj	d��|k��}|�6|�tj�d��|k��}|	�6|�tj�d��|	k��}|
�pg}
|
D]T}t%|t&��r(|
�t+j|�����?|
�|���Ut/|
t|��}n1|�tj�����}|�|��}|�|��}d�|���D��S)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
    rdzTEST-Nz	$.user_idz$.site_path�REALc�,�g|]}t|����Sr(�r���.0�incs  r*�
<listcomp>z+get_wordpress_incidents.<locals>.<listcomp>zs!��G�G�G��&�s�+�+�G�G�Gr))r�select�whererr�is_null�
startswithr�json_extractr2r�containsr1r rr0r.�cast�
isinstance�str�appendr�
fromstringrr��descr�r�r�)r�r�rAr�r�r�r�r�r�r�r�r��query�converted_order_by�items               r*�get_wordpress_incidentsr�s��L
�$�$�%6�7�7�=�=�	�	!�[�	0�
�
�E��
�����"�*�*�,�,� �%�0�0��9�9�9�
:�
�
��
������O�-�8�+�F�F��
�
�
��
�����-�4�=�=�l�K�K�L�L���"����-�5��H�I�I�������-�4�=�=�i�H�H�I�I��
������"�+�+�F�3�3��+�4�4�V�<�<�
=��&�/�/��7�7�
8� �&�/�/��7�7�
8�
�
��������O�-�8�-�H�H��
�
�
��

�����-�7�<�<�V�D�D��M�N�N��	�~����-�7�<�<�V�D�D��J�K�K�������	0�	0�D��$��$�$�
0�"�)�)�'�*<�T�*B�*B�C�C�C�C�"�)�)�$�/�/�/�/��1�3D�e�L�L������0�:�?�?�A�A�B�B���K�K����E��L�L�� � �E�G�G�u�}�}���G�G�G�Gr)�incidents_datac��|sgSt�|���t�����}d�|D��S)z�
    Bulk create WordPress incidents in a single transaction.
    Args:
        incidents_data: List of dictionaries containing incident field data

    Returns:
        List of created incident dictionaries
    c�,�g|]}t|����Sr(r�r�s  r*r�z3bulk_create_wordpress_incidents.<locals>.<listcomp>�s!��>�>�>��&�s�+�+�>�>�>r))r�insert_manyr�r�)r�r�s  r*�bulk_create_wordpress_incidentsr�}sV�����	�	�%�%�n�5�5�	��$�	%�	%�	�����?�>�v�>�>�>�>r)�daysc�R�tj��t|������z
}t����tjdktj�d��|kz���	��}|S)N)r�rdr�)
�timer�
total_secondsr�deleter�rr.r�r�)r��cutoff_time�deleteds   r*�delete_old_wordpress_incidentsr��s����)�+�+�	�t� 4� 4� 4� B� B� D� D�D�K�� � �"�"�	��
�
%��
4� �*�/�/��7�7�+�E�
G�

�

�
����
��Nr)c��dg}|�d��r|�|d��|�d��r|�|d��|�d��r|�|d��|�d��r|�|d��|�d��r|�|d��d�|��S)z=Build message if plugin didn't provide one (per spec format).z
IM WP plugin:rer:r=r>r;� )r[r��join)r3�partss  r*rjrj�s���
��E�����#�#�/�
���]�9�-�.�.�.�������+�
���]�5�)�*�*�*����� � �,�
���]�6�*�+�+�+�����#�#�/�
���]�9�-�.�.�.����� � �,�
���]�6�*�+�+�+��8�8�E�?�?�r)r;c�&�|dkrdS|dkrdSdS)z!Calculate severity based on mode.�block��pass�r()r;s r*roro�s#���w����q�	
�����q��qr)c�`�|�dSt|t��r|Stj|��S)z>Serialize a value to JSON string if it's not already a string.N)r�r��json�dumps)�values r*rZrZ�s3���}��t��%��������:�e���r))r�rNNNNNNNNNF)2r,�loggingr�r��
contextlibrr�datetimer�peeweerrrr	�playhouse.sqlite_extr
r�defence360agent.internalsr�defence360agent.modelr
r�$defence360agent.model.simplificationr�"defence360agent.rpc_tools.validater�	getLoggerr!rw�objectrkr�dictr_rsrlr�rmr�r�r��intr��boolr�r�r�rjrorZr(r)r*�<module>r�s����������������0�0�0�0�0�0�0�0�������������������/�.�.�.�.�.�.�.�)�)�)�)�)�)�1�1�1�1�1�1�1�1�?�?�?�?�?�?�6�6�6�6�6�6�	��	�8�	$�	$��	�����
�
�
�
�
��
�
�
�>*�D�*�T�*�d�*�*�*�*�\6<�/�/��/�$(�/�	�/�/�/�/�d�
�
���
� �c�D�j��S�4�Z�����5��5�$(�5��5�5�5�5�"-��-�$(�-��-�-�-�-�`�):��t�����6���#�"&� ��"��� � �dH�dH��dH��dH��4�Z�dH���*�	dH�
�4�Z�dH��T�z�
dH�
�$�J�dH��t��dH���:�dH�	�d�
�dH��T�k�dH��dH�dH�dH�dH�N?���J�?�	�$�Z�?�?�?�?�0
��
�
�
�
��$��3�����$�S�4�Z��C������3��:������r)defence360agent/model/__pycache__/wp_disabled_rule.cpython-311.opt-1.pyc0000644000000000000000000004335200000000000023032 0ustar  �

u4�C@ �����dZddlmZddlZddlZddlmZmZmZm	Z	m
Z
mZddlm
Z
mZeje��ZGd�de
��Zdeed	dfd
�ZdS)a�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.
�)�IteratorN)�	CharField�
FloatField�IntegerField�IntegrityError�PrimaryKeyField�fn)�Model�instancec� �eZdZdZGd�d��Ze��Zed���Zed���Z	ed���Z
ed���Zed���Z
ed���ZdZdZd	Zd
Ze	d$ded
eedzdedededzdefd���Zedededededef
d���Zeded
eededededefd���Zededededzdedededefd���Zeded
eedzdefd���Zed$dededzdefd���Ze	d%dededeefd���Z ede!efd���Z"edeedzdefd���Z#e			d&d!ed"edeedzdede$eee%ff
d#���Z&dS)'�WPDisabledRulez�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
    c�$�eZdZejZdZdZdS)�WPDisabledRule.Meta�wp_disabled_rules)))�rule_id�scope�scope_valueTN)�__name__�
__module__�__qualname__r�db�database�db_table�indexes���[/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/wp_disabled_rule.py�Metar-s�������;��&��@���rrF)�nullT�global�domain�	wordpress�agentNr�domains�source�user_id�	timestamp�returnc��|�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      r�storezWPDisabledRule.storeHs\��.���	���I��	��+�+���)�V�W���
��$�$�W�i���I�I�Irc��|�||jd|||���}|rt�d|||��t	|��S)zADisable a rule globally (independent of domain-specific entries).N�rrr�disabled_atr%r&z1Disabled rule %s globally (source=%s, user_id=%s))�_create_if_not_exists�SCOPE_GLOBAL�logger�debug�int)r-rr'r%r&�createds      rr,z WPDisabledRule._disable_globallyhsm���+�+���"��!���
,�
�
���	��L�L�C����	
�
�
��7�|�|�rc	��d}|D]G}|�||j||||���}|r#|dz
}t�d||||���H|S)zBDisable a rule for specific domains (independent of global state).rr0�z6Disabled 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_domains�s������	�	�F��/�/���&�"�%���
0���G��
���
�����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)�insert�executer)r-rrrr1r%r&s       rr2z$WPDisabledRule._create_if_not_exists�sc�� 	��J�J���'�'��#*�

�
�
��g�i�i�i��4���	�	�	��5�5�	���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))�delete�whererrr3r?r4r5r:r�in_)r-rr$r;s    r�removezWPDisabledRule.remove�s����	��
�
������K�7�*��I��!1�1�������

��
B����7��A�A�A���
�
������K�7�*��I��!1�1��O�'�'��0�0���
����
��
����6������
�rc��|�Q|����|j|k|j|jk�����S|����|j|k|j|jk|j|jk|j|kzz�����S)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
        )�selectrBrrr3�existsr:r)r-rr!s   r�is_rule_disabledzWPDisabledRule.is_rule_disabled�s����>��
�
������K�7�*��I��!1�1�������

�
�J�J�L�L�
�U���w�&��Y�#�"2�2���c�&6�6��?�f�4�6��	�	��V�X�X�
	
r�include_globalc�~�|rk|�|j���|j|jk|j|jk|j|kzz�����}nE|�|j���|j|jk|j|k��}d�|D��S)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
        c��g|]	}|j��
Sr�r��.0�rows  r�
<listcomp>z6WPDisabledRule.get_domain_disabled.<locals>.<listcomp>/s��-�-�-����-�-�-r)rFrrBrr3r:r�distinct)r-r!rI�querys    r�get_domain_disabledz"WPDisabledRule.get_domain_disableds����	��
�
�3�;�'�'����Y�#�"2�2���c�&6�6��?�f�4�6��������
�E��J�J�s�{�+�+�1�1��	�S�-�-���6�)���E�.�-�u�-�-�-�-rc��|�|j���|j|jk��}d�|D��S)z�
        Get all rule IDs that are disabled globally.

        Returns:
            Iterator of globally disabled rule IDs
        c3�$K�|]}|jV��dS�NrLrMs  r�	<genexpr>z5WPDisabledRule.get_global_disabled.<locals>.<genexpr>:s$����-�-����-�-�-�-�-�-r)rFrrBrr3)r-rRs  r�get_global_disabledz"WPDisabledRule.get_global_disabled1sC���
�
�3�;�'�'�-�-�c�i�3�;K�.K�L�L��-�-�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��)9�9���#�#�L�1�1��L��
F��	�S�%5�5��E�E����	1��9�� 0�0�0��trr�limit�offsetc��|�||��}|�|j���|j���tj|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
        Nc��g|]	}|j��
SrrLrMs  rrPz(WPDisabledRule.fetch.<locals>.<listcomp>�s'��
�
�
��C�K�
�
�
rF)r�	is_globalr$Trar$)r\rFr�group_by�order_byr	�MAXr1�descrBr;r^r]rCrr3r:�appendr�sorted)r-r]r^rYrI�	condition�rule_ids_query�total_count�paginated_rule_ids�
rows_query�rules_by_idrO�resultr�	rule_datas               r�fetchzWPDisabledRule.fetchSs��@�/�/��n�M�M�	�
�J�J�s�{�#�#�
�X�c�k�
"�
"�
�X�b�f�S�_�-�-�2�2�4�4�
5�
5�	�
� �+�1�1�)�<�<�N�%�*�*�,�,��
�
�#1�#8�#8��#@�#@�#F�#F�u�#M�#M�
�
�
��"�	#���?�"��Z�Z�\�\�'�'�����8J�(K�(K�L�L�
�� �#�)�)�)�4�4�J�(*���	L�	L�C��{�+�-�-�"�{�!&�!�,�,��C�K�(��y�C�,�,�,�8<��C�K�(��5�5���c�.�.�.��C�K�(��3�:�:�3�?�K�K�K����)�	%�	%�G�#�G�,�I�#)�)�I�*>�#?�#?�I�i� ��M�M�)�$�$�$�$��F�"�"rrV)F)rNF)'rrr�__doc__rr�idrrrrrr1r%rr=r3r:�SOURCE_WORDPRESS�SOURCE_AGENT�classmethod�str�listr6�floatr.r,r+�boolr2rDrHrSrrXr\�tuple�dictrprrrr
r
%s��������A�A�A�A�A�A�A�A�

��	�	�B��i�U�#�#�#�G��I�5�!�!�!�E��)��&�&�&�K��*�%�(�(�(�K�
�Y�E�
"�
"�
"�F�%��5�1�1�1���L��L�#���L��#'�
J�J��J��c��T�!�J��	J�
�J��4�<�
J�

�J�J�J��[�J�>�������	�
��

�
����[��2�����c����	�
���
�

�����[��<�������4�Z�	�
���
���
�����[��:�(�S�(�4��9�t�+;�(��(�(�(��[�(�T�"
�"
�s�"
�C�$�J�"
�$�"
�"
�"
��[�"
�H�16�.�.��.�*.�.�	
�c��.�.�.��[�.�B�.�H�S�M�.�.�.��[�.����3�i�$�&�������[��,��)-�$�O#�O#��O#��O#��3�i�$�&�	O#�
�O#�
�s�D��J��	�
O#�O#�O#��[�O#�O#�O#rr
�	incidentsr(c�`�d�|D��}|s|D]}d|d<�dSd�|D��}tjtjk}|r=|tjtjktj�|��zz}t�tjtjtj���tj�|��|��}t��}t��}|D]S}|jtjkr|�
|j���2|�
|j|jf���T|D]N}|�d��}	|�d��}
t|	duo
|	|vp	|
duo|	|
f|v��|d<�OdS)a�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.
    c�H�h|]}|�d���|d�� S)�rule��get�rN�incs  r�	<setcomp>z7enrich_incidents_with_disabled_state.<locals>.<setcomp>�s2������C�G�G�F�O�O�,G��F��,G�,G�,GrFrHNc�H�h|]}|�d���|d�� S)r!r�r�s  rr�z7enrich_incidents_with_disabled_state.<locals>.<setcomp>�s4������c�g�g�h�.?�.?�.K��H�
�.K�.K�.Krrr!)
r
rr3r:rrCrFrrB�set�addr�ry)r|�rule_idsr�r$rhrR�globally_disabled�domain_disabledrOrr!s           r�$enrich_incidents_with_disabled_stater��s�����(����H����	,�	,�C�&+�C�"�#�#�����!*����G��$��(C�C�I��
��
�
!�^�%@�
@��)�-�-�g�6�6�
8�
�	�
�!�!������"�
�
��e�N�"�&�&�x�0�0�)�<�<�	
�#&�%�%��,/�E�E�O��@�@���9��3�3�3��!�!�#�+�.�.�.�.������c�o� >�?�?�?�?��	
�	
���w�w�v��������"�"��"&����
��)�)�N��$�&�L�D�&�>�_�+L�	#
�#
�����	
�	
r)rq�collections.abcr�loggingr*�peeweerrrrrr	�defence360agent.modelr
r�	getLoggerrr4r
rwr{r�rrr�<module>r�s����$%�$�$�$�$�$�������������������������2�1�1�1�1�1�1�1�	��	�8�	$�	$��~#�~#�~#�~#�~#�U�~#�~#�~#�B3
�D��J�3
�4�3
�3
�3
�3
�3
�3
rdefence360agent/model/__pycache__/wp_disabled_rule.cpython-311.pyc0000644000000000000000000004335200000000000022073 0ustar  �

u4�C@ �����dZddlmZddlZddlZddlmZmZmZm	Z	m
Z
mZddlm
Z
mZeje��ZGd�de
��Zdeed	dfd
�ZdS)a�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.
�)�IteratorN)�	CharField�
FloatField�IntegerField�IntegrityError�PrimaryKeyField�fn)�Model�instancec� �eZdZdZGd�d��Ze��Zed���Zed���Z	ed���Z
ed���Zed���Z
ed���ZdZdZd	Zd
Ze	d$ded
eedzdedededzdefd���Zedededededef
d���Zeded
eededededefd���Zededededzdedededefd���Zeded
eedzdefd���Zed$dededzdefd���Ze	d%dededeefd���Z ede!efd���Z"edeedzdefd���Z#e			d&d!ed"edeedzdede$eee%ff
d#���Z&dS)'�WPDisabledRulez�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
    c�$�eZdZejZdZdZdS)�WPDisabledRule.Meta�wp_disabled_rules)))�rule_id�scope�scope_valueTN)�__name__�
__module__�__qualname__r�db�database�db_table�indexes���[/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/wp_disabled_rule.py�Metar-s�������;��&��@���rrF)�nullT�global�domain�	wordpress�agentNr�domains�source�user_id�	timestamp�returnc��|�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      r�storezWPDisabledRule.storeHs\��.���	���I��	��+�+���)�V�W���
��$�$�W�i���I�I�Irc��|�||jd|||���}|rt�d|||��t	|��S)zADisable a rule globally (independent of domain-specific entries).N�rrr�disabled_atr%r&z1Disabled rule %s globally (source=%s, user_id=%s))�_create_if_not_exists�SCOPE_GLOBAL�logger�debug�int)r-rr'r%r&�createds      rr,z WPDisabledRule._disable_globallyhsm���+�+���"��!���
,�
�
���	��L�L�C����	
�
�
��7�|�|�rc	��d}|D]G}|�||j||||���}|r#|dz
}t�d||||���H|S)zBDisable a rule for specific domains (independent of global state).rr0�z6Disabled 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_domains�s������	�	�F��/�/���&�"�%���
0���G��
���
�����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)�insert�executer)r-rrrr1r%r&s       rr2z$WPDisabledRule._create_if_not_exists�sc�� 	��J�J���'�'��#*�

�
�
��g�i�i�i��4���	�	�	��5�5�	���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))�delete�whererrr3r?r4r5r:r�in_)r-rr$r;s    r�removezWPDisabledRule.remove�s����	��
�
������K�7�*��I��!1�1�������

��
B����7��A�A�A���
�
������K�7�*��I��!1�1��O�'�'��0�0���
����
��
����6������
�rc��|�Q|����|j|k|j|jk�����S|����|j|k|j|jk|j|jk|j|kzz�����S)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
        )�selectrBrrr3�existsr:r)r-rr!s   r�is_rule_disabledzWPDisabledRule.is_rule_disabled�s����>��
�
������K�7�*��I��!1�1�������

�
�J�J�L�L�
�U���w�&��Y�#�"2�2���c�&6�6��?�f�4�6��	�	��V�X�X�
	
r�include_globalc�~�|rk|�|j���|j|jk|j|jk|j|kzz�����}nE|�|j���|j|jk|j|k��}d�|D��S)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
        c��g|]	}|j��
Sr�r��.0�rows  r�
<listcomp>z6WPDisabledRule.get_domain_disabled.<locals>.<listcomp>/s��-�-�-����-�-�-r)rFrrBrr3r:r�distinct)r-r!rI�querys    r�get_domain_disabledz"WPDisabledRule.get_domain_disableds����	��
�
�3�;�'�'����Y�#�"2�2���c�&6�6��?�f�4�6��������
�E��J�J�s�{�+�+�1�1��	�S�-�-���6�)���E�.�-�u�-�-�-�-rc��|�|j���|j|jk��}d�|D��S)z�
        Get all rule IDs that are disabled globally.

        Returns:
            Iterator of globally disabled rule IDs
        c3�$K�|]}|jV��dS�NrLrMs  r�	<genexpr>z5WPDisabledRule.get_global_disabled.<locals>.<genexpr>:s$����-�-����-�-�-�-�-�-r)rFrrBrr3)r-rRs  r�get_global_disabledz"WPDisabledRule.get_global_disabled1sC���
�
�3�;�'�'�-�-�c�i�3�;K�.K�L�L��-�-�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��)9�9���#�#�L�1�1��L��
F��	�S�%5�5��E�E����	1��9�� 0�0�0��trr�limit�offsetc��|�||��}|�|j���|j���tj|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
        Nc��g|]	}|j��
SrrLrMs  rrPz(WPDisabledRule.fetch.<locals>.<listcomp>�s'��
�
�
��C�K�
�
�
rF)r�	is_globalr$Trar$)r\rFr�group_by�order_byr	�MAXr1�descrBr;r^r]rCrr3r:�appendr�sorted)r-r]r^rYrI�	condition�rule_ids_query�total_count�paginated_rule_ids�
rows_query�rules_by_idrO�resultr�	rule_datas               r�fetchzWPDisabledRule.fetchSs��@�/�/��n�M�M�	�
�J�J�s�{�#�#�
�X�c�k�
"�
"�
�X�b�f�S�_�-�-�2�2�4�4�
5�
5�	�
� �+�1�1�)�<�<�N�%�*�*�,�,��
�
�#1�#8�#8��#@�#@�#F�#F�u�#M�#M�
�
�
��"�	#���?�"��Z�Z�\�\�'�'�����8J�(K�(K�L�L�
�� �#�)�)�)�4�4�J�(*���	L�	L�C��{�+�-�-�"�{�!&�!�,�,��C�K�(��y�C�,�,�,�8<��C�K�(��5�5���c�.�.�.��C�K�(��3�:�:�3�?�K�K�K����)�	%�	%�G�#�G�,�I�#)�)�I�*>�#?�#?�I�i� ��M�M�)�$�$�$�$��F�"�"rrV)F)rNF)'rrr�__doc__rr�idrrrrrr1r%rr=r3r:�SOURCE_WORDPRESS�SOURCE_AGENT�classmethod�str�listr6�floatr.r,r+�boolr2rDrHrSrrXr\�tuple�dictrprrrr
r
%s��������A�A�A�A�A�A�A�A�

��	�	�B��i�U�#�#�#�G��I�5�!�!�!�E��)��&�&�&�K��*�%�(�(�(�K�
�Y�E�
"�
"�
"�F�%��5�1�1�1���L��L�#���L��#'�
J�J��J��c��T�!�J��	J�
�J��4�<�
J�

�J�J�J��[�J�>�������	�
��

�
����[��2�����c����	�
���
�

�����[��<�������4�Z�	�
���
���
�����[��:�(�S�(�4��9�t�+;�(��(�(�(��[�(�T�"
�"
�s�"
�C�$�J�"
�$�"
�"
�"
��[�"
�H�16�.�.��.�*.�.�	
�c��.�.�.��[�.�B�.�H�S�M�.�.�.��[�.����3�i�$�&�������[��,��)-�$�O#�O#��O#��O#��3�i�$�&�	O#�
�O#�
�s�D��J��	�
O#�O#�O#��[�O#�O#�O#rr
�	incidentsr(c�`�d�|D��}|s|D]}d|d<�dSd�|D��}tjtjk}|r=|tjtjktj�|��zz}t�tjtjtj���tj�|��|��}t��}t��}|D]S}|jtjkr|�
|j���2|�
|j|jf���T|D]N}|�d��}	|�d��}
t|	duo
|	|vp	|
duo|	|
f|v��|d<�OdS)a�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.
    c�H�h|]}|�d���|d�� S)�rule��get�rN�incs  r�	<setcomp>z7enrich_incidents_with_disabled_state.<locals>.<setcomp>�s2������C�G�G�F�O�O�,G��F��,G�,G�,GrFrHNc�H�h|]}|�d���|d�� S)r!r�r�s  rr�z7enrich_incidents_with_disabled_state.<locals>.<setcomp>�s4������c�g�g�h�.?�.?�.K��H�
�.K�.K�.Krrr!)
r
rr3r:rrCrFrrB�set�addr�ry)r|�rule_idsr�r$rhrR�globally_disabled�domain_disabledrOrr!s           r�$enrich_incidents_with_disabled_stater��s�����(����H����	,�	,�C�&+�C�"�#�#�����!*����G��$��(C�C�I��
��
�
!�^�%@�
@��)�-�-�g�6�6�
8�
�	�
�!�!������"�
�
��e�N�"�&�&�x�0�0�)�<�<�	
�#&�%�%��,/�E�E�O��@�@���9��3�3�3��!�!�#�+�.�.�.�.������c�o� >�?�?�?�?��	
�	
���w�w�v��������"�"��"&����
��)�)�N��$�&�L�D�&�>�_�+L�	#
�#
�����	
�	
r)rq�collections.abcr�loggingr*�peeweerrrrrr	�defence360agent.modelr
r�	getLoggerrr4r
rwr{r�rrr�<module>r�s����$%�$�$�$�$�$�������������������������2�1�1�1�1�1�1�1�	��	�8�	$�	$��~#�~#�~#�~#�~#�U�~#�~#�~#�B3
�D��J�3
�4�3
�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.py0000644000000000000000000000257400000000000015552 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_oldest(cls, limit=1):
        old = cls.select().order_by(cls.timestamp).limit(limit)
        return old

    @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<peewee.Node>
        """
        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<str>
        :return: list<peewee.Node>
        """
        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<OrderBy>
        :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.py0000644000000000000000000003564400000000000016141 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 contextlib import ExitStack, contextmanager
from datetime import timedelta


from peewee import (
    CharField,
    FloatField,
    IntegerField,
    TextField,
)
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

logger = logging.getLogger(__name__)

_UNSET = object()


class WordpressIncident(Model):
    """
    WordPress incident model for CVE protection.
    Uses dedicated wordpress_incident table created in migration 191.

    Unique constraint on (abuser, name, plugin, rule, severity, domain)
    allows deduplication similar to the aggregate plugin.
    """

    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)

    class Meta:
        database = instance.db
        db_table = "wordpress_incident"
        indexes = (
            # Unique composite index for deduplication (migration 192)
            (("abuser", "name", "plugin", "rule", "severity", "domain"), 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)

    return {
        # Standard incident fields
        "plugin": "wordpress",
        "rule": incident_data.get("rule_id", "unknown"),
        "timestamp": float(incident_data.get("ts", 0)),
        "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 upsert_wordpress_incident(
    incident_data: dict, site_info: dict
) -> WordpressIncident:
    """
    Insert or update a WordPress incident in the wordpress_incident table.

    If an incident with the same aggregate key (abuser, name, plugin, rule,
    severity, domain) exists, increment its retries counter and update timestamp.
    Otherwise, create a new incident with retries=1.

    This implements similar deduplication logic as the aggregate plugin.

    Args:
        incident_data: Dict with incident fields from PHP incident file
        site_info: Dict with site information (domain, site_path, username, user_id)

    Returns:
        WordpressIncident instance (either newly created or updated)
    """
    incident_dict = build_incident_dict(incident_data, site_info)

    # Use INSERT ... ON CONFLICT for efficient upsert
    # On conflict: increment retries and update timestamp
    # Use RETURNING to get the inserted/updated record without a separate query
    result = (
        WordpressIncident.insert(**incident_dict)
        .on_conflict(
            conflict_target=[
                WordpressIncident.abuser,
                WordpressIncident.name,
                WordpressIncident.plugin,
                WordpressIncident.rule,
                WordpressIncident.severity,
                WordpressIncident.domain,
            ],
            update={
                WordpressIncident.retries: WordpressIncident.retries + 1,
                WordpressIncident.timestamp: incident_dict["timestamp"],
            },
        )
        .returning(WordpressIncident)
        .execute()
    )

    # Get the first (and only) returned row
    return list(result)[0]


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],
) -> list[dict]:
    """
    Bulk create WordPress incidents in a single transaction.
    Args:
        incidents_data: List of dictionaries containing incident field data

    Returns:
        List of created incident dictionaries
    """
    if not incidents_data:
        return []

    # Insert all incidents in bulk with RETURNING to get the created objects
    result = (
        WordpressIncident.insert_many(incidents_data)
        .returning(WordpressIncident)
        .execute()
    )

    return [wordpress_incident_to_dict(inc) for inc in result]


def delete_old_wordpress_incidents(days: int):
    cutoff_time = time.time() - timedelta(days=days).total_seconds()
    deleted = (
        WordpressIncident.delete()
        .where(
            (WordpressIncident.plugin == "wordpress")
            & (WordpressIncident.timestamp.cast("REAL") < cutoff_time)
        )
        .execute()
    )
    return deleted


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  �

n�M�K/@��6�dZddlmZmZGd�de��ZdS)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
�)�ABC�abstractmethodc�j�eZdZeeed�������Zeed�����ZdS)�BaseCleanerc��dS�N���clss �W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/mr_proper/__init__.py�PERIODzBaseCleaner.PERIODs	��	
��c��
K�dSrr	r
s r�cleanupzBaseCleaner.cleanups
����	
�rN)�__name__�
__module__�__qualname__�property�classmethodrr
rr	rrrrsf������
���
�
��^��[��X�
���
�
��^��[�
�
�
rrN)�__doc__�abcrrrr	rr�<module>rs]��	�	�$�#�#�#�#�#�#�#�

�

�

�

�

�#�

�

�

�

�

rdefence360agent/mr_proper/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000231000000000000021220 0ustar  �

n�M�K/@��6�dZddlmZmZGd�de��ZdS)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
�)�ABC�abstractmethodc�j�eZdZeeed�������Zeed�����ZdS)�BaseCleanerc��dS�N���clss �W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/mr_proper/__init__.py�PERIODzBaseCleaner.PERIODs	��	
��c��
K�dSrr	r
s r�cleanupzBaseCleaner.cleanups
����	
�rN)�__name__�
__module__�__qualname__�property�classmethodrr
rr	rrrrsf������
���
�
��^��[��X�
���
�
��^��[�
�
�
rrN)�__doc__�abcrrrr	rr�<module>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  �

s�����s���dS)N�r��W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/__init__.py�<module>rs���rdefence360agent/myimunify/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000030400000000000021242 0ustar  �

s�����s���dS)N�r��W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/__init__.py�<module>rs���rdefence360agent/myimunify/__pycache__/billing.cpython-311.opt-1.pyc0000644000000000000000000000476500000000000022101 0ustar  �

�`�0������ddlmZmZddlmZeGd�d����ZeGd�d����ZeGd�d����Zd	�Zd
�Z	dS)�)�	dataclass�asdict)�configc��eZdZdZdS)�
MILicenseType�FreemiumN)�__name__�
__module__�__qualname__�FREEMIUM���V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/billing.pyrrs�������H�H�Hrrc��eZdZdZdZdS)�IncompatibilityIDz=
    Contains unique incompatibilities IDs for a billing
    �LICENSE_IS_NOT_SUPPORTEDN)r	r
r�__doc__�UNSUPPORTED_LICENSEr
rrrr
s"��������5���rrc�>�eZdZUdZeed<eed<ed���ZdS)�CompatibilityIssuezC
    Generic class for keeping compatibility issues with WHMCS
    �type�descriptionc� �t|��S�N)r)�selfs r�	dict_reprzCompatibilityIssue.dict_reprs���d�|�|�rN)r	r
rr�str�__annotations__�propertyrr
rrrrsP����������I�I�I�����
����X���rrc�D�tj��rtjSdSr)r�is_mi_freemium_licenserrr
rr�get_license_typer"!s!��
�$�&�&�&��%�%��4rc��K�g}t��tjkr3|�t	t
jd���j��|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����]�3�3�3��
�
��&�:�K�
�
�
�
�
	
�	
�	
��MrN)
�dataclassesrr�defence360agent.contractsrrrrr"r&r
rr�<module>r)s���)�)�)�)�)�)�)�)�,�,�,�,�,�,�������������5�5�5�5�5�5�5���5��
�
�
�
�
�
�
���
��������rdefence360agent/myimunify/__pycache__/billing.cpython-311.pyc0000644000000000000000000000476500000000000021142 0ustar  �

�`�0������ddlmZmZddlmZeGd�d����ZeGd�d����ZeGd�d����Zd	�Zd
�Z	dS)�)�	dataclass�asdict)�configc��eZdZdZdS)�
MILicenseType�FreemiumN)�__name__�
__module__�__qualname__�FREEMIUM���V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/billing.pyrrs�������H�H�Hrrc��eZdZdZdZdS)�IncompatibilityIDz=
    Contains unique incompatibilities IDs for a billing
    �LICENSE_IS_NOT_SUPPORTEDN)r	r
r�__doc__�UNSUPPORTED_LICENSEr
rrrr
s"��������5���rrc�>�eZdZUdZeed<eed<ed���ZdS)�CompatibilityIssuezC
    Generic class for keeping compatibility issues with WHMCS
    �type�descriptionc� �t|��S�N)r)�selfs r�	dict_reprzCompatibilityIssue.dict_reprs���d�|�|�rN)r	r
rr�str�__annotations__�propertyrr
rrrrsP����������I�I�I�����
����X���rrc�D�tj��rtjSdSr)r�is_mi_freemium_licenserrr
rr�get_license_typer"!s!��
�$�&�&�&��%�%��4rc��K�g}t��tjkr3|�t	t
jd���j��|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����]�3�3�3��
�
��&�:�K�
�
�
�
�
	
�	
�	
��MrN)
�dataclassesrr�defence360agent.contractsrrrrr"r&r
rr�<module>r)s���)�)�)�)�)�)�)�)�,�,�,�,�,�,�������������5�5�5�5�5�5�5���5��
�
�
�
�
�
�
���
��������rdefence360agent/myimunify/__pycache__/constants.cpython-311.opt-1.pyc0000644000000000000000000000033300000000000022460 0ustar  �

��X��Qu��
�dZdS)�	myimunifyN)�	MYIMUNIFY���X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/constants.py�<module>rs���	�	�	rdefence360agent/myimunify/__pycache__/constants.cpython-311.pyc0000644000000000000000000000033300000000000021521 0ustar  �

��X��Qu��
�dZdS)�	myimunifyN)�	MYIMUNIFY���X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/constants.py�<module>rs���	�	�	rdefence360agent/myimunify/__pycache__/model.cpython-311.opt-1.pyc0000644000000000000000000001624200000000000021552 0ustar  �

��!�
�2��N�ddlZddlZddlZddlmZmZddlmZmZddl	m
cmcmZ
ddlmZddlmZmZddlmZddlmZmZddlmZejd	d
d���Zeje��ZGd�d
e��Zd�Z 	ddee!de"de"fd�Z#de"fd�Z$dee!de"fd�Z%dS)�N)�List�Optional)�BooleanField�	CharField)�MessageType)�Model�instance)�run_in_executor)�execute_iterable_expression�importer��
update_configzimav.malwarelib.model�
MalwareHit)�module�name�defaultc��eZdZdZGd�d��Zed���Zedd���Ze	de
ed	efd
���Z
e	deedefd
���ZdS)�	MyImunifyzSecure-site related settingsc� �eZdZejZdZdS)�MyImunify.Meta�	myimunifyN)�__name__�
__module__�__qualname__r	�db�database�db_table���T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/model.py�Metars�������;�����rr!T)�uniqueF)�nullr�user�returnc�\�|�|dkrdS|�|ddi���\}}|jS)z%Get SecureSite protection by usernameN�rootT�
protectionF)r$�defaults)�
get_or_creater()�clsr$�perm�_s    r �get_protectionzMyImunify.get_protection$s?���<�4�6�>�>��4��#�#���u�8M�#�N�N���a���r�users�statusc���|��fd�|D�����|jgg|j�i������dS)Nc���g|]}|�d���S))r$r(r)�.0r$r0s  �r �
<listcomp>z5MyImunify.update_users_protection.<locals>.<listcomp>1s!���D�D�D�d�d�&�
1�
1�D�D�Dr)�conflict_target�preserve�update)�insert_many�on_conflictr$r(�execute)r+r/r0s  `r �update_users_protectionz!MyImunify.update_users_protection.sd������D�D�D�D�e�D�D�D�	
�	
�
�+� �X�J���N�F�+��
�
��'�)�)�)�)�)rN)rrr�__doc__r!rr$rr(�classmethodr�str�boolr.rr;rrr rrs�������&�&����������9�D�!�!�!�D���5�%�8�8�8�J���(�3�-��D�����[����D��I��t�����[���rrc��K�t�dSt�|d���}|r0|�tj|������d{V��dSdS)NT)r$�cleanup)�hits)r�malicious_select�process_messager�MalwareCleanupTask)�sinkr$rBs   r �malware_cleanuprG9sx���������&�&�D�$�&�?�?�D��N��"�"�;�#A�t�#L�#L�#L�M�M�M�M�M�M�M�M�M�M�M�N�NrFr/r0�force_config_updatec�������K�td��fd����d{V��d��sd�|rt�dd�ii��g}n��fd��D��}�r|�fd��D��z
}tj|��d{V��dS)Nc�:��t�����S�N)rr;)r0r/s��r �<lambda>z)update_users_protection.<locals>.<lambda>Fs���	�1�1�%��@�@�r�LOG�PROACTIVE_DEFENCE�modec�:��g|]}t�dd�ii|����S)rNrOr
)r3r$�proactive_moderFs  ��r r4z+update_users_protection.<locals>.<listcomp>TsH���
�
�
��
��$�v�~�&>�?��
�
�
�
�
rc�0��g|]}t�|����Sr)rG)r3r$rFs  �r r4z+update_users_protection.<locals>.<listcomp>^s#���@�@�@�$�/�$��-�-�@�@�@r)r
r�asyncio�gather)rFr/r0rH�tasksrQs```  @r r;r;As����������@�@�@�@�@�����������N������
���$�v�~�&>�?�
�
�
���
�
�
�
�
��

�
�
���A�
�@�@�@�@�%�@�@�@�@��
�.�%�
 � � � � � � � � � rc��K�tj������d{V��}t|||���d{V��dS)z#Set protection status for all usersN)�hp�HostingPanel�	get_usersr;)rFr0�panel_userss   r �#set_protection_status_for_all_usersr[cs[������)�)�3�3�5�5�5�5�5�5�5�5�K�
!�$��V�
<�
<�<�<�<�<�<�<�<�<�<rr%c��K�t|��}t�tj��}tt	j|������}||z
}|r;t�d|��d�}t|t|����||z
}|r2t�d|��t||d���d{V��t|��pt|��S)z5Synchronize existing permissions with myimunify userszRemove myimunify users %sc��t����tj�|����SrK)r�delete�wherer$�in_)�users_to_removes r �
expressionzsync_users.<locals>.expressionus9���#�#�%�%�+�+���"�"�?�3�3���
rzAdd permissions to users %sFN)
�setr�selectr$�	itertools�chain�tuples�logger�infor�listr;r?)rFr/rZ�myimunify_usersrarb�users_to_adds       r �
sync_usersrmis������e�*�*�K��&�&�y�~�6�6�O��)�/�?�+A�+A�+C�+C�D�E�E�O�%��3�O��G����/��A�A�A�	�	�	�
	$�J��_�0E�0E�F�F�F���0�L��:����1�<�@�@�@�%�d�E�5�9�9�9�9�9�9�9�9�9�����6��o�!6�!6�6r)F)&rSre�logging�typingrr�peeweerr�+defence360agent.subsys.panels.hosting_panel�subsys�panels�
hosting_panelrW�"defence360agent.contracts.messagesr�defence360agent.modelrr	�$defence360agent.model.simplificationr
�defence360agent.utilsrr�defence360agent.utils.configr�getr�	getLoggerrrhrrGr>r?r;r[rmrrr �<module>r|s���������������!�!�!�!�!�!�!�!�*�*�*�*�*�*�*�*�8�8�8�8�8�8�8�8�8�8�8�8�:�:�:�:�:�:�1�1�1�1�1�1�1�1�@�@�@�@�@�@�G�G�G�G�G�G�G�G�6�6�6�6�6�6�
�X�\�"��t����
�
��	�8�	$�	$�� � � � � �� � � �FN�N�N�GL�!�!��c��!�$(�!�?C�!�!�!�!�D=�D�=�=�=�=�7�$�s�)�7��7�7�7�7�7�7rdefence360agent/myimunify/__pycache__/model.cpython-311.pyc0000644000000000000000000001624200000000000020613 0ustar  �

��!�
�2��N�ddlZddlZddlZddlmZmZddlmZmZddl	m
cmcmZ
ddlmZddlmZmZddlmZddlmZmZddlmZejd	d
d���Zeje��ZGd�d
e��Zd�Z 	ddee!de"de"fd�Z#de"fd�Z$dee!de"fd�Z%dS)�N)�List�Optional)�BooleanField�	CharField)�MessageType)�Model�instance)�run_in_executor)�execute_iterable_expression�importer��
update_configzimav.malwarelib.model�
MalwareHit)�module�name�defaultc��eZdZdZGd�d��Zed���Zedd���Ze	de
ed	efd
���Z
e	deedefd
���ZdS)�	MyImunifyzSecure-site related settingsc� �eZdZejZdZdS)�MyImunify.Meta�	myimunifyN)�__name__�
__module__�__qualname__r	�db�database�db_table���T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/model.py�Metars�������;�����rr!T)�uniqueF)�nullr�user�returnc�\�|�|dkrdS|�|ddi���\}}|jS)z%Get SecureSite protection by usernameN�rootT�
protectionF)r$�defaults)�
get_or_creater()�clsr$�perm�_s    r �get_protectionzMyImunify.get_protection$s?���<�4�6�>�>��4��#�#���u�8M�#�N�N���a���r�users�statusc���|��fd�|D�����|jgg|j�i������dS)Nc���g|]}|�d���S))r$r(r)�.0r$r0s  �r �
<listcomp>z5MyImunify.update_users_protection.<locals>.<listcomp>1s!���D�D�D�d�d�&�
1�
1�D�D�Dr)�conflict_target�preserve�update)�insert_many�on_conflictr$r(�execute)r+r/r0s  `r �update_users_protectionz!MyImunify.update_users_protection.sd������D�D�D�D�e�D�D�D�	
�	
�
�+� �X�J���N�F�+��
�
��'�)�)�)�)�)rN)rrr�__doc__r!rr$rr(�classmethodr�str�boolr.rr;rrr rrs�������&�&����������9�D�!�!�!�D���5�%�8�8�8�J���(�3�-��D�����[����D��I��t�����[���rrc��K�t�dSt�|d���}|r0|�tj|������d{V��dSdS)NT)r$�cleanup)�hits)r�malicious_select�process_messager�MalwareCleanupTask)�sinkr$rBs   r �malware_cleanuprG9sx���������&�&�D�$�&�?�?�D��N��"�"�;�#A�t�#L�#L�#L�M�M�M�M�M�M�M�M�M�M�M�N�NrFr/r0�force_config_updatec�������K�td��fd����d{V��d��sd�|rt�dd�ii��g}n��fd��D��}�r|�fd��D��z
}tj|��d{V��dS)Nc�:��t�����S�N)rr;)r0r/s��r �<lambda>z)update_users_protection.<locals>.<lambda>Fs���	�1�1�%��@�@�r�LOG�PROACTIVE_DEFENCE�modec�:��g|]}t�dd�ii|����S)rNrOr
)r3r$�proactive_moderFs  ��r r4z+update_users_protection.<locals>.<listcomp>TsH���
�
�
��
��$�v�~�&>�?��
�
�
�
�
rc�0��g|]}t�|����Sr)rG)r3r$rFs  �r r4z+update_users_protection.<locals>.<listcomp>^s#���@�@�@�$�/�$��-�-�@�@�@r)r
r�asyncio�gather)rFr/r0rH�tasksrQs```  @r r;r;As����������@�@�@�@�@�����������N������
���$�v�~�&>�?�
�
�
���
�
�
�
�
��

�
�
���A�
�@�@�@�@�%�@�@�@�@��
�.�%�
 � � � � � � � � � rc��K�tj������d{V��}t|||���d{V��dS)z#Set protection status for all usersN)�hp�HostingPanel�	get_usersr;)rFr0�panel_userss   r �#set_protection_status_for_all_usersr[cs[������)�)�3�3�5�5�5�5�5�5�5�5�K�
!�$��V�
<�
<�<�<�<�<�<�<�<�<�<rr%c��K�t|��}t�tj��}tt	j|������}||z
}|r;t�d|��d�}t|t|����||z
}|r2t�d|��t||d���d{V��t|��pt|��S)z5Synchronize existing permissions with myimunify userszRemove myimunify users %sc��t����tj�|����SrK)r�delete�wherer$�in_)�users_to_removes r �
expressionzsync_users.<locals>.expressionus9���#�#�%�%�+�+���"�"�?�3�3���
rzAdd permissions to users %sFN)
�setr�selectr$�	itertools�chain�tuples�logger�infor�listr;r?)rFr/rZ�myimunify_usersrarb�users_to_adds       r �
sync_usersrmis������e�*�*�K��&�&�y�~�6�6�O��)�/�?�+A�+A�+C�+C�D�E�E�O�%��3�O��G����/��A�A�A�	�	�	�
	$�J��_�0E�0E�F�F�F���0�L��:����1�<�@�@�@�%�d�E�5�9�9�9�9�9�9�9�9�9�����6��o�!6�!6�6r)F)&rSre�logging�typingrr�peeweerr�+defence360agent.subsys.panels.hosting_panel�subsys�panels�
hosting_panelrW�"defence360agent.contracts.messagesr�defence360agent.modelrr	�$defence360agent.model.simplificationr
�defence360agent.utilsrr�defence360agent.utils.configr�getr�	getLoggerrrhrrGr>r?r;r[rmrrr �<module>r|s���������������!�!�!�!�!�!�!�!�*�*�*�*�*�*�*�*�8�8�8�8�8�8�8�8�8�8�8�8�:�:�:�:�:�:�1�1�1�1�1�1�1�1�@�@�@�@�@�@�G�G�G�G�G�G�G�G�6�6�6�6�6�6�
�X�\�"��t����
�
��	�8�	$�	$�� � � � � �� � � �FN�N�N�GL�!�!��c��!�$(�!�?C�!�!�!�!�D=�D�=�=�=�=�7�$�s�)�7��7�7�7�7�7�7rdefence360agent/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  �

s�����s���dS)N�r��^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/advice/__init__.py�<module>rs���rdefence360agent/myimunify/advice/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000031300000000000022475 0ustar  �

s�����s���dS)N�r��^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/advice/__init__.py�<module>rs���rdefence360agent/myimunify/advice/__pycache__/advice_manager.cpython-311.opt-1.pyc0000644000000000000000000002072600000000000024634 0ustar  �

�^w"������ddlZddlZddlmZddlmZddlmZddlm	Z	ddl
mZddlm
Z
ddlmZdd	lmZeje��Zd
Zd�Zd�Zd
efd�Zd
egfd�ZdS)�N)�
find_wp_paths)�	EventsAPI)�config)�get_myimunify_users)�get_upgrade_url_link)�HostingPanel)�MyImunifyWPAdvice)�	MyImunify�IMUNIFY_PROTECTIONc��|g}tj���tj�|�������}|r|d�dd��rdSdS)Nr�
protectionF�active�no)r
�select�where�user�in_�dicts�get)�username�item�responses   �d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/advice/advice_manager.py�get_myimunify_protection_statusrsl���:�D���!�!�'�'�	��(:�(:�4�(@�(@�A�A�G�G�I�I�H���H�Q�K�O�O�L�%�8�8���x��t�c��zK�g}|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|
dz�d|�dt�dd�d|�d|�dd�dd�dd�d|�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_user�smartadvice_domain�smartadvice_docroot�upgrade_url�smartadvice_title�smartadvice_description�iaidN�/�-zutf-8�_r�domain�website�	panel_urlz?show_cleanup_dialog=true�id�type�status�review�description�detailed_description�
is_premiumF�module_name�imunify�license_status�NOT_REQUIRED�subscription_status�total_stagesr�completed_stages�)rr�panel_user_linkr�hashlib�md5�encode�	hexdigestr	�ADV_TYPE�append�	to_advice)�imunify_advice�advices_by_docrootr�protection_statusr'�infected_docrootr r.r/r#r)�siter(�
hashed_udw�adv_id�im360_protection_advices                r�_make_advicerIs����|���0�1�H�7��A�A��
�0�
1�F�%�&;�<�� ��/�K� �!4�5�K�)�*C�D���&�!�D�"�n�n�4�4�X�>�>�>�>�>�>�>�>�I��.�/�/�G�G���d�*�*���[��,�,�&�,�,�7�,�,�3�3�G�<�<�
�
�
�)�+�+�	��'�'�:�'�'��"3�#
�#
�#
��X�#
��6�#
��G�#
� �)�*�*�	#
��v�
#
���#
��8�#
�$��#
�"6�!5�#
��u�#
�"�	�#
�*�>�#
�!2� 1�#
�$��#
� ��!#
�"�Q�##
��&	�!�!�"9�"C�"C�"E�"E�F�F�F�F��r�returnc	��K�g}t���d{V��}t�dt|����|r~|D]{}	t	|���d{V��}nM#t
$r@}t�dt|��t|����Yd}~�^d}~wwxYw|�|���||S)NzIM360 advice list: %sz<Unable to make advice based on item: %s, malformed error: %s)�get_advice_notifications�logger�info�strrI�KeyError�error�extend)�advices�advice_listr�advice_item�es     r�make_advicerW�s������G�0�2�2�2�2�2�2�2�2�K�
�K�K�'��[�)9�)9�:�:�:��(��	(�	(�D�	
�$0��$6�$6�6�6�6�6�6�6�����
�
�
����!���I�I���F�F�	������������
����
�N�N�;�'�'�'�'��Ns�A�
B(�(6B#�#B(c��F�K�td�t���d{V��D�����t��}�D]A}tj|��}|�dd��s|�|���B�|z
�t
j���d{V��}t�	dt|��t������fd�|D��}|D];}t|�d��|�d����|d<�<|S)	Nc�.�g|]}|d�
|d��S)r
rr8)�.0rs  r�
<listcomp>z,get_advice_notifications.<locals>.<listcomp>�s8��	
�	
�	
����%�	
����	
�	
�	
r�
CONTROL_PANEL�smart_advice_allowedzLSmart Advice events API response with notifications: %s, users to report: %sc�B��g|]}|�d���v�|��S)r)r)rZ�event�users_to_reports  �rr[z,get_advice_notifications.<locals>.<listcomp>�s;��������9�9�'�(�(�O�;�;�	�;�;�;rrrr )�setrr�
ConfigFiler�addr�
smart_advicesrMrNrOr)�users_to_popr�confr�datarr`s      @rrLrL�so������	
�	
�1�3�3�3�3�3�3�3�3�	
�	
�	
���O��5�5�L��#�#��� ��&�&���x�x��)?�@�@�	#����T�"�"�"��%��4�O��,�.�.�.�.�.�.�.�.�H�
�K�K�	6��H�
�
��O���	�����������D��
�
��2��H�H�'�(�(�$�(�(�3G�*H�*H�
�
��]����Kr)�loggingr:�clcommon.clwpos_libr�!defence360agent.api.server.eventsr�defence360agent.contractsr�&defence360agent.contracts.myimunify_idr�defence360agent.utils.whmcsr�+defence360agent.subsys.panels.hosting_panelr�*defence360agent.myimunify.advice.dataclassr	�defence360agent.myimunify.modelr
�	getLogger�__name__rMr>rrI�listrW�dictrLr8rr�<module>rus%����������-�-�-�-�-�-�7�7�7�7�7�7�,�,�,�,�,�,�F�F�F�F�F�F�<�<�<�<�<�<�D�D�D�D�D�D�I�H�H�H�H�H�5�5�5�5�5�5�	��	�8�	$�	$�������c�c�c�L�4�����(��������rdefence360agent/myimunify/advice/__pycache__/advice_manager.cpython-311.pyc0000644000000000000000000002072600000000000023675 0ustar  �

�^w"������ddlZddlZddlmZddlmZddlmZddlm	Z	ddl
mZddlm
Z
ddlmZdd	lmZeje��Zd
Zd�Zd�Zd
efd�Zd
egfd�ZdS)�N)�
find_wp_paths)�	EventsAPI)�config)�get_myimunify_users)�get_upgrade_url_link)�HostingPanel)�MyImunifyWPAdvice)�	MyImunify�IMUNIFY_PROTECTIONc��|g}tj���tj�|�������}|r|d�dd��rdSdS)Nr�
protectionF�active�no)r
�select�where�user�in_�dicts�get)�username�item�responses   �d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/advice/advice_manager.py�get_myimunify_protection_statusrsl���:�D���!�!�'�'�	��(:�(:�4�(@�(@�A�A�G�G�I�I�H���H�Q�K�O�O�L�%�8�8���x��t�c��zK�g}|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|
dz�d|�dt�dd�d|�d|�dd�dd�dd�d|�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_user�smartadvice_domain�smartadvice_docroot�upgrade_url�smartadvice_title�smartadvice_description�iaidN�/�-zutf-8�_r�domain�website�	panel_urlz?show_cleanup_dialog=true�id�type�status�review�description�detailed_description�
is_premiumF�module_name�imunify�license_status�NOT_REQUIRED�subscription_status�total_stagesr�completed_stages�)rr�panel_user_linkr�hashlib�md5�encode�	hexdigestr	�ADV_TYPE�append�	to_advice)�imunify_advice�advices_by_docrootr�protection_statusr'�infected_docrootr r.r/r#r)�siter(�
hashed_udw�adv_id�im360_protection_advices                r�_make_advicerIs����|���0�1�H�7��A�A��
�0�
1�F�%�&;�<�� ��/�K� �!4�5�K�)�*C�D���&�!�D�"�n�n�4�4�X�>�>�>�>�>�>�>�>�I��.�/�/�G�G���d�*�*���[��,�,�&�,�,�7�,�,�3�3�G�<�<�
�
�
�)�+�+�	��'�'�:�'�'��"3�#
�#
�#
��X�#
��6�#
��G�#
� �)�*�*�	#
��v�
#
���#
��8�#
�$��#
�"6�!5�#
��u�#
�"�	�#
�*�>�#
�!2� 1�#
�$��#
� ��!#
�"�Q�##
��&	�!�!�"9�"C�"C�"E�"E�F�F�F�F��r�returnc	��K�g}t���d{V��}t�dt|����|r~|D]{}	t	|���d{V��}nM#t
$r@}t�dt|��t|����Yd}~�^d}~wwxYw|�|���||S)NzIM360 advice list: %sz<Unable to make advice based on item: %s, malformed error: %s)�get_advice_notifications�logger�info�strrI�KeyError�error�extend)�advices�advice_listr�advice_item�es     r�make_advicerW�s������G�0�2�2�2�2�2�2�2�2�K�
�K�K�'��[�)9�)9�:�:�:��(��	(�	(�D�	
�$0��$6�$6�6�6�6�6�6�6�����
�
�
����!���I�I���F�F�	������������
����
�N�N�;�'�'�'�'��Ns�A�
B(�(6B#�#B(c��F�K�td�t���d{V��D�����t��}�D]A}tj|��}|�dd��s|�|���B�|z
�t
j���d{V��}t�	dt|��t������fd�|D��}|D];}t|�d��|�d����|d<�<|S)	Nc�.�g|]}|d�
|d��S)r
rr8)�.0rs  r�
<listcomp>z,get_advice_notifications.<locals>.<listcomp>�s8��	
�	
�	
����%�	
����	
�	
�	
r�
CONTROL_PANEL�smart_advice_allowedzLSmart Advice events API response with notifications: %s, users to report: %sc�B��g|]}|�d���v�|��S)r)r)rZ�event�users_to_reports  �rr[z,get_advice_notifications.<locals>.<listcomp>�s;��������9�9�'�(�(�O�;�;�	�;�;�;rrrr )�setrr�
ConfigFiler�addr�
smart_advicesrMrNrOr)�users_to_popr�confr�datarr`s      @rrLrL�so������	
�	
�1�3�3�3�3�3�3�3�3�	
�	
�	
���O��5�5�L��#�#��� ��&�&���x�x��)?�@�@�	#����T�"�"�"��%��4�O��,�.�.�.�.�.�.�.�.�H�
�K�K�	6��H�
�
��O���	�����������D��
�
��2��H�H�'�(�(�$�(�(�3G�*H�*H�
�
��]����Kr)�loggingr:�clcommon.clwpos_libr�!defence360agent.api.server.eventsr�defence360agent.contractsr�&defence360agent.contracts.myimunify_idr�defence360agent.utils.whmcsr�+defence360agent.subsys.panels.hosting_panelr�*defence360agent.myimunify.advice.dataclassr	�defence360agent.myimunify.modelr
�	getLogger�__name__rMr>rrI�listrW�dictrLr8rr�<module>rus%����������-�-�-�-�-�-�7�7�7�7�7�7�,�,�,�,�,�,�F�F�F�F�F�F�<�<�<�<�<�<�D�D�D�D�D�D�I�H�H�H�H�H�5�5�5�5�5�5�	��	�8�	$�	$�������c�c�c�L�4�����(��������rdefence360agent/myimunify/advice/__pycache__/dataclass.cpython-311.opt-1.pyc0000644000000000000000000000522500000000000023643 0ustar  �

%SD�R#���\�ddlmZmZddlmZmZddlmZeGd�d����ZdS)�)�	dataclass�field)�datetime�timezone)�Optionalc��eZdZUeed<eed<eed<eed<eed<eed<eed<eed<eed	<eed
<eed<eed<eed
<eed<eed<eed<ed����Zee	ed<ed����Z
ee	ed<d�ZdS)�MyImunifyWPAdvice�username�domain�website�	panel_url�id�type�status�description�detailed_description�
is_premium�module_name�license_status�subscription_status�upgrade_url�total_stages�completed_stagesc�b�tjtj�����S�N�r�nowr�utc�	isoformat���_/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/advice/dataclass.py�<lambda>zMyImunifyWPAdvice.<lambda>�����X�\� :� :� D� D� F� F�r!)�default_factory�
created_atc�b�tjtj�����Srrr r!r"r#zMyImunifyWPAdvice.<lambda>r$r!�
updated_atc���|j|jd|j|j|j|jd�|j|j|j|j	|j
|j|j|j
|jd�|j|j|jd�d�S)N�imunify)�appr
rrr
)rr)rrrrrrr�subscriptionrrr)r&r(�metadata�advice)r&r(r
rr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	s3��������M�M�M��K�K�K�
�L�L�L��N�N�N��G�G�G�

�I�I�I��K�K�K����������O�O�O�������������������������%*�U�F�F�&�&�&�J���"����&+�U�F�F�&�&�&�J���"����
�
�
�
�
r!r	N)�dataclassesrrrr�typingrr	r r!r"�<module>r9s���(�(�(�(�(�(�(�(�'�'�'�'�'�'�'�'��������3
�3
�3
�3
�3
�3
�3
���3
�3
�3
r!defence360agent/myimunify/advice/__pycache__/dataclass.cpython-311.pyc0000644000000000000000000000522500000000000022704 0ustar  �

%SD�R#���\�ddlmZmZddlmZmZddlmZeGd�d����ZdS)�)�	dataclass�field)�datetime�timezone)�Optionalc��eZdZUeed<eed<eed<eed<eed<eed<eed<eed<eed	<eed
<eed<eed<eed
<eed<eed<eed<ed����Zee	ed<ed����Z
ee	ed<d�ZdS)�MyImunifyWPAdvice�username�domain�website�	panel_url�id�type�status�description�detailed_description�
is_premium�module_name�license_status�subscription_status�upgrade_url�total_stages�completed_stagesc�b�tjtj�����S�N�r�nowr�utc�	isoformat���_/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/advice/dataclass.py�<lambda>zMyImunifyWPAdvice.<lambda>�����X�\� :� :� D� D� F� F�r!)�default_factory�
created_atc�b�tjtj�����Srrr r!r"r#zMyImunifyWPAdvice.<lambda>r$r!�
updated_atc���|j|jd|j|j|j|jd�|j|j|j|j	|j
|j|j|j
|jd�|j|j|jd�d�S)N�imunify)�appr
rrr
)rr)rrrrrrr�subscriptionrrr)r&r(�metadata�advice)r&r(r
rr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	s3��������M�M�M��K�K�K�
�L�L�L��N�N�N��G�G�G�

�I�I�I��K�K�K����������O�O�O�������������������������%*�U�F�F�&�&�&�J���"����&+�U�F�F�&�&�&�J���"����
�
�
�
�
r!r	N)�dataclassesrrrr�typingrr	r r!r"�<module>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  �

�d﮽��T�ddlZddlZddlmZmZeje��ZdZdZ	d�Z
dS)�N)�
CheckRunError�	check_runzcl-hosting-smart-advice�imunifyc��4K�tj|��}	ttddtd|g���d{V��}tj|��}|�dd��S#t$r&}t�	d|��Yd}~dSd}~wwxYw)N�syncz--appz--json�successFz9Failed to sync advices with `cl-hosting-smart-advice`: %s)
�json�dumpsr�
EXECUTABLE�APP_NAME�loads�getr�logger�warning)�advices�payload�out�result�es     �n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/advice/hosting_smart_advice_api.py�sync_advicesrs������j��!�!�G�,��
���(�H�g�F�
�
�
�
�
�
�
�
����C�����z�z�)�U�+�+�+���������G��	
�	
�	
��u�u�u�u�u�����	���s�%A'�'
B�1B�B)r	�logging�defence360agent.utilsrr�	getLogger�__name__rrrr��r�<module>rsd����������:�:�:�:�:�:�:�:�	��	�8�	$�	$��
&�
���
,�
,�
,�
,�
,rdefence360agent/myimunify/advice/__pycache__/hosting_smart_advice_api.cpython-311.pyc0000644000000000000000000000247500000000000025776 0ustar  �

�d﮽��T�ddlZddlZddlmZmZeje��ZdZdZ	d�Z
dS)�N)�
CheckRunError�	check_runzcl-hosting-smart-advice�imunifyc��4K�tj|��}	ttddtd|g���d{V��}tj|��}|�dd��S#t$r&}t�	d|��Yd}~dSd}~wwxYw)N�syncz--appz--json�successFz9Failed to sync advices with `cl-hosting-smart-advice`: %s)
�json�dumpsr�
EXECUTABLE�APP_NAME�loads�getr�logger�warning)�advices�payload�out�result�es     �n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/advice/hosting_smart_advice_api.py�sync_advicesrs������j��!�!�G�,��
���(�H�g�F�
�
�
�
�
�
�
�
����C�����z�z�)�U�+�+�+���������G��	
�	
�	
��u�u�u�u�u�����	���s�%A'�'
B�1B�B)r	�logging�defence360agent.utilsrr�	getLogger�__name__rrrr��r�<module>rsd����������:�:�:�:�:�:�:�:�	��	�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  �

s�����s���dS)N�r��U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/__init__.py�<module>rs���rdefence360agent/plugins/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000030200000000000020673 0ustar  �

s�����s���dS)N�r��U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/__init__.py�<module>rs���rdefence360agent/plugins/__pycache__/accumulate.cpython-311.opt-1.pyc0000644000000000000000000001417700000000000022235 0ustar  �

Y2��Y����ddlZddlZddlZddlmZddlmZddlmZm	Z	m
Z
ddlmZm
Z
mZddlmZmZee��ZGd�dee
��ZdS)	�N)�	getLogger)�
inactivity)�
Accumulatable�MessageType�
Splittable)�MessageSink�
MessageSource�expect)�recurring_check�safe_cancel_taskc�&��eZdZejjZdZee	j
�dd����Zee	j
�dd����Z
ee
f�fd�	Zd�Zd�Zd	�Zd
�Zeej��defd���Zd
�Z�xZS)�
Accumulate���%IMUNIFY360_AGGREGATE_MESSAGES_TIMEOUT�<�*IMUNIFY360_AGGREGATE_SHUTDOWN_SEND_TIMEOUT�2c���t��jdi|��||_||_t	jt��|_dS)N�)�super�__init__�_period�_shutdown_timeout�collections�defaultdict�list�_data)�self�period�shutdown_timeout�kwargs�	__class__s    ��W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/accumulate.pyrzAccumulate.__init__"sG���	�����"�"�6�"�"�"����!1��� �,�T�2�2��
�
�
�c���K�||_||_|jdkrdn<|�t	|j��|j������|_dS)Nr)�_loop�_sinkr�create_taskr�_flush�_task)r�loop�sinks   r#�
create_sourcezAccumulate.create_source-sf������
���
��|�q� � �
�D��!�!�"L�"?�/�$�,�"?�"?���"L�"L�"N�"N�O�O�	
�
�
�
r$c��K�||_dS)N)r&)rr+s  r#�create_sinkzAccumulate.create_sink6s������
�
�
r$c��$K�	tj|���|j���d{V��dS#tj$rHt
�d|j��|j�t|j���d{V��YdSYdSwxYw)Nz5Timeout (%ss) sending messages to server on shutdown.)	�asyncio�wait_for�stopr�TimeoutError�logger�errorr*r�rs r#�shutdownzAccumulate.shutdown9s�����		3��"�4�9�9�;�;��0F�G�G�G�G�G�G�G�G�G�G�G���#�	3�	3�	3��L�L�G��&�
�
�
��z�%�&�t�z�2�2�2�2�2�2�2�2�2�2�2�2�&�%�%�
	3���s�28�AB�Bc���K�t�d��|j�t|j���d{V��t�d��|����d{V��dS)NzAccumulate.stop cancel _taskzAccumulate.stop wait lock)r5�infor*rr)r7s r#r3zAccumulate.stopEs|�������2�3�3�3��:�!�"�4�:�.�.�.�.�.�.�.�.�.����/�0�0�0��k�k�m�m���������r$�messagec��DK�t|jt��r|jn|jf}|���r`tj�d��5|D]"}|j|�|���#	ddd��dS#1swxYwYdSdS)N�
accumulate)	�
isinstance�
LIST_CLASS�tuple�
do_accumulater�track�taskr�append)rr;�
list_types�	list_types    r#�collectzAccumulate.collectMs�����'�,�e�4�4�
'�G����$�&�	�
� � �"�"�	:��!�&�&�|�4�4�
:�
:�!+�:�:�I��J�y�)�0�0��9�9�9�9�:�
:�
:�
:�
:�
:�
:�
:�
:�
:�
:�
:�
:����
:�
:�
:�
:�
:�
:�	:�	:s�&B�B�Bc
��K�|j}tjt��|_|���D]�\}}t|t��r|�|��n|f}|D]�}t�	d|j
�dt|���d���	|j�
||������d{V���a#t$rt�d||���wxYw��dS)NzPrepare z(<items=z>) for further processing)�itemsz%s, %s)rrrrrI�
issubclassr�batchedr5r:�__name__�lenr'�process_message�	TypeErrorr6)r�	copy_datarF�messagesrK�batchs      r#r)zAccumulate._flushYsB�����J�	� �,�T�2�2��
�#,�?�?�#4�#4�	�	��I�x��i��4�4�!�	�!�!�(�+�+�+��[�
�!�
�
�����-�y�1�-�-�3�u�:�:�-�-�-������*�4�4�Y�Y�U�5K�5K�5K�L�L�L�L�L�L�L�L�L�L�� �����L�L��9�e�<�<�<������
�	�	s�&*C�(C9)rL�
__module__�__qualname__r�ProcessingOrder�POST_PROCESS_MESSAGE�PROCESSING_ORDER�SHUTDOWN_PRIORITY�int�os�environ�get�DEFAULT_AGGREGATE_TIMEOUT�SHUTDOWN_SEND_TIMEOUTrr-r/r8r3r
rrrGr)�
__classcell__)r"s@r#rrs)�������"�2�G����!$��
�
���>��C�C�!�!�� �C�
�
���C�R�H�H����)�.�	3�	3�	3�	3�	3�	3�
�
�
����
3�
3�
3�����V�K�%�&�&�	:�]�	:�	:�	:�'�&�	:�������r$r)r1rrZ�loggingr�defence360agent.apir�"defence360agent.contracts.messagesrrr�!defence360agent.contracts.pluginsrr	r
�defence360agent.utilsrrrLr5rrr$r#�<module>res����������	�	�	�	�������*�*�*�*�*�*�����������
����������
D�C�C�C�C�C�C�C�	��8�	�	��Z�Z�Z�Z�Z��m�Z�Z�Z�Z�Zr$defence360agent/plugins/__pycache__/accumulate.cpython-311.pyc0000644000000000000000000001417700000000000021276 0ustar  �

Y2��Y����ddlZddlZddlZddlmZddlmZddlmZm	Z	m
Z
ddlmZm
Z
mZddlmZmZee��ZGd�dee
��ZdS)	�N)�	getLogger)�
inactivity)�
Accumulatable�MessageType�
Splittable)�MessageSink�
MessageSource�expect)�recurring_check�safe_cancel_taskc�&��eZdZejjZdZee	j
�dd����Zee	j
�dd����Z
ee
f�fd�	Zd�Zd�Zd	�Zd
�Zeej��defd���Zd
�Z�xZS)�
Accumulate���%IMUNIFY360_AGGREGATE_MESSAGES_TIMEOUT�<�*IMUNIFY360_AGGREGATE_SHUTDOWN_SEND_TIMEOUT�2c���t��jdi|��||_||_t	jt��|_dS)N�)�super�__init__�_period�_shutdown_timeout�collections�defaultdict�list�_data)�self�period�shutdown_timeout�kwargs�	__class__s    ��W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/accumulate.pyrzAccumulate.__init__"sG���	�����"�"�6�"�"�"����!1��� �,�T�2�2��
�
�
�c���K�||_||_|jdkrdn<|�t	|j��|j������|_dS)Nr)�_loop�_sinkr�create_taskr�_flush�_task)r�loop�sinks   r#�
create_sourcezAccumulate.create_source-sf������
���
��|�q� � �
�D��!�!�"L�"?�/�$�,�"?�"?���"L�"L�"N�"N�O�O�	
�
�
�
r$c��K�||_dS)N)r&)rr+s  r#�create_sinkzAccumulate.create_sink6s������
�
�
r$c��$K�	tj|���|j���d{V��dS#tj$rHt
�d|j��|j�t|j���d{V��YdSYdSwxYw)Nz5Timeout (%ss) sending messages to server on shutdown.)	�asyncio�wait_for�stopr�TimeoutError�logger�errorr*r�rs r#�shutdownzAccumulate.shutdown9s�����		3��"�4�9�9�;�;��0F�G�G�G�G�G�G�G�G�G�G�G���#�	3�	3�	3��L�L�G��&�
�
�
��z�%�&�t�z�2�2�2�2�2�2�2�2�2�2�2�2�&�%�%�
	3���s�28�AB�Bc���K�t�d��|j�t|j���d{V��t�d��|����d{V��dS)NzAccumulate.stop cancel _taskzAccumulate.stop wait lock)r5�infor*rr)r7s r#r3zAccumulate.stopEs|�������2�3�3�3��:�!�"�4�:�.�.�.�.�.�.�.�.�.����/�0�0�0��k�k�m�m���������r$�messagec��DK�t|jt��r|jn|jf}|���r`tj�d��5|D]"}|j|�|���#	ddd��dS#1swxYwYdSdS)N�
accumulate)	�
isinstance�
LIST_CLASS�tuple�
do_accumulater�track�taskr�append)rr;�
list_types�	list_types    r#�collectzAccumulate.collectMs�����'�,�e�4�4�
'�G����$�&�	�
� � �"�"�	:��!�&�&�|�4�4�
:�
:�!+�:�:�I��J�y�)�0�0��9�9�9�9�:�
:�
:�
:�
:�
:�
:�
:�
:�
:�
:�
:�
:����
:�
:�
:�
:�
:�
:�	:�	:s�&B�B�Bc
��K�|j}tjt��|_|���D]�\}}t|t��r|�|��n|f}|D]�}t�	d|j
�dt|���d���	|j�
||������d{V���a#t$rt�d||���wxYw��dS)NzPrepare z(<items=z>) for further processing)�itemsz%s, %s)rrrrrI�
issubclassr�batchedr5r:�__name__�lenr'�process_message�	TypeErrorr6)r�	copy_datarF�messagesrK�batchs      r#r)zAccumulate._flushYsB�����J�	� �,�T�2�2��
�#,�?�?�#4�#4�	�	��I�x��i��4�4�!�	�!�!�(�+�+�+��[�
�!�
�
�����-�y�1�-�-�3�u�:�:�-�-�-������*�4�4�Y�Y�U�5K�5K�5K�L�L�L�L�L�L�L�L�L�L�� �����L�L��9�e�<�<�<������
�	�	s�&*C�(C9)rL�
__module__�__qualname__r�ProcessingOrder�POST_PROCESS_MESSAGE�PROCESSING_ORDER�SHUTDOWN_PRIORITY�int�os�environ�get�DEFAULT_AGGREGATE_TIMEOUT�SHUTDOWN_SEND_TIMEOUTrr-r/r8r3r
rrrGr)�
__classcell__)r"s@r#rrs)�������"�2�G����!$��
�
���>��C�C�!�!�� �C�
�
���C�R�H�H����)�.�	3�	3�	3�	3�	3�	3�
�
�
����
3�
3�
3�����V�K�%�&�&�	:�]�	:�	:�	:�'�&�	:�������r$r)r1rrZ�loggingr�defence360agent.apir�"defence360agent.contracts.messagesrrr�!defence360agent.contracts.pluginsrr	r
�defence360agent.utilsrrrLr5rrr$r#�<module>res����������	�	�	�	�������*�*�*�*�*�*�����������
����������
D�C�C�C�C�C�C�C�	��8�	�	��Z�Z�Z�Z�Z��m�Z�Z�Z�Z�Zr$defence360agent/plugins/__pycache__/analyst_cleanup_update.cpython-311.opt-1.pyc0000644000000000000000000002042400000000000024626 0ustar  �

"��t�\����$�ddlZddlZddlmZddlmZddlmZddlmZddl	m
Z
mZddlm
Z
ddlmZdd	lmZdd
lmZddlmZddlmZdd
lmZeje��Ze
dej��Zedgd���ZGd�de��Z dS)�N)�datetime)�
namedtuple)�OperationalError)�
MessageSource)�register_lock_file�Scope)�AnalystCleanupRequest)�recurring_check)�DAY)�
check_lock)�AnalystCleanupAPI)�remove_pub_key)�IAIDTokenErrorzanalyst-cleanup-update�UpdateStatusRow)�
zendesk_id�
new_status�
updated_atc�f�eZdZd�Zd�Zededzfd���Zededzgfd���Zd�Z	dS)	�AnalystCleanupUpdatec
���K�||_||_|�ttdt
dzt���|j������|_dS)NT�)�check_period_first�check_lock_period�	lock_file)	�_loop�_sink�create_taskr
rr�	LOCK_FILE�_update_task�_task)�self�loop�sinks   �c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/analyst_cleanup_update.py�
create_sourcez"AnalystCleanupUpdate.create_sourcesv������
���
��%�%�
!�
�O��#'�"%��'�#�	
�
�
�
��
!�
!�
#�
#�
�
��
�
�
�c��VK�|j���|j�d{V��dS�N)r �cancel)r!s r$�shutdownzAnalystCleanupUpdate.shutdown(s:�����
�������j���������r&�returnNc
���K�|4�d{V��|j}||vr2t�d|�d���	ddd���d{V��dS||}|d}tj|d�dd����}dddd	��|d
��}|r�||jkr�t�d|�d|j�d
|�d���|dkrHt�d|j	�d���tjt|j	���d{V��t|||��cddd���d{V��Sddd���d{V��dS#1�d{V��swxYwYdS)NzTicket z" not found in Zendesk API response�statusr�Zz+00:00�pending�	completed)�new�solved�closed�in_progresszUpdating ticket z status from 'z' to '�'zRemoving SSH key for user ')r�logger�warningr�
fromisoformat�replace�getr-�info�username�asyncio�	to_threadrr)�old_request�new_tickets_map�	semaphorer�ticket�
ticket_statusrrs        r$�_processzAnalystCleanupUpdate._process-s-�����&	K�&	K�&	K�&	K�&	K�&	K�&	K�&	K�$�/�J���0�0����L�j�L�L�L�����&	K�&	K�&	K�&	K�&	K�&	K�&	K�&	K�&	K�&	K�&	K�&	K�&	K�&	K�%�Z�0�F�"�8�,�M�!�/��|�$�,�,�S�(�;�;���J�!�%�%����c�-��/�/�	
��
K�j�K�,>�>�>����A�z�A�A�$�+�A�A�3=�A�A�A������,�,��K�K�M�k�6J�M�M�M����"�+�&��(<����������'�z�:�z�J�J�M&	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]*}|s�tj|j|j|j���+dSr()r	�
update_statusrrr)rErBs  r$�_update_db_statusesz(AnalystCleanupUpdate._update_db_statusesYsP���	�	�F��
��!�/��!�6�#4�f�6G�
�
�
�
�	�	r&c��T���K�	tj��}|st�d��dSna#t$rT}dt|��vrt�d��nt�d|����Yd}~dSd}~wwxYwd�|D��}	tj|���d{V��}|st�	d��dSd�|D���tjd	������fd
�|D��}tj|��d{V��}tj
�j|���d{V��dS#t$r(}t�d|����Yd}~dSd}~wt $r(}t�d|����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: c��g|]	}|j��
S�)r)�.0�requests  r$�
<listcomp>z5AnalystCleanupUpdate._update_task.<locals>.<listcomp>~s��J�J�J�g�w�)�J�J�Jr&z4Didn't get tickets info from imunifyAPI but expectedc�:�i|]}t|d��|��S)�id)�str)rLrBs  r$�
<dictcomp>z5AnalystCleanupUpdate._update_task.<locals>.<dictcomp>�s3�����.4��F�4�L�!�!�6���r&�c�>��g|]}��|������SrK)rD)rLr?r@r!rAs  ���r$rNz5AnalystCleanupUpdate._update_task.<locals>.<listcomp>�s9��������
�
�k�?�I�F�F���r&zIAIDTokenError: z)Error updating analyst cleanup requests: )r	�get_all_relevant_requestsr6r;rrQ�errorr
�get_ticketsr7r=�	Semaphore�gatherr>rHr�	Exception)	r!�current_requests�e�zendesk_ids�new_tickets�tasks�resultsr@rAs	`      @@r$rz!AnalystCleanupUpdate._update_taskbsp�������	�%�?�A�A�
�
$�
����J������	
��
 �	�	�	��#�a�&�&�(�(����>�?�?�?�?����F�1�F�F����
�F�F�F�F�F�����	����K�J�9I�J�J�J��	J� 1� =�k� J� J�J�J�J�J�J�J�K��
����J��������8C����O� �)�!�,�,�I�������#3����E�
$�N�E�2�2�2�2�2�2�2�G��#�D�$<�g�F�F�F�F�F�F�F�F�F�F�F���	1�	1�	1��L�L�/�A�/�/�0�0�0�0�0�0�0�0�0������	J�	J�	J��L�L�H�Q�H�H�I�I�I�I�I�I�I�I�I�����	J���sA�/9�
B�A	B�B�'6E�A$E�
F'�E2�2
F'�?F"�"F')
�__name__�
__module__�__qualname__r%r*�staticmethodrrDrHrrKr&r$rrs�������

�

�

����
�)K�	�4�	�)K�)K�)K��\�)K�V��?�T�#9�":�����\��8J�8J�8J�8J�8Jr&r)!�loggingr=r�collectionsr�peeweer�!defence360agent.contracts.pluginsr�'defence360agent.subsys.persistent_staterr�%defence360agent.model.analyst_cleanupr	�defence360agent.utilsr
�defence360agent.utils.commonr� defence360agent.utils.check_lockr�*defence360agent.api.server.analyst_cleanupr
�defence360agent.utils.sshutilr�defence360agent.internals.iaidr�	getLoggerrar6�IM360rrrrKr&r$�<module>rss�����������������"�"�"�"�"�"�#�#�#�#�#�#�;�;�;�;�;�;�M�M�M�M�M�M�M�M�G�G�G�G�G�G�1�1�1�1�1�1�,�,�,�,�,�,�7�7�7�7�7�7�H�H�H�H�H�H�8�8�8�8�8�8�9�9�9�9�9�9�
��	�8�	$�	$����7���E�E�	��*��A�A�A����
J�J�J�J�J�=�J�J�J�J�Jr&defence360agent/plugins/__pycache__/analyst_cleanup_update.cpython-311.pyc0000644000000000000000000002042400000000000023667 0ustar  �

"��t�\����$�ddlZddlZddlmZddlmZddlmZddlmZddl	m
Z
mZddlm
Z
ddlmZdd	lmZdd
lmZddlmZddlmZdd
lmZeje��Ze
dej��Zedgd���ZGd�de��Z dS)�N)�datetime)�
namedtuple)�OperationalError)�
MessageSource)�register_lock_file�Scope)�AnalystCleanupRequest)�recurring_check)�DAY)�
check_lock)�AnalystCleanupAPI)�remove_pub_key)�IAIDTokenErrorzanalyst-cleanup-update�UpdateStatusRow)�
zendesk_id�
new_status�
updated_atc�f�eZdZd�Zd�Zededzfd���Zededzgfd���Zd�Z	dS)	�AnalystCleanupUpdatec
���K�||_||_|�ttdt
dzt���|j������|_dS)NT�)�check_period_first�check_lock_period�	lock_file)	�_loop�_sink�create_taskr
rr�	LOCK_FILE�_update_task�_task)�self�loop�sinks   �c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/analyst_cleanup_update.py�
create_sourcez"AnalystCleanupUpdate.create_sourcesv������
���
��%�%�
!�
�O��#'�"%��'�#�	
�
�
�
��
!�
!�
#�
#�
�
��
�
�
�c��VK�|j���|j�d{V��dS�N)r �cancel)r!s r$�shutdownzAnalystCleanupUpdate.shutdown(s:�����
�������j���������r&�returnNc
���K�|4�d{V��|j}||vr2t�d|�d���	ddd���d{V��dS||}|d}tj|d�dd����}dddd	��|d
��}|r�||jkr�t�d|�d|j�d
|�d���|dkrHt�d|j	�d���tjt|j	���d{V��t|||��cddd���d{V��Sddd���d{V��dS#1�d{V��swxYwYdS)NzTicket z" not found in Zendesk API response�statusr�Zz+00:00�pending�	completed)�new�solved�closed�in_progresszUpdating ticket z status from 'z' to '�'zRemoving SSH key for user ')r�logger�warningr�
fromisoformat�replace�getr-�info�username�asyncio�	to_threadrr)�old_request�new_tickets_map�	semaphorer�ticket�
ticket_statusrrs        r$�_processzAnalystCleanupUpdate._process-s-�����&	K�&	K�&	K�&	K�&	K�&	K�&	K�&	K�$�/�J���0�0����L�j�L�L�L�����&	K�&	K�&	K�&	K�&	K�&	K�&	K�&	K�&	K�&	K�&	K�&	K�&	K�&	K�%�Z�0�F�"�8�,�M�!�/��|�$�,�,�S�(�;�;���J�!�%�%����c�-��/�/�	
��
K�j�K�,>�>�>����A�z�A�A�$�+�A�A�3=�A�A�A������,�,��K�K�M�k�6J�M�M�M����"�+�&��(<����������'�z�:�z�J�J�M&	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]*}|s�tj|j|j|j���+dSr()r	�
update_statusrrr)rErBs  r$�_update_db_statusesz(AnalystCleanupUpdate._update_db_statusesYsP���	�	�F��
��!�/��!�6�#4�f�6G�
�
�
�
�	�	r&c��T���K�	tj��}|st�d��dSna#t$rT}dt|��vrt�d��nt�d|����Yd}~dSd}~wwxYwd�|D��}	tj|���d{V��}|st�	d��dSd�|D���tjd	������fd
�|D��}tj|��d{V��}tj
�j|���d{V��dS#t$r(}t�d|����Yd}~dSd}~wt $r(}t�d|����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: c��g|]	}|j��
S�)r)�.0�requests  r$�
<listcomp>z5AnalystCleanupUpdate._update_task.<locals>.<listcomp>~s��J�J�J�g�w�)�J�J�Jr&z4Didn't get tickets info from imunifyAPI but expectedc�:�i|]}t|d��|��S)�id)�str)rLrBs  r$�
<dictcomp>z5AnalystCleanupUpdate._update_task.<locals>.<dictcomp>�s3�����.4��F�4�L�!�!�6���r&�c�>��g|]}��|������SrK)rD)rLr?r@r!rAs  ���r$rNz5AnalystCleanupUpdate._update_task.<locals>.<listcomp>�s9��������
�
�k�?�I�F�F���r&zIAIDTokenError: z)Error updating analyst cleanup requests: )r	�get_all_relevant_requestsr6r;rrQ�errorr
�get_ticketsr7r=�	Semaphore�gatherr>rHr�	Exception)	r!�current_requests�e�zendesk_ids�new_tickets�tasks�resultsr@rAs	`      @@r$rz!AnalystCleanupUpdate._update_taskbsp�������	�%�?�A�A�
�
$�
����J������	
��
 �	�	�	��#�a�&�&�(�(����>�?�?�?�?����F�1�F�F����
�F�F�F�F�F�����	����K�J�9I�J�J�J��	J� 1� =�k� J� J�J�J�J�J�J�J�K��
����J��������8C����O� �)�!�,�,�I�������#3����E�
$�N�E�2�2�2�2�2�2�2�G��#�D�$<�g�F�F�F�F�F�F�F�F�F�F�F���	1�	1�	1��L�L�/�A�/�/�0�0�0�0�0�0�0�0�0������	J�	J�	J��L�L�H�Q�H�H�I�I�I�I�I�I�I�I�I�����	J���sA�/9�
B�A	B�B�'6E�A$E�
F'�E2�2
F'�?F"�"F')
�__name__�
__module__�__qualname__r%r*�staticmethodrrDrHrrKr&r$rrs�������

�

�

����
�)K�	�4�	�)K�)K�)K��\�)K�V��?�T�#9�":�����\��8J�8J�8J�8J�8Jr&r)!�loggingr=r�collectionsr�peeweer�!defence360agent.contracts.pluginsr�'defence360agent.subsys.persistent_staterr�%defence360agent.model.analyst_cleanupr	�defence360agent.utilsr
�defence360agent.utils.commonr� defence360agent.utils.check_lockr�*defence360agent.api.server.analyst_cleanupr
�defence360agent.utils.sshutilr�defence360agent.internals.iaidr�	getLoggerrar6�IM360rrrrKr&r$�<module>rss�����������������"�"�"�"�"�"�#�#�#�#�#�#�;�;�;�;�;�;�M�M�M�M�M�M�M�M�G�G�G�G�G�G�1�1�1�1�1�1�,�,�,�,�,�,�7�7�7�7�7�7�H�H�H�H�H�H�8�8�8�8�8�8�9�9�9�9�9�9�
��	�8�	$�	$����7���E�E�	��*��A�A�A����
J�J�J�J�J�=�J�J�J�J�Jr&defence360agent/plugins/__pycache__/backup_info_sender.cpython-311.opt-1.pyc0000644000000000000000000001417200000000000023725 0ustar  �

��z��v��
�ddlZddlZddlmZddlmZddlmZddlm	Z	ddl
mZddlm
Z
mZddlmZmZdd	lmZmZmZee��Zeed
��������ZdZGd
�de��ZdS)�N)�	timedelta)�	getLogger)�Union)�MessageType)�
MessageSource)�get_current_backend�get_last_backup_timestamp)�
load_state�
save_state)�Scope�recurring_check�safe_cancel_task�)�hours�c���eZdZdZejZd�Zd�Ze	de
eefde
fd���Zdde
eeffd	�Zd
�Zee��d���Zed��d
���Zd�ZdS)�BackupInfoSenderz.Send user backup statistics to CH periodicallyc��LK�||_||_tj��|_|���|_|j�|�����|_	|j�|�
����|_dS�N)�_loop�_sink�asyncio�Event�_send_event�load_last_send_timestamp�_last_send_timestamp�create_task�_recurring_check_data_to_send�_check_task�_recurring_send_stat�_send_stat_task)�self�loop�sinks   �_/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/backup_info_sender.py�
create_sourcezBackupInfoSender.create_sources�������
���
�"�=�?�?���$(�$A�$A�$C�$C��!��:�1�1��.�.�0�0�
�
��� $�z�5�5��%�%�'�'� 
� 
�����c��~K�|j|jfD]}t|���d{V���|���dSr)rr!r�save_last_send_timestamp)r"�tasks  r%�shutdownzBackupInfoSender.shutdown's[�����%�t�';�<�	)�	)�D�"�4�(�(�(�(�(�(�(�(�(�(��%�%�'�'�'�'�'r'�	timestamp�returnc�F�t|ttf��o|dkS)Nr)�
isinstance�int�float)r,s r%�is_valid_timestampz#BackupInfoSender.is_valid_timestamp,s���)�c�5�\�2�2�D�y�1�}�Dr'N�tsc��|�|jn|}|�|��st�d|��dSt	dd|i��dS)NzInvalid timestamp: %sr�last_send_timestamp)rr2�logger�warningr)r"r3r,s   r%r)z)BackupInfoSender.save_last_send_timestamp0s`��13��D�-�-��	��&�&�y�1�1�	��N�N�2�I�>�>�>��F��%�(=�y�'I�J�J�J�J�Jr'c��td���d��}|�|��st�d��d}|S)Nrr5z(Invalid timestamp loaded, resetting to 0r)r
�getr2r6r7)r"r,s  r%rz)BackupInfoSender.load_last_send_timestamp7sS���1�2�2�6�6�7L�M�M�	��&�&�y�1�1�	��N�N�E�F�F�F��I��r'c��K�tj��|jz
tkr|j���dSdSr)�timer�
SEND_INTERVALr�set)r"s r%rz.BackupInfoSender._recurring_check_data_to_send>sC�����9�;�;��2�2�m�C�C��� � �"�"�"�"�"�D�Cr'rc��K�|j����d{V��	|����d{V��n2#t$r%}t�d|��Yd}~nd}~wwxYwt
j��|_|j���dS#t
j��|_|j���wxYw)Nz!Failed to collect backup info: %s)	r�wait�_send_server_config�	Exceptionr6�	exceptionr;r�clear)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#�#3Cc��K�tjt��t���d{V�����}|j�|���d{V��dS)N)�backup_provider_type�last_backup_timestamp)r�
BackupInforr	r�process_message)r"�	confg_msgs  r%r@z$BackupInfoSender._send_server_configOsq�����*�!4�!6�!6�(A�(C�(C�"C�"C�"C�"C�"C�"C�
�
�
�	��j�(�(��3�3�3�3�3�3�3�3�3�3�3r'r)�__name__�
__module__�__qualname__�__doc__r�IM360�SCOPEr&r+�staticmethodrr0r1�boolr2r)rr
�RECURRING_CHECK_INTERVALrr r@�r'r%rrs������8�8��K�E�

�

�

�(�(�(�
�E�e�C��J�&7�E�D�E�E�E��\�E�K�K�5��e��+<�K�K�K�K�����_�-�.�.�#�#�/�.�#��_�Q���	%�	%���	%�4�4�4�4�4r'r)rr;�datetimer�loggingr�typingr�"defence360agent.contracts.messagesr�!defence360agent.contracts.pluginsr�%defence360agent.subsys.backup_systemsrr	�'defence360agent.subsys.persistent_stater
r�defence360agent.utilsrr
rrKr6r0�
total_secondsr<rSrrTr'r%�<module>r^sC����������������������������:�:�:�:�:�:�;�;�;�;�;�;���������K�J�J�J�J�J�J�J�J�J�J�J�J�J�J�J�J�J�	��8�	�	����I�I�B�'�'�'�5�5�7�7�8�8�
���>4�>4�>4�>4�>4�}�>4�>4�>4�>4�>4r'defence360agent/plugins/__pycache__/backup_info_sender.cpython-311.pyc0000644000000000000000000001417200000000000022766 0ustar  �

��z��v��
�ddlZddlZddlmZddlmZddlmZddlm	Z	ddl
mZddlm
Z
mZddlmZmZdd	lmZmZmZee��Zeed
��������ZdZGd
�de��ZdS)�N)�	timedelta)�	getLogger)�Union)�MessageType)�
MessageSource)�get_current_backend�get_last_backup_timestamp)�
load_state�
save_state)�Scope�recurring_check�safe_cancel_task�)�hours�c���eZdZdZejZd�Zd�Ze	de
eefde
fd���Zdde
eeffd	�Zd
�Zee��d���Zed��d
���Zd�ZdS)�BackupInfoSenderz.Send user backup statistics to CH periodicallyc��LK�||_||_tj��|_|���|_|j�|�����|_	|j�|�
����|_dS�N)�_loop�_sink�asyncio�Event�_send_event�load_last_send_timestamp�_last_send_timestamp�create_task�_recurring_check_data_to_send�_check_task�_recurring_send_stat�_send_stat_task)�self�loop�sinks   �_/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/backup_info_sender.py�
create_sourcezBackupInfoSender.create_sources�������
���
�"�=�?�?���$(�$A�$A�$C�$C��!��:�1�1��.�.�0�0�
�
��� $�z�5�5��%�%�'�'� 
� 
�����c��~K�|j|jfD]}t|���d{V���|���dSr)rr!r�save_last_send_timestamp)r"�tasks  r%�shutdownzBackupInfoSender.shutdown's[�����%�t�';�<�	)�	)�D�"�4�(�(�(�(�(�(�(�(�(�(��%�%�'�'�'�'�'r'�	timestamp�returnc�F�t|ttf��o|dkS)Nr)�
isinstance�int�float)r,s r%�is_valid_timestampz#BackupInfoSender.is_valid_timestamp,s���)�c�5�\�2�2�D�y�1�}�Dr'N�tsc��|�|jn|}|�|��st�d|��dSt	dd|i��dS)NzInvalid timestamp: %sr�last_send_timestamp)rr2�logger�warningr)r"r3r,s   r%r)z)BackupInfoSender.save_last_send_timestamp0s`��13��D�-�-��	��&�&�y�1�1�	��N�N�2�I�>�>�>��F��%�(=�y�'I�J�J�J�J�Jr'c��td���d��}|�|��st�d��d}|S)Nrr5z(Invalid timestamp loaded, resetting to 0r)r
�getr2r6r7)r"r,s  r%rz)BackupInfoSender.load_last_send_timestamp7sS���1�2�2�6�6�7L�M�M�	��&�&�y�1�1�	��N�N�E�F�F�F��I��r'c��K�tj��|jz
tkr|j���dSdSr)�timer�
SEND_INTERVALr�set)r"s r%rz.BackupInfoSender._recurring_check_data_to_send>sC�����9�;�;��2�2�m�C�C��� � �"�"�"�"�"�D�Cr'rc��K�|j����d{V��	|����d{V��n2#t$r%}t�d|��Yd}~nd}~wwxYwt
j��|_|j���dS#t
j��|_|j���wxYw)Nz!Failed to collect backup info: %s)	r�wait�_send_server_config�	Exceptionr6�	exceptionr;r�clear)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#�#3Cc��K�tjt��t���d{V�����}|j�|���d{V��dS)N)�backup_provider_type�last_backup_timestamp)r�
BackupInforr	r�process_message)r"�	confg_msgs  r%r@z$BackupInfoSender._send_server_configOsq�����*�!4�!6�!6�(A�(C�(C�"C�"C�"C�"C�"C�"C�
�
�
�	��j�(�(��3�3�3�3�3�3�3�3�3�3�3r'r)�__name__�
__module__�__qualname__�__doc__r�IM360�SCOPEr&r+�staticmethodrr0r1�boolr2r)rr
�RECURRING_CHECK_INTERVALrr r@�r'r%rrs������8�8��K�E�

�

�

�(�(�(�
�E�e�C��J�&7�E�D�E�E�E��\�E�K�K�5��e��+<�K�K�K�K�����_�-�.�.�#�#�/�.�#��_�Q���	%�	%���	%�4�4�4�4�4r'r)rr;�datetimer�loggingr�typingr�"defence360agent.contracts.messagesr�!defence360agent.contracts.pluginsr�%defence360agent.subsys.backup_systemsrr	�'defence360agent.subsys.persistent_stater
r�defence360agent.utilsrr
rrKr6r0�
total_secondsr<rSrrTr'r%�<module>r^sC����������������������������:�:�:�:�:�:�;�;�;�;�;�;���������K�J�J�J�J�J�J�J�J�J�J�J�J�J�J�J�J�J�	��8�	�	����I�I�B�'�'�'�5�5�7�7�8�8�
���>4�>4�>4�>4�>4�}�>4�>4�>4�>4�>4r'defence360agent/plugins/__pycache__/cagefs.cpython-311.opt-1.pyc0000644000000000000000000001711700000000000021337 0ustar  �

�H�m˘mJ����dZddlZddlZddlZddlZddlZddlmZddlm	Z	ddl
mZddlm
Z
mZddlmZmZddlmZd	Zd
Zeje��ZGd�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)�MessageSink�expect)�
load_state�
save_state)�timefunz/usr/sbin/cagefsctlz--wait-lockc��eZdZdejfd�Zd�Zeej	��d���Z
d�Zee
j���deefd���Zed	���Zd
S)�CageFS�loopc��K�||_tj��|_t	d���dd��|_|j�|�����|_	dS)Nr�last_force_update_tsr)
�_loop�asyncio�Queue�_queuer�get�_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������
��m�o�o���%/��%9�%9�%=�%=�"�A�&
�&
��"�#�j�4�4�T�^�^�5E�5E�F�F�����c��K�|j���|j�d{V��|j���r2t�d|j�����t
dd|ji��dS)Nz%d item(s) were not consumedrr)r�cancelr�qsize�logger�warningr	r)rs r�shutdownzCageFS.shutdown4s�������"�"�$�$�$��!�!�!�!�!�!�!�!��;�����	P��N�N�9�4�;�;L�;L�;N�;N�O�O�O���-�t�/I�J�	
�	
�	
�	
�	
rc��K�|d}t|dd��}|�|�|j��r|j�|��dSdS)N�conf�username)�getattr�modified_sincerr�
put_nowait)r�message�configr%s    r�put_to_queuezCageFS.put_to_queue?si���������6�:�t�4�4����6�#8�#8��&�$
�$
��
�K�"�"�8�,�,�,�,�,� �rc��NK�		|j����d{V��}tj�t
��s�F|h}		|�|j������-#tj	$rYnwxYwtj�d��5|D]}|�
|���d{V���	ddd��n#1swxYwYn<#tj$rYdSt$rt �d��Y��wxYw��#)z
        :raise never:
        TN�cagefszSomething went wrong)rr�os�path�exists�_CAGEFSCTL_TOOL�add�
get_nowaitr�
QueueEmptyr�track�task�
_commitconfig�CancelledError�	Exceptionr �	exception)r�commitconfig_username�uniqr%s    rrzCageFS._consumerMs�����	�
�.2�k�o�o�.?�.?�(?�(?�(?�(?�(?�(?�%��w�~�~�o�6�6���.�.���;������!7�!7�!9�!9�:�:�:�;���)�����D����� �%�*�*�8�4�4�;�;�$(�;�;��"�0�0��:�:�:�:�:�:�:�:�:�:�;�;�;�;�;�;�;�;�;�;�;�;����;�;�;�;����)�
�
�
�����
�
�
�� � �!7�8�8�8���	
����/	s`�AC)�	C)�
.A;�;B
�
C)�B
�
"C)�/!C�C)�C!�!C)�$C!�%C)�)D"�;#D"�!D")�logr%c��NK�|rttd|g}nttdg}tj��}	tj|t
jt
jt
jdd���d{V��}|�|tj
|j��}|�|tj|j
��}tj||���d{V��|����d{V��\}}|����d{V��}	|	�t"�d��dS|	r t"�d||	||��dSt"�d||	��|�	||_dSdS#tj$rt"�d	|���wxYw)
zJ
        :raise asyncio.CancelledError:
        :raise Exception:
        z--update-etcz--force-update-etcF)�stdin�stdout�stderr�start_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_LOCK�timer�create_subprocess_exec�
subprocess�DEVNULL�PIPE�
_passthru_log�logging�DEBUGr@�WARNrA�gather�communicate�waitr �error�inforr8r!)
rr%�cmd�
started_at�proc�future1�future2�out�err�rcs
          rr7zCageFS._commitconfigns������	F�"�J���I�C�C�"�J�0D�E�C��Y�[�[�
�"	<� �7�� �(�!��!��#(����������D��(�(��g�m�T�[�I�I�G��(�(��g�l�D�K�H�H�G��.��'�2�2�2�2�2�2�2�2�2�!�-�-�/�/�/�/�/�/�/�/�H�C���y�y�{�{�"�"�"�"�"�"�B�
�z����J�K�K�K�K�K��
<����B�������������7��b�A�A�A��#�1;�D�.�.�.�$�#��!�%�	�	�	��N�N�?��E�E�E��	���s�CE8�8,F$c��K�	|����d{V��}|sdSt�|d||���<)NTz%r: %r)�readliner r=)rR�loglevel�streamreader�lines    rrIzCageFS._passthru_log�sX����	6�%�.�.�0�0�0�0�0�0�0�0�D��
����J�J�x��3��5�5�5�		6rN)�__name__�
__module__�__qualname__r�AbstractEventLooprr"rr�ConfigUpdater+rr
r rQr�strr7�staticmethodrI�rrrr+s�������G�g�&?�G�G�G�G�	
�	
�	
��V�K�$�%�%�-�-�&�%�-����B
�W������.<�H�S�M�.<�.<�.<���.<�`�6�6��\�6�6�6rr)�__doc__rrJr.rFrD�typingr�defence360agent.apir�"defence360agent.contracts.messagesr�!defence360agent.contracts.pluginsrr�'defence360agent.subsys.persistent_staterr	�defence360agent.utilsr
r1rC�	getLoggerr_r rrfrr�<module>ros����.��������	�	�	�	���������������*�*�*�*�*�*�:�:�:�:�:�:�A�A�A�A�A�A�A�A�J�J�J�J�J�J�J�J�)�)�)�)�)�)�'��
�
�	��	�8�	$�	$��z6�z6�z6�z6�z6�[�z6�z6�z6�z6�z6rdefence360agent/plugins/__pycache__/cagefs.cpython-311.pyc0000644000000000000000000001711700000000000020400 0ustar  �

�H�m˘mJ����dZddlZddlZddlZddlZddlZddlmZddlm	Z	ddl
mZddlm
Z
mZddlmZmZddlmZd	Zd
Zeje��ZGd�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)�MessageSink�expect)�
load_state�
save_state)�timefunz/usr/sbin/cagefsctlz--wait-lockc��eZdZdejfd�Zd�Zeej	��d���Z
d�Zee
j���deefd���Zed	���Zd
S)�CageFS�loopc��K�||_tj��|_t	d���dd��|_|j�|�����|_	dS)Nr�last_force_update_tsr)
�_loop�asyncio�Queue�_queuer�get�_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������
��m�o�o���%/��%9�%9�%=�%=�"�A�&
�&
��"�#�j�4�4�T�^�^�5E�5E�F�F�����c��K�|j���|j�d{V��|j���r2t�d|j�����t
dd|ji��dS)Nz%d item(s) were not consumedrr)r�cancelr�qsize�logger�warningr	r)rs r�shutdownzCageFS.shutdown4s�������"�"�$�$�$��!�!�!�!�!�!�!�!��;�����	P��N�N�9�4�;�;L�;L�;N�;N�O�O�O���-�t�/I�J�	
�	
�	
�	
�	
rc��K�|d}t|dd��}|�|�|j��r|j�|��dSdS)N�conf�username)�getattr�modified_sincerr�
put_nowait)r�message�configr%s    r�put_to_queuezCageFS.put_to_queue?si���������6�:�t�4�4����6�#8�#8��&�$
�$
��
�K�"�"�8�,�,�,�,�,� �rc��NK�		|j����d{V��}tj�t
��s�F|h}		|�|j������-#tj	$rYnwxYwtj�d��5|D]}|�
|���d{V���	ddd��n#1swxYwYn<#tj$rYdSt$rt �d��Y��wxYw��#)z
        :raise never:
        TN�cagefszSomething went wrong)rr�os�path�exists�_CAGEFSCTL_TOOL�add�
get_nowaitr�
QueueEmptyr�track�task�
_commitconfig�CancelledError�	Exceptionr �	exception)r�commitconfig_username�uniqr%s    rrzCageFS._consumerMs�����	�
�.2�k�o�o�.?�.?�(?�(?�(?�(?�(?�(?�%��w�~�~�o�6�6���.�.���;������!7�!7�!9�!9�:�:�:�;���)�����D����� �%�*�*�8�4�4�;�;�$(�;�;��"�0�0��:�:�:�:�:�:�:�:�:�:�;�;�;�;�;�;�;�;�;�;�;�;����;�;�;�;����)�
�
�
�����
�
�
�� � �!7�8�8�8���	
����/	s`�AC)�	C)�
.A;�;B
�
C)�B
�
"C)�/!C�C)�C!�!C)�$C!�%C)�)D"�;#D"�!D")�logr%c��NK�|rttd|g}nttdg}tj��}	tj|t
jt
jt
jdd���d{V��}|�|tj
|j��}|�|tj|j
��}tj||���d{V��|����d{V��\}}|����d{V��}	|	�t"�d��dS|	r t"�d||	||��dSt"�d||	��|�	||_dSdS#tj$rt"�d	|���wxYw)
zJ
        :raise asyncio.CancelledError:
        :raise Exception:
        z--update-etcz--force-update-etcF)�stdin�stdout�stderr�start_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_LOCK�timer�create_subprocess_exec�
subprocess�DEVNULL�PIPE�
_passthru_log�logging�DEBUGr@�WARNrA�gather�communicate�waitr �error�inforr8r!)
rr%�cmd�
started_at�proc�future1�future2�out�err�rcs
          rr7zCageFS._commitconfigns������	F�"�J���I�C�C�"�J�0D�E�C��Y�[�[�
�"	<� �7�� �(�!��!��#(����������D��(�(��g�m�T�[�I�I�G��(�(��g�l�D�K�H�H�G��.��'�2�2�2�2�2�2�2�2�2�!�-�-�/�/�/�/�/�/�/�/�H�C���y�y�{�{�"�"�"�"�"�"�B�
�z����J�K�K�K�K�K��
<����B�������������7��b�A�A�A��#�1;�D�.�.�.�$�#��!�%�	�	�	��N�N�?��E�E�E��	���s�CE8�8,F$c��K�	|����d{V��}|sdSt�|d||���<)NTz%r: %r)�readliner r=)rR�loglevel�streamreader�lines    rrIzCageFS._passthru_log�sX����	6�%�.�.�0�0�0�0�0�0�0�0�D��
����J�J�x��3��5�5�5�		6rN)�__name__�
__module__�__qualname__r�AbstractEventLooprr"rr�ConfigUpdater+rr
r rQr�strr7�staticmethodrI�rrrr+s�������G�g�&?�G�G�G�G�	
�	
�	
��V�K�$�%�%�-�-�&�%�-����B
�W������.<�H�S�M�.<�.<�.<���.<�`�6�6��\�6�6�6rr)�__doc__rrJr.rFrD�typingr�defence360agent.apir�"defence360agent.contracts.messagesr�!defence360agent.contracts.pluginsrr�'defence360agent.subsys.persistent_staterr	�defence360agent.utilsr
r1rC�	getLoggerr_r rrfrr�<module>ros����.��������	�	�	�	���������������*�*�*�*�*�*�:�:�:�:�:�:�A�A�A�A�A�A�A�A�J�J�J�J�J�J�J�J�)�)�)�)�)�)�'��
�
�	��	�8�	$�	$��z6�z6�z6�z6�z6�[�z6�z6�z6�z6�z6rdefence360agent/plugins/__pycache__/checkpoint.cpython-311.opt-1.pyc0000644000000000000000000000420600000000000022231 0ustar  �

R��i�����F�ddlmZddlmZddlmZGd�de��ZdS)�)�MessageSink)�db)�recurring_checkc�6�eZdZdZdZeed�d�Zd�Zd�Zd�Z	dS)	�
CheckpointzU
    Checkpoint imunify360.db periodically to limit unexpected WAL file growing.
    i�Q)�checkpoint_periodrc�0�||_||_d|_dS�N)�_checkpoint_period�_db�_task)�selfrrs   �W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/checkpoint.py�__init__zCheckpoint.__init__
s��"3��������
�
�
�c��K�||_|j�t|j��|j������|_dSr
)�_loop�create_taskrr�_checkpointr
)r�loops  r�create_sinkzCheckpoint.create_sinksN������
��Z�+�+�F�4�O�D�3�4�4�T�5E�F�F�H�H�
�
��
�
�
rc��K�|jdc}|_|�|���rdS|���|�d{V��dSr
)r
�	cancelled�cancel)r�tasks  r�shutdownzCheckpoint.shutdownsP�����:�t���d�j��<�4�>�>�+�+�<��F����
�
�
��
�
�
�
�
�
�
�
�
rc��>K�|j�d��dS)NzPRAGMA wal_checkpoint(TRUNCATE))r�execute_sql)rs rrzCheckpoint._checkpoint s%����
	
����>�?�?�?�?�?rN)
�__name__�
__module__�__qualname__�__doc__�ONE_DAYrrrrr�rrrrsu���������G�,3�������

�
�
����@�@�@�@�@rrN)�!defence360agent.contracts.pluginsr�defence360agent.model.instancer�defence360agent.utilsrrr$rr�<module>r(sy��9�9�9�9�9�9�-�-�-�-�-�-�1�1�1�1�1�1�@�@�@�@�@��@�@�@�@�@rdefence360agent/plugins/__pycache__/checkpoint.cpython-311.pyc0000644000000000000000000000420600000000000021272 0ustar  �

R��i�����F�ddlmZddlmZddlmZGd�de��ZdS)�)�MessageSink)�db)�recurring_checkc�6�eZdZdZdZeed�d�Zd�Zd�Zd�Z	dS)	�
CheckpointzU
    Checkpoint imunify360.db periodically to limit unexpected WAL file growing.
    i�Q)�checkpoint_periodrc�0�||_||_d|_dS�N)�_checkpoint_period�_db�_task)�selfrrs   �W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/checkpoint.py�__init__zCheckpoint.__init__
s��"3��������
�
�
�c��K�||_|j�t|j��|j������|_dSr
)�_loop�create_taskrr�_checkpointr
)r�loops  r�create_sinkzCheckpoint.create_sinksN������
��Z�+�+�F�4�O�D�3�4�4�T�5E�F�F�H�H�
�
��
�
�
rc��K�|jdc}|_|�|���rdS|���|�d{V��dSr
)r
�	cancelled�cancel)r�tasks  r�shutdownzCheckpoint.shutdownsP�����:�t���d�j��<�4�>�>�+�+�<��F����
�
�
��
�
�
�
�
�
�
�
�
rc��>K�|j�d��dS)NzPRAGMA wal_checkpoint(TRUNCATE))r�execute_sql)rs rrzCheckpoint._checkpoint s%����
	
����>�?�?�?�?�?rN)
�__name__�
__module__�__qualname__�__doc__�ONE_DAYrrrrr�rrrrsu���������G�,3�������

�
�
����@�@�@�@�@rrN)�!defence360agent.contracts.pluginsr�defence360agent.model.instancer�defence360agent.utilsrrr$rr�<module>r(sy��9�9�9�9�9�9�-�-�-�-�-�-�1�1�1�1�1�1�@�@�@�@�@��@�@�@�@�@rdefence360agent/plugins/__pycache__/client.cpython-311.opt-1.pyc0000644000000000000000000005207000000000000021362 0ustar  �

�K��La�a���ddlZddlZddlZddlZddlZddlZddlZddlZddl	m
Z
ddlmZm
Z
mZmZddlmZddlmZddlmZmZmZddlmZmZddlmZdd	lmZmZdd
l m!Z!ddl"m#Z#m$Z$m%Z%m&Z&ddl'm(Z(ej)e*��Z+e��Z,e��Z-e��Z.Gd
�d��Z/Gd�de/e��Z0dS)�N)�	Generator)�APIError�APIErrorTooManyRequests�
APITokenError�send_message)�license)�Core)�Message�MessageList�MessageType)�MessageSink�expect)�
feature_flags)�Gen�	publisher)�PersistentMessagesQueue)�log_future_errors�recurring_check�safe_cancel_task�Scope)�ServerJSONEncoderc��eZdZdZeej�dd����ZdZ	dZ
dZdZde
jfd	�Zdd�Zd
�Zed���Zejd
eejddffd���Zeej��ded
dfd���Zee��d���Z d
e!fd�Z"ed��d���Z#ded
e$fd�Z%de$d
efd�Z&d�Z'd�Z(d�Z)dd�Z*dS)�SendToServerClienta�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.� IMUNIFYAV_MESSAGES_COUNT_TO_SEND�g�message_send_batching�<�2�loopc��K�||_t��|_tj��|_tj��|_tj��|_d|_	|�
|�����|_|�
|�
����|_dS�N)�_loopr�_pending�asyncio�Event�	_try_send�Lock�_lock�_shutting_down�_flush_deadline�create_task�_send�_sender_task�_invoke_send_message�_invoke_send_message_task)�selfrs  �S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/client.py�create_sinkzSendToServerClient.create_sinkBs�������
�/�1�1��
� �������\�^�^��
�%�m�o�o���#��� �,�,�T�Z�Z�\�\�:�:���)-�)9�)9��%�%�'�'�*
�*
��&�&�&��returnNc��xK�|j���	tj|���|j���d{V��nh#tj$rVt�d|j��|j	�
��st|j	���d{V��YnwxYw|jj
dkrrt�d|jj
��|j���t�d|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)r)�setr$�wait_for�stop�_SHUTDOWN_SEND_TIMEOUT�TimeoutError�logger�errorr-�	cancelledrr#�buffer_size�warning�push_buffer_to_storage�qsize�r0s r1�shutdownzSendToServerClient.shutdownNsJ����	
����!�!�!�		:��"�4�9�9�;�;��0K�L�L�L�L�L�L�L�L�L�L���#�	:�	:�	:��L�L�G��+�
�
�
��$�.�.�0�0�
:�&�t�'8�9�9�9�9�9�9�9�9�9���	:�����=�$�q�(�(��N�N�8��
�)�
�
�
�
�M�0�0�2�2�2��N�N�,�d�m�.A�.A�.C�.C�D�D�D�D�D�
)�(s�2A�A"B5�4B5c���K�t�d��t|j���d{V��t�d��|j4�d{V��t�d��t|j���d{V��|j���|����d{V��ddd���d{V��dS#1�d{V��swxYwYdS)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)	r;�inforr/r(r-r)�clear�_send_pending_messagesrBs r1r8zSendToServerClient.stopqs�����0	���H�I�I�I��t�=�>�>�>�>�>�>�>�>�>����1�2�2�2��:�		0�		0�		0�		0�		0�		0�		0�		0�
�K�K�N�O�O�O�"�4�#4�5�5�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�		0�		0�		0s�A(C�
C#�&C#c�,�|�tj�����|�tj�����|�tj�����|Sr!)�set_product_namer�
LicenseCLN�get_product_name�
set_server_id�
get_server_id�set_license�	get_token)�apis r1�_set_api_attrsz!SendToServerClient._set_api_attrs�sn�����W�/�@�@�B�B�C�C�C����'�,�:�:�<�<�=�=�=�����*�4�4�6�6�7�7�7��
r3c#�*K�tj�d��}tj�d���5}t
jtj	||���}|�
|��V�ddd��dS#1swxYwYdS)N�IMUNIFYAV_API_BASE�)�max_workers)�executor)�os�environ�get�
concurrent�futures�ThreadPoolExecutorr�SendMessageAPIr	�VERSIONrQ)r0�base_urlrVrPs    r1�_get_apizSendToServerClient._get_api�s������:�>�>�"6�7�7��
�
�
2�
2�q�
2�
A�
A�	+�X��-���h�����C��%�%�c�*�*�*�*�*�	+�	+�	+�	+�	+�	+�	+�	+�	+�	+�	+�	+����	+�	+�	+�	+�	+�	+s�9B�B�B�messagec��@K�d|vrtj��|d<d|vrtj��j|d<|j�|�|����|j���tj
|td���dS)N�	timestamp�
message_idzagent-queued��stage)�time�uuid�uuid4�hexr#�put�_encode_data_to_put_in_queuer&r6r�report�_reporter_gen_queued)r0ras  r1�send_to_serverz!SendToServerClient.send_to_server�s������g�%�%�#'�9�;�;�G�K� ��w�&�&�$(�J�L�L�$4�G�L�!��
���$�;�;�G�D�D�E�E�E�����������"6�n�M�M�M�M�M�Mr3c��<K�|j���dSr!)r&r6rBs r1r.z'SendToServerClient._invoke_send_message�s ������������r3c��tj|j��r=tj|j��D]#}	t	|��cS#t
$rY� wxYw|jSr!)r�
is_enabled�_BATCHING_FLAG�
get_params�float�
ValueError�_MAX_SEND_DELAY)r0�values  r1�_max_send_delayz"SendToServerClient._max_send_delay�su���#�D�$7�8�8�	�&�1�$�2E�F�F�
�
��� ��<�<�'�'�'��!�����D������#�#s�A�
A�Arc��K�|j� |j����d{V��n�td|j|j���z
��}t
jtj	��5tj
|j���|���d{V��ddd��n#1swxYwY|j���|j�
��}|dkr	d|_dS|j�3|j���|���z|_||jkr$|j���|jkrdSd|_t �d��d}|j4�d{V��t �d��	|����d{V��n8#tj$r&}t �d��|}Yd}~nd}~wwxYwddd���d{V��n#1�d{V��swxYwYt �d��|r|�dS)NrzSendToServer._send wait lockz SendToServer._send lock acquiredz&SendToServer._send cancelled unlockingz SendToServer._send lock released)r*r&�wait�maxr"rg�
contextlib�suppressr$r:r7rFr#rAry�_PENDING_MESSAGES_LIMITr;rEr(rG�CancelledError)r0�timeoutrA�need_to_cancel�es     r1r,zSendToServerClient._send�s'������'��.�%�%�'�'�'�'�'�'�'�'�'�'��!�T�1�D�J�O�O�4E�4E�E�F�F�G��$�W�%9�:�:�
G�
G��&�t�~�':�':�'<�'<�g�F�F�F�F�F�F�F�F�F�
G�
G�
G�
G�
G�
G�
G�
G�
G�
G�
G����
G�
G�
G�
G���������
�#�#�%�%���A�:�:�#'�D� ��F���'�#'�:�?�?�#4�#4�t�7K�7K�7M�7M�#M�D� ��D�0�0�0��
���!�!�D�$8�8�8��F�#������2�3�3�3����:�	#�	#�	#�	#�	#�	#�	#�	#��K�K�:�;�;�;�
#��1�1�3�3�3�3�3�3�3�3�3�3���)�
#�
#�
#����D�E�E�E�!"�����������
#����		#�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#����	#�	#�	#�	#�	���6�7�7�7��	!� � �	!�	!sN�73B6�6B:�=B:�H�7G�H�H�!H�=H�H�H�
H&�)H&�datac�f�tj|t���dz}|���S)N)�cls�
)�json�dumpsr�encode)r0r��msgs   r1rlz/SendToServerClient._encode_data_to_put_in_queue�s*���j��#4�5�5�5��<���z�z�|�|�r3c��tj|��}|�d��rHt|d��}|�d�|���D����|St
|��S)N�listc�&�i|]\}}|dk�||��S)r��)�.0�k�vs   r1�
<dictcomp>z6SendToServerClient._decode_message.<locals>.<dictcomp>�s#��E�E�E���A��f����1���r3)r��loadsrYr�update�itemsr
)r0rar�r�s    r1�_decode_messagez"SendToServerClient._decode_message�sp���z�'�"�"���8�8�F���	��d�6�l�+�+�C��J�J�E�E������E�E�E�F�F�F��J��t�}�}�r3c��|�dd��dz|d<|j�|�|��|���dS)N�api_retries_countr��rc)rYr#rkrl)r0rarcs   r1�_requeue_messagez#SendToServerClient._requeue_message�s]��'.�{�{�3F��'J�'J�Q�'N��#�$��
����-�-�g�6�6�)�	�	
�	
�	
�	
�	
r3c��K�tj|�|����}|�d���tj|j�����}	tj||htj����d{V��\}}n<#tj$r*|���|����wxYw|D]}t|���d{V���||vr|�
��dSdS)z�Race the HTTP send against the shutdown signal.

        Returns True on success, raises on API error,
        or returns False if shutdown interrupted the send.
        c�6�t|tj��Sr!)rr;�debug)�tasks r1�<lambda>z6SendToServerClient._send_one_message.<locals>.<lambda>s��*�4���>�>�r3)�return_whenNTF)r$�
ensure_futurer�add_done_callbackr)r{�FIRST_COMPLETEDr��cancelr�result)r0rPra�	send_task�
shutdown_task�done�
pending_tasksr�s        r1�_send_one_messagez$SendToServerClient._send_one_message�sW�����)�#�*:�*:�7�*C�*C�D�D�	��#�#�>�>�	
�	
�	
� �-�d�.A�.F�.F�.H�.H�I�I�
�	�(/���M�*�#�3�)�)�)�#�#�#�#�#�#��D�-�-���%�	�	�	�������� � �"�"�"��	����"�	)�	)�D�"�4�(�(�(�(�(�(�(�(�(�(������������4��us�,+B�9Cc��K�|j���r8t�d��|j�||���dS|�|��}|�d��|�d��d�}	tj	|td���|�||���d	{V��}|s8t�d
��|j�||���dStj	|td���t�
d|��d
S#ttf$r=}t�d||��|�||��Yd	}~dSd	}~wt"$r=}t�d||��|�||��Yd	}~dSd	}~wwxYw)z�Try sending a single message.

        Returns (stop, failed) where *stop* is True if further sends
        should stop (shutdown or server-level error) and *failed* is
        True when the message could not be delivered.
        z3Shutdown signal received, saving remaining messagesr�)TF�methodrd)r�rdz
agent-sendingreNz?Shutdown signal received during send, saving remaining messagesz
agent-sentzmessage sent %s)FFz'Failed to send message %s to server: %s)TT)FT)r)�is_setr;r?r#rkr�rYrrm�_reporter_gen_sendingr��_reporter_gen_sentrErrr�r)r0rPrc�
message_bytesra�msg_info�sent�excs        r1�
_try_send_onez SendToServerClient._try_send_ones������%�%�'�'�	��N�N�E�
�
�
�
�M���m�y��A�A�A��;��&�&�}�5�5���k�k�(�+�+�!�+�+�l�3�3�
�
��	����.�o�
�
�
�
��/�/��W�=�=�=�=�=�=�=�=�D��
#����1�����
�!�!�-�9�!�E�E�E�"�{���W�&8��M�M�M�M��K�K�)�8�4�4�4��<��'��7�	�	�	��N�N�9�8�S�
�
�
�
�!�!�'�9�5�5�5��:�:�:�:�:������	�	�	��N�N�9�8�S�
�
�
�
�!�!�'�9�5�5�5��;�;�;�;�;�����	���s+�A0E�7E�G�2F	�	
G�2G�Gc��fK�|j���}t�dt	|����d}d}|���5}|j�2|D]!\}}|j�||����"	ddd��dS	|D]�\}}|�|||���d{V��\}}|dz
}|r|dz
}|rS||d�}	|	D]!\}
}|j�||
����"|r|t	|	��z
}t	|��}n��||d�D]!\}}|j�||����"n1#||d�D]!\}}|j�||����"wxYw	ddd��n#1swxYwYt�d|��dS)NzSending %s messagesrr�r�z Unsuccessful to send %s messages)	r#�pop_allr;rE�lenr`�	server_idrkr�)r0�messages�
failure_count�	processedrPrcr�r8�failed�	remaining�ts�mbs            r1rGz)SendToServerClient._send_pending_messagesFs������=�(�(�*�*�����)�3�x�=�=�9�9�9��
��	�
�]�]�_�_�	J���}�$�08�J�J�,�I�}��M�%�%�m�y�%�I�I�I�I��		J�	J�	J�	J�	J�	J�	J�	J�

J�08���,�I�}�)-�);�);��Y�
�*�*�$�$�$�$�$�$�L�D�&���N�I��+�%��*�
��
�$,�Y�Z�Z�$8�	�&/�@�@�F�B�� �M�-�-�b�B�-�?�?�?�?�!�<�*�S��^�^�;�M�$'��M�M�	���
�19����0D�J�J�,�I�}��M�%�%�m�y�%�I�I�I�I�J������0D�J�J�,�I�}��M�%�%�m�y�%�I�I�I�I�J����J�7	J�	J�	J�	J�	J�	J�	J�	J�	J�	J�	J����	J�	J�	J�	J�:	���6�
�F�F�F�F�Fs+�-F�B	E
� -F�
.E;�;F�F�F)r4N)+�__name__�
__module__�__qualname__�__doc__�intrWrXrYrrwrs�_SEND_MESSAGE_RECURRING_TIMEr9r$�AbstractEventLoopr2rCr8�staticmethodrQr}�contextmanagerrrr]r`rr�
Reportabler
rorr.ruryr,�bytesrlr�r�r�r�rGr�r3r1rr-s2������7�7�"�c�
�
���9�2�>�>�����O�,�N�#%� ���

�g�&?�

�

�

�

�!E�!E�!E�!E�F$0�$0�$0�L����\����+�)�L�$?��t�$K�L�+�+�+���+��V�K�"�#�#�	N�G�	N��	N�	N�	N�$�#�	N��_�1�2�2���3�2��$��$�$�$�$��_�Q��� !� !��� !�D���U������u�������
�
�
����@-�-�-�^"G�"G�"G�"G�"G�"Gr3rc� �eZdZejZdZdS)�SendToServeri�N)r�r�r�r�AV�SCOPE�SHUTDOWN_PRIORITYr�r3r1r�r�ks�������H�E����r3r�)1r$�concurrent.futuresrZr}r��loggingrWrgrh�typingr�defence360agent.api.serverrrrr�defence360agent.contractsr� defence360agent.contracts.configr	�"defence360agent.contracts.messagesr
rr�!defence360agent.contracts.pluginsr
r�defence360agent.internalsr�2defence360agent.internals.message_status_publisherrr�,defence360agent.internals.persistent_messager�defence360agent.utilsrrrr�defence360agent.utils.jsonr�	getLoggerr�r;rnr�r�rr�r�r3r1�<module>r�sR����������������������	�	�	�	���������������������������.�-�-�-�-�-�1�1�1�1�1�1�����������
B�A�A�A�A�A�A�A�3�3�3�3�3�3�M�M�M�M�M�M�M�M�������������������9�8�8�8�8�8�	��	�8�	$�	$���s�u�u��������S�U�U��{G�{G�{G�{G�{G�{G�{G�{G�|	�����%�{�����r3defence360agent/plugins/__pycache__/client.cpython-311.pyc0000644000000000000000000005207000000000000020423 0ustar  �

�K��La�a���ddlZddlZddlZddlZddlZddlZddlZddlZddl	m
Z
ddlmZm
Z
mZmZddlmZddlmZddlmZmZmZddlmZmZddlmZdd	lmZmZdd
l m!Z!ddl"m#Z#m$Z$m%Z%m&Z&ddl'm(Z(ej)e*��Z+e��Z,e��Z-e��Z.Gd
�d��Z/Gd�de/e��Z0dS)�N)�	Generator)�APIError�APIErrorTooManyRequests�
APITokenError�send_message)�license)�Core)�Message�MessageList�MessageType)�MessageSink�expect)�
feature_flags)�Gen�	publisher)�PersistentMessagesQueue)�log_future_errors�recurring_check�safe_cancel_task�Scope)�ServerJSONEncoderc��eZdZdZeej�dd����ZdZ	dZ
dZdZde
jfd	�Zdd�Zd
�Zed���Zejd
eejddffd���Zeej��ded
dfd���Zee��d���Z d
e!fd�Z"ed��d���Z#ded
e$fd�Z%de$d
efd�Z&d�Z'd�Z(d�Z)dd�Z*dS)�SendToServerClienta�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.� IMUNIFYAV_MESSAGES_COUNT_TO_SEND�g�message_send_batching�<�2�loopc��K�||_t��|_tj��|_tj��|_tj��|_d|_	|�
|�����|_|�
|�
����|_dS�N)�_loopr�_pending�asyncio�Event�	_try_send�Lock�_lock�_shutting_down�_flush_deadline�create_task�_send�_sender_task�_invoke_send_message�_invoke_send_message_task)�selfrs  �S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/client.py�create_sinkzSendToServerClient.create_sinkBs�������
�/�1�1��
� �������\�^�^��
�%�m�o�o���#��� �,�,�T�Z�Z�\�\�:�:���)-�)9�)9��%�%�'�'�*
�*
��&�&�&��returnNc��xK�|j���	tj|���|j���d{V��nh#tj$rVt�d|j��|j	�
��st|j	���d{V��YnwxYw|jj
dkrrt�d|jj
��|j���t�d|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)r)�setr$�wait_for�stop�_SHUTDOWN_SEND_TIMEOUT�TimeoutError�logger�errorr-�	cancelledrr#�buffer_size�warning�push_buffer_to_storage�qsize�r0s r1�shutdownzSendToServerClient.shutdownNsJ����	
����!�!�!�		:��"�4�9�9�;�;��0K�L�L�L�L�L�L�L�L�L�L���#�	:�	:�	:��L�L�G��+�
�
�
��$�.�.�0�0�
:�&�t�'8�9�9�9�9�9�9�9�9�9���	:�����=�$�q�(�(��N�N�8��
�)�
�
�
�
�M�0�0�2�2�2��N�N�,�d�m�.A�.A�.C�.C�D�D�D�D�D�
)�(s�2A�A"B5�4B5c���K�t�d��t|j���d{V��t�d��|j4�d{V��t�d��t|j���d{V��|j���|����d{V��ddd���d{V��dS#1�d{V��swxYwYdS)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)	r;�inforr/r(r-r)�clear�_send_pending_messagesrBs r1r8zSendToServerClient.stopqs�����0	���H�I�I�I��t�=�>�>�>�>�>�>�>�>�>����1�2�2�2��:�		0�		0�		0�		0�		0�		0�		0�		0�
�K�K�N�O�O�O�"�4�#4�5�5�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�		0�		0�		0s�A(C�
C#�&C#c�,�|�tj�����|�tj�����|�tj�����|Sr!)�set_product_namer�
LicenseCLN�get_product_name�
set_server_id�
get_server_id�set_license�	get_token)�apis r1�_set_api_attrsz!SendToServerClient._set_api_attrs�sn�����W�/�@�@�B�B�C�C�C����'�,�:�:�<�<�=�=�=�����*�4�4�6�6�7�7�7��
r3c#�*K�tj�d��}tj�d���5}t
jtj	||���}|�
|��V�ddd��dS#1swxYwYdS)N�IMUNIFYAV_API_BASE�)�max_workers)�executor)�os�environ�get�
concurrent�futures�ThreadPoolExecutorr�SendMessageAPIr	�VERSIONrQ)r0�base_urlrVrPs    r1�_get_apizSendToServerClient._get_api�s������:�>�>�"6�7�7��
�
�
2�
2�q�
2�
A�
A�	+�X��-���h�����C��%�%�c�*�*�*�*�*�	+�	+�	+�	+�	+�	+�	+�	+�	+�	+�	+�	+����	+�	+�	+�	+�	+�	+s�9B�B�B�messagec��@K�d|vrtj��|d<d|vrtj��j|d<|j�|�|����|j���tj
|td���dS)N�	timestamp�
message_idzagent-queued��stage)�time�uuid�uuid4�hexr#�put�_encode_data_to_put_in_queuer&r6r�report�_reporter_gen_queued)r0ras  r1�send_to_serverz!SendToServerClient.send_to_server�s������g�%�%�#'�9�;�;�G�K� ��w�&�&�$(�J�L�L�$4�G�L�!��
���$�;�;�G�D�D�E�E�E�����������"6�n�M�M�M�M�M�Mr3c��<K�|j���dSr!)r&r6rBs r1r.z'SendToServerClient._invoke_send_message�s ������������r3c��tj|j��r=tj|j��D]#}	t	|��cS#t
$rY� wxYw|jSr!)r�
is_enabled�_BATCHING_FLAG�
get_params�float�
ValueError�_MAX_SEND_DELAY)r0�values  r1�_max_send_delayz"SendToServerClient._max_send_delay�su���#�D�$7�8�8�	�&�1�$�2E�F�F�
�
��� ��<�<�'�'�'��!�����D������#�#s�A�
A�Arc��K�|j� |j����d{V��n�td|j|j���z
��}t
jtj	��5tj
|j���|���d{V��ddd��n#1swxYwY|j���|j�
��}|dkr	d|_dS|j�3|j���|���z|_||jkr$|j���|jkrdSd|_t �d��d}|j4�d{V��t �d��	|����d{V��n8#tj$r&}t �d��|}Yd}~nd}~wwxYwddd���d{V��n#1�d{V��swxYwYt �d��|r|�dS)NrzSendToServer._send wait lockz SendToServer._send lock acquiredz&SendToServer._send cancelled unlockingz SendToServer._send lock released)r*r&�wait�maxr"rg�
contextlib�suppressr$r:r7rFr#rAry�_PENDING_MESSAGES_LIMITr;rEr(rG�CancelledError)r0�timeoutrA�need_to_cancel�es     r1r,zSendToServerClient._send�s'������'��.�%�%�'�'�'�'�'�'�'�'�'�'��!�T�1�D�J�O�O�4E�4E�E�F�F�G��$�W�%9�:�:�
G�
G��&�t�~�':�':�'<�'<�g�F�F�F�F�F�F�F�F�F�
G�
G�
G�
G�
G�
G�
G�
G�
G�
G�
G����
G�
G�
G�
G���������
�#�#�%�%���A�:�:�#'�D� ��F���'�#'�:�?�?�#4�#4�t�7K�7K�7M�7M�#M�D� ��D�0�0�0��
���!�!�D�$8�8�8��F�#������2�3�3�3����:�	#�	#�	#�	#�	#�	#�	#�	#��K�K�:�;�;�;�
#��1�1�3�3�3�3�3�3�3�3�3�3���)�
#�
#�
#����D�E�E�E�!"�����������
#����		#�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#�	#����	#�	#�	#�	#�	���6�7�7�7��	!� � �	!�	!sN�73B6�6B:�=B:�H�7G�H�H�!H�=H�H�H�
H&�)H&�datac�f�tj|t���dz}|���S)N)�cls�
)�json�dumpsr�encode)r0r��msgs   r1rlz/SendToServerClient._encode_data_to_put_in_queue�s*���j��#4�5�5�5��<���z�z�|�|�r3c��tj|��}|�d��rHt|d��}|�d�|���D����|St
|��S)N�listc�&�i|]\}}|dk�||��S)r��)�.0�k�vs   r1�
<dictcomp>z6SendToServerClient._decode_message.<locals>.<dictcomp>�s#��E�E�E���A��f����1���r3)r��loadsrYr�update�itemsr
)r0rar�r�s    r1�_decode_messagez"SendToServerClient._decode_message�sp���z�'�"�"���8�8�F���	��d�6�l�+�+�C��J�J�E�E������E�E�E�F�F�F��J��t�}�}�r3c��|�dd��dz|d<|j�|�|��|���dS)N�api_retries_countr��rc)rYr#rkrl)r0rarcs   r1�_requeue_messagez#SendToServerClient._requeue_message�s]��'.�{�{�3F��'J�'J�Q�'N��#�$��
����-�-�g�6�6�)�	�	
�	
�	
�	
�	
r3c��K�tj|�|����}|�d���tj|j�����}	tj||htj����d{V��\}}n<#tj$r*|���|����wxYw|D]}t|���d{V���||vr|�
��dSdS)z�Race the HTTP send against the shutdown signal.

        Returns True on success, raises on API error,
        or returns False if shutdown interrupted the send.
        c�6�t|tj��Sr!)rr;�debug)�tasks r1�<lambda>z6SendToServerClient._send_one_message.<locals>.<lambda>s��*�4���>�>�r3)�return_whenNTF)r$�
ensure_futurer�add_done_callbackr)r{�FIRST_COMPLETEDr��cancelr�result)r0rPra�	send_task�
shutdown_task�done�
pending_tasksr�s        r1�_send_one_messagez$SendToServerClient._send_one_message�sW�����)�#�*:�*:�7�*C�*C�D�D�	��#�#�>�>�	
�	
�	
� �-�d�.A�.F�.F�.H�.H�I�I�
�	�(/���M�*�#�3�)�)�)�#�#�#�#�#�#��D�-�-���%�	�	�	�������� � �"�"�"��	����"�	)�	)�D�"�4�(�(�(�(�(�(�(�(�(�(������������4��us�,+B�9Cc��K�|j���r8t�d��|j�||���dS|�|��}|�d��|�d��d�}	tj	|td���|�||���d	{V��}|s8t�d
��|j�||���dStj	|td���t�
d|��d
S#ttf$r=}t�d||��|�||��Yd	}~dSd	}~wt"$r=}t�d||��|�||��Yd	}~dSd	}~wwxYw)z�Try sending a single message.

        Returns (stop, failed) where *stop* is True if further sends
        should stop (shutdown or server-level error) and *failed* is
        True when the message could not be delivered.
        z3Shutdown signal received, saving remaining messagesr�)TF�methodrd)r�rdz
agent-sendingreNz?Shutdown signal received during send, saving remaining messagesz
agent-sentzmessage sent %s)FFz'Failed to send message %s to server: %s)TT)FT)r)�is_setr;r?r#rkr�rYrrm�_reporter_gen_sendingr��_reporter_gen_sentrErrr�r)r0rPrc�
message_bytesra�msg_info�sent�excs        r1�
_try_send_onez SendToServerClient._try_send_ones������%�%�'�'�	��N�N�E�
�
�
�
�M���m�y��A�A�A��;��&�&�}�5�5���k�k�(�+�+�!�+�+�l�3�3�
�
��	����.�o�
�
�
�
��/�/��W�=�=�=�=�=�=�=�=�D��
#����1�����
�!�!�-�9�!�E�E�E�"�{���W�&8��M�M�M�M��K�K�)�8�4�4�4��<��'��7�	�	�	��N�N�9�8�S�
�
�
�
�!�!�'�9�5�5�5��:�:�:�:�:������	�	�	��N�N�9�8�S�
�
�
�
�!�!�'�9�5�5�5��;�;�;�;�;�����	���s+�A0E�7E�G�2F	�	
G�2G�Gc��fK�|j���}t�dt	|����d}d}|���5}|j�2|D]!\}}|j�||����"	ddd��dS	|D]�\}}|�|||���d{V��\}}|dz
}|r|dz
}|rS||d�}	|	D]!\}
}|j�||
����"|r|t	|	��z
}t	|��}n��||d�D]!\}}|j�||����"n1#||d�D]!\}}|j�||����"wxYw	ddd��n#1swxYwYt�d|��dS)NzSending %s messagesrr�r�z Unsuccessful to send %s messages)	r#�pop_allr;rE�lenr`�	server_idrkr�)r0�messages�
failure_count�	processedrPrcr�r8�failed�	remaining�ts�mbs            r1rGz)SendToServerClient._send_pending_messagesFs������=�(�(�*�*�����)�3�x�=�=�9�9�9��
��	�
�]�]�_�_�	J���}�$�08�J�J�,�I�}��M�%�%�m�y�%�I�I�I�I��		J�	J�	J�	J�	J�	J�	J�	J�

J�08���,�I�}�)-�);�);��Y�
�*�*�$�$�$�$�$�$�L�D�&���N�I��+�%��*�
��
�$,�Y�Z�Z�$8�	�&/�@�@�F�B�� �M�-�-�b�B�-�?�?�?�?�!�<�*�S��^�^�;�M�$'��M�M�	���
�19����0D�J�J�,�I�}��M�%�%�m�y�%�I�I�I�I�J������0D�J�J�,�I�}��M�%�%�m�y�%�I�I�I�I�J����J�7	J�	J�	J�	J�	J�	J�	J�	J�	J�	J�	J����	J�	J�	J�	J�:	���6�
�F�F�F�F�Fs+�-F�B	E
� -F�
.E;�;F�F�F)r4N)+�__name__�
__module__�__qualname__�__doc__�intrWrXrYrrwrs�_SEND_MESSAGE_RECURRING_TIMEr9r$�AbstractEventLoopr2rCr8�staticmethodrQr}�contextmanagerrrr]r`rr�
Reportabler
rorr.ruryr,�bytesrlr�r�r�r�rGr�r3r1rr-s2������7�7�"�c�
�
���9�2�>�>�����O�,�N�#%� ���

�g�&?�

�

�

�

�!E�!E�!E�!E�F$0�$0�$0�L����\����+�)�L�$?��t�$K�L�+�+�+���+��V�K�"�#�#�	N�G�	N��	N�	N�	N�$�#�	N��_�1�2�2���3�2��$��$�$�$�$��_�Q��� !� !��� !�D���U������u�������
�
�
����@-�-�-�^"G�"G�"G�"G�"G�"Gr3rc� �eZdZejZdZdS)�SendToServeri�N)r�r�r�r�AV�SCOPE�SHUTDOWN_PRIORITYr�r3r1r�r�ks�������H�E����r3r�)1r$�concurrent.futuresrZr}r��loggingrWrgrh�typingr�defence360agent.api.serverrrrr�defence360agent.contractsr� defence360agent.contracts.configr	�"defence360agent.contracts.messagesr
rr�!defence360agent.contracts.pluginsr
r�defence360agent.internalsr�2defence360agent.internals.message_status_publisherrr�,defence360agent.internals.persistent_messager�defence360agent.utilsrrrr�defence360agent.utils.jsonr�	getLoggerr�r;rnr�r�rr�r�r3r1�<module>r�sR����������������������	�	�	�	���������������������������.�-�-�-�-�-�1�1�1�1�1�1�����������
B�A�A�A�A�A�A�A�3�3�3�3�3�3�M�M�M�M�M�M�M�M�������������������9�8�8�8�8�8�	��	�8�	$�	$���s�u�u��������S�U�U��{G�{G�{G�{G�{G�{G�{G�{G�|	�����%�{�����r3defence360agent/plugins/__pycache__/config_merger.cpython-311.opt-1.pyc0000644000000000000000000000424700000000000022715 0ustar  �

��A.Ԟ���v�ddlZddlmZmZddlmZddlmZmZej	e
��ZGd�de��ZdS)�N)�ConfigValidationError�Merger)�MessageType)�MessageSink�expectc�d�eZdZejjZd�Zd�Ze	e
j��d���ZdS)�ConfigMergerc��d|_dS�N��loop)�selfs �Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/config_merger.py�__init__zConfigMerger.__init__
s
����	�	�	�c��K�||_dSrr)rr
s  r�create_sinkzConfigMerger.create_sinks������	�	�	rc��TK�	tj��n2#t$r%}t�d|��Yd}~nd}~wwxYw|�d��x}r|���dSdS#|�d��x}r|���wwxYw)Nz&Config is invalid. Will not update: %s�event)r�update_merged_configr�logger�error�get�set)r�message�errrs    rrz!ConfigMerger.update_merged_configs�����	��'�)�)�)�)��$�	H�	H�	H��L�L�A�3�G�G�G�G�G�G�G�G�����	H���� ���G�,�,�,�u�
��	�	������
�
�����G�,�,�,�u�
��	�	�����
���s*��A9�
A�A�A9�A�A9�9.B'N)
�__name__�
__module__�__qualname__r�ProcessingOrder�PRE_PROCESS_MESSAGE�PROCESSING_ORDERrrrr�ConfigUpdater�rrr	r	
sg������"�2�F���������V�K�$�%�%���&�%���rr	)
�logging� defence360agent.contracts.configrr�"defence360agent.contracts.messagesr�!defence360agent.contracts.pluginsrr�	getLoggerrrr	r$rr�<module>r*s�������J�J�J�J�J�J�J�J�:�:�:�:�:�:�A�A�A�A�A�A�A�A�	��	�8�	$�	$�������;�����rdefence360agent/plugins/__pycache__/config_merger.cpython-311.pyc0000644000000000000000000000424700000000000021756 0ustar  �

��A.Ԟ���v�ddlZddlmZmZddlmZddlmZmZej	e
��ZGd�de��ZdS)�N)�ConfigValidationError�Merger)�MessageType)�MessageSink�expectc�d�eZdZejjZd�Zd�Ze	e
j��d���ZdS)�ConfigMergerc��d|_dS�N��loop)�selfs �Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/config_merger.py�__init__zConfigMerger.__init__
s
����	�	�	�c��K�||_dSrr)rr
s  r�create_sinkzConfigMerger.create_sinks������	�	�	rc��TK�	tj��n2#t$r%}t�d|��Yd}~nd}~wwxYw|�d��x}r|���dSdS#|�d��x}r|���wwxYw)Nz&Config is invalid. Will not update: %s�event)r�update_merged_configr�logger�error�get�set)r�message�errrs    rrz!ConfigMerger.update_merged_configs�����	��'�)�)�)�)��$�	H�	H�	H��L�L�A�3�G�G�G�G�G�G�G�G�����	H���� ���G�,�,�,�u�
��	�	������
�
�����G�,�,�,�u�
��	�	�����
���s*��A9�
A�A�A9�A�A9�9.B'N)
�__name__�
__module__�__qualname__r�ProcessingOrder�PRE_PROCESS_MESSAGE�PROCESSING_ORDERrrrr�ConfigUpdater�rrr	r	
sg������"�2�F���������V�K�$�%�%���&�%���rr	)
�logging� defence360agent.contracts.configrr�"defence360agent.contracts.messagesr�!defence360agent.contracts.pluginsrr�	getLoggerrrr	r$rr�<module>r*s�������J�J�J�J�J�J�J�J�:�:�:�:�:�:�A�A�A�A�A�A�A�A�	��	�8�	$�	$�������;�����rdefence360agent/plugins/__pycache__/config_watcher.cpython-311.opt-1.pyc0000644000000000000000000000653100000000000023067 0ustar  �

��@�������ddlZddlmZddlmZddlmZmZmZddl	m
Z
mZejdd��Z
Gd�d	ee��ZdS)
�N)�config)�MessageType)�MessageSink�
MessageSource�expect)�recurring_check�Scope�READ_CONFIG_POLLING_INTERVAL�c��eZdZdZejZd�Zd�Ze	e
j��d���Zd�Z
d�Zee��d���ZdS)	�
ConfigWatcherz�Send 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��
K�dS)zplugins.MessageSink methodN�)r�loops  r�create_sinkzConfigWatcher.create_sinks
�����rc��$K�|d|_dS)N�	timestamp)r�r�messages  r�on_config_update_messagez&ConfigWatcher.on_config_update_message!s����")��!5����rc��pK�||_|�|�����|_dS�N)r�create_task�
_check_configr)rr�sinks   r�
create_sourcezConfigWatcher.create_source's2������
��%�%�d�&8�&8�&:�&:�;�;��
�
�
rc��|K�|j�+|jdc}|_|���|�d{V��d|_dSr")r�cancelr)r�ts  r�shutdownzConfigWatcher.shutdown+sF�����:�!� �J��M�A�t�z�
�H�H�J�J�J��G�G�G�G�G�G�G���
�
�
rc���K�tj|j��r[tj|jt
j�����}|j�|���d{V��|d|_dSdS)N)�confrr)	r�any_layer_modified_sincerr�ConfigUpdater�timer�process_messagers  rr$zConfigWatcher._check_config2s������*�4�+A�B�B�	:�!�.��\�T�Y�[�[����G��*�,�,�W�5�5�5�5�5�5�5�5�5�&-�[�%9�D�"�"�"�	:�	:rN)�__name__�
__module__�__qualname__�__doc__r	�AV�SCOPErrrrr.r r&r*r�POLLING_INTERVALr$rrrr
r
s���������
�H�E����%�%�%��V�K�$�%�%�6�6�&�%�6�
<�<�<�����_�%�&�&�	:�	:�'�&�	:�	:�	:rr
)r/�defence360agent.contractsr�"defence360agent.contracts.messagesr�!defence360agent.contracts.pluginsrrr�defence360agent.utilsrr	�int_from_envvarr7r
rrr�<module>r=s�������,�,�,�,�,�,�:�:�:�:�:�:�����������
9�8�8�8�8�8�8�8�)�6�)�*H�"�M�M��.:�.:�.:�.:�.:�K��.:�.:�.:�.:�.:rdefence360agent/plugins/__pycache__/config_watcher.cpython-311.pyc0000644000000000000000000000653100000000000022130 0ustar  �

��@�������ddlZddlmZddlmZddlmZmZmZddl	m
Z
mZejdd��Z
Gd�d	ee��ZdS)
�N)�config)�MessageType)�MessageSink�
MessageSource�expect)�recurring_check�Scope�READ_CONFIG_POLLING_INTERVAL�c��eZdZdZejZd�Zd�Ze	e
j��d���Zd�Z
d�Zee��d���ZdS)	�
ConfigWatcherz�Send 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��
K�dS)zplugins.MessageSink methodN�)r�loops  r�create_sinkzConfigWatcher.create_sinks
�����rc��$K�|d|_dS)N�	timestamp)r�r�messages  r�on_config_update_messagez&ConfigWatcher.on_config_update_message!s����")��!5����rc��pK�||_|�|�����|_dS�N)r�create_task�
_check_configr)rr�sinks   r�
create_sourcezConfigWatcher.create_source's2������
��%�%�d�&8�&8�&:�&:�;�;��
�
�
rc��|K�|j�+|jdc}|_|���|�d{V��d|_dSr")r�cancelr)r�ts  r�shutdownzConfigWatcher.shutdown+sF�����:�!� �J��M�A�t�z�
�H�H�J�J�J��G�G�G�G�G�G�G���
�
�
rc���K�tj|j��r[tj|jt
j�����}|j�|���d{V��|d|_dSdS)N)�confrr)	r�any_layer_modified_sincerr�ConfigUpdater�timer�process_messagers  rr$zConfigWatcher._check_config2s������*�4�+A�B�B�	:�!�.��\�T�Y�[�[����G��*�,�,�W�5�5�5�5�5�5�5�5�5�&-�[�%9�D�"�"�"�	:�	:rN)�__name__�
__module__�__qualname__�__doc__r	�AV�SCOPErrrrr.r r&r*r�POLLING_INTERVALr$rrrr
r
s���������
�H�E����%�%�%��V�K�$�%�%�6�6�&�%�6�
<�<�<�����_�%�&�&�	:�	:�'�&�	:�	:�	:rr
)r/�defence360agent.contractsr�"defence360agent.contracts.messagesr�!defence360agent.contracts.pluginsrrr�defence360agent.utilsrr	�int_from_envvarr7r
rrr�<module>r=s�������,�,�,�,�,�,�:�:�:�:�:�:�����������
9�8�8�8�8�8�8�8�)�6�)�*H�"�M�M��.:�.:�.:�.:�.:�K��.:�.:�.:�.:�.:rdefence360agent/plugins/__pycache__/event_hook_executor.cpython-311.opt-1.pyc0000644000000000000000000000343700000000000024166 0ustar  �

�h(`�����ddlmZddlmZmZmZddlmZejej	ej
ejejfZ
Gd�dee��ZdS)�)�	HookEvent)�MessageSink�
MessageSource�expect)�
execute_hooksc�N�eZdZejjZd�Zd�Ze	e
�d���ZdS)�EventHookExecutorc��K�||_dS�N)�_loop)�self�loops  �`/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/event_hook_executor.py�create_sinkzEventHookExecutor.create_sinks������
�
�
�c��&K�||_||_dSr)r�_sink)r
r�sinks   r�
create_sourcezEventHookExecutor.create_sources������
���
�
�
rc��XK�|j�t|����dSr)r�create_taskr)r
�events  r�
receive_eventzEventHookExecutor.receive_events*�����
���}�U�3�3�4�4�4�4�4rN)�__name__�
__module__�__qualname__r�ProcessingOrder�
EVENT_HOOK�PROCESSING_ORDERrrr�EVENTSr�rrr	r	s_������"�2�=���������V�V�_�5�5��_�5�5�5rr	N)�%defence360agent.contracts.hook_eventsr�!defence360agent.contracts.pluginsrrr�defence360agent.hooks.executer�AgentStarted�AgentMisconfig�LicenseExpired�LicenseExpiring�LicenseRenewedr r	r!rr�<module>r*s���;�;�;�;�;�;�����������
8�7�7�7�7�7���
��
��
��
��
��5�5�5�5�5��]�5�5�5�5�5rdefence360agent/plugins/__pycache__/event_hook_executor.cpython-311.pyc0000644000000000000000000000343700000000000023227 0ustar  �

�h(`�����ddlmZddlmZmZmZddlmZejej	ej
ejejfZ
Gd�dee��ZdS)�)�	HookEvent)�MessageSink�
MessageSource�expect)�
execute_hooksc�N�eZdZejjZd�Zd�Ze	e
�d���ZdS)�EventHookExecutorc��K�||_dS�N)�_loop)�self�loops  �`/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/event_hook_executor.py�create_sinkzEventHookExecutor.create_sinks������
�
�
�c��&K�||_||_dSr)r�_sink)r
r�sinks   r�
create_sourcezEventHookExecutor.create_sources������
���
�
�
rc��XK�|j�t|����dSr)r�create_taskr)r
�events  r�
receive_eventzEventHookExecutor.receive_events*�����
���}�U�3�3�4�4�4�4�4rN)�__name__�
__module__�__qualname__r�ProcessingOrder�
EVENT_HOOK�PROCESSING_ORDERrrr�EVENTSr�rrr	r	s_������"�2�=���������V�V�_�5�5��_�5�5�5rr	N)�%defence360agent.contracts.hook_eventsr�!defence360agent.contracts.pluginsrrr�defence360agent.hooks.executer�AgentStarted�AgentMisconfig�LicenseExpired�LicenseExpiring�LicenseRenewedr r	r!rr�<module>r*s���;�;�;�;�;�;�����������
8�7�7�7�7�7���
��
��
��
��
��5�5�5�5�5��]�5�5�5�5�5rdefence360agent/plugins/__pycache__/event_monitor.cpython-311.opt-1.pyc0000644000000000000000000001412100000000000022767 0ustar  �

z��9�5�8����ddlZddlmZddlmZddlmZddlmZm	Z	m
Z
ddlmZddl
mZddlmZdd	lmZdd
lmZmZddlmZmZee��ZGd�d
ee��ZdS)�N)�ABC)�	getLogger)�Path)�Dict�List�Optional)�Core)�MessageType)�
MessageSource)�%NativeFeatureManagementSettingsChange)�EventProcessorBase�UserConfigProcessor)�recurring_check�safe_cancel_taskc��eZdZejZdZd�Zd�Zd�Z	e
defd���Ze
dede
fd���Zdeejfd	�Zed
��d���ZdS)
�EventMonitorz*.*.*.*.jsonc�>�d|_d|_g|_d|_dS�N)�_loop�_sink�_processors�_processing_task��selfs �Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/event_monitor.py�__init__zEventMonitor.__init__s&����
���
�57��� $�����c��$K�||_||_|j�t	|����|j�t|����|j�|�����|_dSr)	rrr�appendrr�create_task�#_check_inbox_folder_generate_eventsr)r�loop�sinks   r�
create_sourcezEventMonitor.create_source s�������
���
����� E�d� K� K�L�L�L����� 3�D� 9� 9�:�:�:� $�
� 6� 6��4�4�6�6�!
�!
����rc��>K�t|j���d{V��dSr)rrrs r�shutdownzEventMonitor.shutdown)s/�����t�4�5�5�5�5�5�5�5�5�5�5�5r�filec��	|���dS#t$rYdSt$r'}t�d||��Yd}~dSd}~wwxYw)NzCouldn't remove file %s %s)�unlink�FileNotFoundError�	Exception�logger�warning)r'�es  r�_rmfilezEventMonitor._rmfile,s~��	B��K�K�M�M�M�M�M�� �	�	�	��D�D��	B�	B�	B��N�N�7��q�A�A�A�A�A�A�A�A�A�����	B���s��
A�	A�A�A�returnc�N�tj|�����Sr)�json�loads�	read_text)r's r�
_from_jsonzEventMonitor._from_json5s���z�$�.�.�*�*�+�+�+rc���	|j�d��^}}}}}t|dz|z��}n,#t$rt�d|��YdSwxYw	tj�||||�	|�����S#t$rt�d|��Yn/tj$rt�d|��YnwxYwdS)N�.z+hook-event-file detected with wrong name %s)�username�hook�ts�fieldszhook file disappeared %szhook file have broken json %s)
�name�split�float�
ValueErrorr,r-r
�cPanelEvent�from_hook_eventr5r*r2�JSONDecodeError)rr'r8r9�ts1�ts2�_r:s        r�_event_to_messagezEventMonitor._event_to_message9s$��	�+/�9�?�?�3�+?�+?�(�H�d�C��q��s�S�y�3��'�'�B�B���	�	�	��N�N�H�$�O�O�O��4�4�	����	B��*�:�:�!������t�,�,�	;���
��!�	=�	=�	=��N�N�5�t�<�<�<�<�<��#�	B�	B�	B��N�N�:�D�A�A�A�A�A�	B�����ts'�47�%A �A �$5B�%C-�)C-�,C-�c��K�t|j���d��D]�}	|�|��}|�9|jD]1}|����d{V��r|�|���2n2#t$r%}t�	d|��Yd}~nd}~wwxYw|�
|����#|�
|��wxYw|jD]}|����d{V���dS)Nz
*.*.*.jsonzFailed to process %s hook event)r�	EVENT_DIR�globrFr�
is_enabled�add_messager+r,�errorr/�process_messages)rr'�message�	processor�excs     rr!z0EventMonitor._check_inbox_folder_generate_eventsOsR�������(�(�-�-�l�;�;�
	#�
	#�D�	
#��0�0��6�6���&�%)�%5�;�;�	�!*�!5�!5�!7�!7�7�7�7�7�7�7�;�%�1�1�'�:�:�:�����
E�
E�
E����>��D�D�D�D�D�D�D�D�����
E�������T�"�"�"�"�����T�"�"�"�"�����)�	/�	/�I��,�,�.�.�.�.�.�.�.�.�.�.�	/�	/s0�AA>�=C�>
B-�B(�#C�(B-�-C�CN)�__name__�
__module__�__qualname__r	�INBOX_HOOKS_DIRrI�PATTERNrr$r&�staticmethodrr/rr5rr
r@rFrr!�rrrrs��������$�I��G�%�%�%�
�
�
�6�6�6��B�d�B�B�B��\�B��,��,�$�,�,�,��\�,���+�2I�)J�����,�_�R���
/�
/���
/�
/�
/rr)r2�abcr�loggingr�pathlibr�typingrrr� defence360agent.contracts.configr	�"defence360agent.contracts.messagesr
�!defence360agent.contracts.pluginsr�1defence360agent.feature_management.plugins.nativer�7defence360agent.plugins.event_monitor_message_processorr
r�defence360agent.utilsrrrRr,rrXrr�<module>rcsH������������������������'�'�'�'�'�'�'�'�'�'�1�1�1�1�1�1�:�:�:�:�:�:�;�;�;�;�;�;���������������D�C�C�C�C�C�C�C�	��8�	�	��G/�G/�G/�G/�G/�=�#�G/�G/�G/�G/�G/rdefence360agent/plugins/__pycache__/event_monitor.cpython-311.pyc0000644000000000000000000001412100000000000022030 0ustar  �

z��9�5�8����ddlZddlmZddlmZddlmZddlmZm	Z	m
Z
ddlmZddl
mZddlmZdd	lmZdd
lmZmZddlmZmZee��ZGd�d
ee��ZdS)�N)�ABC)�	getLogger)�Path)�Dict�List�Optional)�Core)�MessageType)�
MessageSource)�%NativeFeatureManagementSettingsChange)�EventProcessorBase�UserConfigProcessor)�recurring_check�safe_cancel_taskc��eZdZejZdZd�Zd�Zd�Z	e
defd���Ze
dede
fd���Zdeejfd	�Zed
��d���ZdS)
�EventMonitorz*.*.*.*.jsonc�>�d|_d|_g|_d|_dS�N)�_loop�_sink�_processors�_processing_task��selfs �Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/event_monitor.py�__init__zEventMonitor.__init__s&����
���
�57��� $�����c��$K�||_||_|j�t	|����|j�t|����|j�|�����|_dSr)	rrr�appendrr�create_task�#_check_inbox_folder_generate_eventsr)r�loop�sinks   r�
create_sourcezEventMonitor.create_source s�������
���
����� E�d� K� K�L�L�L����� 3�D� 9� 9�:�:�:� $�
� 6� 6��4�4�6�6�!
�!
����rc��>K�t|j���d{V��dSr)rrrs r�shutdownzEventMonitor.shutdown)s/�����t�4�5�5�5�5�5�5�5�5�5�5�5r�filec��	|���dS#t$rYdSt$r'}t�d||��Yd}~dSd}~wwxYw)NzCouldn't remove file %s %s)�unlink�FileNotFoundError�	Exception�logger�warning)r'�es  r�_rmfilezEventMonitor._rmfile,s~��	B��K�K�M�M�M�M�M�� �	�	�	��D�D��	B�	B�	B��N�N�7��q�A�A�A�A�A�A�A�A�A�����	B���s��
A�	A�A�A�returnc�N�tj|�����Sr)�json�loads�	read_text)r's r�
_from_jsonzEventMonitor._from_json5s���z�$�.�.�*�*�+�+�+rc���	|j�d��^}}}}}t|dz|z��}n,#t$rt�d|��YdSwxYw	tj�||||�	|�����S#t$rt�d|��Yn/tj$rt�d|��YnwxYwdS)N�.z+hook-event-file detected with wrong name %s)�username�hook�ts�fieldszhook file disappeared %szhook file have broken json %s)
�name�split�float�
ValueErrorr,r-r
�cPanelEvent�from_hook_eventr5r*r2�JSONDecodeError)rr'r8r9�ts1�ts2�_r:s        r�_event_to_messagezEventMonitor._event_to_message9s$��	�+/�9�?�?�3�+?�+?�(�H�d�C��q��s�S�y�3��'�'�B�B���	�	�	��N�N�H�$�O�O�O��4�4�	����	B��*�:�:�!������t�,�,�	;���
��!�	=�	=�	=��N�N�5�t�<�<�<�<�<��#�	B�	B�	B��N�N�:�D�A�A�A�A�A�	B�����ts'�47�%A �A �$5B�%C-�)C-�,C-�c��K�t|j���d��D]�}	|�|��}|�9|jD]1}|����d{V��r|�|���2n2#t$r%}t�	d|��Yd}~nd}~wwxYw|�
|����#|�
|��wxYw|jD]}|����d{V���dS)Nz
*.*.*.jsonzFailed to process %s hook event)r�	EVENT_DIR�globrFr�
is_enabled�add_messager+r,�errorr/�process_messages)rr'�message�	processor�excs     rr!z0EventMonitor._check_inbox_folder_generate_eventsOsR�������(�(�-�-�l�;�;�
	#�
	#�D�	
#��0�0��6�6���&�%)�%5�;�;�	�!*�!5�!5�!7�!7�7�7�7�7�7�7�;�%�1�1�'�:�:�:�����
E�
E�
E����>��D�D�D�D�D�D�D�D�����
E�������T�"�"�"�"�����T�"�"�"�"�����)�	/�	/�I��,�,�.�.�.�.�.�.�.�.�.�.�	/�	/s0�AA>�=C�>
B-�B(�#C�(B-�-C�CN)�__name__�
__module__�__qualname__r	�INBOX_HOOKS_DIRrI�PATTERNrr$r&�staticmethodrr/rr5rr
r@rFrr!�rrrrs��������$�I��G�%�%�%�
�
�
�6�6�6��B�d�B�B�B��\�B��,��,�$�,�,�,��\�,���+�2I�)J�����,�_�R���
/�
/���
/�
/�
/rr)r2�abcr�loggingr�pathlibr�typingrrr� defence360agent.contracts.configr	�"defence360agent.contracts.messagesr
�!defence360agent.contracts.pluginsr�1defence360agent.feature_management.plugins.nativer�7defence360agent.plugins.event_monitor_message_processorr
r�defence360agent.utilsrrrRr,rrXrr�<module>rcsH������������������������'�'�'�'�'�'�'�'�'�'�1�1�1�1�1�1�:�:�:�:�:�:�;�;�;�;�;�;���������������D�C�C�C�C�C�C�C�	��8�	�	��G/�G/�G/�G/�G/�=�#�G/�G/�G/�G/�G/rdefence360agent/plugins/__pycache__/event_monitor_message_processor.cpython-311.opt-1.pyc0000644000000000000000000003220600000000000026576 0ustar  �

��������ddlZddlZddlZddlmZmZddlmZddlm	Z	m
Z
ddlmZddl
mZddlmZddlmZmZdd	lmZmZej��ZGd
�dee��ZGd�d
ee��ZGd�de��ZdS)�N)�ABC�abstractmethod)�defaultdict)�heappop�heappush)�Dict)�Core)�MessageType)�BaseMessageProcessor�expect)�is_safe_subdir_name�rmtreec���eZdZd�Zd�Zd�Zeej��d���Z	d�Z
ed���Zed���Z
ed���Zed	���Zed
���Zed���ZdS)
�EventProcessorBasec�F�tt��|_||_dS�N)r�list�_msg_buf�_loop)�self�loops  �l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/event_monitor_message_processor.py�__init__zEventProcessorBase.__init__s��#�D�)�)��
���
�
�
�c�X�t|j|d|d|f��dS)N�username�	timestamp)rr�r�messages  r�add_messagezEventProcessorBase.add_messages9����M�'�*�-�.���1E�w�0O�	
�	
�	
�	
�	
rc��z�K�tj�fd��j���D����d{V��dS)Nc3�B�K�|]}��|��V��dSr)�process_user_messages)�.0�
user_messagesrs  �r�	<genexpr>z6EventProcessorBase.process_messages.<locals>.<genexpr>sE�������!��*�*�=�9�9������r)�asyncio�gatherr�values�rs`r�process_messagesz#EventProcessorBase.process_messagessr������n�����%)�]�%9�%9�%;�%;����
�	
�	
�	
�	
�	
�	
�	
�	
�	
rc��xK�|�|��sdS|jdkr|�|���d{V��dS|jdkr|�|���d{V��dS|jdkr|�|���d{V��dS|jdkr|�|���d{V��dSdS)N�Modify�Create�change_package�Remove)�_message_is_relatable�hook�_process_modify�_process_create�_process_change_package�_process_account_removedrs  r�
process_eventz EventProcessorBase.process_event$s�����)�)�'�2�2�	��F��<�8�#�#��&�&�w�/�/�/�/�/�/�/�/�/�/�/�
�\�X�
%�
%��&�&�w�/�/�/�/�/�/�/�/�/�/�/�
�\�-�
-�
-��.�.�w�7�7�7�7�7�7�7�7�7�7�7�
�\�X�
%�
%��/�/��8�8�8�8�8�8�8�8�8�8�8�&�
%rc��K�tt|����D]0}|�t|��d���d{V���1dS)N�)�range�len�process_messager)r�messages�_s   rr#z(EventProcessorBase.process_user_messages2sc�����s�8�}�}�%�%�	=�	=�A��&�&�w�x�'8�'8��';�<�<�<�<�<�<�<�<�<�<�	=�	=rc��
K�dS)zModify hookN�rs  rr3z"EventProcessorBase._process_modify6�
�����rc��
K�dS�zCreate hookNr@rs  rr4z"EventProcessorBase._process_create:rArc��
K�dS�zchange_package hookNr@rs  rr5z*EventProcessorBase._process_change_package>rArc��
K�dS)zRemove hookNr@rs  rr6z+EventProcessorBase._process_account_removedBrArc��dS�z'Whether the message should be processedNr@rs  rr1z(EventProcessorBase._message_is_relatableF����rc��
K�dS�z$Whether messages should be processedNr@r*s r�
is_enabledzEventProcessorBase.is_enabledJrArN)�__name__�
__module__�__qualname__rr r+rr
�cPanelEventr7r#rr3r4r5r6r1rLr@rrrrs���������

�
�
�

�
�
��V�K�#�$�$�9�9�%�$�9�=�=�=�����^������^���"�"��^�"�����^���6�6��^�6��3�3��^�3�3�3rrc
� �eZdZdZd�Zd�Zd�Zd�Z	ddede	d	d
fd�Z
d�Zed
���Z
ed���Zeed	eeeffd�����Zed���Zeedede	d	eeeffd�����Zed���Zd
S)�SettingsChangeBasez'Process hook event messages from cPanelc��\K�d|jvrdnd}|�||���d{V��dS)N�plan�exclude)�data�_get_settings_and_update)rr�
package_fields   rr3z"SettingsChangeBase._process_modifyRsI����"(�G�L�"8�"8���i�
��+�+�G�]�C�C�C�C�C�C�C�C�C�C�Crc��DK�|�|dd���d{V��dS)NrTT�rWrs  rr4z"SettingsChangeBase._process_createVs6�����+�+�G�V�T�B�B�B�B�B�B�B�B�B�B�Brc��DK�|�|dd���d{V��dS)N�new_pkgTrZrs  rr5z*SettingsChangeBase._process_change_packageYs6�����+�+�G�Y��E�E�E�E�E�E�E�E�E�E�Erc��
K�dSrr@rs  rr6z+SettingsChangeBase._process_account_removed\s�����rFrX�add_to_package�returnNc��K�t�d|��|�|���d{V��}|�||||���d{V��dS)NzGet settings from %s)�logger�info�_get_settings_from_message�_apply_settings)rrrXr^�settingss     rrWz+SettingsChangeBase._get_settings_and_update_s�����	���*�G�4�4�4��8�8��A�A�A�A�A�A�A�A���"�"��]�N�H�
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
rc��K�t�d|��|�d���9|ddkr-td�|���D����rdSt|�����s�	|j|}|�||���d{V��}n>#t$r1t�d��|�	��}YnwxYw|�
��D]\}}|�||||<�t�d||d��|�
��D](\}}|�|d||���d{V���)dS)	Nz
Step 1 %s rTr2r-c3�K�|]}|duV��	dSrr@)r$�values  rr&z5SettingsChangeBase._apply_settings.<locals>.<genexpr>ts&����A�A�e�E�T�M�A�A�A�A�A�Arz'No information about package in messagez,Settings specified in hook message %s for %sr)rarb�get�allr)rV�_get_package_settings�KeyError�warning�_default_settings�items�on_settings_change)	rrrXr^re�package_name�fallback_settings�featurerhs	         rrdz"SettingsChangeBase._apply_settingsks�����	���L�(�+�+�+�
�K�K����'����8�+�+��A�A�x���/@�/@�A�A�A�A�A�,�
�F��8�?�?�$�$�%�%�	C�
�&�|�M�:��
+/�*D�*D� �.�+�+�%�%�%�%�%�%�!�!��	�
=�
=�
=����H�I�I�I�$(�$:�$:�$<�$<�!�!�!�
=����#+�.�.�"2�"2�
C�
C�����=�(9�'�(B�H�W�%�����:���J��	
�	
�	
�
'�n�n�.�.�	O�	O�N�G�U��)�)�'�*�*=�w��N�N�N�N�N�N�N�N�N�N�	O�	Os�
B8�88C3�2C3c��dSrHr@rs  rr1z(SettingsChangeBase._message_is_relatable�rIrc��
K�dS)z9What to do after settings were changed (e.g. sync the DB)Nr@)r�userrsrhs    rrpz%SettingsChangeBase.on_settings_change�rArc��dS)zGet default package settingsNr@r@rrrnz$SettingsChangeBase._default_settings�rIrc��
K�dS)z"Retrieve settings from the messageNr@rs  rrcz-SettingsChangeBase._get_settings_from_message�rArrqc��
K�dS)zGet current package settingsNr@)�clsrqr^s   rrkz(SettingsChangeBase._get_package_settings�rArc��
K�dSrKr@r*s rrLzSettingsChangeBase.is_enabled�rAr)F)rMrNrO�__doc__r3r4r5r6�str�boolrWrdrr1rp�staticmethodrrnrc�classmethodrkrLr@rrrRrROs�������1�1�D�D�D�C�C�C�F�F�F�
�
�
� %�	

�

��

��	

�

�

�

�

�

�O�O�O�B�6�6��^�6��H�H��^�H���+�t�C��H�~�+�+�+��^��\�+��1�1��^�1���+��+�04�+�	
�c�3�h��+�+�+��^��[�+�
�3�3��^�3�3�3rrRc�2�eZdZd�Zd�Zd�Zd�Zd�Zd�ZdS)�UserConfigProcessorc��dS�NTr@rs  rr1z)UserConfigProcessor._message_is_relatable�s���trc��
K�dSr�r@r*s rrLzUserConfigProcessor.is_enabled�s�����trc��tK�|�d��p|�d��}t|��sdStj�t
j|��}	t|��dS#t$rYdSt$r'}t�d||��Yd}~dSd}~wwxYw)Nrvrz'Failed to remove user_config dir %s: %s)rir
�os�path�joinr	�USER_CONFDIRr�FileNotFoundError�OSErrorrarm)rrrv�target�es     rr6z,UserConfigProcessor._process_account_removed�s������{�{�6�"�"�=�g�k�k�*�&=�&=��"�4�(�(�	��F�����d�/��6�6��	��6�N�N�N�N�N�� �	�	�	��D�D��	�	�	��N�N�9�6�1�
�
�
�
�
�
�
�
�
�����	���s�)A:�:
B7�	B7�B2�2B7c���K�|j�d��}|j}|rt|��rt|��sdS	t	jtj�tj	|��tj�tj	|����dS#t$rYdSt$r(}t�
d|||��Yd}~dSd}~wwxYw)N�old_usernamez)Failed to rename user_config %s -> %s: %s)rVrirr
r��renamer�r�r	r�r�r�rarm)rrr��new_usernamer�s     rr3z#UserConfigProcessor._process_modify�s�����|�'�'��7�7���'���	�#�L�1�1�	�$�L�1�1�	�

�F�
	��I�����T�.��=�=�����T�.��=�=�
�
�
�
�
��!�	�	�	��D�D��	�	�	��N�N�;����	
�
�
�
�
�
�
�
�
�����	���s�A%B.�.
C,�;	C,�C'�'C,c��
K�dSrCr@rs  rr4z#UserConfigProcessor._process_create�rArc��
K�dSrEr@rs  rr5z+UserConfigProcessor._process_change_package�rArN)	rMrNrOr1rLr6r3r4r5r@rrr�r��sn������������������0���"�"�"�"�"rr�)r'�loggingr��abcrr�collectionsr�heapqrr�typingr� defence360agent.contracts.configr	�"defence360agent.contracts.messagesr
�!defence360agent.contracts.pluginsrr�defence360agent.utilsr
r�	getLoggerrarrRr�r@rr�<module>r�sq����������	�	�	�	�#�#�#�#�#�#�#�#�#�#�#�#�#�#�#�#�#�#�#�#�#�#�������1�1�1�1�1�1�:�:�:�:�:�:�J�J�J�J�J�J�J�J�=�=�=�=�=�=�=�=�	��	�	�	��;3�;3�;3�;3�;3�-�s�;3�;3�;3�|W3�W3�W3�W3�W3�+�S�W3�W3�W3�t1"�1"�1"�1"�1"�,�1"�1"�1"�1"�1"rdefence360agent/plugins/__pycache__/event_monitor_message_processor.cpython-311.pyc0000644000000000000000000003220600000000000025637 0ustar  �

��������ddlZddlZddlZddlmZmZddlmZddlm	Z	m
Z
ddlmZddl
mZddlmZddlmZmZdd	lmZmZej��ZGd
�dee��ZGd�d
ee��ZGd�de��ZdS)�N)�ABC�abstractmethod)�defaultdict)�heappop�heappush)�Dict)�Core)�MessageType)�BaseMessageProcessor�expect)�is_safe_subdir_name�rmtreec���eZdZd�Zd�Zd�Zeej��d���Z	d�Z
ed���Zed���Z
ed���Zed	���Zed
���Zed���ZdS)
�EventProcessorBasec�F�tt��|_||_dS�N)r�list�_msg_buf�_loop)�self�loops  �l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/event_monitor_message_processor.py�__init__zEventProcessorBase.__init__s��#�D�)�)��
���
�
�
�c�X�t|j|d|d|f��dS)N�username�	timestamp)rr�r�messages  r�add_messagezEventProcessorBase.add_messages9����M�'�*�-�.���1E�w�0O�	
�	
�	
�	
�	
rc��z�K�tj�fd��j���D����d{V��dS)Nc3�B�K�|]}��|��V��dSr)�process_user_messages)�.0�
user_messagesrs  �r�	<genexpr>z6EventProcessorBase.process_messages.<locals>.<genexpr>sE�������!��*�*�=�9�9������r)�asyncio�gatherr�values�rs`r�process_messagesz#EventProcessorBase.process_messagessr������n�����%)�]�%9�%9�%;�%;����
�	
�	
�	
�	
�	
�	
�	
�	
�	
rc��xK�|�|��sdS|jdkr|�|���d{V��dS|jdkr|�|���d{V��dS|jdkr|�|���d{V��dS|jdkr|�|���d{V��dSdS)N�Modify�Create�change_package�Remove)�_message_is_relatable�hook�_process_modify�_process_create�_process_change_package�_process_account_removedrs  r�
process_eventz EventProcessorBase.process_event$s�����)�)�'�2�2�	��F��<�8�#�#��&�&�w�/�/�/�/�/�/�/�/�/�/�/�
�\�X�
%�
%��&�&�w�/�/�/�/�/�/�/�/�/�/�/�
�\�-�
-�
-��.�.�w�7�7�7�7�7�7�7�7�7�7�7�
�\�X�
%�
%��/�/��8�8�8�8�8�8�8�8�8�8�8�&�
%rc��K�tt|����D]0}|�t|��d���d{V���1dS)N�)�range�len�process_messager)r�messages�_s   rr#z(EventProcessorBase.process_user_messages2sc�����s�8�}�}�%�%�	=�	=�A��&�&�w�x�'8�'8��';�<�<�<�<�<�<�<�<�<�<�	=�	=rc��
K�dS)zModify hookN�rs  rr3z"EventProcessorBase._process_modify6�
�����rc��
K�dS�zCreate hookNr@rs  rr4z"EventProcessorBase._process_create:rArc��
K�dS�zchange_package hookNr@rs  rr5z*EventProcessorBase._process_change_package>rArc��
K�dS)zRemove hookNr@rs  rr6z+EventProcessorBase._process_account_removedBrArc��dS�z'Whether the message should be processedNr@rs  rr1z(EventProcessorBase._message_is_relatableF����rc��
K�dS�z$Whether messages should be processedNr@r*s r�
is_enabledzEventProcessorBase.is_enabledJrArN)�__name__�
__module__�__qualname__rr r+rr
�cPanelEventr7r#rr3r4r5r6r1rLr@rrrrs���������

�
�
�

�
�
��V�K�#�$�$�9�9�%�$�9�=�=�=�����^������^���"�"��^�"�����^���6�6��^�6��3�3��^�3�3�3rrc
� �eZdZdZd�Zd�Zd�Zd�Z	ddede	d	d
fd�Z
d�Zed
���Z
ed���Zeed	eeeffd�����Zed���Zeedede	d	eeeffd�����Zed���Zd
S)�SettingsChangeBasez'Process hook event messages from cPanelc��\K�d|jvrdnd}|�||���d{V��dS)N�plan�exclude)�data�_get_settings_and_update)rr�
package_fields   rr3z"SettingsChangeBase._process_modifyRsI����"(�G�L�"8�"8���i�
��+�+�G�]�C�C�C�C�C�C�C�C�C�C�Crc��DK�|�|dd���d{V��dS)NrTT�rWrs  rr4z"SettingsChangeBase._process_createVs6�����+�+�G�V�T�B�B�B�B�B�B�B�B�B�B�Brc��DK�|�|dd���d{V��dS)N�new_pkgTrZrs  rr5z*SettingsChangeBase._process_change_packageYs6�����+�+�G�Y��E�E�E�E�E�E�E�E�E�E�Erc��
K�dSrr@rs  rr6z+SettingsChangeBase._process_account_removed\s�����rFrX�add_to_package�returnNc��K�t�d|��|�|���d{V��}|�||||���d{V��dS)NzGet settings from %s)�logger�info�_get_settings_from_message�_apply_settings)rrrXr^�settingss     rrWz+SettingsChangeBase._get_settings_and_update_s�����	���*�G�4�4�4��8�8��A�A�A�A�A�A�A�A���"�"��]�N�H�
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
rc��K�t�d|��|�d���9|ddkr-td�|���D����rdSt|�����s�	|j|}|�||���d{V��}n>#t$r1t�d��|�	��}YnwxYw|�
��D]\}}|�||||<�t�d||d��|�
��D](\}}|�|d||���d{V���)dS)	Nz
Step 1 %s rTr2r-c3�K�|]}|duV��	dSrr@)r$�values  rr&z5SettingsChangeBase._apply_settings.<locals>.<genexpr>ts&����A�A�e�E�T�M�A�A�A�A�A�Arz'No information about package in messagez,Settings specified in hook message %s for %sr)rarb�get�allr)rV�_get_package_settings�KeyError�warning�_default_settings�items�on_settings_change)	rrrXr^re�package_name�fallback_settings�featurerhs	         rrdz"SettingsChangeBase._apply_settingsks�����	���L�(�+�+�+�
�K�K����'����8�+�+��A�A�x���/@�/@�A�A�A�A�A�,�
�F��8�?�?�$�$�%�%�	C�
�&�|�M�:��
+/�*D�*D� �.�+�+�%�%�%�%�%�%�!�!��	�
=�
=�
=����H�I�I�I�$(�$:�$:�$<�$<�!�!�!�
=����#+�.�.�"2�"2�
C�
C�����=�(9�'�(B�H�W�%�����:���J��	
�	
�	
�
'�n�n�.�.�	O�	O�N�G�U��)�)�'�*�*=�w��N�N�N�N�N�N�N�N�N�N�	O�	Os�
B8�88C3�2C3c��dSrHr@rs  rr1z(SettingsChangeBase._message_is_relatable�rIrc��
K�dS)z9What to do after settings were changed (e.g. sync the DB)Nr@)r�userrsrhs    rrpz%SettingsChangeBase.on_settings_change�rArc��dS)zGet default package settingsNr@r@rrrnz$SettingsChangeBase._default_settings�rIrc��
K�dS)z"Retrieve settings from the messageNr@rs  rrcz-SettingsChangeBase._get_settings_from_message�rArrqc��
K�dS)zGet current package settingsNr@)�clsrqr^s   rrkz(SettingsChangeBase._get_package_settings�rArc��
K�dSrKr@r*s rrLzSettingsChangeBase.is_enabled�rAr)F)rMrNrO�__doc__r3r4r5r6�str�boolrWrdrr1rp�staticmethodrrnrc�classmethodrkrLr@rrrRrROs�������1�1�D�D�D�C�C�C�F�F�F�
�
�
� %�	

�

��

��	

�

�

�

�

�

�O�O�O�B�6�6��^�6��H�H��^�H���+�t�C��H�~�+�+�+��^��\�+��1�1��^�1���+��+�04�+�	
�c�3�h��+�+�+��^��[�+�
�3�3��^�3�3�3rrRc�2�eZdZd�Zd�Zd�Zd�Zd�Zd�ZdS)�UserConfigProcessorc��dS�NTr@rs  rr1z)UserConfigProcessor._message_is_relatable�s���trc��
K�dSr�r@r*s rrLzUserConfigProcessor.is_enabled�s�����trc��tK�|�d��p|�d��}t|��sdStj�t
j|��}	t|��dS#t$rYdSt$r'}t�d||��Yd}~dSd}~wwxYw)Nrvrz'Failed to remove user_config dir %s: %s)rir
�os�path�joinr	�USER_CONFDIRr�FileNotFoundError�OSErrorrarm)rrrv�target�es     rr6z,UserConfigProcessor._process_account_removed�s������{�{�6�"�"�=�g�k�k�*�&=�&=��"�4�(�(�	��F�����d�/��6�6��	��6�N�N�N�N�N�� �	�	�	��D�D��	�	�	��N�N�9�6�1�
�
�
�
�
�
�
�
�
�����	���s�)A:�:
B7�	B7�B2�2B7c���K�|j�d��}|j}|rt|��rt|��sdS	t	jtj�tj	|��tj�tj	|����dS#t$rYdSt$r(}t�
d|||��Yd}~dSd}~wwxYw)N�old_usernamez)Failed to rename user_config %s -> %s: %s)rVrirr
r��renamer�r�r	r�r�r�rarm)rrr��new_usernamer�s     rr3z#UserConfigProcessor._process_modify�s�����|�'�'��7�7���'���	�#�L�1�1�	�$�L�1�1�	�

�F�
	��I�����T�.��=�=�����T�.��=�=�
�
�
�
�
��!�	�	�	��D�D��	�	�	��N�N�;����	
�
�
�
�
�
�
�
�
�����	���s�A%B.�.
C,�;	C,�C'�'C,c��
K�dSrCr@rs  rr4z#UserConfigProcessor._process_create�rArc��
K�dSrEr@rs  rr5z+UserConfigProcessor._process_change_package�rArN)	rMrNrOr1rLr6r3r4r5r@rrr�r��sn������������������0���"�"�"�"�"rr�)r'�loggingr��abcrr�collectionsr�heapqrr�typingr� defence360agent.contracts.configr	�"defence360agent.contracts.messagesr
�!defence360agent.contracts.pluginsrr�defence360agent.utilsr
r�	getLoggerrarrRr�r@rr�<module>r�sq����������	�	�	�	�#�#�#�#�#�#�#�#�#�#�#�#�#�#�#�#�#�#�#�#�#�#�������1�1�1�1�1�1�:�:�:�:�:�:�J�J�J�J�J�J�J�J�=�=�=�=�=�=�=�=�	��	�	�	��;3�;3�;3�;3�;3�-�s�;3�;3�;3�|W3�W3�W3�W3�W3�+�S�W3�W3�W3�t1"�1"�1"�1"�1"�,�1"�1"�1"�1"�1"rdefence360agent/plugins/__pycache__/feature_flags.cpython-311.opt-1.pyc0000644000000000000000000003253500000000000022717 0ustar  �

��|x��R���dZddlZddlZddlZddlZddlZddlZddlm	Z	ddl
mZddlm
Z
mZmZmZmZmZmZddlmZmZddlmZmZeje��ZdZd	ed
edefd�Z e!hd
���Z"e!hd���Z#d	ed
e$de$fd�Z%e dd��Z&e dd��Z'e dd��Z(e%dd��Z)dZ*dedefd�Z+Gd�de��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_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)�IAIDTokenError�IndependentAgentIDAPI)�Scope�atomic_rewritez/api/sync/v1/feature-flags�name�default�returnc���tj�|��}|s|S	t|��S#t$r"t
�d|||��|cYSwxYw)u�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.
    z4feature-flags: %s=%r is not an int, using default %d)�os�environ�get�int�
ValueError�logger�warning)rr�raws   �Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/feature_flags.py�_env_intr+s|��
�*�.�.��
�
�C�����	��3�x�x����������B����		
�	
�	
��������s�4�)A �A >�1�on�yes�true>�0�no�off�falsec��tj�|��}|s|S|������}|t
vrdS|tvrdSt�d|||��|S)NTFz4feature-flags: %s=%r is not a bool, using default %s)	rrr�strip�lower�_TRUE_VALUES�
_FALSE_VALUESrr)rrr�
normalizeds    r�	_env_boolr,Es���
�*�.�.��
�
�C����������"�"�$�$�J��\�!�!��t��]�"�"��u�
�N�N�>����	����N�� I360_FEATURE_FLAGS_SYNC_INTERVALi�I360_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-c��eZdZejZd�Zd�Zdefd�Z	d�Z
defd�Ze
dejjdefd���Ze
ddd
���Zd	S)
�FeatureFlagsSyncc��~K�||_||_|�|�����|_dS�N)�_loop�_sink�create_task�
_sync_loop�_task)�self�loop�sinks   r�
create_sourcezFeatureFlagsSync.create_sourcegs7������
���
��%�%�d�o�o�&7�&7�8�8��
�
�
r-c��K�|j�?|j���	|j�d{V��dS#tj$rYdSwxYwdSr<)rA�cancel�asyncio�CancelledError�rBs r�shutdownzFeatureFlagsSync.shutdownlst�����:�!��J������
��j� � � � � � � � � ���)�
�
�
����
����	"�!s�
3�A�Arc�*�tt��Sr<)r
rrJs r�_local_checksumz FeatureFlagsSync._local_checksumts��0��<�<�<r-c��K�tjt���d{V��	t}	t	j��st}n't|����d{V����}n;#tj	$r�t$rt�dd���YnwxYwtj|���d{V����)NTzfeature flags sync failed��exc_info)
rH�sleep�_INITIAL_DELAYr7r
�
is_registered�_UNREGISTERED_DELAYr8�_do_syncrI�	Exceptionrr)rB�delays  rr@zFeatureFlagsSync._sync_loopws������m�N�+�+�+�+�+�+�+�+�+�	'�"�E�
K�,�:�<�<�?�/�E�E�'�d�m�m�o�o�(=�(=�(=�(=�(=�(=�>�>�E����)�
�
�
���
K�
K�
K����:�T��J�J�J�J�J�
K�����-��&�&�&�&�&�&�&�&�&�	's�AA.�.5B&�%B&c
��K�	tj���d{V��}n+#t$rt�d��YdSwxYwtj��}|�d|j���d{V��}tj
d|i�����}tj
dtj��}|�d��t"z}t$j�||d|d�d�	��}	|�d|j|���d{V��}n�#t$jj$ro}	d
|	jcxkrdkr+nn(t�d|	j||	j��n't�d|	j||	j��Yd}	~	dSd}	~	wt$jjt6f$r6}	t�d
|t9|	d|	����Yd}	~	dSd}	~	wt:$r!t�d|d���YdSwxYw	tj|��}
n0#tj$rt�d��YdSwxYw|
� dd��}|
� d��durt�!d��|S|
� d��}|
� d��pi}
|�#|�d|j"||
���d{V��|S)Nz*no IAID token, skipping feature flags syncr�checksum�I360_FEATURE_FLAGS_API_URL�/zapplication/json)zContent-TypezX-Auth�POST)�data�headers�methodi�iXz$feature flags sync HTTP %s on %s: %sz.feature flags sync connection failed on %s: %s�reasonz'feature flags sync request failed on %sTrOz&failed to parse feature flags responserW�changedFz'feature flags unchanged, skipping write�flags�params)#r
�	get_tokenrrrrH�get_event_loop�run_in_executorrM�json�dumps�encoder�getenvr�API_BASE_URL�rstrip�	_SYNC_URL�urllib�request�Request�_blocking_request�error�	HTTPError�coder`�URLError�TimeoutError�getattrrV�loads�JSONDecodeErrorr�debug�_write_flags)rB�tokenrCrY�payload�base_url�url�req�	resp_body�e�resultr4rbrcs              rrUzFeatureFlagsSync._do_sync�s�����	�/�9�;�;�;�;�;�;�;�;�E�E���	�	�	��N�N�G�H�H�H��1�1�	�����%�'�'���-�-�d�D�4H�I�I�I�I�I�I�I�I���*�j�(�3�4�4�;�;�=�=���9�9�4�;L�M�M���o�o�c�"�"�Y�.���n�$�$��� 2�����%�
�
��(	�"�2�2��d�,�c���������I�I���|�%�	�	�	��a�f�"�"�"�"�s�"�"�"�"�"����:��F���H�	�������:��F���H�	����1�1�1�1�1�������%�|�4�
	�
	�
	�

�N�N�@����8�Q�'�'�
�
�
�
�1�1�1�1�1������	�	�	��L�L�9���
�
�
�
�
�1�1�
	����	��Z�	�*�*�F�F���#�	�	�	��L�L�A�B�B�B��1�1�	�����z�z�'�1�-�-���:�:�i� � �E�)�)��L�L�B�C�C�C����
�
�7�#�#�����H�%�%�+������&�&�t�T�->��v�N�N�N�N�N�N�N�N�N��sL��$A�A�"D+�+H&�?A$F)�)H&�+G8�8*H&�%H&�*H?�?)I,�+I,r�c��tj�|t���5}|���cddd��S#1swxYwYdS)N)�timeout)rnro�urlopen�
_HTTP_TIMEOUT�read)r��resps  rrqz"FeatureFlagsSync._blocking_request�s���
�^�
#�
#�C��
#�
?�
?�	�4��9�9�;�;�	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	�	s�A�A�ANc��|pi}	t|t��r;d�|D��}d�|���D��}t||��}nt	|��}n>#t
$r1t�dt|��j	��YdSwxYwtt|����}	tj
tj�t ��d���t#t |d���t%|��}t#t&|d���t�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���
isinstance�str)�.0�ns  r�
<listcomp>z1FeatureFlagsSync._write_flags.<locals>.<listcomp>�s'��@�@�@�q�Z��3�-?�-?�@��@�@�@r-c��i|]<\}}t|t���t|t���/|d�|D����=S)c�<�g|]}t|t���|��Sr�r�)r��vs  rr�z<FeatureFlagsSync._write_flags.<locals>.<dictcomp>.<listcomp>�s'��A�A�A��j��C�.@�.@�A�1�A�A�Ar-)r�r��list)r�r�valss   r�
<dictcomp>z1FeatureFlagsSync._write_flags.<locals>.<dictcomp>�sa�����"��d�!�$��,�,��2<�D�$�1G�1G���A�A�d�A�A�A���r-z<feature flags sync: unexpected flags type %r, skipping writeNT)�exist_okF)�backupz%feature flags synced: %d flags activezfailed to write flags filerO)r�r��itemsrr	�	TypeErrorrr�type�__name__�lenrr�makedirs�path�dirnamerrrr�info�OSErrorrr)rbrc�names�cleanedr]�n_active�plains       rr{zFeatureFlagsSync._write_flags�s�����2��	��%��&�&�	
C�@�@�E�@�@�@����&,�l�l�n�n�����
0��w�?�?���;�E�B�B�����	�	�	��N�N�N��U���$�
�
�
�
�F�F�	�����0��7�7�8�8��
	F��K�����
�3�3�d�C�C�C�C�
�:�t�E�:�:�:�:�8��?�?�E��+�U�5�A�A�A�A��K�K�?��J�J�J�J�J���	F�	F�	F��L�L�5��L�E�E�E�E�E�E�	F���s%�AA&�&7B!� B!�BE�&E=�<E=r<)rN)r��
__module__�__qualname__r�AV�SCOPErErKr�rMr@rrU�staticmethodrnrorp�bytesrqr{r�r-rr:r:ds��������H�E�9�9�9�
���=��=�=�=�=�
'�
'�
'�Q��Q�Q�Q�Q�f��v�~�5��%�����\���%F�%F�%F�%F��\�%F�%F�%Fr-r:)-�__doc__rHrg�loggingr�urllib.errorrn�urllib.request� defence360agent.contracts.configr�!defence360agent.contracts.pluginsr�'defence360agent.internals.feature_flagsrrrrr	r
r�defence360agent.internals.iaidrr
�defence360agent.utilsrr�	getLoggerr�rrmr�rr�	frozensetr)r*�boolr,r7rRrTr6r�r8r:r�r-r�<module>r�s_����������������	�	�	�	���������1�1�1�1�1�1�;�;�;�;�;�;���������������������������8�7�7�7�7�7�7�7�	��	�8�	$�	$��(�	��3���������,�y�3�3�3�4�4���	�5�5�5�6�6�
��C��$��4�����$��<�d�C�C����9�2�>�>���h�?��D�D���I�C�T�J�J���
��c��c�����`F�`F�`F�`F�`F�}�`F�`F�`F�`F�`Fr-defence360agent/plugins/__pycache__/feature_flags.cpython-311.pyc0000644000000000000000000003253500000000000021760 0ustar  �

��|x��R���dZddlZddlZddlZddlZddlZddlZddlm	Z	ddl
mZddlm
Z
mZmZmZmZmZmZddlmZmZddlmZmZeje��ZdZd	ed
edefd�Z e!hd
���Z"e!hd���Z#d	ed
e$de$fd�Z%e dd��Z&e dd��Z'e dd��Z(e%dd��Z)dZ*dedefd�Z+Gd�de��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_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)�IAIDTokenError�IndependentAgentIDAPI)�Scope�atomic_rewritez/api/sync/v1/feature-flags�name�default�returnc���tj�|��}|s|S	t|��S#t$r"t
�d|||��|cYSwxYw)u�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.
    z4feature-flags: %s=%r is not an int, using default %d)�os�environ�get�int�
ValueError�logger�warning)rr�raws   �Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/feature_flags.py�_env_intr+s|��
�*�.�.��
�
�C�����	��3�x�x����������B����		
�	
�	
��������s�4�)A �A >�1�on�yes�true>�0�no�off�falsec��tj�|��}|s|S|������}|t
vrdS|tvrdSt�d|||��|S)NTFz4feature-flags: %s=%r is not a bool, using default %s)	rrr�strip�lower�_TRUE_VALUES�
_FALSE_VALUESrr)rrr�
normalizeds    r�	_env_boolr,Es���
�*�.�.��
�
�C����������"�"�$�$�J��\�!�!��t��]�"�"��u�
�N�N�>����	����N�� I360_FEATURE_FLAGS_SYNC_INTERVALi�I360_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-c��eZdZejZd�Zd�Zdefd�Z	d�Z
defd�Ze
dejjdefd���Ze
ddd
���Zd	S)
�FeatureFlagsSyncc��~K�||_||_|�|�����|_dS�N)�_loop�_sink�create_task�
_sync_loop�_task)�self�loop�sinks   r�
create_sourcezFeatureFlagsSync.create_sourcegs7������
���
��%�%�d�o�o�&7�&7�8�8��
�
�
r-c��K�|j�?|j���	|j�d{V��dS#tj$rYdSwxYwdSr<)rA�cancel�asyncio�CancelledError�rBs r�shutdownzFeatureFlagsSync.shutdownlst�����:�!��J������
��j� � � � � � � � � ���)�
�
�
����
����	"�!s�
3�A�Arc�*�tt��Sr<)r
rrJs r�_local_checksumz FeatureFlagsSync._local_checksumts��0��<�<�<r-c��K�tjt���d{V��	t}	t	j��st}n't|����d{V����}n;#tj	$r�t$rt�dd���YnwxYwtj|���d{V����)NTzfeature flags sync failed��exc_info)
rH�sleep�_INITIAL_DELAYr7r
�
is_registered�_UNREGISTERED_DELAYr8�_do_syncrI�	Exceptionrr)rB�delays  rr@zFeatureFlagsSync._sync_loopws������m�N�+�+�+�+�+�+�+�+�+�	'�"�E�
K�,�:�<�<�?�/�E�E�'�d�m�m�o�o�(=�(=�(=�(=�(=�(=�>�>�E����)�
�
�
���
K�
K�
K����:�T��J�J�J�J�J�
K�����-��&�&�&�&�&�&�&�&�&�	's�AA.�.5B&�%B&c
��K�	tj���d{V��}n+#t$rt�d��YdSwxYwtj��}|�d|j���d{V��}tj
d|i�����}tj
dtj��}|�d��t"z}t$j�||d|d�d�	��}	|�d|j|���d{V��}n�#t$jj$ro}	d
|	jcxkrdkr+nn(t�d|	j||	j��n't�d|	j||	j��Yd}	~	dSd}	~	wt$jjt6f$r6}	t�d
|t9|	d|	����Yd}	~	dSd}	~	wt:$r!t�d|d���YdSwxYw	tj|��}
n0#tj$rt�d��YdSwxYw|
� dd��}|
� d��durt�!d��|S|
� d��}|
� d��pi}
|�#|�d|j"||
���d{V��|S)Nz*no IAID token, skipping feature flags syncr�checksum�I360_FEATURE_FLAGS_API_URL�/zapplication/json)zContent-TypezX-Auth�POST)�data�headers�methodi�iXz$feature flags sync HTTP %s on %s: %sz.feature flags sync connection failed on %s: %s�reasonz'feature flags sync request failed on %sTrOz&failed to parse feature flags responserW�changedFz'feature flags unchanged, skipping write�flags�params)#r
�	get_tokenrrrrH�get_event_loop�run_in_executorrM�json�dumps�encoder�getenvr�API_BASE_URL�rstrip�	_SYNC_URL�urllib�request�Request�_blocking_request�error�	HTTPError�coder`�URLError�TimeoutError�getattrrV�loads�JSONDecodeErrorr�debug�_write_flags)rB�tokenrCrY�payload�base_url�url�req�	resp_body�e�resultr4rbrcs              rrUzFeatureFlagsSync._do_sync�s�����	�/�9�;�;�;�;�;�;�;�;�E�E���	�	�	��N�N�G�H�H�H��1�1�	�����%�'�'���-�-�d�D�4H�I�I�I�I�I�I�I�I���*�j�(�3�4�4�;�;�=�=���9�9�4�;L�M�M���o�o�c�"�"�Y�.���n�$�$��� 2�����%�
�
��(	�"�2�2��d�,�c���������I�I���|�%�	�	�	��a�f�"�"�"�"�s�"�"�"�"�"����:��F���H�	�������:��F���H�	����1�1�1�1�1�������%�|�4�
	�
	�
	�

�N�N�@����8�Q�'�'�
�
�
�
�1�1�1�1�1������	�	�	��L�L�9���
�
�
�
�
�1�1�
	����	��Z�	�*�*�F�F���#�	�	�	��L�L�A�B�B�B��1�1�	�����z�z�'�1�-�-���:�:�i� � �E�)�)��L�L�B�C�C�C����
�
�7�#�#�����H�%�%�+������&�&�t�T�->��v�N�N�N�N�N�N�N�N�N��sL��$A�A�"D+�+H&�?A$F)�)H&�+G8�8*H&�%H&�*H?�?)I,�+I,r�c��tj�|t���5}|���cddd��S#1swxYwYdS)N)�timeout)rnro�urlopen�
_HTTP_TIMEOUT�read)r��resps  rrqz"FeatureFlagsSync._blocking_request�s���
�^�
#�
#�C��
#�
?�
?�	�4��9�9�;�;�	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	�	s�A�A�ANc��|pi}	t|t��r;d�|D��}d�|���D��}t||��}nt	|��}n>#t
$r1t�dt|��j	��YdSwxYwtt|����}	tj
tj�t ��d���t#t |d���t%|��}t#t&|d���t�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���
isinstance�str)�.0�ns  r�
<listcomp>z1FeatureFlagsSync._write_flags.<locals>.<listcomp>�s'��@�@�@�q�Z��3�-?�-?�@��@�@�@r-c��i|]<\}}t|t���t|t���/|d�|D����=S)c�<�g|]}t|t���|��Sr�r�)r��vs  rr�z<FeatureFlagsSync._write_flags.<locals>.<dictcomp>.<listcomp>�s'��A�A�A��j��C�.@�.@�A�1�A�A�Ar-)r�r��list)r�r�valss   r�
<dictcomp>z1FeatureFlagsSync._write_flags.<locals>.<dictcomp>�sa�����"��d�!�$��,�,��2<�D�$�1G�1G���A�A�d�A�A�A���r-z<feature flags sync: unexpected flags type %r, skipping writeNT)�exist_okF)�backupz%feature flags synced: %d flags activezfailed to write flags filerO)r�r��itemsrr	�	TypeErrorrr�type�__name__�lenrr�makedirs�path�dirnamerrrr�info�OSErrorrr)rbrc�names�cleanedr]�n_active�plains       rr{zFeatureFlagsSync._write_flags�s�����2��	��%��&�&�	
C�@�@�E�@�@�@����&,�l�l�n�n�����
0��w�?�?���;�E�B�B�����	�	�	��N�N�N��U���$�
�
�
�
�F�F�	�����0��7�7�8�8��
	F��K�����
�3�3�d�C�C�C�C�
�:�t�E�:�:�:�:�8��?�?�E��+�U�5�A�A�A�A��K�K�?��J�J�J�J�J���	F�	F�	F��L�L�5��L�E�E�E�E�E�E�	F���s%�AA&�&7B!� B!�BE�&E=�<E=r<)rN)r��
__module__�__qualname__r�AV�SCOPErErKr�rMr@rrU�staticmethodrnrorp�bytesrqr{r�r-rr:r:ds��������H�E�9�9�9�
���=��=�=�=�=�
'�
'�
'�Q��Q�Q�Q�Q�f��v�~�5��%�����\���%F�%F�%F�%F��\�%F�%F�%Fr-r:)-�__doc__rHrg�loggingr�urllib.errorrn�urllib.request� defence360agent.contracts.configr�!defence360agent.contracts.pluginsr�'defence360agent.internals.feature_flagsrrrrr	r
r�defence360agent.internals.iaidrr
�defence360agent.utilsrr�	getLoggerr�rrmr�rr�	frozensetr)r*�boolr,r7rRrTr6r�r8r:r�r-r�<module>r�s_����������������	�	�	�	���������1�1�1�1�1�1�;�;�;�;�;�;���������������������������8�7�7�7�7�7�7�7�	��	�8�	$�	$��(�	��3���������,�y�3�3�3�4�4���	�5�5�5�6�6�
��C��$��4�����$��<�d�C�C����9�2�>�>���h�?��D�D���I�C�T�J�J���
��c��c�����`F�`F�`F�`F�`F�}�`F�`F�`F�`F�`Fr-defence360agent/plugins/__pycache__/files_recurring_update.cpython-311.opt-1.pyc0000644000000000000000000000532500000000000024631 0ustar  �

j����b����~�ddlZddlmZddlmZmZddlmZddlm	Z	ej
e��ZGd�de��Z
dS)�N)�files)�config�messages)�
MessageSource)�recurring_checkc�t�eZdZdejdeddfd�Zd�Zd�Ze	e
jj��d���Z
dS)	�FilesRecurringUpdateTask�index�
is_updated�returnNc��K�|rGtj�|j|��}|j�|���d{V��dSdS�N)r�MessageType�FilesUpdated�type�_sink�process_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��*�7�7��
�E�J�J�G��*�,�,�W�5�5�5�5�5�5�5�5�5�5�5�	6�	6�c��
K�||_||_|�|�����|_t
j���D]'}t
j�||j	���(dSr)
�_loopr�create_task�_update_task�_taskr�Index�types�add_hookr)r�loop�sink�type_s    r�
create_sourcez&FilesRecurringUpdateTask.create_sourcesz������
���
��%�%�d�&7�&7�&9�&9�:�:��
��[�&�&�(�(�	?�	?�E��K� � ���(=�>�>�>�>�	?�	?rc��VK�|j���|j�d{V��dSr)r�cancel�rs r�shutdownz!FilesRecurringUpdateTask.shutdowns:�����
�������j���������rc��<K�tj���d{V��dSr)r�update_and_log_errorr's rrz%FilesRecurringUpdateTask._update_task s-�����(�*�*�*�*�*�*�*�*�*�*�*r)�__name__�
__module__�__qualname__rr�boolrr$r(rr�FilesUpdate�PERIODr�rrr	r	s�������6��[�6�.2�6�	
�6�6�6�6�?�?�?����
�_�V�'�.�/�/�+�+�0�/�+�+�+rr	)�logging�defence360agentr�defence360agent.contractsrr�!defence360agent.contracts.pluginsr�defence360agent.utilsr�	getLoggerr+�loggerr	r1rr�<module>r9s�������!�!�!�!�!�!�6�6�6�6�6�6�6�6�;�;�;�;�;�;�1�1�1�1�1�1�	��	�8�	$�	$��+�+�+�+�+�}�+�+�+�+�+rdefence360agent/plugins/__pycache__/files_recurring_update.cpython-311.pyc0000644000000000000000000000532500000000000023672 0ustar  �

j����b����~�ddlZddlmZddlmZmZddlmZddlm	Z	ej
e��ZGd�de��Z
dS)�N)�files)�config�messages)�
MessageSource)�recurring_checkc�t�eZdZdejdeddfd�Zd�Zd�Ze	e
jj��d���Z
dS)	�FilesRecurringUpdateTask�index�
is_updated�returnNc��K�|rGtj�|j|��}|j�|���d{V��dSdS�N)r�MessageType�FilesUpdated�type�_sink�process_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��*�7�7��
�E�J�J�G��*�,�,�W�5�5�5�5�5�5�5�5�5�5�5�	6�	6�c��
K�||_||_|�|�����|_t
j���D]'}t
j�||j	���(dSr)
�_loopr�create_task�_update_task�_taskr�Index�types�add_hookr)r�loop�sink�type_s    r�
create_sourcez&FilesRecurringUpdateTask.create_sourcesz������
���
��%�%�d�&7�&7�&9�&9�:�:��
��[�&�&�(�(�	?�	?�E��K� � ���(=�>�>�>�>�	?�	?rc��VK�|j���|j�d{V��dSr)r�cancel�rs r�shutdownz!FilesRecurringUpdateTask.shutdowns:�����
�������j���������rc��<K�tj���d{V��dSr)r�update_and_log_errorr's rrz%FilesRecurringUpdateTask._update_task s-�����(�*�*�*�*�*�*�*�*�*�*�*r)�__name__�
__module__�__qualname__rr�boolrr$r(rr�FilesUpdate�PERIODr�rrr	r	s�������6��[�6�.2�6�	
�6�6�6�6�?�?�?����
�_�V�'�.�/�/�+�+�0�/�+�+�+rr	)�logging�defence360agentr�defence360agent.contractsrr�!defence360agent.contracts.pluginsr�defence360agent.utilsr�	getLoggerr+�loggerr	r1rr�<module>r9s�������!�!�!�!�!�!�6�6�6�6�6�6�6�6�;�;�;�;�;�;�1�1�1�1�1�1�	��	�8�	$�	$��+�+�+�+�+�}�+�+�+�+�+rdefence360agent/plugins/__pycache__/icontact_sender.cpython-311.opt-1.pyc0000644000000000000000000001577500000000000023263 0ustar  �

���w����"�ddlZddlZddlZddlmZddlmZddlmZddl	m
Z
ddlmZm
Z
ddlmZddlmZmZdd	lmZdd
lmZddlmZddlmZdd
lmZddlmZmZm Z m!Z!m"Z"ddl#m$Z$ej%e&��Z'd�Z(Gd�dee��Z)dS)�N)�Path)�IAIDTokenError)�APIError)�	EventsAPI)�Core�IContactMessageType)�MessageType)�MessageSink�
MessageSource)�TheSink)�IContactThrottle)�cPanel)�Plesk)�HostingPanel)�	await_for�create_task_and_log_exceptions�recurring_check�retry_on�Scope)�DAYc��bK�t�d||��td���dS)Nz[Can't get recommendations for the dashboard due to iaid token error, reason: %s. Attempt %s�d��seconds)�logger�warningr)�e�is  �\/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/icontact_sender.py�async_log_on_errorr #s@����
�N�N�	3�	�	�	����c�������c���eZdZejjZejZ	�fd�Z
d�Zdd�d�Zde
fd�Zd�Zeeed	�
��dde�
��eeedde�
��defd�����Zee��d���Z�xZS)�IContactSenderc���t��j|i|��g|_ttj��dz|_dS)N�icontact_generic_notifications)�super�__init__�_tasksrr�TMPDIR�_notification_flag_path)�self�args�kwargs�	__class__s   �rr'zIContactSender.__init__1sG��������$�)�&�)�)�)��������� @�@�	
�$�$�$r!c��
K�dS�N�)r+�loops  r�create_sinkzIContactSender.create_sink8s�����r!N��userc��|K�|�dStj|||���sdS|j�tj||����d{V��}|rmtj||���tj|ttj
����|���}|j�|���d{V��dSdS)Nr4)�message_type�paramsr5)r7�	timestamp�
template_args)
r
�may_be_notified�_panel�notifyr�GENERIC�refreshr	�IContactSent�int�time�_sink�process_message)r+r7r8�period_limitr5r:�sent_messages       r�_send_icontact_messagez%IContactSender._send_icontact_message;s�������F��/����
�
�
�	�

�F�"�k�0�0�,�4���1�
�
�
�
�
�
�
�
�
�
�	;��$�\��=�=�=�=�&�3�)��d�i�k�k�*�*�+����L�
�*�,�,�\�:�:�:�:�:�:�:�:�:�:�:�	;�	;r!�sinkc��K�||_t��|_|jjtjt
jfvrt
||j��g|_dSdSr0)	rCrr<�NAMErrr�generic_notificationsr()r+r2rHs   r�
create_sourcezIContactSender.create_sourceYs[������
�"�n�n����;����U�Z�8�8�8�.��$�4����D�K�K�K�9�8r!c��~K�|jD]}|����tj|jddi��d{V��dS)N�return_exceptionsT)r(�cancel�asyncio�gather)r+�tasks  r�shutdownzIContactSender.shutdowncsU�����K�	�	�D��K�K�M�M�M�M��n�d�k�B�T�B�B�B�B�B�B�B�B�B�B�Br!�
r�T)�on_error�	max_tries�silent�log�returnc�� K�g}|j���r;|j���jtztj��kr5t
j���d{V��}|j�dd���|S)Ni�T)�mode�exist_ok)	r*�exists�stat�st_mtimerrBr�notification�touch)r+�
notificationss  r�get_notificationsz IContactSender.get_notificationshs������
��,�3�3�5�5�	J��,�1�1�3�3�<�s�B��i�k�k���#,�"8�":�":�:�:�:�:�:�:�M��(�.�.�E�D�.�I�I�I��r!c	��:K�|����d{V��x}rzt�dt|����|D]Q}|�|d|d|dd�|d|�d������d{V���PdSdS)	Nz)Sending %s generic icontact notifications�type�notification_subject�notification_body_html)�subject�	body_html�notification_period_limit�notification_user)r7r8rEr5)rdr�info�lenrG�get)r+rcras   rrKz$IContactSender.generic_notifications�s�����"&�"8�"8�":�":�:�:�:�:�:�:�:�=�
	��K�K�;�S��=O�=O�
�
�
�!.�	
�	
���1�1�!-�f�!5�#/�0F�#G�%1�2J�%K���".�.I�!J�%�)�)�*=�>�>�2�����������
	�
	�	
�	
r!)�__name__�
__module__�__qualname__r
�ProcessingOrder�
ICONTACT_SENT�PROCESSING_ORDERr�AV_IM360�SCOPEr'r3rGrrLrSrrrrrr �listrdrrrK�
__classcell__)r.s@rr#r#-sQ�������"�2�@���N�E�
�
�
�
�
�
�
�
��
;�;�;�;�;�<�g�����C�C�C�
�X����2�&�&�&��������X��#�������
��
�
�
�����
��_�S�����������r!r#)*rP�loggingrB�pathlibr�defence360agent.internals.iaidr�defence360agent.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_panelr�defence360agent.utilsrrrrr�defence360agent.utils.commonr�	getLoggerrprr r#r1r!r�<module>r�s���������������������9�9�9�9�9�9�/�/�/�/�/�/�7�7�7�7�7�7���������;�:�:�:�:�:���������7�6�6�6�6�6�;�;�;�;�;�;�7�7�7�7�7�7�5�5�5�5�5�5�D�D�D�D�D�D���������������-�,�,�,�,�,�	��	�8�	$�	$�����d�d�d�d�d�[�-�d�d�d�d�dr!defence360agent/plugins/__pycache__/icontact_sender.cpython-311.pyc0000644000000000000000000001577500000000000022324 0ustar  �

���w����"�ddlZddlZddlZddlmZddlmZddlmZddl	m
Z
ddlmZm
Z
ddlmZddlmZmZdd	lmZdd
lmZddlmZddlmZdd
lmZddlmZmZm Z m!Z!m"Z"ddl#m$Z$ej%e&��Z'd�Z(Gd�dee��Z)dS)�N)�Path)�IAIDTokenError)�APIError)�	EventsAPI)�Core�IContactMessageType)�MessageType)�MessageSink�
MessageSource)�TheSink)�IContactThrottle)�cPanel)�Plesk)�HostingPanel)�	await_for�create_task_and_log_exceptions�recurring_check�retry_on�Scope)�DAYc��bK�t�d||��td���dS)Nz[Can't get recommendations for the dashboard due to iaid token error, reason: %s. Attempt %s�d��seconds)�logger�warningr)�e�is  �\/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/icontact_sender.py�async_log_on_errorr #s@����
�N�N�	3�	�	�	����c�������c���eZdZejjZejZ	�fd�Z
d�Zdd�d�Zde
fd�Zd�Zeeed	�
��dde�
��eeedde�
��defd�����Zee��d���Z�xZS)�IContactSenderc���t��j|i|��g|_ttj��dz|_dS)N�icontact_generic_notifications)�super�__init__�_tasksrr�TMPDIR�_notification_flag_path)�self�args�kwargs�	__class__s   �rr'zIContactSender.__init__1sG��������$�)�&�)�)�)��������� @�@�	
�$�$�$r!c��
K�dS�N�)r+�loops  r�create_sinkzIContactSender.create_sink8s�����r!N��userc��|K�|�dStj|||���sdS|j�tj||����d{V��}|rmtj||���tj|ttj
����|���}|j�|���d{V��dSdS)Nr4)�message_type�paramsr5)r7�	timestamp�
template_args)
r
�may_be_notified�_panel�notifyr�GENERIC�refreshr	�IContactSent�int�time�_sink�process_message)r+r7r8�period_limitr5r:�sent_messages       r�_send_icontact_messagez%IContactSender._send_icontact_message;s�������F��/����
�
�
�	�

�F�"�k�0�0�,�4���1�
�
�
�
�
�
�
�
�
�
�	;��$�\��=�=�=�=�&�3�)��d�i�k�k�*�*�+����L�
�*�,�,�\�:�:�:�:�:�:�:�:�:�:�:�	;�	;r!�sinkc��K�||_t��|_|jjtjt
jfvrt
||j��g|_dSdSr0)	rCrr<�NAMErrr�generic_notificationsr()r+r2rHs   r�
create_sourcezIContactSender.create_sourceYs[������
�"�n�n����;����U�Z�8�8�8�.��$�4����D�K�K�K�9�8r!c��~K�|jD]}|����tj|jddi��d{V��dS)N�return_exceptionsT)r(�cancel�asyncio�gather)r+�tasks  r�shutdownzIContactSender.shutdowncsU�����K�	�	�D��K�K�M�M�M�M��n�d�k�B�T�B�B�B�B�B�B�B�B�B�B�Br!�
r�T)�on_error�	max_tries�silent�log�returnc�� K�g}|j���r;|j���jtztj��kr5t
j���d{V��}|j�dd���|S)Ni�T)�mode�exist_ok)	r*�exists�stat�st_mtimerrBr�notification�touch)r+�
notificationss  r�get_notificationsz IContactSender.get_notificationshs������
��,�3�3�5�5�	J��,�1�1�3�3�<�s�B��i�k�k���#,�"8�":�":�:�:�:�:�:�:�M��(�.�.�E�D�.�I�I�I��r!c	��:K�|����d{V��x}rzt�dt|����|D]Q}|�|d|d|dd�|d|�d������d{V���PdSdS)	Nz)Sending %s generic icontact notifications�type�notification_subject�notification_body_html)�subject�	body_html�notification_period_limit�notification_user)r7r8rEr5)rdr�info�lenrG�get)r+rcras   rrKz$IContactSender.generic_notifications�s�����"&�"8�"8�":�":�:�:�:�:�:�:�:�=�
	��K�K�;�S��=O�=O�
�
�
�!.�	
�	
���1�1�!-�f�!5�#/�0F�#G�%1�2J�%K���".�.I�!J�%�)�)�*=�>�>�2�����������
	�
	�	
�	
r!)�__name__�
__module__�__qualname__r
�ProcessingOrder�
ICONTACT_SENT�PROCESSING_ORDERr�AV_IM360�SCOPEr'r3rGrrLrSrrrrrr �listrdrrrK�
__classcell__)r.s@rr#r#-sQ�������"�2�@���N�E�
�
�
�
�
�
�
�
��
;�;�;�;�;�<�g�����C�C�C�
�X����2�&�&�&��������X��#�������
��
�
�
�����
��_�S�����������r!r#)*rP�loggingrB�pathlibr�defence360agent.internals.iaidr�defence360agent.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_panelr�defence360agent.utilsrrrrr�defence360agent.utils.commonr�	getLoggerrprr r#r1r!r�<module>r�s���������������������9�9�9�9�9�9�/�/�/�/�/�/�7�7�7�7�7�7���������;�:�:�:�:�:���������7�6�6�6�6�6�;�;�;�;�;�;�7�7�7�7�7�7�5�5�5�5�5�5�D�D�D�D�D�D���������������-�,�,�,�,�,�	��	�8�	$�	$�����d�d�d�d�d�[�-�d�d�d�d�dr!defence360agent/plugins/__pycache__/idle_time_out.cpython-311.opt-1.pyc0000644000000000000000000000501600000000000022724 0ustar  �

���(GP���|�ddlmZddlmZddlmZddlmZddlm	Z	m
Z
mZee��Z
Gd�de��ZdS)	�)�	getLogger)�
inactivity)�	SimpleRpc)�MessageSink)�clip�fail_agent_service�recurring_checkc� �eZdZd�Zd�Zd�ZdS)�IdleTimeOutCheckc��4K�||_tjr{tj���|�tttj	dzdd������|j
������|_dSd|_dS)N���<)�low�high)�period)�_loopr�SOCKET_ACTIVATIONr�track�reset_timer�create_taskr	r�INACTIVITY_TIMEOUT�_check_timeout�_task)�self�loops  �Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/idle_time_out.py�create_sinkzIdleTimeOutCheck.create_sinks�������
��&�	���(�(�*�*�*��)�)�����!�4��9�q�r�������
�'�������D�J�J�J��D�J�J�J�c��hK�|jr(|j���|j�d{V��dSdS)N)r�cancel�rs r�shutdownzIdleTimeOutCheck.shutdownsN�����:�	��J�������*����������	�	rc���K�t�dtj��tj���r*t�d��t
��dSdS)NzPeriodical check %s z Shutting down due to inactivity.)�logger�inforr�
is_timeout�warningrr"s rrzIdleTimeOutCheck._check_timeout"sd�������*�J�,<�=�=�=���&�&�(�(�	!��N�N�=�>�>�>�� � � � � �	!�	!rN)�__name__�
__module__�__qualname__rr#r�rrrrsA��������� ���!�!�!�!�!rrN)�loggingr�defence360agent.apir� defence360agent.contracts.configr�!defence360agent.contracts.pluginsr�defence360agent.utilsrrr	r)r%rr,rr�<module>r2s���������*�*�*�*�*�*�6�6�6�6�6�6�9�9�9�9�9�9�K�K�K�K�K�K�K�K�K�K�	��8�	�	��!�!�!�!�!�{�!�!�!�!�!rdefence360agent/plugins/__pycache__/idle_time_out.cpython-311.pyc0000644000000000000000000000501600000000000021765 0ustar  �

���(GP���|�ddlmZddlmZddlmZddlmZddlm	Z	m
Z
mZee��Z
Gd�de��ZdS)	�)�	getLogger)�
inactivity)�	SimpleRpc)�MessageSink)�clip�fail_agent_service�recurring_checkc� �eZdZd�Zd�Zd�ZdS)�IdleTimeOutCheckc��4K�||_tjr{tj���|�tttj	dzdd������|j
������|_dSd|_dS)N���<)�low�high)�period)�_loopr�SOCKET_ACTIVATIONr�track�reset_timer�create_taskr	r�INACTIVITY_TIMEOUT�_check_timeout�_task)�self�loops  �Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/idle_time_out.py�create_sinkzIdleTimeOutCheck.create_sinks�������
��&�	���(�(�*�*�*��)�)�����!�4��9�q�r�������
�'�������D�J�J�J��D�J�J�J�c��hK�|jr(|j���|j�d{V��dSdS)N)r�cancel�rs r�shutdownzIdleTimeOutCheck.shutdownsN�����:�	��J�������*����������	�	rc���K�t�dtj��tj���r*t�d��t
��dSdS)NzPeriodical check %s z Shutting down due to inactivity.)�logger�inforr�
is_timeout�warningrr"s rrzIdleTimeOutCheck._check_timeout"sd�������*�J�,<�=�=�=���&�&�(�(�	!��N�N�=�>�>�>�� � � � � �	!�	!rN)�__name__�
__module__�__qualname__rr#r�rrrrsA��������� ���!�!�!�!�!rrN)�loggingr�defence360agent.apir� defence360agent.contracts.configr�!defence360agent.contracts.pluginsr�defence360agent.utilsrrr	r)r%rr,rr�<module>r2s���������*�*�*�*�*�*�6�6�6�6�6�6�9�9�9�9�9�9�K�K�K�K�K�K�K�K�K�K�	��8�	�	��!�!�!�!�!�{�!�!�!�!�!rdefence360agent/plugins/__pycache__/lve_utils_install.cpython-311.opt-1.pyc0000644000000000000000000000535000000000000023637 0ustar  �

]�=d��}��R�ddlmZddlmZmZmZddlmZmZGd�de��Z	dS)�)�MessageSink)�check_run_outside_sandbox�recurring_check�RecurringCheckStop)�
is_lve_active�
has_lvectlc�0�eZdZdZdd�d�Zd�Zd�Zd�ZdS)	�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|_dS�N)�
_check_period�_task)�selfrs  �^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/lve_utils_install.py�__init__zLveUtilsAutoInstaller.__init__s��)�����
�
�
�c��K�||_|j�t|j��|j������|_dSr
)�_loop�create_taskrr�_install_lve_utils_if_neededr)r�loops  r�create_sinkz!LveUtilsAutoInstaller.create_sinksY������
��Z�+�+�
�/�O�D�.�/�/��1�
�
�
�
�
�
��
�
�
rc��vK�|j�/|j���|j�d{V��d|_dSdSr
)r�cancel�rs r�shutdownzLveUtilsAutoInstaller.shutdown$sO�����:�!��J�������*���������D�J�J�J�"�!rc��K�t��st���t��stgd����d{V��dSdS)N)�yumz-y�installz	lve-utils)rrrrrs rrz2LveUtilsAutoInstaller._install_lve_utils_if_needed*sy�������	'�$�&�&�&��|�|�	�,�5�5�5���
�
�
�
�
�
�
�
�
�	�	rN)�__name__�
__module__�__qualname__�__doc__rrrr�rrr
r

si��������(,������
�
�
����
�
�
�
�
rr
N)
�!defence360agent.contracts.pluginsr�defence360agent.utilsrrr�%defence360agent.utils.resource_limitsrrr
r%rr�<module>r)s���9�9�9�9�9�9�����������
L�K�K�K�K�K�K�K�*�*�*�*�*�K�*�*�*�*�*rdefence360agent/plugins/__pycache__/lve_utils_install.cpython-311.pyc0000644000000000000000000000535000000000000022700 0ustar  �

]�=d��}��R�ddlmZddlmZmZmZddlmZmZGd�de��Z	dS)�)�MessageSink)�check_run_outside_sandbox�recurring_check�RecurringCheckStop)�
is_lve_active�
has_lvectlc�0�eZdZdZdd�d�Zd�Zd�Zd�ZdS)	�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|_dS�N)�
_check_period�_task)�selfrs  �^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/lve_utils_install.py�__init__zLveUtilsAutoInstaller.__init__s��)�����
�
�
�c��K�||_|j�t|j��|j������|_dSr
)�_loop�create_taskrr�_install_lve_utils_if_neededr)r�loops  r�create_sinkz!LveUtilsAutoInstaller.create_sinksY������
��Z�+�+�
�/�O�D�.�/�/��1�
�
�
�
�
�
��
�
�
rc��vK�|j�/|j���|j�d{V��d|_dSdSr
)r�cancel�rs r�shutdownzLveUtilsAutoInstaller.shutdown$sO�����:�!��J�������*���������D�J�J�J�"�!rc��K�t��st���t��stgd����d{V��dSdS)N)�yumz-y�installz	lve-utils)rrrrrs rrz2LveUtilsAutoInstaller._install_lve_utils_if_needed*sy�������	'�$�&�&�&��|�|�	�,�5�5�5���
�
�
�
�
�
�
�
�
�	�	rN)�__name__�
__module__�__qualname__�__doc__rrrr�rrr
r

si��������(,������
�
�
����
�
�
�
�
rr
N)
�!defence360agent.contracts.pluginsr�defence360agent.utilsrrr�%defence360agent.utils.resource_limitsrrr
r%rr�<module>r)s���9�9�9�9�9�9�����������
L�K�K�K�K�K�K�K�*�*�*�*�*�K�*�*�*�*�*rdefence360agent/plugins/__pycache__/myimunify.cpython-311.opt-1.pyc0000644000000000000000000000665500000000000022142 0ustar  �

C|��Rez���ddlZddlmZddlmZddlmZmZmZddl	m
Z
ddlmZddl
mZmZeje��ZGd�d	ee��ZdS)
�N)�MyImunifyConfig)�MessageType)�MessageSink�
MessageSource�expect)�update_users_protection)�
hosting_panel)�
load_state�
save_statec�n�eZdZd�Zd�Zd�Zd�Zd�Zee	j
��de	j
fd���ZdS)	�MyImunifyPluginc��td���d��ptj|_d|_d|_dS�Nr
�myimunify_enabled)r
�getr�ENABLED�_previous_myimunify_status�_loop�_sink��selfs �V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/myimunify.py�__init__zMyImunifyPlugin.__init__sD���(�)�)�-�-�.A�B�B�
'��&�	
�'���
���
�
�
�c��8K�tdd|ji��dSr)rrrs r�shutdownzMyImunifyPlugin.shutdowns0������
 �$�"A�B�	
�	
�	
�	
�	
rc��
K�dS�N�)r�loops  r�create_sinkzMyImunifyPlugin.create_sink s�����rc��&K�||_||_dSr)rr)rr �sinks   r�
create_sourcezMyImunifyPlugin.create_source#s������
���
�
�
rc��K�tj������d{V��}t|j|dd����d{V��dS)NFT)�force_config_update)r	�HostingPanel�	get_usersrr)r�existing_userss  r�_update_myimunify_usersz'MyImunifyPlugin._update_myimunify_users's|����,�9�;�;�E�E�G�G�G�G�G�G�G�G��%��J���4�
�
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
r�messagec���K�tj}|j}||_|r|s|����d{V��||kr/t	j���|����d{V��dSdS)N)r)rrrr*r	r'�switch_ui_config)rr+r�previous_statuss    r�on_config_updatez MyImunifyPlugin.on_config_update-s�����+�3���9��+<��'��	1�_�	1��.�.�0�0�0�0�0�0�0�0�0���/�/��,�.�.�?�?�"3�@���
�
�
�
�
�
�
�
�
�0�/rN)�__name__�
__module__�__qualname__rrr!r$r*rr�ConfigUpdater/rrrr
r
s����������
�
�
�
�
�
����
�
�
��V�K�$�%�%��k�.F����&�%���rr
)�logging� defence360agent.contracts.configr�"defence360agent.contracts.messagesr�!defence360agent.contracts.pluginsrrr�defence360agent.myimunify.modelr�defence360agent.subsys.panelsr	�'defence360agent.subsys.persistent_stater
r�	getLoggerr0�loggerr
rrr�<module>r=s�������<�<�<�<�<�<�:�:�:�:�:�:�����������
D�C�C�C�C�C�7�7�7�7�7�7�J�J�J�J�J�J�J�J�	��	�8�	$�	$��)�)�)�)�)�k�=�)�)�)�)�)rdefence360agent/plugins/__pycache__/myimunify.cpython-311.pyc0000644000000000000000000000665500000000000021203 0ustar  �

C|��Rez���ddlZddlmZddlmZddlmZmZmZddl	m
Z
ddlmZddl
mZmZeje��ZGd�d	ee��ZdS)
�N)�MyImunifyConfig)�MessageType)�MessageSink�
MessageSource�expect)�update_users_protection)�
hosting_panel)�
load_state�
save_statec�n�eZdZd�Zd�Zd�Zd�Zd�Zee	j
��de	j
fd���ZdS)	�MyImunifyPluginc��td���d��ptj|_d|_d|_dS�Nr
�myimunify_enabled)r
�getr�ENABLED�_previous_myimunify_status�_loop�_sink��selfs �V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/myimunify.py�__init__zMyImunifyPlugin.__init__sD���(�)�)�-�-�.A�B�B�
'��&�	
�'���
���
�
�
�c��8K�tdd|ji��dSr)rrrs r�shutdownzMyImunifyPlugin.shutdowns0������
 �$�"A�B�	
�	
�	
�	
�	
rc��
K�dS�N�)r�loops  r�create_sinkzMyImunifyPlugin.create_sink s�����rc��&K�||_||_dSr)rr)rr �sinks   r�
create_sourcezMyImunifyPlugin.create_source#s������
���
�
�
rc��K�tj������d{V��}t|j|dd����d{V��dS)NFT)�force_config_update)r	�HostingPanel�	get_usersrr)r�existing_userss  r�_update_myimunify_usersz'MyImunifyPlugin._update_myimunify_users's|����,�9�;�;�E�E�G�G�G�G�G�G�G�G��%��J���4�
�
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
r�messagec���K�tj}|j}||_|r|s|����d{V��||kr/t	j���|����d{V��dSdS)N)r)rrrr*r	r'�switch_ui_config)rr+r�previous_statuss    r�on_config_updatez MyImunifyPlugin.on_config_update-s�����+�3���9��+<��'��	1�_�	1��.�.�0�0�0�0�0�0�0�0�0���/�/��,�.�.�?�?�"3�@���
�
�
�
�
�
�
�
�
�0�/rN)�__name__�
__module__�__qualname__rrr!r$r*rr�ConfigUpdater/rrrr
r
s����������
�
�
�
�
�
����
�
�
��V�K�$�%�%��k�.F����&�%���rr
)�logging� defence360agent.contracts.configr�"defence360agent.contracts.messagesr�!defence360agent.contracts.pluginsrrr�defence360agent.myimunify.modelr�defence360agent.subsys.panelsr	�'defence360agent.subsys.persistent_stater
r�	getLoggerr0�loggerr
rrr�<module>r=s�������<�<�<�<�<�<�:�:�:�:�:�:�����������
D�C�C�C�C�C�7�7�7�7�7�7�J�J�J�J�J�J�J�J�	��	�8�	$�	$��)�)�)�)�)�k�=�)�)�)�)�)rdefence360agent/plugins/__pycache__/ping.cpython-311.opt-1.pyc0000644000000000000000000000277100000000000021044 0ustar  �

�s�����D�ddlmZddlmZmZmZGd�dee��ZdS)�)�MessageType)�MessageSink�
MessageSource�expectc�X�eZdZd�Zd�Zeejej��d���Z	dS)�SendPingc��K�||_dS�N)�_loop)�self�loops  �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������
���
�
�
rc��lK�|j�tj�����d{V��dSr
)r�process_messager�Ping)r�_s  r�	send_pingzSendPing.send_pings=�����j�(�(��)9�);�);�<�<�<�<�<�<�<�<�<�<�<rN)
�__name__�
__module__�__qualname__rrrr�ServerConnected�ServerReconnectedr�rrrr	sa�������������V�K�'��)F�G�G�=�=�H�G�=�=�=rrN)�"defence360agent.contracts.messagesr�!defence360agent.contracts.pluginsrrrrrrr�<module>r"s��:�:�:�:�:�:�����������
=�
=�
=�
=�
=�}�k�
=�
=�
=�
=�
=rdefence360agent/plugins/__pycache__/ping.cpython-311.pyc0000644000000000000000000000277100000000000020105 0ustar  �

�s�����D�ddlmZddlmZmZmZGd�dee��ZdS)�)�MessageType)�MessageSink�
MessageSource�expectc�X�eZdZd�Zd�Zeejej��d���Z	dS)�SendPingc��K�||_dS�N)�_loop)�self�loops  �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������
���
�
�
rc��lK�|j�tj�����d{V��dSr
)r�process_messager�Ping)r�_s  r�	send_pingzSendPing.send_pings=�����j�(�(��)9�);�);�<�<�<�<�<�<�<�<�<�<�<rN)
�__name__�
__module__�__qualname__rrrr�ServerConnected�ServerReconnectedr�rrrr	sa�������������V�K�'��)F�G�G�=�=�H�G�=�=�=rrN)�"defence360agent.contracts.messagesr�!defence360agent.contracts.pluginsrrrrrrr�<module>r"s��:�:�:�:�:�:�����������
=�
=�
=�
=�
=�}�k�
=�
=�
=�
=�
=rdefence360agent/plugins/__pycache__/send_domain_list.cpython-311.opt-1.pyc0000644000000000000000000001176200000000000023422 0ustar  �

Y���:\v6����ddlZddlZddlZddlZddlmZddlmZddlm	Z	ddl
mZddlm
Z
mZddlmZddlmZmZmZeje��ZGd	�d
e
e��ZdS)�N)�
AsyncIterator)�int_from_envvar)�
DomainList)�get_myimunify_users)�MessageSink�
MessageSource)�HostingPanel)�Scope�recurring_check�split_for_chunkc�d�eZdZejZdd�Zd�Zd�Zd�Z	de
de
fd�Zdee
fd	�Zd
�ZdS)�SendDomainListNc��d|_|r	||_dStdtt	jd����������|_dS)N�IMUNIFY360_SEND_DOMAIN_PERIOD�)�days)�_task�_periodr�int�datetime�	timedelta�
total_seconds)�self�periods  �]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/send_domain_list.py�__init__zSendDomainList.__init__sY����
��	�!�D�L�L�L�*�/��H�&�A�.�.�.�<�<�>�>�?�?���D�L�L�L�c��
K�dS)zMessageSink methodN�)r�loops  r�create_sinkzSendDomainList.create_sink's
�����rc��K�||_||_|j�t|j��|j������|_dS�N)�_loop�_sink�create_taskrr�_send_domain_listr)rr �sinks   r�
create_sourcezSendDomainList.create_source*sT������
���
��Z�+�+�A�)�O�D�L�)�)�$�*@�A�A�C�C�
�
��
�
�
rc��rK�|j�-d|jc|_}|���|�d{V��dSdSr#)r�cancel)r�ts  r�shutdownzSendDomainList.shutdown2sG�����:�!� �$�*�M�D�J��
�H�H�J�J�J��G�G�G�G�G�G�G�G�G�"�!r�
panel_type�returnc�4�ddd��||��S)N�primary�alias)�main�parked)�get)rr.s  r�_panel_domain_type_to_imunifyz,SendDomainList._panel_domain_type_to_imunify8s(����
�
��#�j�*�
%�
%�	&rc�*K�t��}t�d|j��g}t	���d{V��}|D]�}|d}tj|��}|�|���d{V��D]K}|�||j	|j
|d|j|�|j
��d����L��tj��}t|d���D]}	t!��}
||
d<|	|
d<|
WV�� dS)	NzHostingsPanel: %s�username�myimunify_id)r8�docroot�name�securesite_user_id�uid�typei�)�
chunk_size�	timestamp�domains)r	�logger�info�NAMEr�pwd�getpwnam�get_user_domains_details�appendr:�domain�pw_uidr6r>�timerr)r�hprA�myimunify_users�userr8�user_pwd�domain_datar@�chunk�msgs           r�_create_domain_list_msgz&SendDomainList._create_domain_list_msg>sZ����
�^�^�����'���1�1�1��� 3� 5� 5�5�5�5�5�5�5��#�	�	�D��J�'�H��|�H�-�-�H�%'�%@�%@��%J�%J�J�J�J�J�J�J�
�
�����$,�#.�#6� +� 2�.2�>�.B�'�� $� B� B�'�,�!�!�
	�	�����
��I�K�K�	�$�W��>�>�>�	�	�E��,�,�C�(�C���"�C�	�N��I�I�I�I�I�		�	rc��K�|���23d{V��}|j�|���d{V���(6dSr#)rSr%�process_message)rrRs  rr'z SendDomainList._send_domain_listZsp�����5�5�7�7�	2�	2�	2�	2�	2�	2�	2�#��*�,�,�S�1�1�1�1�1�1�1�1�1�1�8�7�7s�?r#)�__name__�
__module__�__qualname__r
�AV_IM360�SCOPErr!r)r-�strr6rrrSr'rrrrrs��������N�E�����!�!�!�
�
�
����&��&��&�&�&�&��}�Z�/H�����82�2�2�2�2rr)r�loggingrErK�typingr� defence360agent.contracts.configr�"defence360agent.contracts.messagesr�&defence360agent.contracts.myimunify_idr�!defence360agent.contracts.pluginsrr�+defence360agent.subsys.panels.hosting_panelr	�defence360agent.utilsr
rr�	getLoggerrVrBrrrr�<module>resB����������
�
�
�
����� � � � � � �������:�9�9�9�9�9�F�F�F�F�F�F���������E�D�D�D�D�D�����������
��	�8�	$�	$��B2�B2�B2�B2�B2�[�-�B2�B2�B2�B2�B2rdefence360agent/plugins/__pycache__/send_domain_list.cpython-311.pyc0000644000000000000000000001176200000000000022463 0ustar  �

Y���:\v6����ddlZddlZddlZddlZddlmZddlmZddlm	Z	ddl
mZddlm
Z
mZddlmZddlmZmZmZeje��ZGd	�d
e
e��ZdS)�N)�
AsyncIterator)�int_from_envvar)�
DomainList)�get_myimunify_users)�MessageSink�
MessageSource)�HostingPanel)�Scope�recurring_check�split_for_chunkc�d�eZdZejZdd�Zd�Zd�Zd�Z	de
de
fd�Zdee
fd	�Zd
�ZdS)�SendDomainListNc��d|_|r	||_dStdtt	jd����������|_dS)N�IMUNIFY360_SEND_DOMAIN_PERIOD�)�days)�_task�_periodr�int�datetime�	timedelta�
total_seconds)�self�periods  �]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/send_domain_list.py�__init__zSendDomainList.__init__sY����
��	�!�D�L�L�L�*�/��H�&�A�.�.�.�<�<�>�>�?�?���D�L�L�L�c��
K�dS)zMessageSink methodN�)r�loops  r�create_sinkzSendDomainList.create_sink's
�����rc��K�||_||_|j�t|j��|j������|_dS�N)�_loop�_sink�create_taskrr�_send_domain_listr)rr �sinks   r�
create_sourcezSendDomainList.create_source*sT������
���
��Z�+�+�A�)�O�D�L�)�)�$�*@�A�A�C�C�
�
��
�
�
rc��rK�|j�-d|jc|_}|���|�d{V��dSdSr#)r�cancel)r�ts  r�shutdownzSendDomainList.shutdown2sG�����:�!� �$�*�M�D�J��
�H�H�J�J�J��G�G�G�G�G�G�G�G�G�"�!r�
panel_type�returnc�4�ddd��||��S)N�primary�alias)�main�parked)�get)rr.s  r�_panel_domain_type_to_imunifyz,SendDomainList._panel_domain_type_to_imunify8s(����
�
��#�j�*�
%�
%�	&rc�*K�t��}t�d|j��g}t	���d{V��}|D]�}|d}tj|��}|�|���d{V��D]K}|�||j	|j
|d|j|�|j
��d����L��tj��}t|d���D]}	t!��}
||
d<|	|
d<|
WV�� dS)	NzHostingsPanel: %s�username�myimunify_id)r8�docroot�name�securesite_user_id�uid�typei�)�
chunk_size�	timestamp�domains)r	�logger�info�NAMEr�pwd�getpwnam�get_user_domains_details�appendr:�domain�pw_uidr6r>�timerr)r�hprA�myimunify_users�userr8�user_pwd�domain_datar@�chunk�msgs           r�_create_domain_list_msgz&SendDomainList._create_domain_list_msg>sZ����
�^�^�����'���1�1�1��� 3� 5� 5�5�5�5�5�5�5��#�	�	�D��J�'�H��|�H�-�-�H�%'�%@�%@��%J�%J�J�J�J�J�J�J�
�
�����$,�#.�#6� +� 2�.2�>�.B�'�� $� B� B�'�,�!�!�
	�	�����
��I�K�K�	�$�W��>�>�>�	�	�E��,�,�C�(�C���"�C�	�N��I�I�I�I�I�		�	rc��K�|���23d{V��}|j�|���d{V���(6dSr#)rSr%�process_message)rrRs  rr'z SendDomainList._send_domain_listZsp�����5�5�7�7�	2�	2�	2�	2�	2�	2�	2�#��*�,�,�S�1�1�1�1�1�1�1�1�1�1�8�7�7s�?r#)�__name__�
__module__�__qualname__r
�AV_IM360�SCOPErr!r)r-�strr6rrrSr'rrrrrs��������N�E�����!�!�!�
�
�
����&��&��&�&�&�&��}�Z�/H�����82�2�2�2�2rr)r�loggingrErK�typingr� defence360agent.contracts.configr�"defence360agent.contracts.messagesr�&defence360agent.contracts.myimunify_idr�!defence360agent.contracts.pluginsrr�+defence360agent.subsys.panels.hosting_panelr	�defence360agent.utilsr
rr�	getLoggerrVrBrrrr�<module>resB����������
�
�
�
����� � � � � � �������:�9�9�9�9�9�F�F�F�F�F�F���������E�D�D�D�D�D�����������
��	�8�	$�	$��B2�B2�B2�B2�B2�[�-�B2�B2�B2�B2�B2rdefence360agent/plugins/__pycache__/send_server_config.cpython-311.opt-1.pyc0000644000000000000000000004705200000000000023754 0ustar  �

��-�������ddlZddlZddlZddlZddlZddlZddlmZddlm	Z	ddl
mZddlm
Z
mZddlmZddlmZmZmZmZmZmZmZmZddlmZdd	lmZdd
lmZm Z m!Z!ddl"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;��Z<hd�Z=d�Z>ed���d���Z?e2d���Z@e2d���ZAe2d���ZBe2d���ZCe2d���ZDe2d���ZEe2d���ZFd �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)�Dict�List)�sentry)�
ConfigFile�Core�CustomBillingConfig�Malware�MalwareSignatures�SystemConfig�int_from_envvar�FREEMIUM_FEATURE_FLAG)�
LicenseCLN)�MessageType)�MessageSink�
MessageSource�expect)�get_myimunify_users)�$is_native_feature_management_enabled�&is_native_feature_management_supported)�IndependentAgentIDAPI)�HostingPanel)�cPanel)�log_error_and_ignore�recurring_check�safe_cancel_task�Scope�stub_unexpected_error�safe_run�system_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-core�imunify-common�imunify360-pam�imunify-release�imunify360-venv�alt-php-internal�imunify-notifier�imunify-patchman�imunify360-ossec�alt-php-hyperscan�imunify-antivirus�alt-common-release�imunify-realtime-av�imunify-wp-security�imunify360-firewall�imunify360-php-i360�app-version-detector�cloudlinux-backup-utils�imunify360-ossec-server�imunify-auditd-log-reader�imunify-realtime-av-imrt2�imunify360-webshield-bundle� imunify360-unified-access-logger�	rustbolit�
minidaemonc�|�td��5}|���cddd��S#1swxYwYdS)Nz
/proc/cpuinfo)�open�read)�fs �_/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/send_server_config.py�
read_cpu_inforF^s|��	
�o�	�	��!��v�v�x�x���������������������s�1�5�5�)�maxsizec��t��}tjd|tj���}g}i}|D]-\}}|dkr|rd|vr|�|��i}|||<�.|�|��|S)Nz^(.*?)[ 	]*:[ 	]*(.*)$)�flags�	processor)rF�re�findall�M�append)�text�tuples�res�current�key�values      rE�get_cpu_inforVcs����?�?�D�
�Z�2�D���
E�
E�
E�F��C��G����
��U��+����
�;�'�1�1��
�
�7�#�#�#���������J�J�w�����J�c��i}t��D]H}|�d|d��x}|vr&t|�dd����||<�It|�����S)Nzphysical idrKz	cpu coresrG)rV�get�int�sum�values)�physical_idsrK�physical_ids   rE�
get_cpu_coresr_ts����L�!�^�^�K�K�	�$�=�=��	�+�8N�O�O�O�K����),�I�M�M�+�q�,I�,I�(J�(J�L��%���|�"�"�$�$�%�%�%rWc���t��d}d�|�d��p|d|�d��p|d��S)Nrz{} {}z
model name�	ProcessorrJ�Features)rV�formatrY)rKs rE�get_cpu_model_and_flagsrdsX�����q�!�I��>�>��
�
�l�#�#�=�y��'=��
�
�g���7�)�J�"7���rWc��:K�|����d{V��S�N)�version��hps rE�get_hosting_panel_versionrj�s&���������������rWc��:K�|����d{V��Srf)�users_countrhs rE�get_users_amountrm�s(�������!�!�!�!�!�!�!�!�!rWc��TK�t|����d{V����Srf)�len�get_domain_to_ownerrhs rE�get_domains_amountrq�s2�����R�+�+�-�-�-�-�-�-�-�-�.�.�.rWc���tj�tj��r6ttj�tj����SdSrf)�os�path�existsr
�AI_BOLIT_HOSTERrZ�getmtime�rWrE�get_malware_db_update_timery�sK��	�w�~�~�'�7�8�8�H��2�7�#�#�$5�$E�F�F�G�G�G�H�HrWc��ZK�t���d{V��rt���d{V��SdSrf)rrrxrWrE�
get_nfm_stater{�sN����
3�
5�
5�5�5�5�5�5�5�<�9�;�;�;�;�;�;�;�;�;�<�<rWc��td��}|���r8tj|��������SdS)Nz/etc/machine-id)rru�hashlib�sha256�
read_bytes�	hexdigest)�
machine_ids rE�get_sha256_machine_idr��sR���'�(�(�J������C��~�j�3�3�5�5�6�6�@�@�B�B�B��4rWc�t�t������d��}|dkS)zA
    True only if active in whmcs config
    otherwise False
    �status�active)r%rCrY)�activation_states rE�$get_myimunify_whmcs_activation_stater��s3��!�{�{�'�'�)�)�-�-�h�7�7���x�'�'rWc
��K�t��t��t|���d{V��t|���d{V��t	|���d{V��t
jt
jt��t���d{V��t��tj�
t��t��d�S)N)�	cpu_cores�cpuinfo�hosting_panel_version�users_amount�domains_amount�trim_malicious�days_to_keep_backup�malware_db_update_time�!native_feature_management_enabledr��myimunify_freemium_flag_exists�myimunify_whmcs_activated)r_rdrjrmrqr�CLEANUP_TRIM�CLEANUP_KEEPryr{r�rsrtrurr�rhs rE�get_additional_infor��s�����"�_�_�*�,�,�'@��'D�'D�!D�!D�!D�!D�!D�!D�.�r�2�2�2�2�2�2�2�2� 2�2� 6� 6�6�6�6�6�6�6�!�.�&�3�"<�">�">�3@�?�?�-B�-B�-B�-B�-B�-B�+�-�-�*,�'�.�.�!�+
�+
�&J�%K�%K���rW�returnc��,K�t��}	t|����d{V����}n8#t$r+t�d��t��}YnwxYwtj�dtj
��}ttj���
|��D]K}|jj|vr;t!|jj����d���||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�	Exception�logger�	exceptionrsrt�joinr
�USER_CONFIG_FILE_NAMEr�USER_CONFDIR�glob�parent�namer	�config_to_dict)ri�result�
current_users�
users_conf�
userconf_files     rE�get_users_configsr��s����
�V�V�F�$�!������"6�"6�"6�"6�"6�"6�7�7�
�
���$�$�$����C�D�D�D�!���
�
�
�$��������c�4�#=�>�>�J��d�/�0�0�5�5�j�A�A�.�.�
���$�
�5�5�0:�&�-�2�1�1�1��n�u�n�-�-�
�=�'�,�-���Ms�':�2A/�.A/c��eZdZdZejZd
d�Zd�Ze	e
j��e��d�����Z
d�Zd�Zd�Zd	�ZdS)�SendServerConfigz�
    This plugin is to provide central server with
    different server metrics. Message is sent on plugin creation,
    and then every :period: seconds
    Nc���d|_d|_|r	||_dStdt	tjd������dz����|_dS)N�$IMUNIFY360_SEND_SERVER_CONFIG_PERIODrG)�days�)�_task�_last_send_time�_periodrrZ�datetime�	timedelta�
total_seconds)�self�periods  rE�__init__zSendServerConfig.__init__�sf����
�#����	�!�D�L�L�L�*�6��H�&�A�.�.�.�<�<�>�>��B�C�C���D�L�L�LrWc��
K�dS)zMessageSink methodNrx)r��loops  rE�create_sinkzSendServerConfig.create_sink�s
�����rWc��K�|j�	d|_dSt|dt��sdS|d�|j��r;|d|_|j�|�����dSdS)Nr�conf�	timestamp)r��
isinstancer�modified_since�_loop�create_task�_send_server_config)r��messages  rE�on_config_update_messagez)SendServerConfig.on_config_update_message�s�������'�#$�D� ��F��'�&�/�<�8�8�	�
�F��6�?�)�)�$�*>�?�?�	?�#*�;�#7�D� ��J�"�"�4�#;�#;�#=�#=�>�>�>�>�>�	?�	?rWc��K�||_||_|j�t|j��|j������|_dSrf)r��_sinkr�rr�r�r�)r�r��sinks   rE�
create_sourcezSendServerConfig.create_sourcesT������
���
��Z�+�+�C�)�O�D�L�)�)�$�*B�C�C�E�E�
�
��
�
�
rWc��dK�|j�&d|jc|_}t|���d{V��dSdSrf)r�r)r��ts  rE�shutdownzSendServerConfig.shutdownsK�����:�!� �$�*�M�D�J��"�1�%�%�%�%�%�%�%�%�%�%�%�"�!rWc��K�tjt�����}t��}|r||d<t	��}tj��}|�tj	����|�t|���d{V����|jtjkrt���d{V��|d<tj��|d<tj��|d<t#���d{V��t%���d{V��t'���d{V��d�|d<t)�����t-�����z|d<t/|���d{V��|d	<t1t2���d{V��|d
<t5t6���d{V��|d<|�d��|dd
<|�d��|dd<t;d���d��|dd<t=dddi��|S)N)�uname�	diskstats�users�iaid�status_license)�uptime_since�devices�mac�system_info�agent_global_config�agent_users_configs�paths�components_versions�upgrade_urlz$CUSTOM_BILLING.effective_upgrade_url�upgrade_url_360z(CUSTOM_BILLING.effective_upgrade_url_360�
doctor_keyzCORE.doctor_report)r�ServerConfig�_uname_info�	_diskstatrr�license_info�updater�tagsr��NAMErrr�get_iaid�is_valid�_uptime�_blkid�_mac_addressr	r�rr��_get_path_sizes�CH_PATHSr"�PACKAGES_TO_REPORTrYr#r$)r��msg�diskstatrir�s     rE�_create_server_config_msgz*SendServerConfig._create_server_config_msgs������&�[�]�]�;�;�;���;�;���	(�'�C���
�^�^��!�.�0�0���
�
�6�;�=�=�!�!�!��
�
�,�R�0�0�0�0�0�0�0�0�1�1�1�
�7�f�k�!�!�!4�!6�!6�6�6�6�6�6�6�C��L�+�4�6�6��F�� *� 3� 5� 5����")�)�)�O�O�O�O�O�O�#�X�X�~�~�~�~�~�~�%���'�'�'�'�'�'�
�
��M��
�L�L�'�'�)�)�!�#�#�2�2�4�4�
5�	�!�"�,=�R�+@�+@�%@�%@�%@�%@�%@�%@��!�"�,�X�6�6�6�6�6�6�6�6��G��+?��,
�,
�&
�&
�&
�&
�&
�&
��!�"�

���]�+�+�	�!�"�2�	
�

���.�/�/�	�!�"�6�	
�<F��<
�<
�

�#�l�
�
�	�!�"�#7�8�	�<�,��!5�6�6�6��
rWc��zK�|j�|����d{V�����d{V��dSrf)r��process_messager�)r�s rEr�z$SendServerConfig._send_server_config<si�����j�(�(��0�0�2�2�2�2�2�2�2�2�
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
rWrf)�__name__�
__module__�__qualname__�__doc__r�AV�SCOPEr�r�rr�ConfigUpdaterr�r�r�r�r�rxrWrEr�r��s���������
�H�E�
�
�
�
�!�!�!��V�K�$�%�%�����
?�
?���&�%�
?�
�
�
�&�&�&�
)�)�)�V
�
�
�
�
rWr�c��lK�i}ttj|��D]�}	tj�|��rt|��}ntj�|��}|||<�W#t$r1}t�	d||��|j
||<Yd}~��d}~wwxYw|S)zFReturn path->size mapping for *paths*.

    Send -errno on error.
    z!Can't get size for %s, reason: %sN)�maprs�fspathrt�isdir�_compute_dir_size�getsize�OSErrorr��warning�errno)r��sizesrt�size�es     rEr�r�Bs�����

�E��B�I�u�%�%�
�
��		��w�}�}�T�"�"�
-�(��.�.����w���t�,�,��
�E�$�K�K��	�	#�	#�	#��N�N�>��a�H�H�H��7�(�E�$�K�K�K�K�K�K�����	#����
�Ls�AA6�6
B1�'B,�,B1�directory_pathc��d}dtfd�}tj||d���D]b\}}}|D]Y}tj�||��}	|tj�|��z
}�F#t$r}|�d}~wwxYw�c|S)Nr�errc��|�rfrx)rs rE�_onerrorz#_compute_dir_size.<locals>._onerrorYs���	rWF)�onerror�followlinks)rrs�walkrtr�r)	r	�
total_sizer
�root�_dirs�files�	file_name�	file_pathrs	         rErrVs����J��g�����!�g���e��������e�U��	�	�I�����T�9�5�5�I�
��b�g�o�o�i�8�8�8�
�
���
�
�
�������
����		��s�"A3�3
B�=A?�?Bc��	td��5}|���cddd��S#1swxYwYdS#t$r3}t�dt|����Yd}~dSd}~wwxYw)Nz/proc/diskstatszCan't get diskstat: %s)rBrCrr�r�str)rDrs  rEr�r�hs���9�
�#�
$�
$�	���6�6�8�8�	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	�	���9�9�9����/��Q���8�8�8�8�8�8�8�8�8�����9���s-�?�2�?�6�?�6�?�
A<�	(A7�7A<c�^�ttdtj������S)N)�sysname�nodename�releaserg�machine)r��ziprsr�rxrWrEr�r�ps-����D��H�J�J�	
�	
���rWc��4K�tddg���d{V��S)zSystem up since�uptimez--sinceN�r!rxrWrEr�r�ys+�����8�Y�/�0�0�0�0�0�0�0�0�0rWc��2K�tdg���d{V��S)z8Executes utility to locate/print block device attributes�blkidNr!rxrWrEr�r�~s(�����7�)�$�$�$�$�$�$�$�$�$rWc��K�tjtj���dd��d�����S)zOMAC address in formatted way, like it specifies in
    /sys/class/net/*/address��big�:)�binascii�hexlify�uuid�getnode�to_bytes�decoderxrWrEr�r��s=������D�L�N�N�3�3�A�u�=�=�s�C�C�J�J�L�L�LrW)Vr(r�r}rsrLr*�	functoolsr�loggingr�pathlibr�typingrr�defence360agent.contractsr� defence360agent.contracts.configr	r
rrr
rrr�!defence360agent.contracts.licenser�"defence360agent.contracts.messagesr�!defence360agent.contracts.pluginsrrr�&defence360agent.contracts.myimunify_idr�*defence360agent.feature_management.controlrr�defence360agent.internals.iaidr�+defence360agent.subsys.panels.hosting_panelr�$defence360agent.subsys.panels.cpanelr�defence360agent.utilsrrrrr r!r"�'defence360agent.subsys.persistent_stater#r$�defence360agent.utils.whmcsr%r�r�r�r�rFrVr_rdrjrmrqryr{r�r�r�r�r�rrZr�rr�r�r�r�r�r�rxrWrE�<module>r?s���������������	�	�	�	�	�	�	�	�������������������������������,�,�,�,�,�,�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�9�8�8�8�8�8�:�:�:�:�:�:�����������
G�F�F�F�F�F���������A�@�@�@�@�@�D�D�D�D�D�D�7�7�7�7�7�7�������������������K�J�J�J�J�J�J�J�1�1�1�1�1�1���
��8�	�	������>���
��1����
�
���
� �&�&���&��������������"�"���"��/�/���/��H�H���H�
�<�<���<�
����(�(���(����&�4�����(d
�d
�d
�d
�d
�{�M�d
�d
�d
�N�D��c��N�����(�c��c�����$9�9�9��T�����1�s�1�1�1�1�
%�c�%�%�%�%�
M�C�M�M�M�M�M�MrWdefence360agent/plugins/__pycache__/send_server_config.cpython-311.pyc0000644000000000000000000004705200000000000023015 0ustar  �

��-�������ddlZddlZddlZddlZddlZddlZddlmZddlm	Z	ddl
mZddlm
Z
mZddlmZddlmZmZmZmZmZmZmZmZddlmZdd	lmZdd
lmZm Z m!Z!ddl"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;��Z<hd�Z=d�Z>ed���d���Z?e2d���Z@e2d���ZAe2d���ZBe2d���ZCe2d���ZDe2d���ZEe2d���ZFd �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)�Dict�List)�sentry)�
ConfigFile�Core�CustomBillingConfig�Malware�MalwareSignatures�SystemConfig�int_from_envvar�FREEMIUM_FEATURE_FLAG)�
LicenseCLN)�MessageType)�MessageSink�
MessageSource�expect)�get_myimunify_users)�$is_native_feature_management_enabled�&is_native_feature_management_supported)�IndependentAgentIDAPI)�HostingPanel)�cPanel)�log_error_and_ignore�recurring_check�safe_cancel_task�Scope�stub_unexpected_error�safe_run�system_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-core�imunify-common�imunify360-pam�imunify-release�imunify360-venv�alt-php-internal�imunify-notifier�imunify-patchman�imunify360-ossec�alt-php-hyperscan�imunify-antivirus�alt-common-release�imunify-realtime-av�imunify-wp-security�imunify360-firewall�imunify360-php-i360�app-version-detector�cloudlinux-backup-utils�imunify360-ossec-server�imunify-auditd-log-reader�imunify-realtime-av-imrt2�imunify360-webshield-bundle� imunify360-unified-access-logger�	rustbolit�
minidaemonc�|�td��5}|���cddd��S#1swxYwYdS)Nz
/proc/cpuinfo)�open�read)�fs �_/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/send_server_config.py�
read_cpu_inforF^s|��	
�o�	�	��!��v�v�x�x���������������������s�1�5�5�)�maxsizec��t��}tjd|tj���}g}i}|D]-\}}|dkr|rd|vr|�|��i}|||<�.|�|��|S)Nz^(.*?)[ 	]*:[ 	]*(.*)$)�flags�	processor)rF�re�findall�M�append)�text�tuples�res�current�key�values      rE�get_cpu_inforVcs����?�?�D�
�Z�2�D���
E�
E�
E�F��C��G����
��U��+����
�;�'�1�1��
�
�7�#�#�#���������J�J�w�����J�c��i}t��D]H}|�d|d��x}|vr&t|�dd����||<�It|�����S)Nzphysical idrKz	cpu coresrG)rV�get�int�sum�values)�physical_idsrK�physical_ids   rE�
get_cpu_coresr_ts����L�!�^�^�K�K�	�$�=�=��	�+�8N�O�O�O�K����),�I�M�M�+�q�,I�,I�(J�(J�L��%���|�"�"�$�$�%�%�%rWc���t��d}d�|�d��p|d|�d��p|d��S)Nrz{} {}z
model name�	ProcessorrJ�Features)rV�formatrY)rKs rE�get_cpu_model_and_flagsrdsX�����q�!�I��>�>��
�
�l�#�#�=�y��'=��
�
�g���7�)�J�"7���rWc��:K�|����d{V��S�N)�version��hps rE�get_hosting_panel_versionrj�s&���������������rWc��:K�|����d{V��Srf)�users_countrhs rE�get_users_amountrm�s(�������!�!�!�!�!�!�!�!�!rWc��TK�t|����d{V����Srf)�len�get_domain_to_ownerrhs rE�get_domains_amountrq�s2�����R�+�+�-�-�-�-�-�-�-�-�.�.�.rWc���tj�tj��r6ttj�tj����SdSrf)�os�path�existsr
�AI_BOLIT_HOSTERrZ�getmtime�rWrE�get_malware_db_update_timery�sK��	�w�~�~�'�7�8�8�H��2�7�#�#�$5�$E�F�F�G�G�G�H�HrWc��ZK�t���d{V��rt���d{V��SdSrf)rrrxrWrE�
get_nfm_stater{�sN����
3�
5�
5�5�5�5�5�5�5�<�9�;�;�;�;�;�;�;�;�;�<�<rWc��td��}|���r8tj|��������SdS)Nz/etc/machine-id)rru�hashlib�sha256�
read_bytes�	hexdigest)�
machine_ids rE�get_sha256_machine_idr��sR���'�(�(�J������C��~�j�3�3�5�5�6�6�@�@�B�B�B��4rWc�t�t������d��}|dkS)zA
    True only if active in whmcs config
    otherwise False
    �status�active)r%rCrY)�activation_states rE�$get_myimunify_whmcs_activation_stater��s3��!�{�{�'�'�)�)�-�-�h�7�7���x�'�'rWc
��K�t��t��t|���d{V��t|���d{V��t	|���d{V��t
jt
jt��t���d{V��t��tj�
t��t��d�S)N)�	cpu_cores�cpuinfo�hosting_panel_version�users_amount�domains_amount�trim_malicious�days_to_keep_backup�malware_db_update_time�!native_feature_management_enabledr��myimunify_freemium_flag_exists�myimunify_whmcs_activated)r_rdrjrmrqr�CLEANUP_TRIM�CLEANUP_KEEPryr{r�rsrtrurr�rhs rE�get_additional_infor��s�����"�_�_�*�,�,�'@��'D�'D�!D�!D�!D�!D�!D�!D�.�r�2�2�2�2�2�2�2�2� 2�2� 6� 6�6�6�6�6�6�6�!�.�&�3�"<�">�">�3@�?�?�-B�-B�-B�-B�-B�-B�+�-�-�*,�'�.�.�!�+
�+
�&J�%K�%K���rW�returnc��,K�t��}	t|����d{V����}n8#t$r+t�d��t��}YnwxYwtj�dtj
��}ttj���
|��D]K}|jj|vr;t!|jj����d���||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�	Exception�logger�	exceptionrsrt�joinr
�USER_CONFIG_FILE_NAMEr�USER_CONFDIR�glob�parent�namer	�config_to_dict)ri�result�
current_users�
users_conf�
userconf_files     rE�get_users_configsr��s����
�V�V�F�$�!������"6�"6�"6�"6�"6�"6�7�7�
�
���$�$�$����C�D�D�D�!���
�
�
�$��������c�4�#=�>�>�J��d�/�0�0�5�5�j�A�A�.�.�
���$�
�5�5�0:�&�-�2�1�1�1��n�u�n�-�-�
�=�'�,�-���Ms�':�2A/�.A/c��eZdZdZejZd
d�Zd�Ze	e
j��e��d�����Z
d�Zd�Zd�Zd	�ZdS)�SendServerConfigz�
    This plugin is to provide central server with
    different server metrics. Message is sent on plugin creation,
    and then every :period: seconds
    Nc���d|_d|_|r	||_dStdt	tjd������dz����|_dS)N�$IMUNIFY360_SEND_SERVER_CONFIG_PERIODrG)�days�)�_task�_last_send_time�_periodrrZ�datetime�	timedelta�
total_seconds)�self�periods  rE�__init__zSendServerConfig.__init__�sf����
�#����	�!�D�L�L�L�*�6��H�&�A�.�.�.�<�<�>�>��B�C�C���D�L�L�LrWc��
K�dS)zMessageSink methodNrx)r��loops  rE�create_sinkzSendServerConfig.create_sink�s
�����rWc��K�|j�	d|_dSt|dt��sdS|d�|j��r;|d|_|j�|�����dSdS)Nr�conf�	timestamp)r��
isinstancer�modified_since�_loop�create_task�_send_server_config)r��messages  rE�on_config_update_messagez)SendServerConfig.on_config_update_message�s�������'�#$�D� ��F��'�&�/�<�8�8�	�
�F��6�?�)�)�$�*>�?�?�	?�#*�;�#7�D� ��J�"�"�4�#;�#;�#=�#=�>�>�>�>�>�	?�	?rWc��K�||_||_|j�t|j��|j������|_dSrf)r��_sinkr�rr�r�r�)r�r��sinks   rE�
create_sourcezSendServerConfig.create_sourcesT������
���
��Z�+�+�C�)�O�D�L�)�)�$�*B�C�C�E�E�
�
��
�
�
rWc��dK�|j�&d|jc|_}t|���d{V��dSdSrf)r�r)r��ts  rE�shutdownzSendServerConfig.shutdownsK�����:�!� �$�*�M�D�J��"�1�%�%�%�%�%�%�%�%�%�%�%�"�!rWc��K�tjt�����}t��}|r||d<t	��}tj��}|�tj	����|�t|���d{V����|jtjkrt���d{V��|d<tj��|d<tj��|d<t#���d{V��t%���d{V��t'���d{V��d�|d<t)�����t-�����z|d<t/|���d{V��|d	<t1t2���d{V��|d
<t5t6���d{V��|d<|�d��|dd
<|�d��|dd<t;d���d��|dd<t=dddi��|S)N)�uname�	diskstats�users�iaid�status_license)�uptime_since�devices�mac�system_info�agent_global_config�agent_users_configs�paths�components_versions�upgrade_urlz$CUSTOM_BILLING.effective_upgrade_url�upgrade_url_360z(CUSTOM_BILLING.effective_upgrade_url_360�
doctor_keyzCORE.doctor_report)r�ServerConfig�_uname_info�	_diskstatrr�license_info�updater�tagsr��NAMErrr�get_iaid�is_valid�_uptime�_blkid�_mac_addressr	r�rr��_get_path_sizes�CH_PATHSr"�PACKAGES_TO_REPORTrYr#r$)r��msg�diskstatrir�s     rE�_create_server_config_msgz*SendServerConfig._create_server_config_msgs������&�[�]�]�;�;�;���;�;���	(�'�C���
�^�^��!�.�0�0���
�
�6�;�=�=�!�!�!��
�
�,�R�0�0�0�0�0�0�0�0�1�1�1�
�7�f�k�!�!�!4�!6�!6�6�6�6�6�6�6�C��L�+�4�6�6��F�� *� 3� 5� 5����")�)�)�O�O�O�O�O�O�#�X�X�~�~�~�~�~�~�%���'�'�'�'�'�'�
�
��M��
�L�L�'�'�)�)�!�#�#�2�2�4�4�
5�	�!�"�,=�R�+@�+@�%@�%@�%@�%@�%@�%@��!�"�,�X�6�6�6�6�6�6�6�6��G��+?��,
�,
�&
�&
�&
�&
�&
�&
��!�"�

���]�+�+�	�!�"�2�	
�

���.�/�/�	�!�"�6�	
�<F��<
�<
�

�#�l�
�
�	�!�"�#7�8�	�<�,��!5�6�6�6��
rWc��zK�|j�|����d{V�����d{V��dSrf)r��process_messager�)r�s rEr�z$SendServerConfig._send_server_config<si�����j�(�(��0�0�2�2�2�2�2�2�2�2�
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
rWrf)�__name__�
__module__�__qualname__�__doc__r�AV�SCOPEr�r�rr�ConfigUpdaterr�r�r�r�r�rxrWrEr�r��s���������
�H�E�
�
�
�
�!�!�!��V�K�$�%�%�����
?�
?���&�%�
?�
�
�
�&�&�&�
)�)�)�V
�
�
�
�
rWr�c��lK�i}ttj|��D]�}	tj�|��rt|��}ntj�|��}|||<�W#t$r1}t�	d||��|j
||<Yd}~��d}~wwxYw|S)zFReturn path->size mapping for *paths*.

    Send -errno on error.
    z!Can't get size for %s, reason: %sN)�maprs�fspathrt�isdir�_compute_dir_size�getsize�OSErrorr��warning�errno)r��sizesrt�size�es     rEr�r�Bs�����

�E��B�I�u�%�%�
�
��		��w�}�}�T�"�"�
-�(��.�.����w���t�,�,��
�E�$�K�K��	�	#�	#�	#��N�N�>��a�H�H�H��7�(�E�$�K�K�K�K�K�K�����	#����
�Ls�AA6�6
B1�'B,�,B1�directory_pathc��d}dtfd�}tj||d���D]b\}}}|D]Y}tj�||��}	|tj�|��z
}�F#t$r}|�d}~wwxYw�c|S)Nr�errc��|�rfrx)rs rE�_onerrorz#_compute_dir_size.<locals>._onerrorYs���	rWF)�onerror�followlinks)rrs�walkrtr�r)	r	�
total_sizer
�root�_dirs�files�	file_name�	file_pathrs	         rErrVs����J��g�����!�g���e��������e�U��	�	�I�����T�9�5�5�I�
��b�g�o�o�i�8�8�8�
�
���
�
�
�������
����		��s�"A3�3
B�=A?�?Bc��	td��5}|���cddd��S#1swxYwYdS#t$r3}t�dt|����Yd}~dSd}~wwxYw)Nz/proc/diskstatszCan't get diskstat: %s)rBrCrr�r�str)rDrs  rEr�r�hs���9�
�#�
$�
$�	���6�6�8�8�	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	�	���9�9�9����/��Q���8�8�8�8�8�8�8�8�8�����9���s-�?�2�?�6�?�6�?�
A<�	(A7�7A<c�^�ttdtj������S)N)�sysname�nodename�releaserg�machine)r��ziprsr�rxrWrEr�r�ps-����D��H�J�J�	
�	
���rWc��4K�tddg���d{V��S)zSystem up since�uptimez--sinceN�r!rxrWrEr�r�ys+�����8�Y�/�0�0�0�0�0�0�0�0�0rWc��2K�tdg���d{V��S)z8Executes utility to locate/print block device attributes�blkidNr!rxrWrEr�r�~s(�����7�)�$�$�$�$�$�$�$�$�$rWc��K�tjtj���dd��d�����S)zOMAC address in formatted way, like it specifies in
    /sys/class/net/*/address��big�:)�binascii�hexlify�uuid�getnode�to_bytes�decoderxrWrEr�r��s=������D�L�N�N�3�3�A�u�=�=�s�C�C�J�J�L�L�LrW)Vr(r�r}rsrLr*�	functoolsr�loggingr�pathlibr�typingrr�defence360agent.contractsr� defence360agent.contracts.configr	r
rrr
rrr�!defence360agent.contracts.licenser�"defence360agent.contracts.messagesr�!defence360agent.contracts.pluginsrrr�&defence360agent.contracts.myimunify_idr�*defence360agent.feature_management.controlrr�defence360agent.internals.iaidr�+defence360agent.subsys.panels.hosting_panelr�$defence360agent.subsys.panels.cpanelr�defence360agent.utilsrrrrr r!r"�'defence360agent.subsys.persistent_stater#r$�defence360agent.utils.whmcsr%r�r�r�r�rFrVr_rdrjrmrqryr{r�r�r�r�r�rrZr�rr�r�r�r�r�r�rxrWrE�<module>r?s���������������	�	�	�	�	�	�	�	�������������������������������,�,�,�,�,�,�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�9�8�8�8�8�8�:�:�:�:�:�:�����������
G�F�F�F�F�F���������A�@�@�@�@�@�D�D�D�D�D�D�7�7�7�7�7�7�������������������K�J�J�J�J�J�J�J�1�1�1�1�1�1���
��8�	�	������>���
��1����
�
���
� �&�&���&��������������"�"���"��/�/���/��H�H���H�
�<�<���<�
����(�(���(����&�4�����(d
�d
�d
�d
�d
�{�M�d
�d
�d
�N�D��c��N�����(�c��c�����$9�9�9��T�����1�s�1�1�1�1�
%�c�%�%�%�%�
M�C�M�M�M�M�M�MrWdefence360agent/plugins/__pycache__/service_manager.cpython-311.opt-1.pyc0000644000000000000000000000766200000000000023245 0ustar  �

�q<nH��|�dZddlZddlZddlmZddlmZmZeje	��Z
Gd�dej��ZdS)z�Base service manager plugin.

Provides the shared start/stop/enable/disable logic that product-specific
service managers (imav, im360) inherit from.
�N)�utils)�messages�pluginsc��eZdZdZd�Zd�Zejej	j
��dej	j
fd���Zej
��	d	d���ZdS)
�BaseServiceManageru�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__``.
    c�R�tj��|_g|_i|_dS�N)�asyncio�Lock�_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{V���dSr	)r
)r�services  r�!_ensure_consistent_services_statez4BaseServiceManager._ensure_consistent_services_states:�����~�	�	�G��'�)�)�O�O�O�O�O�O�O�O�	�	r�message_ignoredc��K�|j4�d{V��|����d{V��ddd���d{V��dS#1�d{V��swxYwYdSr	)rr)rrs  r�on_config_updatez#BaseServiceManager.on_config_update s������:�	;�	;�	;�	;�	;�	;�	;�	;��8�8�:�:�:�:�:�:�:�:�:�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;����	;�	;�	;�	;�	;�	;s�>�
A�AFc��K�|����d{V��}||ur�|rTt�d|��|�d����d{V��t�d|��dSt�d|��|�d����d{V��t�d|��dS|r9|r9|����d{V��t�d|��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...zDisabled %sz#Reloading %s after config update...)�	is_active�logger�info�enable�disable�reload)r�unitctl�service_name�should_be_runningr �
is_runnings      r�_ensure_service_statusz)BaseServiceManager._ensure_service_status'si����#�,�,�.�.�.�.�.�.�.�.�
��.�.�.� �
9����/� ����
�n�n��n�.�.�.�.�.�.�.�.�.����L�,�7�7�7�7�7����0� ����
�o�o�$�o�/�/�/�/�/�/�/�/�/����M�<�8�8�8�8�8��
�f�
��n�n�&�&�&�&�&�&�&�&�&����9�<������
�
�
�
rN)F)�__name__�
__module__�__qualname__�__doc__rrr�expectr�MessageType�ConfigUpdaterr�log_error_and_ignorer%�rrrrs������������
����W�^�H�(�5�6�6�;�'�3�@�;�;�;�7�6�;� �U��!�!�?D����"�!���rr)
r)r
�logging�defence360agentr�defence360agent.contractsrr�	getLoggerr&r�MessageSinkrr.rr�<module>r4s�������������!�!�!�!�!�!�7�7�7�7�7�7�7�7�	��	�8�	$�	$��2�2�2�2�2��,�2�2�2�2�2rdefence360agent/plugins/__pycache__/service_manager.cpython-311.pyc0000644000000000000000000000766200000000000022306 0ustar  �

�q<nH��|�dZddlZddlZddlmZddlmZmZeje	��Z
Gd�dej��ZdS)z�Base service manager plugin.

Provides the shared start/stop/enable/disable logic that product-specific
service managers (imav, im360) inherit from.
�N)�utils)�messages�pluginsc��eZdZdZd�Zd�Zejej	j
��dej	j
fd���Zej
��	d	d���ZdS)
�BaseServiceManageru�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__``.
    c�R�tj��|_g|_i|_dS�N)�asyncio�Lock�_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{V���dSr	)r
)r�services  r�!_ensure_consistent_services_statez4BaseServiceManager._ensure_consistent_services_states:�����~�	�	�G��'�)�)�O�O�O�O�O�O�O�O�	�	r�message_ignoredc��K�|j4�d{V��|����d{V��ddd���d{V��dS#1�d{V��swxYwYdSr	)rr)rrs  r�on_config_updatez#BaseServiceManager.on_config_update s������:�	;�	;�	;�	;�	;�	;�	;�	;��8�8�:�:�:�:�:�:�:�:�:�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;����	;�	;�	;�	;�	;�	;s�>�
A�AFc��K�|����d{V��}||ur�|rTt�d|��|�d����d{V��t�d|��dSt�d|��|�d����d{V��t�d|��dS|r9|r9|����d{V��t�d|��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...zDisabled %sz#Reloading %s after config update...)�	is_active�logger�info�enable�disable�reload)r�unitctl�service_name�should_be_runningr �
is_runnings      r�_ensure_service_statusz)BaseServiceManager._ensure_service_status'si����#�,�,�.�.�.�.�.�.�.�.�
��.�.�.� �
9����/� ����
�n�n��n�.�.�.�.�.�.�.�.�.����L�,�7�7�7�7�7����0� ����
�o�o�$�o�/�/�/�/�/�/�/�/�/����M�<�8�8�8�8�8��
�f�
��n�n�&�&�&�&�&�&�&�&�&����9�<������
�
�
�
rN)F)�__name__�
__module__�__qualname__�__doc__rrr�expectr�MessageType�ConfigUpdaterr�log_error_and_ignorer%�rrrrs������������
����W�^�H�(�5�6�6�;�'�3�@�;�;�;�7�6�;� �U��!�!�?D����"�!���rr)
r)r
�logging�defence360agentr�defence360agent.contractsrr�	getLoggerr&r�MessageSinkrr.rr�<module>r4s�������������!�!�!�!�!�!�7�7�7�7�7�7�7�7�	��	�8�	$�	$��2�2�2�2�2��,�2�2�2�2�2rdefence360agent/plugins/__pycache__/wordpress.cpython-311.opt-1.pyc0000644000000000000000000012056100000000000022135 0ustar  �

b�)�^�n�����ddlZddlZddlZddlmZddlmZddlmZddl	m
Z
mZmZm
Z
mZddlmZddlmZddlmZdd	lmZmZmZdd
lmZddlmZmZmZdd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;m<Z<m=Z=ddl*m>Z>m?Z?m@Z@ddlAmBZBddlCmDZDejEeF��ZGede jH��ZIede jH��ZJede jH��ZKede jH��ZLed��ZMed ��ZNeMd!zZOeMd"zZPe!jQd#d$d�%��ZRd&eSd'eTfd(�ZUGd)�d*ee��ZVdS)+�N)�suppress)�Path)�	Coroutine)�ANTIVIRUS_MODE�ConfigValidationError�SystemConfig�
UserConfig�	Wordpress)�	HookEvent)�
LicenseCLN)�MessageType)�MessageSink�
MessageSource�expect)�
hosting_panel)�
load_state�register_lock_file�
save_state)�Scope�importer�recurring_check�system_packages_info)�
check_lock)�IndependentAgentIDAPI)�DAY)�cli)�plugin)�_prepare_ai_bot_settings)�resolve_ai_bot_protection)�	tls_check)�WPSite�
WordpressSite)�get_sites_by_path�get_sites_for_user�get_installed_sites)�is_secret_expired�
rotate_secret)�ChangelogProcessor�IncidentCollector�IncidentSender)�update_disabled_rules_on_sites)�delete_old_wordpress_incidentszwp-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)�module�name�default�started_timestamp�returnc�|�t�t�d��gSt�|��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)�_MalwareHit�logger�debug�
cleaned_since)r1s �V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/wordpress.py�_get_cleaned_malware_hitsr9_s?�������I�	
�	
�	
��	��$�$�%6�7�7�7�c��eZdZejZd�Zd�Zd�Zd�Z	d�Z
d�Zd�Zd�Z
d	ejfd
�Zd$defd
�Zd�Zeedee���d���Zeeddee���d���Zeedde���d���Zd�Zd�Zd�Zd�Z d�Z!e"e#j$��d���Z%e"e#j&��d���Z'e"e#j&��d���Z(eedde)���d���Z*d�Z+e"e#j&��d ���Z,e"e-j.��d!���Z/e"e-j0��d"���Z1d#S)%�ImunifySecurityPluginc��d|_d|_td��}|�d��|_|�d��}|�|nt
j|_tj	��|_
tj��|_i|_
tj��|_tj��|_d|_d|_d|_d|_t-��|_t1��|_t5��|_t9��|_d|_d|_d|_ dS)Nr<�	installed�enabled)!�_loop�_sinkr�get�installation_completedr
�SECURITY_PLUGIN_ENABLED�last_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_type�installation_task�
deleting_task�install_and_update_task�set�freshly_installed_sitesr)�incident_collectorr*�incident_senderr(�changelog_processor�_site_processing_task�_stats_task�_license_reconverge_task)�self�state�persisted_enableds   r8�__init__zImunifySecurityPlugin.__init__ps/����
���
��2�3�3��&+�i�i��&<�&<��#�"�I�I�i�0�0��!�,�
���2�	
��
"(�!?�!A�!A���!'�!8�!:�!:���>@��#�(.�'K�'M�'M��$��7�9�9�	
�+�
#'���6:���26���<@��$�47�E�E��$�#4�"5�"5���-�/�/���#5�#7�#7�� �:>��"�04���=A��%�%�%r:c��
K�dS�N�)r[�loops  r8�create_sinkz!ImunifySecurityPlugin.create_sink�s�����r:c��\K�||_||_|j�|�����|_|j�|�����|_|j�|�����|_|j�|�	����|_
tr|����d{V��nt�d���|����d{V��dS)NT)�
missing_ok)r@rA�create_task�refresh_auth_files�_update_auth_task�process_wordpress_sitesrX�
send_statsrY�reconverge_license_typerZr�_apply_first_install_config�FIRST_INSTALL_FLAG�unlink� _recover_installation_on_startup)r[rb�sinks   r8�
create_sourcez#ImunifySecurityPlugin.create_source�s'������
���
�!%��!7�!7��#�#�%�%�"
�"
���&*�Z�%;�%;��(�(�*�*�&
�&
��"� �:�1�1�$�/�/�2C�2C�D�D���(,�
�(>�(>��(�(�*�*�)
�)
��%��	7��2�2�4�4�4�4�4�4�4�4�4�4��%�%��%�6�6�6��3�3�5�5�5�5�5�5�5�5�5�5�5r:c��,K�|jstjsdSt�d��d|_|�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-recoveryT�rp)
rCr
rDr5�inforE�process_installationr�install_everywhererArP�add_done_callback�_mark_installation_done�r[s r8roz6ImunifySecurityPlugin._recover_installation_on_startup�s�����
�'�	��4�	�
�F����
/�	
�	
�	
�"&����'�'��%�4�:�6�6�6�
�
�	
�	
�	
�	
�	
�	
�	
��!�-��"�4�4��,�
�
�
�
�
�.�-r:c��fK�t���sdStj������d{V��dkrKt
�t�����}t
�	d��t�
��dS)N�i�)rm�existsr�HostingPanel�users_count�FIRST_INSTALL_CONFIG_PATH�
write_text�FIRST_INSTALL_CONFIG_FILE�	read_text�chmodrn)r[�_s  r8rlz1ImunifySecurityPlugin._apply_first_install_config�s�����!�(�(�*�*�	��F��+�-�-�9�9�;�;�;�;�;�;�;�;�q�@�@�)�4�4�)�3�3�5�5���A�
&�+�+�E�2�2�2��!�!�#�#�#�#�#r:c��hK�|j���|j�d{V��|jr&|j���|j�d{V��|jr&|j���|j�d{V��|jr(|j���|j�d{V��dSdSr`)rh�cancelrXrYrZrys r8�shutdownzImunifySecurityPlugin.shutdown�s�������%�%�'�'�'��$�$�$�$�$�$�$�$��%�	-��&�-�-�/�/�/��,�,�,�,�,�,�,�,���	#���#�#�%�%�%��"�"�"�"�"�"�"�"��(�	0��)�0�0�2�2�2��/�/�/�/�/�/�/�/�/�/�	0�	0r:c���t||��st�d|��dSt||��}|duo)|���o|���S)NzUnknown task '%s'F)�hasattrr5�error�getattr�done�	cancelled)r[�task_attr_name�tasks   r8�_task_in_progressz'ImunifySecurityPlugin._task_in_progress�si���t�^�,�,�	��L�L�,�n�=�=�=��5��t�^�,�,���4��L��	�	���O�L�D�N�N�<L�<L�8L�Lr:c�@�td|j|jd���dS)Nr<)r>r?)rrCrErys r8�_save_installation_statez.ImunifySecurityPlugin._save_installation_state�s7���#�!�8��1�
�
�	
�	
�	
�	
�	
r:r�c�H�|���rt�d��dS|���}|�t�d|��dS|jst�d��dSd|_|���dS)NzInstallation task was cancelledzInstallation task failed: %sz>Feature was disabled during installation, skipping flag updateT)r�r5rt�	exceptionr�rErCr�)r[r��excs   r8rxz-ImunifySecurityPlugin._mark_installation_done�s����>�>���	��K�K�9�:�:�:��F��n�n�����?��L�L�7��=�=�=��F��%�	��K�K�'�
�
�
�
�F�&*��#��%�%�'�'�'�'�'r:F�coroc��K�|�d��r\|r|���dS|jr=|j���	|j�d{V��n#tj$rYnwxYw|�d��r0t�d��|���dSt	j|��|_	dS)NrQrPzInstallation is already running)
r��closerQr��asyncio�CancelledErrorr5�warningrfrP)r[r��
for_new_sitess   r8ruz*ImunifySecurityPlugin.process_installations������!�!�/�2�2�
	��
��
�
�������!�
��"�)�)�+�+�+���,�,�,�,�,�,�,�,�,���-�����D������!�!�"5�6�6�	��N�N�<�=�=�=��J�J�L�L�L��F�!(�!4�T�!:�!:����s�
A�A1�0A1c��PK�|�d��rD|jr=|j���	|j�d{V��n#tj$rYnwxYw|�d��rt
�d��dStj|��|_dS)NrPrQzDeleting is already running)	r�rPr�r�r�r5r�rfrQ)r[r�s  r8�process_deletingz&ImunifySecurityPlugin.process_deleting$s������!�!�"5�6�6�	��%�
��&�-�-�/�/�/���0�0�0�0�0�0�0�0�0���-�����D������!�!�/�2�2�	��N�N�8�9�9�9��F�$�0��6�6����s�
A�A�AT)�check_period_first�check_lock_period�	lock_filec��K�t��rt���d{V��tj|j����d{V��dS�Nrs)r&r'r�update_auth_everywhererArys r8rgz(ImunifySecurityPlugin.refresh_auth_files3s`�������	"��/�/�!�!�!�!�!�!�!��+���<�<�<�<�<�<�<�<�<�<�<�<r:)r��jitterr�r�c��K�tjsdS|j�dt���d{V��}d�}|j�d|���d{V��}d}d}dddd�}i}|D�]�}tj|���d{V��}	|	dz}
|
dz}|j�d|j���d{V��r|dz
}|j|vr�	|j�dtj
|j���d{V��}|j�dt|j���d{V��||j<nE#t$r8}
t�d|j|
��d	d
d�||j<Yd}
~
nd}
~
wwxYw||j}|j�dt |j|
|jt%|�d����|�d
d
�����d{V��\}}|r|dz
}||vr||xxdz
cc<���t)hd����d{V��}|�d��pd}|�d��pd}|�d��pd}|�d��pd}t+j||||t/|��|t1t3j����t1t3j����d�t1|��t1|��t1|d
��t1|d��t1|d��d����}|j�dt8j���d{V��|d<|j�|���d{V��dS)z8Send WP plugin adoption stats to the correlation server.Nc�4�ttj��5tj��ddd��n#1swxYwYt	j���tj�d�����	��S)NF)
rr �OverridingReset�resetr"�select�where�manually_deleted_at�is_null�countrar:r8�_count_manually_removedzAImunifySecurityPlugin.send_stats.<locals>._count_manually_removedLs����)�3�4�4�
"�
"���!�!�!�
"�
"�
"�
"�
"�
"�
"�
"�
"�
"�
"����
"�
"�
"�
"��$�&�&���}�8�@�@��G�G�H�H�����
s
�:�>�>r)�balanced�strict�monitorzimunify-securityz	rules.phpr{zOCould not load AI bot protection config for uid %s, counting it as disabled: %sFr�)�ai_bot_protection�presetr�r�>�imunify-core�imunify-antivirus�imunify-wp-security�imunify360-firewallr��r�r�r�)�waf_enabledr�r�r�)�waf_enabled_sites�ai_bot_protection_enabled_sites�!ai_bot_protection_preset_balanced�ai_bot_protection_preset_strict� ai_bot_protection_preset_monitor)�core_version�
av_version�firewall_version�
wp_version�installed_sites�manually_removed_sites�
server_config�stats�iaid) r
rDr@�run_in_executorr%�wp_cli�get_content_dirr|�uid�pwd�getpwuidr�pw_name�	Exceptionr5rtr�docroot�boolrBrr
�WpSecurityPluginStats�len�strrrFrKr�get_iaidrA�process_message)r[�sitesr��manually_removedr��ai_bot_enabled_sites�
preset_counts�user_ai_config�site�content_dir�data_dir�	rules_php�	pw_recordr��
hoster_cfg�
ai_enabledr��pkgsr�r�r�r��msgs                       r8rjz ImunifySecurityPlugin.send_stats>s������0�	��F��j�0�0��7J�K�K�K�K�K�K�K�K��	�	�	�"&��!;�!;��)�"
�"
�
�
�
�
�
�
���� ��%&�!��B�B�
�*,���,	/�,	/�D� &� 6�t� <� <�<�<�<�<�<�<�K�"�%7�7�H� �;�.�I��Z�/�/��i�6F�G�G�G�G�G�G�G�G�
'�!�Q�&�!��x�~�-�-��&*�j�&@�&@��c�l�D�H�'�'�!�!�!�!�!�!�I�
#�j�8�8��0�!�)���������#������!�
�
�
��K�K�;����	���.3�",�0�0�N�4�8�,�,�,�,�,�,�����
����(���1�J�'+�z�'A�'A��)�������Z�^�^�$7�8�8�9�9����x��4�4�(�(�"�"�"�"�"�"��J���
/�$��)�$��]�*�*�!�&�)�)�)�Q�.�)�)�)��)�
�
�
�
�
�
�
�
�
�
�
���X�X�1�2�2�8�b�
��8�8�$9�:�:�@�b���x�x��/�/�5�2���X�X�3�4�4�:��
��/�%�!�-�!���J�J�#3�"�6�#A�#C�#C�D�D�%(��8�:�:�&�&���&)�):�%;�%;�36�7K�3L�3L�58�!�*�-�6�6�47�!�(�+�4�4�58�!�)�,�5�5���
�
�
��6!�J�6�6��'�0�
�
�
�
�
�
�
�
��F���j�(�(��-�-�-�-�-�-�-�-�-�-�-s�A%D+�+
E-�5.E(�(E-�<c��JK�t�d��	t��}|st�d��dS|j�||j���d{V��}|r&t
d�|D��|j����d{V��|j�|d����d{V��}|j	�
|j|���d{V��td�	��dS#t$r&}t�
d
|��Yd}~dSd}~wwxYw)a
        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
        zTProcessing rule disable changelogs and collecting WordPress CVE protection incidentsz0No WordPress sites found for periodic processingNc��g|]	}|j��
Sra)�domain)�.0�ss  r8�
<listcomp>zAImunifySecurityPlugin.process_wordpress_sites.<locals>.<listcomp>�s��>�>�>�!�Q�X�>�>�>r:)�domainsrpT)�delete_after_processing�)�daysz*Error in WordPress periodic processing: %s)r5r6r%rW�process_changelogs_for_sitesrAr+rU�collect_incidents_for_sitesrV�send_incidentsr,r�r�)r[r��affected_sites�	incidents�es     r8riz-ImunifySecurityPlugin.process_wordpress_sites�s�����	���
A�	
�	
�	
�!	J�'�)�)�E��
����F�������.�K�K��4�:���������
�
�
�4�>�>�~�>�>�>��������������-�I�I��,0�J���������
��&�5�5�d�j�)�L�L�L�L�L�L�L�L�L�*��3�3�3�3�3�3���	J�	J�	J��L�L�E�q�I�I�I�I�I�I�I�I�I�����	J���s�*C2�
B&C2�2
D"�<D�D"c���K��j����fd�}��|��d����d{V��dS)z&Install plugin on new WordPress sites.c���K�tj�j����d{V��}|r�j�|��|Sr�)rrvrArT�update)r�r[s �r8�install_and_trackzFImunifySecurityPlugin._install_on_new_sites.<locals>.install_and_track�sT�����$*�$=�4�:�$N�$N�$N�N�N�N�N�N�N�O��
E��,�3�3�O�D�D�D�"�"r:T)r�N)rT�clearru)r[r�s` r8�_install_on_new_sitesz+ImunifySecurityPlugin._install_on_new_sites�s������	
�$�*�*�,�,�,�	#�	#�	#�	#�	#��'�'������(�
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
r:c��\K�tj|j|j����d{V��tj|j����d{V��t
jsW|�tj|j������d{V��d|_	d|_
|���dSdS)zCTidy up sites from which the WordPress plugin was deleted manually.)rprTNrsF)r�tidy_up_manually_deletedrArT�$fix_data_file_permissions_everywherer
rDr��remove_all_installedrCrEr�rys r8�_tidy_upzImunifySecurityPlugin._tidy_up�s������-���$(�$@�
�
�
�	
�	
�	
�	
�	
�	
�	
��9�t�z�J�J�J�J�J�J�J�J�J�J��0�	,��'�'��+���<�<�<���
�
�
�
�
�
�
�+0�D�'�%*�D�"��)�)�+�+�+�+�+�
	,�	,r:c��K�tj|j����d{V��}|r|j�|��dSdS)zFAdopt sites where plugin is installed but not tracked in our database.rsN)r�adopt_found_sitesrArTr�)r[�
adopted_sitess  r8�_adopt_found_sitesz(ImunifySecurityPlugin._adopt_found_sites
s^����$�6�D�J�G�G�G�G�G�G�G�G�G�
��	?��(�/�/�
�>�>�>�>�>�	?�	?r:c��JK�tj|j����d{V��dS)z1Update plugin on all sites where it is installed.rsN)r�update_everywhererArys r8�_update_existingz&ImunifySecurityPlugin._update_existings4�����&�D�J�7�7�7�7�7�7�7�7�7�7�7�7r:c��K�|����d{V��|jr
|j�d{V��|����d{V��|����d{V��|����d{V��dS)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)r�rPrrrrys r8�_run_install_and_updatez-ImunifySecurityPlugin._run_install_and_updates������(�(�*�*�*�*�*�*�*�*�*��!�	)��(�(�(�(�(�(�(�(��%�%�'�'�'�'�'�'�'�'�'��m�m�o�o���������#�#�%�%�%�%�%�%�%�%�%�%�%r:c��RK�t�d|j|j��|�d��r"t�d|j��dS|jdkr%|jsdS|����d{V��dS|�d��r"t�d|j��dS|�d��r"t�d|j��dS|jd	kr|����d{V��dS|jd
kr|�	���d{V��dS|jdkrP|jst�d��dStj|�����|_
dSdS)
Nz<ImunifySecurityPlugin received message action: %s method: %srRz7Install-and-update is still running, skipping action %s�install_on_new_sitesrPz1Installation is still running, skipping action %srQz5Uninstallation is already running, skipping action %s�update_existing�tidy_up�install_and_updatez>Installation is not completed yet, skipping install_and_update)r5rt�action�methodr�r�rCr�rrr�rfr
rR)r[�messages  r8�manage_plugin_actionz*ImunifySecurityPlugin.manage_plugin_action,s��������J��N��N�	
�	
�	
��!�!�";�<�<�	��N�N�I���
�
�
�
�F��>�3�3�3��.�
����,�,�.�.�.�.�.�.�.�.�.��F��!�!�"5�6�6�	��N�N�C���
�
�
�
�F��!�!�/�2�2�	��N�N�G���
�
�
�
�F��>�.�.�.��'�'�)�)�)�)�)�)�)�)�)��F��>�Y�&�&��-�-�/�/�!�!�!�!�!�!�!��F��>�1�1�1��.�
����*������
,3�+>��,�,�.�.�,�,�D�(�(�(�2�1r:c���K�t|dt��sdStj}||jkrdS||_|�r-|j�s%	t���dddii��d|_n*#t$rt�
d��YnwxYw	t���dddii��d|_tj
��|_n*#t$rt�
d��YnwxYw|�tj|j�	�����d{V��|j�!|j�|j��dSdS|sl|js|�d
��rR|�tj|j�	�����d{V��d|_|���dSdSdS)N�conf�	WORDPRESSr�Tz9waf_enabled config reset skipped, field not in schema yetr�Fz?ai_bot_protection config reset skipped, field not in schema yetrsrP)�
isinstancerr
rDrErC�dict_to_configrGrr5r6rLrrMrNrurvrArPrwrxr�r�rr�)r[r�current_config_values   r8�manage_plugin_installationz0ImunifySecurityPlugin.manage_plugin_installationjs������'�&�/�<�8�8�	��F�(�@���4�#9�9�9��F�"6����,	,��(C�,	,�
����-�-� �=�$�"7�8����*.��&�&��(�
�
�
����O������
����
����-�-� �#6��">�?����05��,��?�A�A��3�3��)�
�
�
����/������
����
�+�+��)�t�z�:�:�:���
�
�
�
�
�
�
��%�1��&�8�8��0������2�1�
&�	,��'�	,��%�%�&9�:�:�	,��'�'��+���<�<�<���
�
�
�
�
�
�
�+0�D�'��)�)�+�+�+�+�+�	,�	,�	,�	,s%�,A9�9$B �B �$AC)�)$D�Dc���K�t|dt��sdStjsdS|jsdStj��}tj��}||jkr
||j	krdS|j
�dt���d{V��}|s||_||_	dStj
|���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@r�r%�update_plugin_config_on_sitesr�)r[r�current_enabled�current_presetr��writtens      r8�manage_ai_bot_protection_configz5ImunifySecurityPlugin.manage_ai_bot_protection_config�s�����'�&�/�<�8�8�	��F��0�	�
�F��*�	�
�F� �>�@�@���D�F�F���t�;�;�;��$�"E�E�E��F��j�0�0��7J�K�K�K�K�K�K�K�K���	�+:�D�(�2@�D�/��F��<�U�C�C�C�C�C�C�C�C���c�%�j�j� � �+:�D�(�2@�D�/�/�/�!� r:c��>K�|����d{V��dS)z�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.
        N)�_reconverge_license_type_oncerys r8rkz-ImunifySecurityPlugin.reconverge_license_type�s2�����0�0�2�2�2�2�2�2�2�2�2�2�2r:c��FK�tjsdS|jsdStj��}||jkrdS|j�dt���d{V��}|s	||_dStj
|���d{V��}|t|��kr	||_dSdSr`)r
rDrCr�get_license_typerOr@r�r%rrr�)r[�currentr�rs    r8r"z3ImunifySecurityPlugin._reconverge_license_type_once�s������0�	��F��*�	��F��-�/�/���d�-�-�-��F��j�0�0��7J�K�K�K�K�K�K�K�K���	�&-�D�#��F��<�U�C�C�C�C�C�C�C�C���c�%�j�j� � �&-�D�#�#�#�!� r:c��K�tjsdS|d}|���}|�di���d��}t	|t
���r|��|j�|jd��}|�dStj
|j���d{V��}|s"|r tj|j���d{V��n#|r!|stj|j���d{V��dS||j�|j��krdS||j|j<|rtj
��s!tj|j���d{V��dStj|j���d{V��dSt	|t��r�	tj}||jkr<||_|stj���d{V��n*tj���d{V��n#t&$rYnwxYw	tj}||jkr"||_tj���d{V��dSdS#t&$rYdSwxYwdS)u\Caches are dispatch markers, not apply receipts — apply failures propagate, no auto-retry.Nrrr�)r
rD�config_to_dictrBrr	rJ�pop�usernamer�is_waf_enabled_for_user�redeploy_waf_for_user�remove_waf_rules_for_userrFr�WAF_ENABLEDrG�remove_waf_rules_for_all_sites�redeploy_waf_for_all_sites�KeyError�WAF_DEFAULTrI�apply_waf_default_change)	r[rr�config_dict�	waf_value�prev�
new_effectiver%�current_defaults	         r8�manage_waf_configz'ImunifySecurityPlugin.manage_waf_config�s�����0�	��F��v����)�)�+�+���O�O�K��4�4�8�8��G�G�	��d�J�'�'�(	<�� ��2�6�6�t�}�d�K�K���<��F�&,�&D��M�'�'�!�!�!�!�!�!�
��J�
�J� �6�t�}�E�E�E�E�E�E�E�E�E�E��J�-�J� �:�4�=�I�I�I�I�I�I�I�I�I����D�7�;�;�D�M�J�J�J�J���9B�D�'��
�6��
B�F�$B�$D�$D�
B��6�t�}�E�E�E�E�E�E�E�E�E�E�E��2�4�=�A�A�A�A�A�A�A�A�A�A�A�
��l�
+�
+�	<�

B�#�/���d�4�4�4�-4�D�*�"�B�$�C�E�E�E�E�E�E�E�E�E�E�$�?�A�A�A�A�A�A�A�A�A����
�
�
���
����
<�"+�"7��#�d�&<�<�<�-<�D�*� �9�;�;�;�;�;�;�;�;�;�;�;�=�<���
�
�
����
����	<�	<s$�G/�/
G<�;G<�H;�;
I	�I	c��K�|jsdS|�d��dks|�d��sdSt|d��}t��}|D]�}|jdkr}	tj|j��}t|��}|r|j	nd}|D]5}|j
�|��r|�||f��n�6�z#t$rY��wxYw��|st�d��dSt�dt#|����d�|D��}	t%j|j|	���d{V��t�d	t#|	����dS)
a�
        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'
            }
        )
        N�status�ok�started�filez3Cleanup finished => no sites found for cleaned hitsz1Cleanup finished => %s site(s) need to be updatedc�8�g|]\}}t|d|�����S)r�)r�r�r�)r!)r��	site_pathr�s   r8r�zIImunifySecurityPlugin.handle_malware_cleanup_finished.<locals>.<listcomp>Xs;��
�
�
��	�3�
�9�R�S�9�9�9�
�
�
r:z"%s site(s) updated after a cleanup)rErBr9rS�
resource_typer��getpwnam�userr$�pw_uid�	orig_file�
startswith�addr0r5r6rtr�r�update_data_on_sitesrA)
r[r�hits�
site_paths�hit�	user_info�
user_sitesr�r?�wordpress_sitess
          r8�handle_malware_cleanup_finishedz5ImunifySecurityPlugin.handle_malware_cleanup_finished s����� �%�	��F��;�;�x� � �D�(�(����I�0F�0F�(��F�)���);�<�<���U�U�
��	�	�C�� �F�*�*�� #��S�X� 6� 6�I�!3�I�!>�!>�J�,5�?�	�(�(�4��&0�"�"�	��=�3�3�I�>�>�"�&�N�N�I�s�+;�<�<�<�!�E�"��� �����D�����+��	��L�L�N�O�O�O��F����?��
�O�O�	
�	
�	
�
�
�",�
�
�
��
�)�$�*�o�F�F�F�F�F�F�F�F�F����8�#�o�:N�:N�O�O�O�O�Os�/A+C�
C(�'C(c���K�|jsdS|�d��dks*|�d��r|�d��sdS|d}t|��}|st�d|��dSt�dt
|����tj|j	|���d{V��t�dt
|����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;�pathr�z+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#r5r6rtr�rrGrA)r[rrPr�s    r8�handle_malware_scan_finishedz2ImunifySecurityPlugin.handle_malware_scan_finishedas����8�%�	��F�
�K�K��!�!�T�)�)��;�;�v�&�&�
*��;�;�w�'�'�
*�
�F��v���!�$�'�'���	��L�L�F��M�M�M��F�	���<�c�%�j�j�	
�	
�	
��)�$�*�e�<�<�<�<�<�<�<�<�<����5�s�5�z�z�B�B�B�B�Br:N)F)2�__name__�
__module__�__qualname__r�AV_IM360�SCOPEr^rcrqrorlr�r�r�r��Taskrxrrur�rrr�	LOCK_FILErg�SEND_WP_PLUGIN_STATS_LOCK_FILErj�SITE_PROCESSING_LOCK_FILErir�rrrr
rr
�WordpressPluginActionr�ConfigUpdaterr �LICENSE_RECONVERGE_LOCK_FILErkr"r8r�MalwareCleanupFinishedrN�MalwareScanningFinishedrQrar:r8r<r<ms>�������N�E�$B�$B�$B�L
�
�
�6�6�6�,���@$�$�$�0�0�0�$M�M�M�
�
�
�(�G�L�(�(�(�(�(;�;�y�;�;�;�;�(
7�
7�
7��_�����	���=�=�
��=�
�_�����0����q.�q.���q.�f�_��� �+�	���-J�-J�
��-J�^
�
�
� ,�,�,� ?�?�?�8�8�8�&�&�&�&�V�K�-�.�.�;�;�/�.�;�z�V�K�$�%�%�7,�7,�&�%�7,�r�V�K�$�%�%�)A�)A�&�%�)A�V�_��� �.�	���3�3�
��3�.�.�.� �V�K�$�%�%�/<�/<�&�%�/<�b�V�I�,�-�-�>P�>P�.�-�>P�@�V�I�-�.�.�4C�4C�/�.�4C�4C�4Cr:r<)Wr��loggingr��
contextlibr�pathlibr�typingr� defence360agent.contracts.configrrrr	r
�%defence360agent.contracts.hook_eventsr�!defence360agent.contracts.licenser�"defence360agent.contracts.messagesr
�!defence360agent.contracts.pluginsrrr�defence360agent.subsys.panelsr�'defence360agent.subsys.persistent_staterrr�defence360agent.utilsrrrr� defence360agent.utils.check_lockr�defence360agent.internals.iaidr�defence360agent.utils.commonr�defence360agent.wordpressrr�r�defence360agent.wordpress.utilsr�(defence360agent.wordpress.bot_protectionr�defence360agent.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,�	getLoggerrRr5rUrXrZrYr]�
CONFIG_DIRr�rrmrBr4�float�listr9r<rar:r8�<module>r|sE����������
�
�
�
���������������������������������<�;�;�;�;�;�8�8�8�8�8�8�:�:�:�:�:�:�����������
8�7�7�7�7�7�����������
������������8�7�7�7�7�7�@�@�@�@�@�@�,�,�,�,�,�,�3�3�3�3�3�3�,�,�,�,�,�,�D�D�D�D�D�D�������,�+�+�+�+�+�A�A�A�A�A�A�A�A�����������
������������������
L�K�K�K�K�K�������

��	�8�	$�	$����}�e�n�=�=�	�.�.��u�~����"4�!3��u�~�"�"�� 2�1��U�^� � ���T�A�
B�
B�
� �D�L����'�)K�K���"C�C���h�l�"��t�����
8��8�4�8�8�8�8�iC�iC�iC�iC�iC�K��iC�iC�iC�iC�iCr:defence360agent/plugins/__pycache__/wordpress.cpython-311.pyc0000644000000000000000000012056100000000000021176 0ustar  �

b�)�^�n�����ddlZddlZddlZddlmZddlmZddlmZddl	m
Z
mZmZm
Z
mZddlmZddlmZddlmZdd	lmZmZmZdd
lmZddlmZmZmZdd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;m<Z<m=Z=ddl*m>Z>m?Z?m@Z@ddlAmBZBddlCmDZDejEeF��ZGede jH��ZIede jH��ZJede jH��ZKede jH��ZLed��ZMed ��ZNeMd!zZOeMd"zZPe!jQd#d$d�%��ZRd&eSd'eTfd(�ZUGd)�d*ee��ZVdS)+�N)�suppress)�Path)�	Coroutine)�ANTIVIRUS_MODE�ConfigValidationError�SystemConfig�
UserConfig�	Wordpress)�	HookEvent)�
LicenseCLN)�MessageType)�MessageSink�
MessageSource�expect)�
hosting_panel)�
load_state�register_lock_file�
save_state)�Scope�importer�recurring_check�system_packages_info)�
check_lock)�IndependentAgentIDAPI)�DAY)�cli)�plugin)�_prepare_ai_bot_settings)�resolve_ai_bot_protection)�	tls_check)�WPSite�
WordpressSite)�get_sites_by_path�get_sites_for_user�get_installed_sites)�is_secret_expired�
rotate_secret)�ChangelogProcessor�IncidentCollector�IncidentSender)�update_disabled_rules_on_sites)�delete_old_wordpress_incidentszwp-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)�module�name�default�started_timestamp�returnc�|�t�t�d��gSt�|��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)�_MalwareHit�logger�debug�
cleaned_since)r1s �V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/wordpress.py�_get_cleaned_malware_hitsr9_s?�������I�	
�	
�	
��	��$�$�%6�7�7�7�c��eZdZejZd�Zd�Zd�Zd�Z	d�Z
d�Zd�Zd�Z
d	ejfd
�Zd$defd
�Zd�Zeedee���d���Zeeddee���d���Zeedde���d���Zd�Zd�Zd�Zd�Z d�Z!e"e#j$��d���Z%e"e#j&��d���Z'e"e#j&��d���Z(eedde)���d���Z*d�Z+e"e#j&��d ���Z,e"e-j.��d!���Z/e"e-j0��d"���Z1d#S)%�ImunifySecurityPluginc��d|_d|_td��}|�d��|_|�d��}|�|nt
j|_tj	��|_
tj��|_i|_
tj��|_tj��|_d|_d|_d|_d|_t-��|_t1��|_t5��|_t9��|_d|_d|_d|_ dS)Nr<�	installed�enabled)!�_loop�_sinkr�get�installation_completedr
�SECURITY_PLUGIN_ENABLED�last_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_type�installation_task�
deleting_task�install_and_update_task�set�freshly_installed_sitesr)�incident_collectorr*�incident_senderr(�changelog_processor�_site_processing_task�_stats_task�_license_reconverge_task)�self�state�persisted_enableds   r8�__init__zImunifySecurityPlugin.__init__ps/����
���
��2�3�3��&+�i�i��&<�&<��#�"�I�I�i�0�0��!�,�
���2�	
��
"(�!?�!A�!A���!'�!8�!:�!:���>@��#�(.�'K�'M�'M��$��7�9�9�	
�+�
#'���6:���26���<@��$�47�E�E��$�#4�"5�"5���-�/�/���#5�#7�#7�� �:>��"�04���=A��%�%�%r:c��
K�dS�N�)r[�loops  r8�create_sinkz!ImunifySecurityPlugin.create_sink�s�����r:c��\K�||_||_|j�|�����|_|j�|�����|_|j�|�����|_|j�|�	����|_
tr|����d{V��nt�d���|����d{V��dS)NT)�
missing_ok)r@rA�create_task�refresh_auth_files�_update_auth_task�process_wordpress_sitesrX�
send_statsrY�reconverge_license_typerZr�_apply_first_install_config�FIRST_INSTALL_FLAG�unlink� _recover_installation_on_startup)r[rb�sinks   r8�
create_sourcez#ImunifySecurityPlugin.create_source�s'������
���
�!%��!7�!7��#�#�%�%�"
�"
���&*�Z�%;�%;��(�(�*�*�&
�&
��"� �:�1�1�$�/�/�2C�2C�D�D���(,�
�(>�(>��(�(�*�*�)
�)
��%��	7��2�2�4�4�4�4�4�4�4�4�4�4��%�%��%�6�6�6��3�3�5�5�5�5�5�5�5�5�5�5�5r:c��,K�|jstjsdSt�d��d|_|�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-recoveryT�rp)
rCr
rDr5�inforE�process_installationr�install_everywhererArP�add_done_callback�_mark_installation_done�r[s r8roz6ImunifySecurityPlugin._recover_installation_on_startup�s�����
�'�	��4�	�
�F����
/�	
�	
�	
�"&����'�'��%�4�:�6�6�6�
�
�	
�	
�	
�	
�	
�	
�	
��!�-��"�4�4��,�
�
�
�
�
�.�-r:c��fK�t���sdStj������d{V��dkrKt
�t�����}t
�	d��t�
��dS)N�i�)rm�existsr�HostingPanel�users_count�FIRST_INSTALL_CONFIG_PATH�
write_text�FIRST_INSTALL_CONFIG_FILE�	read_text�chmodrn)r[�_s  r8rlz1ImunifySecurityPlugin._apply_first_install_config�s�����!�(�(�*�*�	��F��+�-�-�9�9�;�;�;�;�;�;�;�;�q�@�@�)�4�4�)�3�3�5�5���A�
&�+�+�E�2�2�2��!�!�#�#�#�#�#r:c��hK�|j���|j�d{V��|jr&|j���|j�d{V��|jr&|j���|j�d{V��|jr(|j���|j�d{V��dSdSr`)rh�cancelrXrYrZrys r8�shutdownzImunifySecurityPlugin.shutdown�s�������%�%�'�'�'��$�$�$�$�$�$�$�$��%�	-��&�-�-�/�/�/��,�,�,�,�,�,�,�,���	#���#�#�%�%�%��"�"�"�"�"�"�"�"��(�	0��)�0�0�2�2�2��/�/�/�/�/�/�/�/�/�/�	0�	0r:c���t||��st�d|��dSt||��}|duo)|���o|���S)NzUnknown task '%s'F)�hasattrr5�error�getattr�done�	cancelled)r[�task_attr_name�tasks   r8�_task_in_progressz'ImunifySecurityPlugin._task_in_progress�si���t�^�,�,�	��L�L�,�n�=�=�=��5��t�^�,�,���4��L��	�	���O�L�D�N�N�<L�<L�8L�Lr:c�@�td|j|jd���dS)Nr<)r>r?)rrCrErys r8�_save_installation_statez.ImunifySecurityPlugin._save_installation_state�s7���#�!�8��1�
�
�	
�	
�	
�	
�	
r:r�c�H�|���rt�d��dS|���}|�t�d|��dS|jst�d��dSd|_|���dS)NzInstallation task was cancelledzInstallation task failed: %sz>Feature was disabled during installation, skipping flag updateT)r�r5rt�	exceptionr�rErCr�)r[r��excs   r8rxz-ImunifySecurityPlugin._mark_installation_done�s����>�>���	��K�K�9�:�:�:��F��n�n�����?��L�L�7��=�=�=��F��%�	��K�K�'�
�
�
�
�F�&*��#��%�%�'�'�'�'�'r:F�coroc��K�|�d��r\|r|���dS|jr=|j���	|j�d{V��n#tj$rYnwxYw|�d��r0t�d��|���dSt	j|��|_	dS)NrQrPzInstallation is already running)
r��closerQr��asyncio�CancelledErrorr5�warningrfrP)r[r��
for_new_sitess   r8ruz*ImunifySecurityPlugin.process_installations������!�!�/�2�2�
	��
��
�
�������!�
��"�)�)�+�+�+���,�,�,�,�,�,�,�,�,���-�����D������!�!�"5�6�6�	��N�N�<�=�=�=��J�J�L�L�L��F�!(�!4�T�!:�!:����s�
A�A1�0A1c��PK�|�d��rD|jr=|j���	|j�d{V��n#tj$rYnwxYw|�d��rt
�d��dStj|��|_dS)NrPrQzDeleting is already running)	r�rPr�r�r�r5r�rfrQ)r[r�s  r8�process_deletingz&ImunifySecurityPlugin.process_deleting$s������!�!�"5�6�6�	��%�
��&�-�-�/�/�/���0�0�0�0�0�0�0�0�0���-�����D������!�!�/�2�2�	��N�N�8�9�9�9��F�$�0��6�6����s�
A�A�AT)�check_period_first�check_lock_period�	lock_filec��K�t��rt���d{V��tj|j����d{V��dS�Nrs)r&r'r�update_auth_everywhererArys r8rgz(ImunifySecurityPlugin.refresh_auth_files3s`�������	"��/�/�!�!�!�!�!�!�!��+���<�<�<�<�<�<�<�<�<�<�<�<r:)r��jitterr�r�c��K�tjsdS|j�dt���d{V��}d�}|j�d|���d{V��}d}d}dddd�}i}|D�]�}tj|���d{V��}	|	dz}
|
dz}|j�d|j���d{V��r|dz
}|j|vr�	|j�dtj
|j���d{V��}|j�dt|j���d{V��||j<nE#t$r8}
t�d|j|
��d	d
d�||j<Yd}
~
nd}
~
wwxYw||j}|j�dt |j|
|jt%|�d����|�d
d
�����d{V��\}}|r|dz
}||vr||xxdz
cc<���t)hd����d{V��}|�d��pd}|�d��pd}|�d��pd}|�d��pd}t+j||||t/|��|t1t3j����t1t3j����d�t1|��t1|��t1|d
��t1|d��t1|d��d����}|j�dt8j���d{V��|d<|j�|���d{V��dS)z8Send WP plugin adoption stats to the correlation server.Nc�4�ttj��5tj��ddd��n#1swxYwYt	j���tj�d�����	��S)NF)
rr �OverridingReset�resetr"�select�where�manually_deleted_at�is_null�countrar:r8�_count_manually_removedzAImunifySecurityPlugin.send_stats.<locals>._count_manually_removedLs����)�3�4�4�
"�
"���!�!�!�
"�
"�
"�
"�
"�
"�
"�
"�
"�
"�
"����
"�
"�
"�
"��$�&�&���}�8�@�@��G�G�H�H�����
s
�:�>�>r)�balanced�strict�monitorzimunify-securityz	rules.phpr{zOCould not load AI bot protection config for uid %s, counting it as disabled: %sFr�)�ai_bot_protection�presetr�r�>�imunify-core�imunify-antivirus�imunify-wp-security�imunify360-firewallr��r�r�r�)�waf_enabledr�r�r�)�waf_enabled_sites�ai_bot_protection_enabled_sites�!ai_bot_protection_preset_balanced�ai_bot_protection_preset_strict� ai_bot_protection_preset_monitor)�core_version�
av_version�firewall_version�
wp_version�installed_sites�manually_removed_sites�
server_config�stats�iaid) r
rDr@�run_in_executorr%�wp_cli�get_content_dirr|�uid�pwd�getpwuidr�pw_name�	Exceptionr5rtr�docroot�boolrBrr
�WpSecurityPluginStats�len�strrrFrKr�get_iaidrA�process_message)r[�sitesr��manually_removedr��ai_bot_enabled_sites�
preset_counts�user_ai_config�site�content_dir�data_dir�	rules_php�	pw_recordr��
hoster_cfg�
ai_enabledr��pkgsr�r�r�r��msgs                       r8rjz ImunifySecurityPlugin.send_stats>s������0�	��F��j�0�0��7J�K�K�K�K�K�K�K�K��	�	�	�"&��!;�!;��)�"
�"
�
�
�
�
�
�
���� ��%&�!��B�B�
�*,���,	/�,	/�D� &� 6�t� <� <�<�<�<�<�<�<�K�"�%7�7�H� �;�.�I��Z�/�/��i�6F�G�G�G�G�G�G�G�G�
'�!�Q�&�!��x�~�-�-��&*�j�&@�&@��c�l�D�H�'�'�!�!�!�!�!�!�I�
#�j�8�8��0�!�)���������#������!�
�
�
��K�K�;����	���.3�",�0�0�N�4�8�,�,�,�,�,�,�����
����(���1�J�'+�z�'A�'A��)�������Z�^�^�$7�8�8�9�9����x��4�4�(�(�"�"�"�"�"�"��J���
/�$��)�$��]�*�*�!�&�)�)�)�Q�.�)�)�)��)�
�
�
�
�
�
�
�
�
�
�
���X�X�1�2�2�8�b�
��8�8�$9�:�:�@�b���x�x��/�/�5�2���X�X�3�4�4�:��
��/�%�!�-�!���J�J�#3�"�6�#A�#C�#C�D�D�%(��8�:�:�&�&���&)�):�%;�%;�36�7K�3L�3L�58�!�*�-�6�6�47�!�(�+�4�4�58�!�)�,�5�5���
�
�
��6!�J�6�6��'�0�
�
�
�
�
�
�
�
��F���j�(�(��-�-�-�-�-�-�-�-�-�-�-s�A%D+�+
E-�5.E(�(E-�<c��JK�t�d��	t��}|st�d��dS|j�||j���d{V��}|r&t
d�|D��|j����d{V��|j�|d����d{V��}|j	�
|j|���d{V��td�	��dS#t$r&}t�
d
|��Yd}~dSd}~wwxYw)a
        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
        zTProcessing rule disable changelogs and collecting WordPress CVE protection incidentsz0No WordPress sites found for periodic processingNc��g|]	}|j��
Sra)�domain)�.0�ss  r8�
<listcomp>zAImunifySecurityPlugin.process_wordpress_sites.<locals>.<listcomp>�s��>�>�>�!�Q�X�>�>�>r:)�domainsrpT)�delete_after_processing�)�daysz*Error in WordPress periodic processing: %s)r5r6r%rW�process_changelogs_for_sitesrAr+rU�collect_incidents_for_sitesrV�send_incidentsr,r�r�)r[r��affected_sites�	incidents�es     r8riz-ImunifySecurityPlugin.process_wordpress_sites�s�����	���
A�	
�	
�	
�!	J�'�)�)�E��
����F�������.�K�K��4�:���������
�
�
�4�>�>�~�>�>�>��������������-�I�I��,0�J���������
��&�5�5�d�j�)�L�L�L�L�L�L�L�L�L�*��3�3�3�3�3�3���	J�	J�	J��L�L�E�q�I�I�I�I�I�I�I�I�I�����	J���s�*C2�
B&C2�2
D"�<D�D"c���K��j����fd�}��|��d����d{V��dS)z&Install plugin on new WordPress sites.c���K�tj�j����d{V��}|r�j�|��|Sr�)rrvrArT�update)r�r[s �r8�install_and_trackzFImunifySecurityPlugin._install_on_new_sites.<locals>.install_and_track�sT�����$*�$=�4�:�$N�$N�$N�N�N�N�N�N�N�O��
E��,�3�3�O�D�D�D�"�"r:T)r�N)rT�clearru)r[r�s` r8�_install_on_new_sitesz+ImunifySecurityPlugin._install_on_new_sites�s������	
�$�*�*�,�,�,�	#�	#�	#�	#�	#��'�'������(�
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
r:c��\K�tj|j|j����d{V��tj|j����d{V��t
jsW|�tj|j������d{V��d|_	d|_
|���dSdS)zCTidy up sites from which the WordPress plugin was deleted manually.)rprTNrsF)r�tidy_up_manually_deletedrArT�$fix_data_file_permissions_everywherer
rDr��remove_all_installedrCrEr�rys r8�_tidy_upzImunifySecurityPlugin._tidy_up�s������-���$(�$@�
�
�
�	
�	
�	
�	
�	
�	
�	
��9�t�z�J�J�J�J�J�J�J�J�J�J��0�	,��'�'��+���<�<�<���
�
�
�
�
�
�
�+0�D�'�%*�D�"��)�)�+�+�+�+�+�
	,�	,r:c��K�tj|j����d{V��}|r|j�|��dSdS)zFAdopt sites where plugin is installed but not tracked in our database.rsN)r�adopt_found_sitesrArTr�)r[�
adopted_sitess  r8�_adopt_found_sitesz(ImunifySecurityPlugin._adopt_found_sites
s^����$�6�D�J�G�G�G�G�G�G�G�G�G�
��	?��(�/�/�
�>�>�>�>�>�	?�	?r:c��JK�tj|j����d{V��dS)z1Update plugin on all sites where it is installed.rsN)r�update_everywhererArys r8�_update_existingz&ImunifySecurityPlugin._update_existings4�����&�D�J�7�7�7�7�7�7�7�7�7�7�7�7r:c��K�|����d{V��|jr
|j�d{V��|����d{V��|����d{V��|����d{V��dS)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)r�rPrrrrys r8�_run_install_and_updatez-ImunifySecurityPlugin._run_install_and_updates������(�(�*�*�*�*�*�*�*�*�*��!�	)��(�(�(�(�(�(�(�(��%�%�'�'�'�'�'�'�'�'�'��m�m�o�o���������#�#�%�%�%�%�%�%�%�%�%�%�%r:c��RK�t�d|j|j��|�d��r"t�d|j��dS|jdkr%|jsdS|����d{V��dS|�d��r"t�d|j��dS|�d��r"t�d|j��dS|jd	kr|����d{V��dS|jd
kr|�	���d{V��dS|jdkrP|jst�d��dStj|�����|_
dSdS)
Nz<ImunifySecurityPlugin received message action: %s method: %srRz7Install-and-update is still running, skipping action %s�install_on_new_sitesrPz1Installation is still running, skipping action %srQz5Uninstallation is already running, skipping action %s�update_existing�tidy_up�install_and_updatez>Installation is not completed yet, skipping install_and_update)r5rt�action�methodr�r�rCr�rrr�rfr
rR)r[�messages  r8�manage_plugin_actionz*ImunifySecurityPlugin.manage_plugin_action,s��������J��N��N�	
�	
�	
��!�!�";�<�<�	��N�N�I���
�
�
�
�F��>�3�3�3��.�
����,�,�.�.�.�.�.�.�.�.�.��F��!�!�"5�6�6�	��N�N�C���
�
�
�
�F��!�!�/�2�2�	��N�N�G���
�
�
�
�F��>�.�.�.��'�'�)�)�)�)�)�)�)�)�)��F��>�Y�&�&��-�-�/�/�!�!�!�!�!�!�!��F��>�1�1�1��.�
����*������
,3�+>��,�,�.�.�,�,�D�(�(�(�2�1r:c���K�t|dt��sdStj}||jkrdS||_|�r-|j�s%	t���dddii��d|_n*#t$rt�
d��YnwxYw	t���dddii��d|_tj
��|_n*#t$rt�
d��YnwxYw|�tj|j�	�����d{V��|j�!|j�|j��dSdS|sl|js|�d
��rR|�tj|j�	�����d{V��d|_|���dSdSdS)N�conf�	WORDPRESSr�Tz9waf_enabled config reset skipped, field not in schema yetr�Fz?ai_bot_protection config reset skipped, field not in schema yetrsrP)�
isinstancerr
rDrErC�dict_to_configrGrr5r6rLrrMrNrurvrArPrwrxr�r�rr�)r[r�current_config_values   r8�manage_plugin_installationz0ImunifySecurityPlugin.manage_plugin_installationjs������'�&�/�<�8�8�	��F�(�@���4�#9�9�9��F�"6����,	,��(C�,	,�
����-�-� �=�$�"7�8����*.��&�&��(�
�
�
����O������
����
����-�-� �#6��">�?����05��,��?�A�A��3�3��)�
�
�
����/������
����
�+�+��)�t�z�:�:�:���
�
�
�
�
�
�
��%�1��&�8�8��0������2�1�
&�	,��'�	,��%�%�&9�:�:�	,��'�'��+���<�<�<���
�
�
�
�
�
�
�+0�D�'��)�)�+�+�+�+�+�	,�	,�	,�	,s%�,A9�9$B �B �$AC)�)$D�Dc���K�t|dt��sdStjsdS|jsdStj��}tj��}||jkr
||j	krdS|j
�dt���d{V��}|s||_||_	dStj
|���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@r�r%�update_plugin_config_on_sitesr�)r[r�current_enabled�current_presetr��writtens      r8�manage_ai_bot_protection_configz5ImunifySecurityPlugin.manage_ai_bot_protection_config�s�����'�&�/�<�8�8�	��F��0�	�
�F��*�	�
�F� �>�@�@���D�F�F���t�;�;�;��$�"E�E�E��F��j�0�0��7J�K�K�K�K�K�K�K�K���	�+:�D�(�2@�D�/��F��<�U�C�C�C�C�C�C�C�C���c�%�j�j� � �+:�D�(�2@�D�/�/�/�!� r:c��>K�|����d{V��dS)z�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.
        N)�_reconverge_license_type_oncerys r8rkz-ImunifySecurityPlugin.reconverge_license_type�s2�����0�0�2�2�2�2�2�2�2�2�2�2�2r:c��FK�tjsdS|jsdStj��}||jkrdS|j�dt���d{V��}|s	||_dStj
|���d{V��}|t|��kr	||_dSdSr`)r
rDrCr�get_license_typerOr@r�r%rrr�)r[�currentr�rs    r8r"z3ImunifySecurityPlugin._reconverge_license_type_once�s������0�	��F��*�	��F��-�/�/���d�-�-�-��F��j�0�0��7J�K�K�K�K�K�K�K�K���	�&-�D�#��F��<�U�C�C�C�C�C�C�C�C���c�%�j�j� � �&-�D�#�#�#�!� r:c��K�tjsdS|d}|���}|�di���d��}t	|t
���r|��|j�|jd��}|�dStj
|j���d{V��}|s"|r tj|j���d{V��n#|r!|stj|j���d{V��dS||j�|j��krdS||j|j<|rtj
��s!tj|j���d{V��dStj|j���d{V��dSt	|t��r�	tj}||jkr<||_|stj���d{V��n*tj���d{V��n#t&$rYnwxYw	tj}||jkr"||_tj���d{V��dSdS#t&$rYdSwxYwdS)u\Caches are dispatch markers, not apply receipts — apply failures propagate, no auto-retry.Nrrr�)r
rD�config_to_dictrBrr	rJ�pop�usernamer�is_waf_enabled_for_user�redeploy_waf_for_user�remove_waf_rules_for_userrFr�WAF_ENABLEDrG�remove_waf_rules_for_all_sites�redeploy_waf_for_all_sites�KeyError�WAF_DEFAULTrI�apply_waf_default_change)	r[rr�config_dict�	waf_value�prev�
new_effectiver%�current_defaults	         r8�manage_waf_configz'ImunifySecurityPlugin.manage_waf_config�s�����0�	��F��v����)�)�+�+���O�O�K��4�4�8�8��G�G�	��d�J�'�'�(	<�� ��2�6�6�t�}�d�K�K���<��F�&,�&D��M�'�'�!�!�!�!�!�!�
��J�
�J� �6�t�}�E�E�E�E�E�E�E�E�E�E��J�-�J� �:�4�=�I�I�I�I�I�I�I�I�I����D�7�;�;�D�M�J�J�J�J���9B�D�'��
�6��
B�F�$B�$D�$D�
B��6�t�}�E�E�E�E�E�E�E�E�E�E�E��2�4�=�A�A�A�A�A�A�A�A�A�A�A�
��l�
+�
+�	<�

B�#�/���d�4�4�4�-4�D�*�"�B�$�C�E�E�E�E�E�E�E�E�E�E�$�?�A�A�A�A�A�A�A�A�A����
�
�
���
����
<�"+�"7��#�d�&<�<�<�-<�D�*� �9�;�;�;�;�;�;�;�;�;�;�;�=�<���
�
�
����
����	<�	<s$�G/�/
G<�;G<�H;�;
I	�I	c��K�|jsdS|�d��dks|�d��sdSt|d��}t��}|D]�}|jdkr}	tj|j��}t|��}|r|j	nd}|D]5}|j
�|��r|�||f��n�6�z#t$rY��wxYw��|st�d��dSt�dt#|����d�|D��}	t%j|j|	���d{V��t�d	t#|	����dS)
a�
        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'
            }
        )
        N�status�ok�started�filez3Cleanup finished => no sites found for cleaned hitsz1Cleanup finished => %s site(s) need to be updatedc�8�g|]\}}t|d|�����S)r�)r�r�r�)r!)r��	site_pathr�s   r8r�zIImunifySecurityPlugin.handle_malware_cleanup_finished.<locals>.<listcomp>Xs;��
�
�
��	�3�
�9�R�S�9�9�9�
�
�
r:z"%s site(s) updated after a cleanup)rErBr9rS�
resource_typer��getpwnam�userr$�pw_uid�	orig_file�
startswith�addr0r5r6rtr�r�update_data_on_sitesrA)
r[r�hits�
site_paths�hit�	user_info�
user_sitesr�r?�wordpress_sitess
          r8�handle_malware_cleanup_finishedz5ImunifySecurityPlugin.handle_malware_cleanup_finished s����� �%�	��F��;�;�x� � �D�(�(����I�0F�0F�(��F�)���);�<�<���U�U�
��	�	�C�� �F�*�*�� #��S�X� 6� 6�I�!3�I�!>�!>�J�,5�?�	�(�(�4��&0�"�"�	��=�3�3�I�>�>�"�&�N�N�I�s�+;�<�<�<�!�E�"��� �����D�����+��	��L�L�N�O�O�O��F����?��
�O�O�	
�	
�	
�
�
�",�
�
�
��
�)�$�*�o�F�F�F�F�F�F�F�F�F����8�#�o�:N�:N�O�O�O�O�Os�/A+C�
C(�'C(c���K�|jsdS|�d��dks*|�d��r|�d��sdS|d}t|��}|st�d|��dSt�dt
|����tj|j	|���d{V��t�dt
|����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;�pathr�z+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#r5r6rtr�rrGrA)r[rrPr�s    r8�handle_malware_scan_finishedz2ImunifySecurityPlugin.handle_malware_scan_finishedas����8�%�	��F�
�K�K��!�!�T�)�)��;�;�v�&�&�
*��;�;�w�'�'�
*�
�F��v���!�$�'�'���	��L�L�F��M�M�M��F�	���<�c�%�j�j�	
�	
�	
��)�$�*�e�<�<�<�<�<�<�<�<�<����5�s�5�z�z�B�B�B�B�Br:N)F)2�__name__�
__module__�__qualname__r�AV_IM360�SCOPEr^rcrqrorlr�r�r�r��Taskrxrrur�rrr�	LOCK_FILErg�SEND_WP_PLUGIN_STATS_LOCK_FILErj�SITE_PROCESSING_LOCK_FILErir�rrrr
rr
�WordpressPluginActionr�ConfigUpdaterr �LICENSE_RECONVERGE_LOCK_FILErkr"r8r�MalwareCleanupFinishedrN�MalwareScanningFinishedrQrar:r8r<r<ms>�������N�E�$B�$B�$B�L
�
�
�6�6�6�,���@$�$�$�0�0�0�$M�M�M�
�
�
�(�G�L�(�(�(�(�(;�;�y�;�;�;�;�(
7�
7�
7��_�����	���=�=�
��=�
�_�����0����q.�q.���q.�f�_��� �+�	���-J�-J�
��-J�^
�
�
� ,�,�,� ?�?�?�8�8�8�&�&�&�&�V�K�-�.�.�;�;�/�.�;�z�V�K�$�%�%�7,�7,�&�%�7,�r�V�K�$�%�%�)A�)A�&�%�)A�V�_��� �.�	���3�3�
��3�.�.�.� �V�K�$�%�%�/<�/<�&�%�/<�b�V�I�,�-�-�>P�>P�.�-�>P�@�V�I�-�.�.�4C�4C�/�.�4C�4C�4Cr:r<)Wr��loggingr��
contextlibr�pathlibr�typingr� defence360agent.contracts.configrrrr	r
�%defence360agent.contracts.hook_eventsr�!defence360agent.contracts.licenser�"defence360agent.contracts.messagesr
�!defence360agent.contracts.pluginsrrr�defence360agent.subsys.panelsr�'defence360agent.subsys.persistent_staterrr�defence360agent.utilsrrrr� defence360agent.utils.check_lockr�defence360agent.internals.iaidr�defence360agent.utils.commonr�defence360agent.wordpressrr�r�defence360agent.wordpress.utilsr�(defence360agent.wordpress.bot_protectionr�defence360agent.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,�	getLoggerrRr5rUrXrZrYr]�
CONFIG_DIRr�rrmrBr4�float�listr9r<rar:r8�<module>r|sE����������
�
�
�
���������������������������������<�;�;�;�;�;�8�8�8�8�8�8�:�:�:�:�:�:�����������
8�7�7�7�7�7�����������
������������8�7�7�7�7�7�@�@�@�@�@�@�,�,�,�,�,�,�3�3�3�3�3�3�,�,�,�,�,�,�D�D�D�D�D�D�������,�+�+�+�+�+�A�A�A�A�A�A�A�A�����������
������������������
L�K�K�K�K�K�������

��	�8�	$�	$����}�e�n�=�=�	�.�.��u�~����"4�!3��u�~�"�"�� 2�1��U�^� � ���T�A�
B�
B�
� �D�L����'�)K�K���"C�C���h�l�"��t�����
8��8�4�8�8�8�8�iC�iC�iC�iC�iC�K��iC�iC�iC�iC�i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__}(<items={len(batch)}>) "
                    "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.py0000644000000000000000000003430000000000000014057 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 (
    Message,
    MessageList,
    MessageType,
)
from defence360agent.contracts.plugins import MessageSink, expect
from defence360agent.internals import feature_flags
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
    # 50 second because it should be less than DefaultTimeoutStopSec
    _SHUTDOWN_SEND_TIMEOUT = 50

    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._sender_task = loop.create_task(self._send())
        self._invoke_send_message_task = loop.create_task(
            self._invoke_send_message()
        )

    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)
        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()

    @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 _requeue_message(self, message, timestamp):
        message["api_retries_count"] = message.get("api_retries_count", 0) + 1
        self._pending.put(
            self._encode_data_to_put_in_queue(message), timestamp=timestamp
        )

    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, timestamp, message_bytes):
        """Try sending a single message.

        Returns (stop, failed) where *stop* is True if further sends
        should stop (shutdown or server-level error) and *failed* is
        True when the message could not be delivered.
        """
        if self._shutting_down.is_set():
            logger.warning(
                "Shutdown signal received, saving remaining messages"
            )
            self._pending.put(message_bytes, timestamp=timestamp)
            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,"
                    " saving remaining messages"
                )
                self._pending.put(message_bytes, timestamp=timestamp)
                return True, False
            publisher.report(message, _reporter_gen_sent, stage="agent-sent")
            logger.info("message sent %s", msg_info)
            return False, False
        except (APIErrorTooManyRequests, APITokenError) as exc:
            logger.warning(
                "Failed to send message %s to server: %s", msg_info, exc
            )
            self._requeue_message(message, timestamp)
            return True, True
        except APIError as exc:
            logger.warning(
                "Failed to send message %s to server: %s", msg_info, exc
            )
            self._requeue_message(message, timestamp)
            return False, True

    async def _send_pending_messages(self) -> None:
        messages = self._pending.pop_all()
        logger.info("Sending %s messages", len(messages))
        failure_count = 0
        processed = 0
        with self._get_api() as api:
            if api.server_id is None:
                for timestamp, message_bytes in messages:
                    self._pending.put(message_bytes, timestamp=timestamp)
                return
            try:
                for timestamp, message_bytes in messages:
                    stop, failed = await self._try_send_one(
                        api, timestamp, message_bytes
                    )
                    processed += 1
                    if failed:
                        failure_count += 1
                    if stop:
                        # Re-queue remaining messages without sending
                        remaining = messages[processed:]
                        for ts, mb in remaining:
                            self._pending.put(mb, timestamp=ts)
                        if failed:
                            # Server error — remaining messages can't
                            # be delivered either, count them as failed.
                            failure_count += len(remaining)
                        processed = len(messages)
                        break
            finally:
                # Re-queue any messages not yet processed (e.g., if
                # CancelledError from shutdown timeout interrupted us).
                for timestamp, message_bytes in messages[processed:]:
                    self._pending.put(message_bytes, timestamp=timestamp)
        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
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.py0000644000000000000000000000551600000000000016123 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.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):
        async for msg in self._create_domain_list_msg():
            await self._sink.process_message(msg)
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.py0000644000000000000000000010342400000000000014635 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:
            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
            incidents = (
                await self.incident_collector.collect_incidents_for_sites(
                    sites,
                    delete_after_processing=True,
                )
            )

            await self.incident_sender.send_incidents(self._sink, incidents)

            delete_old_wordpress_incidents(days=30)

        except Exception as e:
            logger.error("Error in WordPress periodic processing: %s", e)

    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:
            # Reset waf_enabled to True when the plugin is re-enabled so that
            # any stale "False" from before the plugin was disabled is cleared.
            # This keeps the config and actual deployment state in sync.
            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"
                )
            # Clear any stale "True" written while the plugin was off
            # so the feature stays opt-in across re-enable.
            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"
                )
            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
            # resets ai_bot_protection to False and the install flow
            # writes plugin_config.php for every site, so propagating
            # here would race with the reset and leave stale values on
            # sites that 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(), "<string>", "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__.py0000644000000000000000000000126600000000000014670 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 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

__all__ = [
    "ERROR",
    "SUCCESS",
    "WARNING",
    "ResponseError",
    "ServiceStateError",
    "SocketError",
    "Endpoints",
    "UserType",
    "is_running",
    "ValidationError",
]
defence360agent/rpc_tools/__pycache__/0000755000000000000000000000000000000000000014762 5ustar  defence360agent/rpc_tools/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000000175500000000000022172 0ustar  �

��b�
wH��b�dZddlmZmZmZddlmZmZmZddl	m
Z
mZddlm
Z
ddlmZgd�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.
�)�ERROR�SUCCESS�WARNING�)�
ResponseError�ServiceStateError�SocketError)�	Endpoints�UserType)�
is_running)�ValidationError)
rrrrrr	r
rrr
N)�__doc__�defence360agent.utils.clirrr�
exceptionsrrr	�lookupr
r�utilsr�validater
�__all__���W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/rpc_tools/__init__.py�<module>rs�����>�=�=�=�=�=�=�=�=�=�E�E�E�E�E�E�E�E�E�E�'�'�'�'�'�'�'�'�������%�%�%�%�%�%������rdefence360agent/rpc_tools/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000175500000000000021233 0ustar  �

��b�
wH��b�dZddlmZmZmZddlmZmZmZddl	m
Z
mZddlm
Z
ddlmZgd�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.
�)�ERROR�SUCCESS�WARNING�)�
ResponseError�ServiceStateError�SocketError)�	Endpoints�UserType)�
is_running)�ValidationError)
rrrrrr	r
rrr
N)�__doc__�defence360agent.utils.clirrr�
exceptionsrrr	�lookupr
r�utilsr�validater
�__all__���W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/rpc_tools/__init__.py�<module>rs�����>�=�=�=�=�=�=�=�=�=�E�E�E�E�E�E�E�E�E�E�'�'�'�'�'�'�'�'�������%�%�%�%�%�%������rdefence360agent/rpc_tools/__pycache__/exceptions.cpython-311.opt-1.pyc0000644000000000000000000000335100000000000022606 0ustar  �

,��\N]'���ddlmZGd�de��ZGd�de��ZGd�de��ZGd�d	e��ZGd
�de��ZdS)
�)�configc��eZdZdS)�RpcErrorN��__name__�
__module__�__qualname__���Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/rpc_tools/exceptions.pyrr��������Drrc��eZdZdS)�
ResponseErrorNrr
rrrrr
rrc��eZdZdS)�SocketErrorNrr
rrrrr
rrc� ��eZdZd�fd�	Z�xZS)�ServiceStateError�stoppedc���t���d�tjj|����dS)Nz{} service is {}.)�super�__init__�formatr�Core�PRODUCT)�self�state�	__class__s  �rrzServiceStateError.__init__sA���
������&�&�v�{�':�E�B�B�	
�	
�	
�	
�	
r)r)rrr	r�
__classcell__)rs@rrrs=�������
�
�
�
�
�
�
�
�
�
rrc��eZdZdS)�NonRootValidationErrorNrr
rrr r r
rr N)�defence360agent.contractsr�RuntimeErrorrrrrr r
rr�<module>r#s���,�,�,�,�,�,�	�	�	�	�	�|�	�	�	�	�	�	�	�	�H�	�	�	�	�	�	�	�	�(�	�	�	�
�
�
�
�
��
�
�
�	�	�	�	�	�X�	�	�	�	�	rdefence360agent/rpc_tools/__pycache__/exceptions.cpython-311.pyc0000644000000000000000000000335100000000000021647 0ustar  �

,��\N]'���ddlmZGd�de��ZGd�de��ZGd�de��ZGd�d	e��ZGd
�de��ZdS)
�)�configc��eZdZdS)�RpcErrorN��__name__�
__module__�__qualname__���Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/rpc_tools/exceptions.pyrr��������Drrc��eZdZdS)�
ResponseErrorNrr
rrrrr
rrc��eZdZdS)�SocketErrorNrr
rrrrr
rrc� ��eZdZd�fd�	Z�xZS)�ServiceStateError�stoppedc���t���d�tjj|����dS)Nz{} service is {}.)�super�__init__�formatr�Core�PRODUCT)�self�state�	__class__s  �rrzServiceStateError.__init__sA���
������&�&�v�{�':�E�B�B�	
�	
�	
�	
�	
r)r)rrr	r�
__classcell__)rs@rrrs=�������
�
�
�
�
�
�
�
�
�
rrc��eZdZdS)�NonRootValidationErrorNrr
rrr r r
rr N)�defence360agent.contractsr�RuntimeErrorrrrrr r
rr�<module>r#s���,�,�,�,�,�,�	�	�	�	�	�|�	�	�	�	�	�	�	�	�H�	�	�	�	�	�	�	�	�(�	�	�	�
�
�
�
�
��
�
�
�	�	�	�	�	�X�	�	�	�	�	rdefence360agent/rpc_tools/__pycache__/lookup.cpython-311.opt-1.pyc0000644000000000000000000001637300000000000021746 0ustar  �

	N�Cl����"�ddlZddlZddlmZddlmZddlmZddlm	Z	dZ
Gd�d	e��ZGd
�de��Z
Gd�d
��ZGd�de��ZGd�de��ZGd�de��Zeje
fzZeejfd�Zd�ZdS)�N)�Any)�UserType)�Scope�)�RpcError�
__rpc_commandc��eZdZdS)�DuplicateHandlerErrorN��__name__�
__module__�__qualname__���U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/rpc_tools/lookup.pyr
r

��������Drr
c��eZdZdS)�NotCoroutineErrorNrrrrrrrrrc����eZdZdZejZe��Ze	j
ie	jiiZgZ
�fd�Zed���Zd�Zee	j
fdefd���Zed
d���Zed	���Z�xZS)�	Endpointsz\Endpoints class implements registration and lookup for functions
    implementing RPC calls.c�n��t��jdi|��|j�|��dS)Nr)�super�__init_subclass__�_subclasses�append)�cls�kwargs�	__class__s  �rrzEndpoints.__init_subclass__!s<���!����!�+�+�F�+�+�+�����s�#�#�#�#�#rc�x�g}|jD]/}tj|d���}|r|�|���0|S)Nc�.�t|td��S�N)�getattr�	_RPC_MARK)�items r�<lambda>z0Endpoints.get_active_endpoints.<locals>.<lambda>+s��W�T�9�d�%C�%C�r)r�inspect�
getmembersr)r�active_endpoints�subcls�rpc_handlerss    r�get_active_endpointszEndpoints.get_active_endpoints%s[�����o�	0�	0�F�"�-��C�C���L��
0� �'�'��/�/�/���rc��||_dSr!)�_sink)�self�sinks  r�__init__zEndpoints.__init__1s
����
�
�
r�returnc��.K�|d}t|��}||j|vr+tdd�|d��z���|j||\}}t	||��|��}|di|d���d{V��S)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_MAPr�joinr")	r�requestr/�userr3�key�cls_handler�handler_name�handlers	         r�route_to_endpointzEndpoints.route_to_endpoint4s������)�$���G�n�n���c�'��-�-�-��8��(�(�7�9�-�.�.�/���
�%(�$5�d�$;�C�$@�!��\��+�+�d�+�+�\�:�:���W�1�1�w�x�0�1�1�1�1�1�1�1�1�1rNc��t|��D]�}|�d��r�t||��}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&�iscoroutinefunctionr�APPLICABLE_USER_TYPESr7�formatr
)r�name�attrr3�	user_type�msgs      r�register_rpc_handlerszEndpoints.register_rpc_handlersHs��
��H�H�	D�	D�D����s�#�#�
���3��%�%�D��d�I�t�4�4�G�����.�t�4�4�
?�'�(=�>�>�>� �6�
D�
D�	��c�/�	�:�:�:�K���#�%��-�i�8��A� �	���0��4�4�4�9<�d���!�)�,�W�5�5�
D�	D�	Drc�P�tjtjhD]}i|j|<�
dS)z)Clears all previously made registrations.N)r�NON_ROOT�ROOTr7)rrIs  r�reset_rpc_handlerszEndpoints.reset_rpc_handlersds7��#�+�X�]�;�	.�	.�I�+-�C��i�(�(�	.�	.r)r1N)rr
r�__doc__r�AV_IM360�SCOPE�setrErrNrMr7rr�classmethodr+r0rr?rKrO�
__classcell__)rs@rrrs���������
�N�E��C�E�E���
�r���2��M��K�$�$�$�$�$��	 �	 ��[�	 �����9A��2�2�3�2�2�2��[�2�&�D�D�D��[�D�6�.�.��[�.�.�.�.�.rrc�.�eZdZdZejejhZdS)�CommonEndpointsz5Endpoints available both for root and non root users.N)rr
rrPrrMrNrErrrrWrWks&������?�?�%�.��
�>���rrWc�"�eZdZdZejhZdS)�
RootEndpointsz'Endpoints available only for root user.N)rr
rrPrrNrErrrrYrYqs ������1�1�%�]�O���rrYc�"�eZdZdZejhZdS)�UserOnlyEndpointsz,Endpoints available only for non root users.N)rr
rrPrrMrErrrr[r[ws"������6�6�%�.�/���rr[c�F�tjtj|||���S)z4Decorator replacing functools.wraps for rpc handlers��wrapped�assigned�updated)�	functools�partial�update_wrapperr]s   r�wrapsrd�s-����� ����	���rc����fd�}|S)z4Mark a function as processing RPC calls for command.c�4��t|t���|Sr!)�setattrr#)�funcr3s �r�	decoratorzbind.<locals>.decorator�s�����i��)�)�)��rr)r3ris` r�bindrj�s$���������r)rar&�typingr� defence360agent.contracts.configr�defence360agent.utilsr�
exceptionsrr#�	Exceptionr
rrrWrYr[�WRAPPER_ASSIGNMENTS�LOOKUP_ASSIGNMENTS�WRAPPER_UPDATESrdrjrrr�<module>rss�����������������5�5�5�5�5�5�'�'�'�'�'�'� � � � � � ��	�	�	�	�	�	�I�	�	�	�	�	�	�	�	�	�	�	�	�S.�S.�S.�S.�S.�S.�S.�S.�l?�?�?�?�?�i�?�?�?�,�,�,�,�,�I�,�,�,�0�0�0�0�0�	�0�0�0��2�i�\�A��)�)�2K�	�	�	�	�����rdefence360agent/rpc_tools/__pycache__/lookup.cpython-311.pyc0000644000000000000000000001637300000000000021007 0ustar  �

	N�Cl����"�ddlZddlZddlmZddlmZddlmZddlm	Z	dZ
Gd�d	e��ZGd
�de��Z
Gd�d
��ZGd�de��ZGd�de��ZGd�de��Zeje
fzZeejfd�Zd�ZdS)�N)�Any)�UserType)�Scope�)�RpcError�
__rpc_commandc��eZdZdS)�DuplicateHandlerErrorN��__name__�
__module__�__qualname__���U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/rpc_tools/lookup.pyr
r

��������Drr
c��eZdZdS)�NotCoroutineErrorNrrrrrrrrrc����eZdZdZejZe��Ze	j
ie	jiiZgZ
�fd�Zed���Zd�Zee	j
fdefd���Zed
d���Zed	���Z�xZS)�	Endpointsz\Endpoints class implements registration and lookup for functions
    implementing RPC calls.c�n��t��jdi|��|j�|��dS)Nr)�super�__init_subclass__�_subclasses�append)�cls�kwargs�	__class__s  �rrzEndpoints.__init_subclass__!s<���!����!�+�+�F�+�+�+�����s�#�#�#�#�#rc�x�g}|jD]/}tj|d���}|r|�|���0|S)Nc�.�t|td��S�N)�getattr�	_RPC_MARK)�items r�<lambda>z0Endpoints.get_active_endpoints.<locals>.<lambda>+s��W�T�9�d�%C�%C�r)r�inspect�
getmembersr)r�active_endpoints�subcls�rpc_handlerss    r�get_active_endpointszEndpoints.get_active_endpoints%s[�����o�	0�	0�F�"�-��C�C���L��
0� �'�'��/�/�/���rc��||_dSr!)�_sink)�self�sinks  r�__init__zEndpoints.__init__1s
����
�
�
r�returnc��.K�|d}t|��}||j|vr+tdd�|d��z���|j||\}}t	||��|��}|di|d���d{V��S)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_MAPr�joinr")	r�requestr/�userr3�key�cls_handler�handler_name�handlers	         r�route_to_endpointzEndpoints.route_to_endpoint4s������)�$���G�n�n���c�'��-�-�-��8��(�(�7�9�-�.�.�/���
�%(�$5�d�$;�C�$@�!��\��+�+�d�+�+�\�:�:���W�1�1�w�x�0�1�1�1�1�1�1�1�1�1rNc��t|��D]�}|�d��r�t||��}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&�iscoroutinefunctionr�APPLICABLE_USER_TYPESr7�formatr
)r�name�attrr3�	user_type�msgs      r�register_rpc_handlerszEndpoints.register_rpc_handlersHs��
��H�H�	D�	D�D����s�#�#�
���3��%�%�D��d�I�t�4�4�G�����.�t�4�4�
?�'�(=�>�>�>� �6�
D�
D�	��c�/�	�:�:�:�K���#�%��-�i�8��A� �	���0��4�4�4�9<�d���!�)�,�W�5�5�
D�	D�	Drc�P�tjtjhD]}i|j|<�
dS)z)Clears all previously made registrations.N)r�NON_ROOT�ROOTr7)rrIs  r�reset_rpc_handlerszEndpoints.reset_rpc_handlersds7��#�+�X�]�;�	.�	.�I�+-�C��i�(�(�	.�	.r)r1N)rr
r�__doc__r�AV_IM360�SCOPE�setrErrNrMr7rr�classmethodr+r0rr?rKrO�
__classcell__)rs@rrrs���������
�N�E��C�E�E���
�r���2��M��K�$�$�$�$�$��	 �	 ��[�	 �����9A��2�2�3�2�2�2��[�2�&�D�D�D��[�D�6�.�.��[�.�.�.�.�.rrc�.�eZdZdZejejhZdS)�CommonEndpointsz5Endpoints available both for root and non root users.N)rr
rrPrrMrNrErrrrWrWks&������?�?�%�.��
�>���rrWc�"�eZdZdZejhZdS)�
RootEndpointsz'Endpoints available only for root user.N)rr
rrPrrNrErrrrYrYqs ������1�1�%�]�O���rrYc�"�eZdZdZejhZdS)�UserOnlyEndpointsz,Endpoints available only for non root users.N)rr
rrPrrMrErrrr[r[ws"������6�6�%�.�/���rr[c�F�tjtj|||���S)z4Decorator replacing functools.wraps for rpc handlers��wrapped�assigned�updated)�	functools�partial�update_wrapperr]s   r�wrapsrd�s-����� ����	���rc����fd�}|S)z4Mark a function as processing RPC calls for command.c�4��t|t���|Sr!)�setattrr#)�funcr3s �r�	decoratorzbind.<locals>.decorator�s�����i��)�)�)��rr)r3ris` r�bindrj�s$���������r)rar&�typingr� defence360agent.contracts.configr�defence360agent.utilsr�
exceptionsrr#�	Exceptionr
rrrWrYr[�WRAPPER_ASSIGNMENTS�LOOKUP_ASSIGNMENTS�WRAPPER_UPDATESrdrjrrr�<module>rss�����������������5�5�5�5�5�5�'�'�'�'�'�'� � � � � � ��	�	�	�	�	�	�I�	�	�	�	�	�	�	�	�	�	�	�	�S.�S.�S.�S.�S.�S.�S.�S.�l?�?�?�?�?�i�?�?�?�,�,�,�,�,�I�,�,�,�0�0�0�0�0�	�0�0�0��2�i�\�A��)�)�2K�	�	�	�	�����rdefence360agent/rpc_tools/__pycache__/middleware.cpython-311.opt-1.pyc0000644000000000000000000002353300000000000022546 0ustar  �

؈����i���ddlZddlZddlmZddlmZddlmZmZm	Z	ddl
mZddlm
Z
eje��Zd�Zd�Zd	�Zd
�Zd�Zd�Zd
�Zd�Zd�Zd�Zd�ZdS)�N��wraps)�eula)�Core�UserType�caller_type)�
LicenseCLN)�MessageTypec�<��t����fd���}|S)Nc��(�K�t|��dkr|dn|�dtj��}t	j|��}	�|g|�Ri|���d{V��	t	j|��S#t	j|��wxYw)N��user)�len�getr�ROOTr�set�reset)�request�args�kwargsr�token�fs     ��Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/rpc_tools/middleware.py�wrapperz(set_caller_type_context.<locals>.wrappers������
�d�)�)�a�-�-�t�A�w�w�V�Z�Z���
�-N�-N�����%�%��	%���7�4�T�4�4�4�V�4�4�4�4�4�4�4�4�4���e�$�$�$�$��K��e�$�$�$�$���s�A;�;Br�rrs` r�set_caller_type_contextr
s3���
�1�X�X�	%�	%�	%�	%��X�	%��N�c�<��t����fd���}|S)Nc���K��|i|���d{V��}t|t��s
Jd|z���tj��|d<|S)N� Result should be a dictionary %s�license)�
isinstance�dictr	�license_info�rr�resultrs   �rrzadd_license.<locals>.wrapperss������q�$�)�&�)�)�)�)�)�)�)�)���&�$�'�'�	
�	
�.��7�	
�	
�'�
'�3�5�5��y���
rrrs` r�add_licenser's3���
�1�X�X������X���Nrc�<��t����fd���}|S)Nc���K��|i|���d{V��}t|t��s
Jd|z���tj��}|d|�d��|�d��d�|d<|S)Nr �status�license_type�eligible_for_imunify_patch)r*r+r,r!)r"r#r	r$r)rrr&r!rs    �rrz!add_license_user.<locals>.wrapper-s�������q�$�)�&�)�)�)�)�)�)�)�)���&�$�'�'�	
�	
�.��7�	
�	
�'�
�)�+�+���h�'�#�K�K��7�7�*1�+�+�,�+�+�
�
��y���
rrrs` r�add_license_userr-,s3���
�1�X�X������X��"�Nrc�<��t����fd���}|S)Nc����K��|i|���d{V��}t|t��s
Jd|z���d}tj��r�tj��s�tj���d{V��sx	tj��tj��tj	��d�}n=#t$r0}dd�t|����dd�}Yd}~nd}~wwxYw||d<|S)Nr )�message�text�updatedzFailed to read EULAzFailed to read EULA: {}�r)
r"r#r	�is_valid�is_freer�is_acceptedr0r1r2�OSError�format�str)rrr&�	eula_dict�ers     �rrzadd_eula.<locals>.wrapperCs6������q�$�)�&�)�)�)�)�)�)�)�)���&�$�'�'�	
�	
�.��7�	
�	
�'��	��� � �
	�*�*<�*>�*>�
	��)�+�+�+�+�+�+�+�+�
��#'�<�>�>� $�	���#'�<�>�>�!�!�I�I��
����#8� 9� @� @��Q��� H� H�#%�!�!�I�I�I�I�I�I���������#��v���
s�69B0�0
C*�:&C%�%C*rrs` r�add_eular<Bs3���
�1�X�X������X��0�Nrc�<��t����fd���}|S)Nc���K��|i|���d{V��}t|t��s
Jd|z���tj|d<|S)Nr �version)r"r#r�VERSIONr%s   �rrzadd_version.<locals>.wrapper`sl������q�$�)�&�)�)�)�)�)�)�)�)���&�$�'�'�	
�	
�.��7�	
�	
�'�!�L��y���
rrrs` r�add_versionrA_s3���
�1�X�X������X���Nrc�<��t����fd���}|S)Nc��4�K��|i|���d{V��\}}||d�S)N)�	max_count�items�)rr�countrErs    �rrzmax_count.<locals>.wrapperns?������Q��/��/�/�/�/�/�/�/�/���u�"�U�3�3�3rrrs` rrDrDms3���
�1�X�X�4�4�4�4��X�4��Nrc�<��t����fd���}|S)Nc��8�K��|i|���d{V��\}}}|||d�S)N)rD�countsrErF)rrrDrJrErs     �rrzcounts.<locals>.wrapperwsD�����)*��D�);�F�);�);�#;�#;�#;�#;�#;�#;� �	�6�5�&�&�5�I�I�Irrrs` rrJrJvs8���
�1�X�X�J�J�J�J��X�J��Nrc�<��t����fd���}|S)Nc����K�tjdt��tjd���5}�|i|���d{V��}d�|D��|d<|cddd��S#1swxYwYdS)N�alwaysT)�recordc�L�g|]!}d�|jj����"S)� )�joinr0r)�.0�ws  r�
<listcomp>z5collect_warnings.<locals>.wrapper.<locals>.<listcomp>�s(��!J�!J�!J�q�#�(�(�1�9�>�":�":�!J�!J�!Jr�warnings)rU�simplefilter�DeprecationWarning�catch_warnings)rr�warnsr&rs    �rrz!collect_warnings.<locals>.wrapper�s��������h�(:�;�;�;�
�
$�D�
1�
1�
1�	�U��1�d�-�f�-�-�-�-�-�-�-�-�F�!J�!J�E�!J�!J�!J�F�:���	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	�	s�A�A#�&A#rrs` r�collect_warningsrZs3���
�1�X�X������X���Nrc�<��t����fd���}|S)Nc��Z�K��|i|���d{V��}t|t��sd|i}|S)NrE)r"r#r%s   �rrz!default_to_items.<locals>.wrapper�sN������q�$�)�&�)�)�)�)�)�)�)�)���&�$�'�'�	'��v�&�F��
rrrs` r�default_to_itemsr]�s3���
�1�X�X������X���Nrc�<��t����fd���}|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:
    c��l�K�|d�d��}||d<�|g|�Ri|���d{V��S)N�params�remote_addr�client_addr)r)rrrrars    �rrz%preserve_remote_addr.<locals>.wrapper�s[������h�'�+�+�M�:�:��!,��
���Q�w�0��0�0�0��0�0�0�0�0�0�0�0�0rrrs` r�preserve_remote_addrrc�s5����1�X�X�1�1�1�1��X�1��Nrc�<��t����fd���}|S)Nc����K�d}|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��|d�dd���|g|�Ri|���d{V��S)
Nr�sinkr`rr
T�passwordz***�command�calling_process)rhr`rira)r#rr�NON_ROOTr
�
CommandInvoke�pop�process_message)rrrrfr`�	user_type�msg�coros       �rrz,send_command_invoke_message.<locals>.wrapper�sb��������	"���7�D�D�
�v�
�
��&�>�D����'�(�+�,�,�F��V�#�#� �	��t�9�9�q�=�=� $�Q��I�I��v�%�%� &�v��I��� 1�1�1�%)�F�6�N��V�#�#�%*��z�"��+��	�*�� '���,=�t� D� D����C��&�&�s�+�+�+�+�+�+�+�+�+��H��!�!�-��6�6�6��T�'�3�D�3�3�3�F�3�3�3�3�3�3�3�3�3rr)rprs` r�send_command_invoke_messagerq�s4���
�4�[�[�$4�$4�$4�$4��[�$4�L�Nr)�loggingrU�	functoolsr�defence360agent.contractsr� defence360agent.contracts.configrrr�!defence360agent.contracts.licenser	�"defence360agent.contracts.messagesr
�	getLogger�__name__�loggerrr'r-r<rArDrJrZr]rcrqrFrr�<module>r{s>����������������*�*�*�*�*�*�H�H�H�H�H�H�H�H�H�H�8�8�8�8�8�8�:�:�:�:�:�:�	��	�8�	$�	$��
�
�
� ������,���:���������	�	�	�������*(�(�(�(�(rdefence360agent/rpc_tools/__pycache__/middleware.cpython-311.pyc0000644000000000000000000002353300000000000021607 0ustar  �

؈����i���ddlZddlZddlmZddlmZddlmZmZm	Z	ddl
mZddlm
Z
eje��Zd�Zd�Zd	�Zd
�Zd�Zd�Zd
�Zd�Zd�Zd�Zd�ZdS)�N��wraps)�eula)�Core�UserType�caller_type)�
LicenseCLN)�MessageTypec�<��t����fd���}|S)Nc��(�K�t|��dkr|dn|�dtj��}t	j|��}	�|g|�Ri|���d{V��	t	j|��S#t	j|��wxYw)N��user)�len�getr�ROOTr�set�reset)�request�args�kwargsr�token�fs     ��Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/rpc_tools/middleware.py�wrapperz(set_caller_type_context.<locals>.wrappers������
�d�)�)�a�-�-�t�A�w�w�V�Z�Z���
�-N�-N�����%�%��	%���7�4�T�4�4�4�V�4�4�4�4�4�4�4�4�4���e�$�$�$�$��K��e�$�$�$�$���s�A;�;Br�rrs` r�set_caller_type_contextr
s3���
�1�X�X�	%�	%�	%�	%��X�	%��N�c�<��t����fd���}|S)Nc���K��|i|���d{V��}t|t��s
Jd|z���tj��|d<|S)N� Result should be a dictionary %s�license)�
isinstance�dictr	�license_info�rr�resultrs   �rrzadd_license.<locals>.wrapperss������q�$�)�&�)�)�)�)�)�)�)�)���&�$�'�'�	
�	
�.��7�	
�	
�'�
'�3�5�5��y���
rrrs` r�add_licenser's3���
�1�X�X������X���Nrc�<��t����fd���}|S)Nc���K��|i|���d{V��}t|t��s
Jd|z���tj��}|d|�d��|�d��d�|d<|S)Nr �status�license_type�eligible_for_imunify_patch)r*r+r,r!)r"r#r	r$r)rrr&r!rs    �rrz!add_license_user.<locals>.wrapper-s�������q�$�)�&�)�)�)�)�)�)�)�)���&�$�'�'�	
�	
�.��7�	
�	
�'�
�)�+�+���h�'�#�K�K��7�7�*1�+�+�,�+�+�
�
��y���
rrrs` r�add_license_userr-,s3���
�1�X�X������X��"�Nrc�<��t����fd���}|S)Nc����K��|i|���d{V��}t|t��s
Jd|z���d}tj��r�tj��s�tj���d{V��sx	tj��tj��tj	��d�}n=#t$r0}dd�t|����dd�}Yd}~nd}~wwxYw||d<|S)Nr )�message�text�updatedzFailed to read EULAzFailed to read EULA: {}�r)
r"r#r	�is_valid�is_freer�is_acceptedr0r1r2�OSError�format�str)rrr&�	eula_dict�ers     �rrzadd_eula.<locals>.wrapperCs6������q�$�)�&�)�)�)�)�)�)�)�)���&�$�'�'�	
�	
�.��7�	
�	
�'��	��� � �
	�*�*<�*>�*>�
	��)�+�+�+�+�+�+�+�+�
��#'�<�>�>� $�	���#'�<�>�>�!�!�I�I��
����#8� 9� @� @��Q��� H� H�#%�!�!�I�I�I�I�I�I���������#��v���
s�69B0�0
C*�:&C%�%C*rrs` r�add_eular<Bs3���
�1�X�X������X��0�Nrc�<��t����fd���}|S)Nc���K��|i|���d{V��}t|t��s
Jd|z���tj|d<|S)Nr �version)r"r#r�VERSIONr%s   �rrzadd_version.<locals>.wrapper`sl������q�$�)�&�)�)�)�)�)�)�)�)���&�$�'�'�	
�	
�.��7�	
�	
�'�!�L��y���
rrrs` r�add_versionrA_s3���
�1�X�X������X���Nrc�<��t����fd���}|S)Nc��4�K��|i|���d{V��\}}||d�S)N)�	max_count�items�)rr�countrErs    �rrzmax_count.<locals>.wrapperns?������Q��/��/�/�/�/�/�/�/�/���u�"�U�3�3�3rrrs` rrDrDms3���
�1�X�X�4�4�4�4��X�4��Nrc�<��t����fd���}|S)Nc��8�K��|i|���d{V��\}}}|||d�S)N)rD�countsrErF)rrrDrJrErs     �rrzcounts.<locals>.wrapperwsD�����)*��D�);�F�);�);�#;�#;�#;�#;�#;�#;� �	�6�5�&�&�5�I�I�Irrrs` rrJrJvs8���
�1�X�X�J�J�J�J��X�J��Nrc�<��t����fd���}|S)Nc����K�tjdt��tjd���5}�|i|���d{V��}d�|D��|d<|cddd��S#1swxYwYdS)N�alwaysT)�recordc�L�g|]!}d�|jj����"S)� )�joinr0r)�.0�ws  r�
<listcomp>z5collect_warnings.<locals>.wrapper.<locals>.<listcomp>�s(��!J�!J�!J�q�#�(�(�1�9�>�":�":�!J�!J�!Jr�warnings)rU�simplefilter�DeprecationWarning�catch_warnings)rr�warnsr&rs    �rrz!collect_warnings.<locals>.wrapper�s��������h�(:�;�;�;�
�
$�D�
1�
1�
1�	�U��1�d�-�f�-�-�-�-�-�-�-�-�F�!J�!J�E�!J�!J�!J�F�:���	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	�	s�A�A#�&A#rrs` r�collect_warningsrZs3���
�1�X�X������X���Nrc�<��t����fd���}|S)Nc��Z�K��|i|���d{V��}t|t��sd|i}|S)NrE)r"r#r%s   �rrz!default_to_items.<locals>.wrapper�sN������q�$�)�&�)�)�)�)�)�)�)�)���&�$�'�'�	'��v�&�F��
rrrs` r�default_to_itemsr]�s3���
�1�X�X������X���Nrc�<��t����fd���}|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:
    c��l�K�|d�d��}||d<�|g|�Ri|���d{V��S)N�params�remote_addr�client_addr)r)rrrrars    �rrz%preserve_remote_addr.<locals>.wrapper�s[������h�'�+�+�M�:�:��!,��
���Q�w�0��0�0�0��0�0�0�0�0�0�0�0�0rrrs` r�preserve_remote_addrrc�s5����1�X�X�1�1�1�1��X�1��Nrc�<��t����fd���}|S)Nc����K�d}|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��|d�dd���|g|�Ri|���d{V��S)
Nr�sinkr`rr
T�passwordz***�command�calling_process)rhr`rira)r#rr�NON_ROOTr
�
CommandInvoke�pop�process_message)rrrrfr`�	user_type�msg�coros       �rrz,send_command_invoke_message.<locals>.wrapper�sb��������	"���7�D�D�
�v�
�
��&�>�D����'�(�+�,�,�F��V�#�#� �	��t�9�9�q�=�=� $�Q��I�I��v�%�%� &�v��I��� 1�1�1�%)�F�6�N��V�#�#�%*��z�"��+��	�*�� '���,=�t� D� D����C��&�&�s�+�+�+�+�+�+�+�+�+��H��!�!�-��6�6�6��T�'�3�D�3�3�3�F�3�3�3�3�3�3�3�3�3rr)rprs` r�send_command_invoke_messagerq�s4���
�4�[�[�$4�$4�$4�$4��[�$4�L�Nr)�loggingrU�	functoolsr�defence360agent.contractsr� defence360agent.contracts.configrrr�!defence360agent.contracts.licenser	�"defence360agent.contracts.messagesr
�	getLogger�__name__�loggerrr'r-r<rArDrJrZr]rcrqrFrr�<module>r{s>����������������*�*�*�*�*�*�H�H�H�H�H�H�H�H�H�H�8�8�8�8�8�8�:�:�:�:�:�:�	��	�8�	$�	$��
�
�
� ������,���:���������	�	�	�������*(�(�(�(�(rdefence360agent/rpc_tools/__pycache__/utils.cpython-311.opt-1.pyc0000644000000000000000000001632300000000000021570 0ustar  �

@S0��9��F�ddlZddlZddlZddlmZddlmZmZddlm	Z	ddl
mZddlm
Z
mZmZddlZddlZddlmZddlmZdd	lmZdd
lmZmZmZmZd�Zd�Zdd�Z 	ddeeddffd�Z!dde
eefd�Z"ed��d���Z#d�Z$	dd�Z%dS)�N)�suppress)�	lru_cache�wraps)�chain)�Path)�Optional�Tuple�	Generator)�	SimpleRpc)�run_in_executor)�ValidationError)�AV_PID_PATH�IM360_NON_RESIDENT_PID_PATH�IM360_RESIDENT_PID_PATH�antivirus_modec�V�tjrtnt}|���rztj��}tt��5t|�
����}||kotj|��cddd��S#1swxYwYdS)z/Check if non-resident agent instance is runningNF)
r�enabledrr�exists�os�getpidr�	Exception�int�	read_text�psutil�
pid_exists)�rpc_process_pid_path�current_pid�pids   �T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/rpc_tools/utils.py�rpc_is_runningr s���
&�-�N���3N���"�"�$�$�A��i�k�k��
�i�
 �
 �	A�	A��*�4�4�6�6�7�7�C��+�%�@�&�*;�C�*@�*@�	A�	A�	A�	A�	A�	A�	A�	A�	A�	A�	A�	A����	A�	A�	A�	A��5s�;B�B"�%B"c��tjrt��Stj��rMtj��}ttj����}||kotj
|��SdS)z&Check if the agent instance is runningF)�Config�SOCKET_ACTIVATIONr rrrrrrrr)rrs  r�
is_runningr$'sl��
�� ������%�'�'�=��i�k�k���)�3�5�5�6�6���k�!�<�f�&7��&<�&<�<��5��schemac#�K�t||��D]�}|�d��x}���r'tj|�����}n&t
j|�����}|�	��D]+\}}t|�d����|fV��,��dS)Nz.pickle� )�find_schema_files�with_suffixr�pickle�loads�
read_bytes�yaml�	safe_loadr�items�tuple�split)�base�
schema_dir�path�p�document�k�vs       r�_find_schemar:3s�����!�$�
�3�3�)�)���!�!�)�,�,�,�A�4�4�6�6�	8��|�A�L�L�N�N�3�3�H�H��~�d�n�n�&6�&6�7�7�H��N�N�$�$�	)�	)�D�A�q��������%�%�q�(�(�(�(�(�	)�
)�)r%�returnc#�zK�||z}g|�d���|�d���D]}|V��dS)Nz*.yamlz*.yml)�rglob)r3r4r6r5s    rr)r)?sX����	
�z��A�7�!�'�'�(�#�#�7�a�g�g�g�&6�&6�7�����
�
�
�
��r%�pathsc���|rt|��dd�ng}tt��jjdz}|�||jdzdzg��|S)N�
simple_rpc�feature_management�rpc)�listr�__file__�parent�extend)r>�resultr5s   r�get_schema_pathsrHGsi��$�
,�T�%�[�[����^�^�"�F���>�>� �'�,�6�D�
�M�M���K�.�.��6�	
�����Mr%�c��g}t|��D]$}|�t|�����%tt	|���S�N)rH�appendr:�dictr)r>r&�	base_paths   r�prepare_schemarOSsM��
�F�%�e�,�,�/�/�	��
�
�l�9�-�-�.�.�.�.���v����r%c�<��t����fd���}|S)Nc��d���K�ttj�����fd����d{V��S)Nc�����i���SrK�)�args�f�kwargss���r�<lambda>z<run_in_executor_decorator.<locals>.wrapper.<locals>.<lambda>_s���a�a��.@��.@�.@�r%)r�asyncio�get_event_loop)rTrVrUs``�r�wrapperz*run_in_executor_decorator.<locals>.wrapper\sW�������$��"�$�$�&@�&@�&@�&@�&@�&@�
�
�
�
�
�
�
�
�	
r%)r)rUrZs` r�run_in_executor_decoratorr[[s3���
�1�X�X�
�
�
�
��X�
�
�Nr%c���g}|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
    �rec�listnamerI)�getrL�format�lenr
)
�affected�not_affected�
dest_listname�all_list�success_warning�failure_warning�in_another_list_warning�warnings�item�recordr^�num_deleted�	total_nums
             r�generate_warningsrnes���*�H��K�K����;�����]�(K�(K����	K�/�	K��O�O�3�:�:�6�8�L�L�M�M�M�M��O�O�O�2�2�6�=�I�I�J�J�J�J��h�-�-�K��H�
�
�I��1�}�}��k�1�1�����.�.�{�I�F�F�G�G�G��(��h�'�'�'�
�Ir%)Nr&rK)&r+rXr�
contextlibr�	functoolsrr�	itertoolsr�pathlibr�typingrr	r
r.r� defence360agent.contracts.configrr"�$defence360agent.model.simplificationr�"defence360agent.rpc_tools.validater
�defence360agent.utilsrrrrr r$r:r)rHrOr[rnrSr%r�<module>rxs���
�
�
�
�����	�	�	�	�������&�&�&�&�&�&�&�&�������������-�-�-�-�-�-�-�-�-�-�����
�
�
�
�@�@�@�@�@�@�@�@�@�@�@�@�>�>�>�>�>�>����������������	�	�	�	)�	)�	)�	)�#����t�T�4�� �����	�	�H�U�4�[�1�	�	�	�	���1��� � ��� ����"!�&�&�&�&�&�&r%defence360agent/rpc_tools/__pycache__/utils.cpython-311.pyc0000644000000000000000000001632300000000000020631 0ustar  �

@S0��9��F�ddlZddlZddlZddlmZddlmZmZddlm	Z	ddl
mZddlm
Z
mZmZddlZddlZddlmZddlmZdd	lmZdd
lmZmZmZmZd�Zd�Zdd�Z 	ddeeddffd�Z!dde
eefd�Z"ed��d���Z#d�Z$	dd�Z%dS)�N)�suppress)�	lru_cache�wraps)�chain)�Path)�Optional�Tuple�	Generator)�	SimpleRpc)�run_in_executor)�ValidationError)�AV_PID_PATH�IM360_NON_RESIDENT_PID_PATH�IM360_RESIDENT_PID_PATH�antivirus_modec�V�tjrtnt}|���rztj��}tt��5t|�
����}||kotj|��cddd��S#1swxYwYdS)z/Check if non-resident agent instance is runningNF)
r�enabledrr�exists�os�getpidr�	Exception�int�	read_text�psutil�
pid_exists)�rpc_process_pid_path�current_pid�pids   �T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/rpc_tools/utils.py�rpc_is_runningr s���
&�-�N���3N���"�"�$�$�A��i�k�k��
�i�
 �
 �	A�	A��*�4�4�6�6�7�7�C��+�%�@�&�*;�C�*@�*@�	A�	A�	A�	A�	A�	A�	A�	A�	A�	A�	A�	A����	A�	A�	A�	A��5s�;B�B"�%B"c��tjrt��Stj��rMtj��}ttj����}||kotj
|��SdS)z&Check if the agent instance is runningF)�Config�SOCKET_ACTIVATIONr rrrrrrrr)rrs  r�
is_runningr$'sl��
�� ������%�'�'�=��i�k�k���)�3�5�5�6�6���k�!�<�f�&7��&<�&<�<��5��schemac#�K�t||��D]�}|�d��x}���r'tj|�����}n&t
j|�����}|�	��D]+\}}t|�d����|fV��,��dS)Nz.pickle� )�find_schema_files�with_suffixr�pickle�loads�
read_bytes�yaml�	safe_loadr�items�tuple�split)�base�
schema_dir�path�p�document�k�vs       r�_find_schemar:3s�����!�$�
�3�3�)�)���!�!�)�,�,�,�A�4�4�6�6�	8��|�A�L�L�N�N�3�3�H�H��~�d�n�n�&6�&6�7�7�H��N�N�$�$�	)�	)�D�A�q��������%�%�q�(�(�(�(�(�	)�
)�)r%�returnc#�zK�||z}g|�d���|�d���D]}|V��dS)Nz*.yamlz*.yml)�rglob)r3r4r6r5s    rr)r)?sX����	
�z��A�7�!�'�'�(�#�#�7�a�g�g�g�&6�&6�7�����
�
�
�
��r%�pathsc���|rt|��dd�ng}tt��jjdz}|�||jdzdzg��|S)N�
simple_rpc�feature_management�rpc)�listr�__file__�parent�extend)r>�resultr5s   r�get_schema_pathsrHGsi��$�
,�T�%�[�[����^�^�"�F���>�>� �'�,�6�D�
�M�M���K�.�.��6�	
�����Mr%�c��g}t|��D]$}|�t|�����%tt	|���S�N)rH�appendr:�dictr)r>r&�	base_paths   r�prepare_schemarOSsM��
�F�%�e�,�,�/�/�	��
�
�l�9�-�-�.�.�.�.���v����r%c�<��t����fd���}|S)Nc��d���K�ttj�����fd����d{V��S)Nc�����i���SrK�)�args�f�kwargss���r�<lambda>z<run_in_executor_decorator.<locals>.wrapper.<locals>.<lambda>_s���a�a��.@��.@�.@�r%)r�asyncio�get_event_loop)rTrVrUs``�r�wrapperz*run_in_executor_decorator.<locals>.wrapper\sW�������$��"�$�$�&@�&@�&@�&@�&@�&@�
�
�
�
�
�
�
�
�	
r%)r)rUrZs` r�run_in_executor_decoratorr[[s3���
�1�X�X�
�
�
�
��X�
�
�Nr%c���g}|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
    �rec�listnamerI)�getrL�format�lenr
)
�affected�not_affected�
dest_listname�all_list�success_warning�failure_warning�in_another_list_warning�warnings�item�recordr^�num_deleted�	total_nums
             r�generate_warningsrnes���*�H��K�K����;�����]�(K�(K����	K�/�	K��O�O�3�:�:�6�8�L�L�M�M�M�M��O�O�O�2�2�6�=�I�I�J�J�J�J��h�-�-�K��H�
�
�I��1�}�}��k�1�1�����.�.�{�I�F�F�G�G�G��(��h�'�'�'�
�Ir%)Nr&rK)&r+rXr�
contextlibr�	functoolsrr�	itertoolsr�pathlibr�typingrr	r
r.r� defence360agent.contracts.configrr"�$defence360agent.model.simplificationr�"defence360agent.rpc_tools.validater
�defence360agent.utilsrrrrr r$r:r)rHrOr[rnrSr%r�<module>rxs���
�
�
�
�����	�	�	�	�������&�&�&�&�&�&�&�&�������������-�-�-�-�-�-�-�-�-�-�����
�
�
�
�@�@�@�@�@�@�@�@�@�@�@�@�>�>�>�>�>�>����������������	�	�	�	)�	)�	)�	)�#����t�T�4�� �����	�	�H�U�4�[�1�	�	�	�	���1��� � ��� ����"!�&�&�&�&�&�&r%defence360agent/rpc_tools/__pycache__/validate.cpython-311.opt-1.pyc0000644000000000000000000003145000000000000022217 0ustar  �

�{��x�N��D�ddlZddlZddlZddlZddlZddlmZddlmZddl	m
Z
ddlmZm
Z
mZddlmZddlmZmZeje��Zejd��ZGd	�d
e��Zeddd
g��ZGd�de��ZGd�de
��Zd�Zd�Zd�Z dS)�N)�
namedtuple��wraps)�	Validator)�ANTIVIRUS_MODE�
BackupRestore�Malware)�
LicenseCLN)�BackupSystem�get_backendz^[A-Fa-f0-9]{64}$c��eZdZdd�ZdS)�ValidationErrorNc�b�t|t��r	|g|_n||_|pi|_dS�N)�
isinstance�str�errors�
extra_data)�selfrrs   �W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/rpc_tools/validate.py�__init__zValidationError.__init__s6���f�c�"�"�	!�!�(�D�K�K� �D�K�$�*������r)�__name__�
__module__�__qualname__r�rrrrs(������+�+�+�+�+�+rr�OrderByBase�column_name�descc�4��eZdZ�fd�Zed���Z�xZS)�OrderByc�J��t���|||��Sr)�super�__new__)�clsrr�	__class__s   �rr$zOrderBy.__new__$s����w�w���s�K��6�6�6rc��	tjd���|��dd�\}}|||dk��S#t$r5}td�t|��|�����d}~wwxYw)zP
        :param ob_string: for example: 'user+', 'id-'
        :return:
        z^(.+)([+|-])�����-zIncorrect order_by: ({}): {}N)�re�compile�split�
ValueError�formatr)r%�	ob_string�col_name�sign�es     r�
fromstringzOrderBy.fromstring's���	��Z��7�7�=�=�i�H�H��2��N�N�H�d��3�x����-�-�-���	�	�	��.�5�5�c�!�f�f�i�H�H���
�����	���s�AA�
B�0A>�>B)rrrr$�classmethodr4�
__classcell__�r&s@rr!r!#sS�������7�7�7�7�7�����[�����rr!c���eZdZdZ�fd�Zd�Zd�Zd�Zd�Zde	fd�Z
d	�Zd
�Zd�Z
defd
�Zd�Zd�Zd�Zd�Zd�Zd�Zd�Zde	fd�Zd�Zd�Zde	deddfd�Z�xZS)�SchemaValidatorz%Y-%m-%dc�H��t��j|i|��i|_dSr)r#rr)r�args�kwargsr&s   �rrzSchemaValidator.__init__9s*��������$�)�&�)�)�)�����rc�d�t|t��r|St�|��Sr)rr!r4�r�values  r�_normalize_coerce_order_byz*SchemaValidator._normalize_coerce_order_by=s-���e�W�%�%�	��L��!�!�%�(�(�(rc�h�t|��������Sr)r�strip�lowerr>s  r�_normalize_coerce_sha256hashz,SchemaValidator._normalize_coerce_sha256hashBs&���5�z�z���!�!�'�'�)�)�)rc�4�trdS|�tjS|S)NF)rr	�DATABASE_SCAN_ENABLEDr>s  r�_normalize_coerce_scan_dbz)SchemaValidator._normalize_coerce_scan_dbEs"���	��5��=��0�0��rc�4�t|t��rdSdS)NTF)rr!r>s  r�_validate_type_order_byz'SchemaValidator._validate_type_order_byLs���e�W�%�%�	��4��urr?c�t�t�t|�������Sr)�
SHA256_REGEXP�matchrrBr>s  r�_validate_type_sha256hashz)SchemaValidator._validate_type_sha256hashQs(���"�"�3�u�:�:�#3�#3�#5�#5�6�6�6rc��|rJtj�|��s-|�|d�|����dSdSdS)z#{'type': 'boolean', 'empty': False}zPath {} should be absoluteN)�os�path�isabs�_errorr/)r�is_absolute_path�fieldr?s    r�_validate_is_absolute_pathz*SchemaValidator._validate_is_absolute_pathTsc���	O��7�=�=��'�'�
O����E�#?�#F�#F�u�#M�#M�N�N�N�N�N�	O�	O�
O�
Orc��|r?	|�d��dS#t$r|�|d��YdSwxYwdS)z{'type': 'boolean'}�asciizMust only contain ascii symbolsN)�encode�UnicodeEncodeErrorrR)r�isasciirTr?s    r�_validate_isasciiz!SchemaValidator._validate_isasciiZsr���	F�
F����W�%�%�%�%�%��%�
F�
F�
F����E�#D�E�E�E�E�E�E�
F����	F�	Fs�� ?�?c� �t|��Sr)�intr>s  r�_normalize_coerce_intz%SchemaValidator._normalize_coerce_intbs���5�z�z�r�returnc��tjtj��������Sr)�math�ceil�datetime�now�	timestamp)r�documents  r�_normalize_default_setter_nowz-SchemaValidator._normalize_default_setter_nowes-���y��*�.�.�0�0�:�:�<�<�=�=�=rc��dS)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']}
        }}
        Nr�rr;r<s   r�
_validate_clizSchemaValidator._validate_clii����rc��dS)z"{'type': 'string', 'empty': False}Nrris   r�_validate_helpzSchemaValidator._validate_helpsrkrc��dS)z4{'type': 'boolean', 'empty': True, 'default': False}Nrris   r�_validate_positionalz$SchemaValidator._validate_positionalwrkrc��dS)z!{'type': 'string', 'empty': True}Nrris   r�_validate_return_typez%SchemaValidator._validate_return_type{rkrc��dS)z5{'type': 'boolean', 'empty': False, 'default': False}Nrris   r�_validate_cli_onlyz"SchemaValidator._validate_cli_only~rkrc��dS)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_envvar�rkrc��dS)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_only�rkrc�H�|rtj�|��S|Sr)rOrP�abspathr>s  r�_normalize_coerce_pathz&SchemaValidator._normalize_coerce_path�s#���	*��7�?�?�5�)�)�)��rc�N�t|t��r|St|��Sr)rrrr>s  r�_normalize_coerce_backup_systemz/SchemaValidator._normalize_coerce_backup_system�s'���e�\�*�*�	��L��5�!�!�!rc�t�tjrtj��s|�|d��dSdS)NzBackup is not enabled!)r�ENABLED�
backup_systemrR)rrTr?s   r�_validator_backup_is_enabledz,SchemaValidator._validator_backup_is_enabled�sB���%�	9�-�*E�*G�*G�	9��K�K��7�8�8�8�8�8�	9�	9rrTNc
���	tj�|��dS#t$r5}|�|d|�dt	|���d���Yd}~dSd}~wwxYw)NzIncorrect timestamp: z (�))rc�
fromtimestampr.rRr)rrTr?r3s    r�_validator_timestampz$SchemaValidator._validator_timestamp�s���	K���+�+�E�2�2�2�2�2���	K�	K�	K��K�K��I�u�I�I��A���I�I�I�J�J�J�J�J�J�J�J�J�����	K���s�#�
A"�*A�A")rrr�_DATE_FORMATrr@rDrGrIrrMrUr[r^r]rgrjrmrorqrsrurwrzr|r�r�r6r7s@rr9r96s���������L������)�)�)�
*�*�*�������
7�s�7�7�7�7�O�O�O�F�F�F����>��>�>�>�>����1�1�1�C�C�C�0�0�0�D�D�D��������C�����"�"�"�9�9�9�K�#�K�c�K�d�K�K�K�K�K�K�K�Krr9c�$�|�||id���}|�|||i��sNt�d�|||j����t
|j|j���|j|S)NT)�always_return_documentz6Validation error with command {}, params {}, errors {})	�
normalized�validate�logger�warningr/rrrrf)�	validator�hashable�params�valuess    rr�r��s���
�
!�
!�	�6��4�"���F����x���)9�:�;�;�F����D�K�K��&�)�"2�
�
�	
�	
�	
�
�i�.�	�0D�E�E�E���h�'�'rc����fd�}|S)Nc�@���t�����fd���}|S)Nc���K�t|d��}t�||d��|d<�|g|�Ri|���d{V��}|S)N�commandr�)�tupler�)�requestr;r<r��result�fr�s     ��r�wrapperz5validate_middleware.<locals>.wrapped.<locals>.wrapper�sq������W�Y�/�0�0�H� (��8�W�X�%6�!�!�G�H���1�W�6�t�6�6�6�v�6�6�6�6�6�6�6�6�F��Mrr)r�r�r�s` �r�wrappedz$validate_middleware.<locals>.wrapped�s9����	�q���	�	�	�	�	�
��	��rr)r�r�s` r�validate_middlewarer��s#���
�
�
�
�
��Nrc�p���td���t�����fd���}tr|S�S)zz
    Decorator for CLI commands methods that ensures that the AV+ license
    is valid.

    :raises ValidationError:
    zImunifyAV+ license requiredc��N�K�tj��r�|i|���d{V��S��r)r
�is_valid_av_plus)r;r<�	exception�funcs  ��r�
async_wrapperz/validate_av_plus_license.<locals>.async_wrapper�sD������&�(�(�	/���t�.�v�.�.�.�.�.�.�.�.�.��r)rrr)r�r�r�s` @r�validate_av_plus_licenser��sW���� � =�>�>�I�
�4�[�[�������[��
�����Kr)!rc�loggingrarOr+�collectionsr�	functoolsr�cerberus.validatorr� defence360agent.contracts.configrrr	�!defence360agent.contracts.licenser
�%defence360agent.subsys.backup_systemsrr�	getLoggerrr�r,rK�	Exceptionrrr!r9r�r�r�rrr�<module>r�s���������������	�	�	�	�	�	�	�	�"�"�"�"�"�"�������(�(�(�(�(�(�����������
9�8�8�8�8�8�K�K�K�K�K�K�K�K�	��	�8�	$�	$����
�.�/�/�
�+�+�+�+�+�i�+�+�+��j����(?�@�@�������k����&rK�rK�rK�rK�rK�i�rK�rK�rK�j(�(�(�
�
�
� ����rdefence360agent/rpc_tools/__pycache__/validate.cpython-311.pyc0000644000000000000000000003145000000000000021260 0ustar  �

�{��x�N��D�ddlZddlZddlZddlZddlZddlmZddlmZddl	m
Z
ddlmZm
Z
mZddlmZddlmZmZeje��Zejd��ZGd	�d
e��Zeddd
g��ZGd�de��ZGd�de
��Zd�Zd�Zd�Z dS)�N)�
namedtuple��wraps)�	Validator)�ANTIVIRUS_MODE�
BackupRestore�Malware)�
LicenseCLN)�BackupSystem�get_backendz^[A-Fa-f0-9]{64}$c��eZdZdd�ZdS)�ValidationErrorNc�b�t|t��r	|g|_n||_|pi|_dS�N)�
isinstance�str�errors�
extra_data)�selfrrs   �W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/rpc_tools/validate.py�__init__zValidationError.__init__s6���f�c�"�"�	!�!�(�D�K�K� �D�K�$�*������r)�__name__�
__module__�__qualname__r�rrrrs(������+�+�+�+�+�+rr�OrderByBase�column_name�descc�4��eZdZ�fd�Zed���Z�xZS)�OrderByc�J��t���|||��Sr)�super�__new__)�clsrr�	__class__s   �rr$zOrderBy.__new__$s����w�w���s�K��6�6�6rc��	tjd���|��dd�\}}|||dk��S#t$r5}td�t|��|�����d}~wwxYw)zP
        :param ob_string: for example: 'user+', 'id-'
        :return:
        z^(.+)([+|-])�����-zIncorrect order_by: ({}): {}N)�re�compile�split�
ValueError�formatr)r%�	ob_string�col_name�sign�es     r�
fromstringzOrderBy.fromstring's���	��Z��7�7�=�=�i�H�H��2��N�N�H�d��3�x����-�-�-���	�	�	��.�5�5�c�!�f�f�i�H�H���
�����	���s�AA�
B�0A>�>B)rrrr$�classmethodr4�
__classcell__�r&s@rr!r!#sS�������7�7�7�7�7�����[�����rr!c���eZdZdZ�fd�Zd�Zd�Zd�Zd�Zde	fd�Z
d	�Zd
�Zd�Z
defd
�Zd�Zd�Zd�Zd�Zd�Zd�Zd�Zde	fd�Zd�Zd�Zde	deddfd�Z�xZS)�SchemaValidatorz%Y-%m-%dc�H��t��j|i|��i|_dSr)r#rr)r�args�kwargsr&s   �rrzSchemaValidator.__init__9s*��������$�)�&�)�)�)�����rc�d�t|t��r|St�|��Sr)rr!r4�r�values  r�_normalize_coerce_order_byz*SchemaValidator._normalize_coerce_order_by=s-���e�W�%�%�	��L��!�!�%�(�(�(rc�h�t|��������Sr)r�strip�lowerr>s  r�_normalize_coerce_sha256hashz,SchemaValidator._normalize_coerce_sha256hashBs&���5�z�z���!�!�'�'�)�)�)rc�4�trdS|�tjS|S)NF)rr	�DATABASE_SCAN_ENABLEDr>s  r�_normalize_coerce_scan_dbz)SchemaValidator._normalize_coerce_scan_dbEs"���	��5��=��0�0��rc�4�t|t��rdSdS)NTF)rr!r>s  r�_validate_type_order_byz'SchemaValidator._validate_type_order_byLs���e�W�%�%�	��4��urr?c�t�t�t|�������Sr)�
SHA256_REGEXP�matchrrBr>s  r�_validate_type_sha256hashz)SchemaValidator._validate_type_sha256hashQs(���"�"�3�u�:�:�#3�#3�#5�#5�6�6�6rc��|rJtj�|��s-|�|d�|����dSdSdS)z#{'type': 'boolean', 'empty': False}zPath {} should be absoluteN)�os�path�isabs�_errorr/)r�is_absolute_path�fieldr?s    r�_validate_is_absolute_pathz*SchemaValidator._validate_is_absolute_pathTsc���	O��7�=�=��'�'�
O����E�#?�#F�#F�u�#M�#M�N�N�N�N�N�	O�	O�
O�
Orc��|r?	|�d��dS#t$r|�|d��YdSwxYwdS)z{'type': 'boolean'}�asciizMust only contain ascii symbolsN)�encode�UnicodeEncodeErrorrR)r�isasciirTr?s    r�_validate_isasciiz!SchemaValidator._validate_isasciiZsr���	F�
F����W�%�%�%�%�%��%�
F�
F�
F����E�#D�E�E�E�E�E�E�
F����	F�	Fs�� ?�?c� �t|��Sr)�intr>s  r�_normalize_coerce_intz%SchemaValidator._normalize_coerce_intbs���5�z�z�r�returnc��tjtj��������Sr)�math�ceil�datetime�now�	timestamp)r�documents  r�_normalize_default_setter_nowz-SchemaValidator._normalize_default_setter_nowes-���y��*�.�.�0�0�:�:�<�<�=�=�=rc��dS)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']}
        }}
        Nr�rr;r<s   r�
_validate_clizSchemaValidator._validate_clii����rc��dS)z"{'type': 'string', 'empty': False}Nrris   r�_validate_helpzSchemaValidator._validate_helpsrkrc��dS)z4{'type': 'boolean', 'empty': True, 'default': False}Nrris   r�_validate_positionalz$SchemaValidator._validate_positionalwrkrc��dS)z!{'type': 'string', 'empty': True}Nrris   r�_validate_return_typez%SchemaValidator._validate_return_type{rkrc��dS)z5{'type': 'boolean', 'empty': False, 'default': False}Nrris   r�_validate_cli_onlyz"SchemaValidator._validate_cli_only~rkrc��dS)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_envvar�rkrc��dS)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_only�rkrc�H�|rtj�|��S|Sr)rOrP�abspathr>s  r�_normalize_coerce_pathz&SchemaValidator._normalize_coerce_path�s#���	*��7�?�?�5�)�)�)��rc�N�t|t��r|St|��Sr)rrrr>s  r�_normalize_coerce_backup_systemz/SchemaValidator._normalize_coerce_backup_system�s'���e�\�*�*�	��L��5�!�!�!rc�t�tjrtj��s|�|d��dSdS)NzBackup is not enabled!)r�ENABLED�
backup_systemrR)rrTr?s   r�_validator_backup_is_enabledz,SchemaValidator._validator_backup_is_enabled�sB���%�	9�-�*E�*G�*G�	9��K�K��7�8�8�8�8�8�	9�	9rrTNc
���	tj�|��dS#t$r5}|�|d|�dt	|���d���Yd}~dSd}~wwxYw)NzIncorrect timestamp: z (�))rc�
fromtimestampr.rRr)rrTr?r3s    r�_validator_timestampz$SchemaValidator._validator_timestamp�s���	K���+�+�E�2�2�2�2�2���	K�	K�	K��K�K��I�u�I�I��A���I�I�I�J�J�J�J�J�J�J�J�J�����	K���s�#�
A"�*A�A")rrr�_DATE_FORMATrr@rDrGrIrrMrUr[r^r]rgrjrmrorqrsrurwrzr|r�r�r6r7s@rr9r96s���������L������)�)�)�
*�*�*�������
7�s�7�7�7�7�O�O�O�F�F�F����>��>�>�>�>����1�1�1�C�C�C�0�0�0�D�D�D��������C�����"�"�"�9�9�9�K�#�K�c�K�d�K�K�K�K�K�K�K�Krr9c�$�|�||id���}|�|||i��sNt�d�|||j����t
|j|j���|j|S)NT)�always_return_documentz6Validation error with command {}, params {}, errors {})	�
normalized�validate�logger�warningr/rrrrf)�	validator�hashable�params�valuess    rr�r��s���
�
!�
!�	�6��4�"���F����x���)9�:�;�;�F����D�K�K��&�)�"2�
�
�	
�	
�	
�
�i�.�	�0D�E�E�E���h�'�'rc����fd�}|S)Nc�@���t�����fd���}|S)Nc���K�t|d��}t�||d��|d<�|g|�Ri|���d{V��}|S)N�commandr�)�tupler�)�requestr;r<r��result�fr�s     ��r�wrapperz5validate_middleware.<locals>.wrapped.<locals>.wrapper�sq������W�Y�/�0�0�H� (��8�W�X�%6�!�!�G�H���1�W�6�t�6�6�6�v�6�6�6�6�6�6�6�6�F��Mrr)r�r�r�s` �r�wrappedz$validate_middleware.<locals>.wrapped�s9����	�q���	�	�	�	�	�
��	��rr)r�r�s` r�validate_middlewarer��s#���
�
�
�
�
��Nrc�p���td���t�����fd���}tr|S�S)zz
    Decorator for CLI commands methods that ensures that the AV+ license
    is valid.

    :raises ValidationError:
    zImunifyAV+ license requiredc��N�K�tj��r�|i|���d{V��S��r)r
�is_valid_av_plus)r;r<�	exception�funcs  ��r�
async_wrapperz/validate_av_plus_license.<locals>.async_wrapper�sD������&�(�(�	/���t�.�v�.�.�.�.�.�.�.�.�.��r)rrr)r�r�r�s` @r�validate_av_plus_licenser��sW���� � =�>�>�I�
�4�[�[�������[��
�����Kr)!rc�loggingrarOr+�collectionsr�	functoolsr�cerberus.validatorr� defence360agent.contracts.configrrr	�!defence360agent.contracts.licenser
�%defence360agent.subsys.backup_systemsrr�	getLoggerrr�r,rK�	Exceptionrrr!r9r�r�r�rrr�<module>r�s���������������	�	�	�	�	�	�	�	�"�"�"�"�"�"�������(�(�(�(�(�(�����������
9�8�8�8�8�8�K�K�K�K�K�K�K�K�	��	�8�	$�	$����
�.�/�/�
�+�+�+�+�+�i�+�+�+��j����(?�@�@�������k����&rK�rK�rK�rK�rK�i�rK�rK�rK�j(�(�(�
�
�
� ����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.py0000644000000000000000000001502200000000000015241 0ustar  import logging
import warnings
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

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


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__.py0000644000000000000000000005432500000000000015025 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 contextvars import ContextVar
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
from defence360agent.rpc_tools import ERROR, SUCCESS, WARNING


logger = getLogger(__name__)


caller_uid_var: ContextVar[int] = ContextVar("rpc_caller_uid")


_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 "<unparseable, {} chars>".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.pyc0000644000000000000000000010606200000000000022320 0ustar  �

����g�8��X�UdZddlZddlZddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlZddlm
Z
ddlmZddlmZddlmZddlmZddlZddlmZdd	lmZdd
lmZmZddlm Z ddl!m"Z"dd
l#m$Z$ddl%m&Z&ddl'm(Z(m)Z)ddl*m+Z+m,Z,ddl-m.Z.ddl/m0Z0ddl1m2Z2ddl3m4Z4m5Z5m6Z6ddl7m8Z8m9Z9ddl:m;Z;m<Z<ddl=m>Z>ddl?m@Z@mAZAmBZBeeC��ZDed��ZEeeFeGd<eHhd���ZId�ZJd�ZKGd�d��ZLd �ZMd!�ZNd"eOd#eeOfd$�ZPd%�ZQGd&�d'��ZRGd(�d)��ZSGd*�d+e"��ZTd,�ZUGd-�d.��ZVGd/�d0��ZWGd1�d2eW��ZXGd3�d4eV��ZYGd5�d6��ZZGd7�d8��Z[Gd9�d:��Z\dS);z.
Simple unix socket RPC server implementation
�N)�suppress)�
ContextVar)�	getLogger)�Sequence)�Process)�
inactivity)�app)�Core�	SimpleRpc)�FeatureManagementError)�UnixSocketAuthProtocol)�	tls_check)�run_in_executor)�is_root_user�run_coro)�
LineBuffer�LineBufferOverflow)�
hosting_panel)�InvalidTokenException)�svcctl)�
ResponseError�ServiceStateError�SocketError)�	Endpoints�UserType)�
is_running�rpc_is_running)�ValidationError)�ERROR�SUCCESS�WARNING�rpc_caller_uid�caller_uid_var>�jwt�token�passwordc���t|��}|�d��}t|t��r't|��}tD]}||vrd||<�||d<|S)N�paramsz***)�dict�get�
isinstance�_SENSITIVE_PARAM_KEYS)�decoded�safer(�safe_params�keys     �X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/__init__.py�_redact_for_logr2<sn����=�=�D�
�X�X�h�
�
�F��&�$���%��6�l�l��(�	)�	)�C��k�!�!�#(��C� ��$��X���K�c��	tj|��}n2#t$r%d�t	|����cYSwxYwt|t��st|��Stt|����S)Nz<unparseable, {} chars>)	�json�loads�	Exception�format�lenr+r)�reprr2)�rawr-s  r1�_safe_log_payloadr<Hs���:��*�S�/�/�����:�:�:�(�/�/��C���9�9�9�9�9�:�����g�t�$�$���G�}�}�����(�(�)�)�)s��,A�Ac��eZdZdZdZdZdZdS)�RpcServiceState�running�stopped�any�directN)�__name__�
__module__�__qualname__�RUNNING�STOPPED�ANY�DIRECT�r3r1r>r>Rs*�������G��G��C�
�F�F�Fr3r>c��K�	|�d{V��}tg|d�S#t$r5}t|jd�}|�|j��|cYd}~Sd}~wttf$r@}|j^}}tj
|g|�R�t|t|��zgd�cYd}~Sd}~wt$r^}tj|��t�
d|t!|����tt!|��d�cYd}~Sd}~wwxYw)N)�result�messages�data�rLrMz-Something went wrong while processing %s (%s))r rr!�errors�update�
extra_data�PermissionErrorr�args�logger�errorr�tupler7�
sentry_sdk�capture_exception�str)�coro�methodrL�e�msgrTs      r1�_execute_requestr_dsu����C���������."�r�6�B�B�B��-�������
�
��	�
�
�a�l�#�#�#��
�
�
�
�
�
������3�4�
�
�
��V�
��d���S� �4� � � � ���u�T�{�{�*�+�
�
�	
�	
�	
�	
�	
�	
������5�5�5��$�Q�'�'�'����;�V�S��V�V�	
�	
�	
� �S��V�V�4�4�4�4�4�4�4�4�����
5���s?��
D�*A�D�D�%5B �D� 
D�-AD�D�Dc���tj}t|ttf��r�t	|��}t
j�dg��}t
j�|g��}t
j�|g��}t||z��D]8\}}||vr/||vr+t�d|j��||��}�9|S)NzApplying middleware %s)
r�route_to_endpointr+�listrWr	�
MIDDLEWAREr*�MIDDLEWARE_EXCLUDE�reversedrU�debugrC)	r\�user�cb�hashable�common�specific�excluded�mw�userss	         r1�_apply_middlewarero�s���	�	$�B��&�4��-�(�(����=�=����#�#�D�"�-�-���>�%�%�h��3�3���)�-�-�h��;�;��!�&�8�"3�4�4�	�	�I�B����
�
�B�h�$6�$6����5�r�{�C�C�C��R��V�V���
�Ir3�socket_path�returnc���tdtj��tj�����5}�fd�|D��cddd��S#1swxYwYdS)z9Find inodes corresponding to the unix domain socket path.z/proc/net/unix)�encodingrPc�L��g|] }�|v�|���d��!S)���)�split)�.0�linerps  �r1�
<listcomp>z$_find_uds_inodes.<locals>.<listcomp>�s0���I�I�I�T�[�D�5H�5H��
�
���R� �5H�5H�5Hr3N)�open�sys�getfilesystemencoding�getfilesystemencodeerrors)rp�files` r1�_find_uds_inodesr�s����	
���*�,�,��,�.�.�
�
�
�J�
�I�I�I�I�T�I�I�I�J�J�J�J�J�J�J�J�J�J�J�J����J�J�J�J�J�Js�A�A�Ac��	tj|j��}n#ttf$rYdSwxYw|j}d|vod|vS)zcTrue if cls.__init__ accepts the guard kwargs; legacy *_ signatures TypeError when passed limiter=.F�limiter�read_timeout)�inspect�	signature�__init__�	TypeError�
ValueError�
parameters)�protocol_cls�sigr(s   r1�_protocol_supports_guardr��s_������ 5�6�6�����z�"�����u�u�����
�^�F����;�>�V�#;�;s��1�1c�6�eZdZd�Zd�Zd�Zed���ZdS)�ConnectionLimiterc�0�||_d|_d|_dS)NrF)�max_connections�_count�saturation_logged)�selfr�s  r1r�zConnectionLimiter.__init__�s��.������!&����r3c�J�|j|jkrdS|xjdz
c_dS)NF�T)r�r��r�s r1�acquirezConnectionLimiter.acquire�s,���;�$�.�.�.��5����q�����tr3c�N�|jdkr|xjdzc_d|_dSdS)Nrr�F)r�r�r�s r1�releasezConnectionLimiter.release�s2���;��?�?��K�K�1��K�K�%*�D�"�"�"��?r3c��|jS�N)r�r�s r1�countzConnectionLimiter.count�s
���{�r3N)rCrDrEr�r�r��propertyr�rJr3r1r�r��s\������'�'�'�
���+�+�+�
����X���r3r�c�F�eZdZddd�d�Zd�Zd�Zd�Zd�Zd�Zd	�Z	d
�Z
dS)�ConnectionGuardN�r�r�c��||_||_||_||_d|_d|_d|_d|_d|_dS�NF)	�_loop�_limiter�
_read_timeout�_name�
_transport�_timeout_handle�_slot_acquired�	_peer_pid�	_peer_uid)r��loopr�r��names     r1r�zConnectionGuard.__init__�sH����
���
�)�����
����#���#����������r3c�"�|j�^|j���sE|jjs7t�d|j|jj��d|j_dS|jdu|_||_|�	��dS)Nz6%s connection limit (%d) reached; rejecting new clientTF)
r�r�r�rU�warningr�r�r�r��_schedule_timeout�r��	transports  r1�	try_admitzConnectionGuard.try_admit�s����=�$�T�]�-B�-B�-D�-D�$��=�2�
7����L��J��M�1����
37��
�/��5�"�m�4�7���#������ � � ��tr3c�"�||_||_dSr�)r�r�)r��pid�uids   r1�	note_peerzConnectionGuard.note_peer�s���������r3c�.�|���dSr�)r�r�s r1�on_datazConnectionGuard.on_data�s����� � � � � r3c��|���|jr'|j� |j���d|_d|_dSr�)�_cancel_timeoutr�r�r�r�r�s r1�on_lostzConnectionGuard.on_lost�sM����������	(�4�=�#<��M�!�!�#�#�#�"'�D������r3c��|j�dS|j�|j���|j�|j|j��|_dSr�)r�r��cancelr��
call_later�_on_timeoutr�s r1r�z!ConnectionGuard._schedule_timeout�sY����%��F���+�� �'�'�)�)�)�#�z�4�4���� 0� 
� 
����r3c�X�|j�"|j���d|_dSdSr�)r�r�r�s r1r�zConnectionGuard._cancel_timeout�s6����+�� �'�'�)�)�)�#'�D� � � �,�+r3c��d|_|j�dSt�d|j|j|j|j��|jdc}|_|���|�	��dS)Nz;Closing idle %s connection (pid=%s uid=%s, no data for %ds))
r�r�rUr�r�r�r�r��closer�r�s  r1r�zConnectionGuard._on_timeout�sx��#����?�"��F����I��J��N��N���	
�	
�	
�&*�_�d�"�	�4�?��������������r3)rCrDrEr�r�r�r�r�r�r�r�rJr3r1r�r��s�������(,�4�	�	�	�	�	�
�
�
����!�!�!����
�
�
�(�(�(�

�
�
�
�
r3r�c�P��eZdZddd�d�Z�fd�Zdefd�Zd�Zd�Zd	�Z	d
�Z
�xZS)�_RpcServerProtocolNr�c��||_||_||_d|_t	��|_t
|||d���|_dS)N�RPC)r�r�r�)r��_sinkrgr�r�_bufr��_guard)r�r��sinkrgr�r�s      r1r�z_RpcServerProtocol.__init__sL����
���
���	�����L�L��	�%��'��5�
�
�
����r3c����|j�|��s|���dS	t���|��ny#t
ttjf$rZ}t�
d|��|���d|_|j���Yd}~dSd}~wwxYw|j�
|j|j��dS)Nz5Rejected RPC connection: SO_PEERCRED unavailable (%s))r�r�r��super�connection_made�OSError�AttributeError�structrVrUr�r�r�r��_pid�_uid)r�r��exc�	__class__s   �r1r�z"_RpcServerProtocol.connection_made
s�����{�$�$�Y�/�/�	��O�O�����F�
	��G�G�#�#�I�.�.�.�.������6�	�	�	��N�N�G��
�
�
�
�O�O����"�D�O��K���!�!�!��F�F�F�F�F�����	����	
����d�i���3�3�3�3�3s�!A�C�1AC�CrNc�~�tj|��}tj���||��\}}||_|�!||dd<d|dvr|g|dd<	t
|j�����}n'#t$r}t|��g}Yd}~nd}~wwxYw||d<|S)Nr(rgrn�calling_process)r5r6r�HostingPanel�authenticatergrr��cmdliner7rZ)r�rNr-�	user_type�	user_namer�r]s       r1�preprocess_dataz"_RpcServerProtocol.preprocess_datas����*�T�"�"��,�9�;�;�H�H��'� 
� 
��	�9���	�� �(1�G�H��f�%��'�(�+�+�+�.7�[���!�'�*�	'�%�d�i�0�0�8�8�:�:�O�O���	'�	'�	'�"�1�v�v�h�O�O�O�O�O�O�����	'����%4��!�"��s�*&B�
B5�B0�0B5c��|j�dS|j���	|j�|�����nx#t$rk}t�d|j	|j
|��|j���d|_|j���Yd}~dSd}~wwxYw|jD�]�}	|�
|��}|d}|d}t�d|��t||j��}t"�|j
��}	|j�|�|||||j|j������t"�|��n#t"�|��wxYw��#t0$rO}t�d��|�t4t7|��d���Yd}~��Ld}~wt8$r]}t�dt=|����|�t4t7|��d���Yd}~���d}~wwxYwdS)Nz*Closing RPC connection (pid=%s uid=%s): %s�commandr(zData received: command=%szIncorrect token providedrOz)Something went wrong before processing %s)r�r�r�r��append�decoderrUr�r�r�r�r�r�rfrorgr#�setr��create_task�	_dispatchr��resetr�_write_responserrZr7�	exceptionr<)	r�rNr]r^rLr\r(rhr%s	         r1�
data_receivedz _RpcServerProtocol.data_received0s����?�"��F��������	��I���T�[�[�]�]�+�+�+�+��!�
	�
	�
	��N�N�<��	��	��	
�
�
�
�O�!�!�#�#�#�"�D�O��K���!�!�!��F�F�F�F�F�����
	�����9�$	L�$	L�C�#
L��-�-�c�2�2���	�*����)�����8�&�A�A�A�&�v�t�y�9�9��'�*�*�4�9�5�5��0��J�*�*����"�F�B�B�v�t�z�4�9�,M�,M������#�(�(��/�/�/�/��N�(�(��/�/�/�/����/��(�
L�
L�
L����9�:�:�:��$�$��3�q�6�6�%J�%J�K�K�K�K�K�K�K�K������
L�
L�
L�� � �?�%�c�*�*����
�$�$��3�q�6�6�%J�%J�K�K�K�K�K�K�K�K�����

L����=$	L�$	LsX�,A�
C�A C�C�A4G�	AF(�
G�(G�G�
J�AH�
J�)AJ�Jc��RK�tj�d�|����5t	||���d{V��}t
�d�||����|�|��ddd��dS#1swxYwYdS)Nzrpc_{}z Response: method - {}, data - {})r�track�taskr8r_rU�infor�)r�r\r(r[�responses     r1r�z_RpcServerProtocol._dispatchgs�����
�
�
"�
"�8�?�?�6�#:�#:�
;�
;�	+�	+�-�d�F�;�;�;�;�;�;�;�;�H��K�K�2�9�9�&�(�K�K�
�
�
�
� � ��*�*�*�
	+�	+�	+�	+�	+�	+�	+�	+�	+�	+�	+�	+����	+�	+�	+�	+�	+�	+s�AB�B �#B c�F�|j���d|_dSr�)r�r�r�r�s  r1�connection_lostz"_RpcServerProtocol.connection_lostps!�������������r3c�4�|j�t�d��dS	|j�t	j|��dz�����dS#t$r%}t�|��Yd}~dSd}~wwxYw)Nz*Cannot send RPC response: connection lost.�
)	r�rUr��writer5�dumps�encoder7r�)r�rNr]s   r1r�z"_RpcServerProtocol._write_responsets����?�"��N�N�G�H�H�H��F�
$���%�%�t�z�$�'7�'7�$�'>�&F�&F�&H�&H�I�I�I�I�I���
$�
$�
$�� � ��#�#�#�#�#�#�#�#�#�����
$���s�AA(�(
B�2B�B)rCrDrEr�r�rZr�r�r�r�r��
__classcell__)r�s@r1r�r��s��������48�t�
�
�
�
�
�4�4�4�4�4�"�C�����*5L�5L�5L�n+�+�+����$�$�$�$�$�$�$r3r�c��tj�|��}tj|d���tj|d��dS)NT)�exist_oki�)�os�path�dirname�makedirs�chmod)rp�dir_names  r1� _check_socket_folder_permissionsr�sB���w���{�+�+�H��K��4�(�(�(�(��H�X�u�����r3c�D�eZdZejZejZdZe	d���Z
dS)�	RpcServeri�c������K�t�j��tt��5t	j�j��ddd��n#1swxYwYt
tj�����	����fd��j���d{V��}t	j
�j�j��|S)Nc�J��t���j�tj���S�Nr�)r��USER�Config�READ_TIMEOUT��clsr�r�r�s����r1�<lambda>z"RpcServer.create.<locals>.<lambda>�s+���&������#�0����r3)r��SOCKET_PATHr�FileNotFoundErrorr��unlinkr�r�MAX_CONCURRENT_CONNECTIONS�create_unix_serverr��SOCKET_MODE)rr�r��serverr�s``` @r1�createzRpcServer.create�s��������(���9�9�9�
�'�
(�
(�	'�	'��I�c�o�&�&�&�	'�	'�	'�	'�	'�	'�	'�	'�	'�	'�	'����	'�	'�	'�	'�#�F�$E�F�F���.�.�
�
�
�
�
�
�
�
�O�	
�	
�	
�	
�	
�	
�	
�	
��	����#�/�2�2�2��
s�A�A�AN)rCrDrErr	r�ROOTrr�classmethodrrJr3r1r�r��sA�������$�K��=�D��K�����[���r3r�c�D�eZdZejZejZeZ	e
d���ZdS)�RpcServerAVc��~����K�d�}�j}t|��|�d��r|td��d�}t	|��}d}|D]�}	tjd��5}|D]?}	||	j��d�|��krt|	j
��}
n�@	ddd���g	ddd��nX#1swxYwY��#t$r}|}Yd}~��d}~wwxYwtd�d|z�j�j
����|�tj|
tjtjtjz��}t'�j��r"t+t,j�������fd	�}
n���fd
�}
��|
|����d{V��}|S)a�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
        c��tt��5tj|i|��cddd��S#1swxYwYdS)zReturn empty path on error.N�)rr�r��readlink)rT�kwargss  r1�
safe_readlinkz)RpcServerAV.create.<locals>.safe_readlink�s����'�"�"�
4�
4��{�D�3�F�3�3�
4�
4�
4�
4�
4�
4�
4�
4�
4�
4�
4�
4����
4�
4�
4�
4��2s�3�7�7z/var/runz/varNz
/proc/self/fdzsocket:[{}]z"[{}] Socket {!r} for {} not found.�inodec�V�������j�tj���Sr)�PROTOCOL_CLASSrrrrs����r1rz$RpcServerAV.create.<locals>.<lambda>�s0���c�0�0������#�0�1���r3c�<�������j��Sr�)rr)rr�r�s���r1rz$RpcServerAV.create.<locals>.<lambda>�s ���c�0�0��d�C�H���r3)�sock)r	r��
startswithr9rr��scandirr�r8�intr�r�rr�socket�fromfd�AF_UNIX�SOCK_STREAM�
SOCK_NONBLOCKr�rr�rrr
)rr�r�r�_socket_path�inodes�
last_errorr�it�fd�	socket_fdr]�_socket�factoryrr�s```            @r1rzRpcServerAV.create�s���������	�	�	����(��6�6�6��"�"�:�.�.�	7�'��F���
�
�6�L�!�,�/�/���
��	�	�E�
��Z��0�0�	�B� �!�!��(�=���1�1�]�5I�5I�!�6�6���),�B�G���I�!�E�	�!�	�	�	�	�	�	�	��	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	���
�
�
��
�
�
�
�
�
�����
�����4�;�;��6�z�*�C�O�S�X������	
��-���N����!5�5�
�
��
$�C�$6�7�7�	�'��(I�J�J�G��������G�G�������G��.�.�w�W�.�E�E�E�E�E�E�E�E���
sI�"C �6AC�:C �C�C �C	�C �C	�C � 
C6�*C1�1C6N)rCrDrErrrrr	r�rrrrJr3r1rr�sF�������=�D��$�K�'�N��C�C��[�C�C�Cr3rc�*�eZdZejZejZdS)�NonRootRpcServerAVN)	rCrDrEr�NON_ROOTrr�NON_ROOT_SOCKET_PATHr	rJr3r1r1r1�s��������D��-�K�K�Kr3r1c�.�eZdZejZejZdZ	dS)�NonRootRpcServeri�N)
rCrDrErr3r	rr2rrrJr3r1r5r5�s%�������-�K���D��K�K�Kr3r5c� �eZdZd�Zd�Zd�ZdS)�_RpcClientImplc��	tjtjtjtjz��|_|j�|��dS#tttf$rt���wxYwr�)
r#r%r&r'�_sock�connect�ConnectionRefusedErrorr
�BlockingIOErrorr)r�rps  r1r�z_RpcClientImpl.__init__�sy��	&������ 2�V�5I� I���D�J�
�J���{�+�+�+�+�+��&�(9�?�K�	&�	&�	&�#�%�%�%�	&���s�AA�&A?c��	|j�tj||d���dz�����n$#t
$r}t
d|�����d}~wwxYw	|�d���}n%#t$r}td|����|�d}~wwxYw	tj
|�����}n5#t$r(}td�
|����|�d}~wwxYw|S)N�r�r(r�zcommunication interrupted, �
)�terminator_bytezConnection reset: zError parsing RPC response {!r})r9�sendallr5r�r��BrokenPipeErrorr�_sock_recv_until�ConnectionResetErrorrr6r�r7r8)r�r\r(r]rNr�s      r1�dispatchz_RpcClientImpl.dispatchsJ��	A��J����J�6�V�D�D�E�E��L��&�(�(�
�
�
�
��
�	A�	A�	A��?�A�?�?�@�@�@�����	A����	A��(�(��(�?�?�D�D��#�	A�	A�	A�� 8�Q� 8� 8�9�9�q�@�����	A����	��z�$�+�+�-�-�0�0�H�H���	�	�	��1�8�8��>�>����
�����	����
�sH�AA�
A(�A#�#A(�,B�
B%�
B � B%�)&C�
D�#C=�=Dc��|j���rJ�g}|r||dv�r|j���g}tj|ggtj��}|d}|j���|vr@t
|��r"td�|�����td���|j�	tj��}t|��dkrtd���|�
|��|��||dv��d�|��S)N���rz!select() = {!r} resulted in errorzrequest timeoutz!Empty response from socket.recv()r3)r9�getblocking�fileno�selectr�CLIENT_TIMEOUTrArr8�recv�io�DEFAULT_BUFFER_SIZEr9r��join)r�r@�chunks�fdread_list�
rwx_fdlist�fdready_list�chunks       r1rCz_RpcClientImpl._sock_recv_untilsG���:�)�)�+�+�+�+�+����	!��f�R�j�@�@��:�,�,�.�.�/�K�������%�
��J�&�a�=�L��z� � �"�"�,�6�6��z�?�?�9�%�;�B�B�:�N�N����&�&7�8�8�8��J�O�O�B�$:�;�;�E��5�z�z�Q���!�"E�F�F�F��M�M�%� � � �/�	!��f�R�j�@�@�2�x�x����r3N)rCrDrEr�rErCrJr3r1r7r7�sA������&�&�&����. � � � � r3r7c��eZdZdd�Zd�ZdS)�
_NoRpcImplNc��||_ttj��5t	j��}|�t|tj����ddd��dS#1swxYwYdSr�)	r�rr�OverridingReset�asyncio�get_event_loop�run_until_completerr�)r�r�r�s   r1r�z_NoRpcImpl.__init__:s�����
��i�/�
0�
0�	L�	L��)�+�+�D��#�#�O�D�)�/�$J�$J�K�K�K�	L�	L�	L�	L�	L�	L�	L�	L�	L�	L�	L�	L����	L�	L�	L�	L�	L�	Ls�AA/�/A3�6A3c���tj��}t�d�||����||d�}t
�tj����}	t|tj���}|�t|||j��|����t
�|��S#t
�|��wxYw)NzExecuting {}, params: {}r>)rg)rYrZrUr�r8r#r�r��getuidrorrr[r_r�r�)r�r\r(r��requestr%rhs       r1rEz_NoRpcImpl.dispatchFs����%�'�'�����.�5�5�f�f�E�E�F�F�F�$��7�7���"�"�2�9�;�;�/�/��	(�"�6��
�>�>�>�B��*�*� ���G�T�Z�!8�!8�&�A�A���
� � ��'�'�'�'��N� � ��'�'�'�'���s
�3AC�C6r�)rCrDrEr�rErJr3r1rVrV9s;������
L�
L�
L�
L�(�(�(�(�(r3rVc�D�eZdZdZejddd�d�Zd�Zd�Zd�Z	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_running�reconnect_with_timeout�num_retriesc��d|_t��rtjntj|_|tjkr't��rttj
���|tj
kr+ttj
tj����|tjtj
fvr[	|r|�||��|_nt%|j��|_dS#t$r|tj
kr�YnwxYw|j�-t��s
Jd���t'��|_dSdS)Nz-_NoRpcImpl is not available for non root user)�_implrrr	r3r(r>rGrrrFrr�activate_socket_servicer
�SVC_NAMErH�_reconnect_with_timeoutr7rV)r�rarbrcs    r1r�zRpcClient.__init___se����
��~�~�
-�F����,�	
��
#�o�&=�=�=�� � �
>�$�O�$;�<�<�<�
#��'>�
>�
>��V�3�D�M�B�B�C�C�C�!����#�&
�
�
�

�)�C�!%�!=�!=�.��"�"�D�J�J�"0��0A�!B�!B�D�J����$�
�
�
�)�_�-D�D�D��E�D�
�����:�����
?�
?�>�
?�
?��#���D�J�J�J��s�7C9�9D�Dc�6�tj|j|��Sr���	functools�partialr�)r�r\s  r1�__getattr__zRpcClient.__getattr__�s��� ����8�8�8r3c�6�tj|j|��Sr�rj)r�r�s  r1�cmdz
RpcClient.cmd�s��� ����9�9�9r3c�h�|j�||��}t|ttf��rI|dt
tfvr|d|dfS|dtksJ�|d|dfS|dt
tfvrt|d���|dS)NrLrMrN)	rerEr+rbrWrr!r r)r�r\r(r�s    r1r�zRpcClient._dispatch�s����:�&�&�v�v�6�6���f�t�U�m�,�,�
	$���!�e�W�%5�5�5���)�8�J�+?�?�?���)�W�4�4�4�4���)�8�F�+;�;�;���!�e�W�%5�5�5�#�H�Z�$8�9�9�9��F�#�#r3c��		t|j��S#t$r;|r5t�d|��tj|��|dz}n�YnwxYw�^)NTz$Waiting %d second(s) before retry...r�)r7r(rrUr��time�sleep)r��timeoutrcs   r1rhz!RpcClient._reconnect_with_timeout�s���	�

�%�d�&7�8�8�8��$�
�
�
����K�K�>������J�w�'�'�'��1�$�K�K�� �K�

����	s��AA�A)rCrDrE�__doc__r>rFr�rmror�rhrJr3r1r`r`Ts��������� /�6�#��,&�,&�,&�,&�,&�\9�9�9�:�:�:�
$�
$�
$�����r3r`)]rurYrkr�rMr5r�rJr#r�r{rr�
contextlibr�contextvarsr�loggingr�typingr�psutilrrX�defence360agent.apir�defence360agent.applicationr	� defence360agent.contracts.configr
rr�-defence360agent.feature_management.exceptionsr�'defence360agent.internals.auth_protocolr
�defence360agent.modelr�$defence360agent.model.simplificationr�defence360agent.utilsrr�defence360agent.utils.bufferrr�defence360agent.subsys.panelsr�"defence360agent.subsys.panels.baser�defence360agent.subsysr�$defence360agent.rpc_tools.exceptionsrrr� defence360agent.rpc_tools.lookuprr�defence360agent.rpc_tools.utilsrr�"defence360agent.rpc_tools.validater�defence360agent.rpc_toolsrr r!rCrUr#r"�__annotations__�	frozensetr,r2r<r>r_rorZrr�r�r�r�r�r�rr1r5r7rVr`rJr3r1�<module>r�sE�����������������	�	�	�	�����	�	�	�	�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�����������"�"�"�"�"�"�����������������������*�*�*�*�*�*�+�+�+�+�+�+�F�F�F�F�F�F�F�F�������K�J�J�J�J�J�+�+�+�+�+�+�@�@�@�@�@�@�8�8�8�8�8�8�8�8�G�G�G�G�G�G�G�G�7�7�7�7�7�7�D�D�D�D�D�D�)�)�)�)�)�)�����������
A�@�@�@�@�@�@�@���������?�>�>�>�>�>�=�=�=�=�=�=�=�=�=�=�
��8�	�	��#-�*�-=�">�">��
�3��>�>�>�"�	�">�">�">�?�?��	�	�	�*�*�*���������$C�C�C�8���J�#�J�(�3�-�J�J�J�J�<�<�<���������,D�D�D�D�D�D�D�D�N}$�}$�}$�}$�}$�/�}$�}$�}$�@�����������2I�I�I�I�I�I�I�I�X.�.�.�.�.��.�.�.�
�����y����> �> �> �> �> �> �> �> �B(�(�(�(�(�(�(�(�6Z�Z�Z�Z�Z�Z�Z�Z�Z�Zr3defence360agent/simple_rpc/__pycache__/__init__.cpython-311.pyc0000644000000000000000000010606200000000000021361 0ustar  �

����g�8��X�UdZddlZddlZddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlZddlm
Z
ddlmZddlmZddlmZddlmZddlZddlmZdd	lmZdd
lmZmZddlm Z ddl!m"Z"dd
l#m$Z$ddl%m&Z&ddl'm(Z(m)Z)ddl*m+Z+m,Z,ddl-m.Z.ddl/m0Z0ddl1m2Z2ddl3m4Z4m5Z5m6Z6ddl7m8Z8m9Z9ddl:m;Z;m<Z<ddl=m>Z>ddl?m@Z@mAZAmBZBeeC��ZDed��ZEeeFeGd<eHhd���ZId�ZJd�ZKGd�d��ZLd �ZMd!�ZNd"eOd#eeOfd$�ZPd%�ZQGd&�d'��ZRGd(�d)��ZSGd*�d+e"��ZTd,�ZUGd-�d.��ZVGd/�d0��ZWGd1�d2eW��ZXGd3�d4eV��ZYGd5�d6��ZZGd7�d8��Z[Gd9�d:��Z\dS);z.
Simple unix socket RPC server implementation
�N)�suppress)�
ContextVar)�	getLogger)�Sequence)�Process)�
inactivity)�app)�Core�	SimpleRpc)�FeatureManagementError)�UnixSocketAuthProtocol)�	tls_check)�run_in_executor)�is_root_user�run_coro)�
LineBuffer�LineBufferOverflow)�
hosting_panel)�InvalidTokenException)�svcctl)�
ResponseError�ServiceStateError�SocketError)�	Endpoints�UserType)�
is_running�rpc_is_running)�ValidationError)�ERROR�SUCCESS�WARNING�rpc_caller_uid�caller_uid_var>�jwt�token�passwordc���t|��}|�d��}t|t��r't|��}tD]}||vrd||<�||d<|S)N�paramsz***)�dict�get�
isinstance�_SENSITIVE_PARAM_KEYS)�decoded�safer(�safe_params�keys     �X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/__init__.py�_redact_for_logr2<sn����=�=�D�
�X�X�h�
�
�F��&�$���%��6�l�l��(�	)�	)�C��k�!�!�#(��C� ��$��X���K�c��	tj|��}n2#t$r%d�t	|����cYSwxYwt|t��st|��Stt|����S)Nz<unparseable, {} chars>)	�json�loads�	Exception�format�lenr+r)�reprr2)�rawr-s  r1�_safe_log_payloadr<Hs���:��*�S�/�/�����:�:�:�(�/�/��C���9�9�9�9�9�:�����g�t�$�$���G�}�}�����(�(�)�)�)s��,A�Ac��eZdZdZdZdZdZdS)�RpcServiceState�running�stopped�any�directN)�__name__�
__module__�__qualname__�RUNNING�STOPPED�ANY�DIRECT�r3r1r>r>Rs*�������G��G��C�
�F�F�Fr3r>c��K�	|�d{V��}tg|d�S#t$r5}t|jd�}|�|j��|cYd}~Sd}~wttf$r@}|j^}}tj
|g|�R�t|t|��zgd�cYd}~Sd}~wt$r^}tj|��t�
d|t!|����tt!|��d�cYd}~Sd}~wwxYw)N)�result�messages�data�rLrMz-Something went wrong while processing %s (%s))r rr!�errors�update�
extra_data�PermissionErrorr�args�logger�errorr�tupler7�
sentry_sdk�capture_exception�str)�coro�methodrL�e�msgrTs      r1�_execute_requestr_dsu����C���������."�r�6�B�B�B��-�������
�
��	�
�
�a�l�#�#�#��
�
�
�
�
�
������3�4�
�
�
��V�
��d���S� �4� � � � ���u�T�{�{�*�+�
�
�	
�	
�	
�	
�	
�	
������5�5�5��$�Q�'�'�'����;�V�S��V�V�	
�	
�	
� �S��V�V�4�4�4�4�4�4�4�4�����
5���s?��
D�*A�D�D�%5B �D� 
D�-AD�D�Dc���tj}t|ttf��r�t	|��}t
j�dg��}t
j�|g��}t
j�|g��}t||z��D]8\}}||vr/||vr+t�d|j��||��}�9|S)NzApplying middleware %s)
r�route_to_endpointr+�listrWr	�
MIDDLEWAREr*�MIDDLEWARE_EXCLUDE�reversedrU�debugrC)	r\�user�cb�hashable�common�specific�excluded�mw�userss	         r1�_apply_middlewarero�s���	�	$�B��&�4��-�(�(����=�=����#�#�D�"�-�-���>�%�%�h��3�3���)�-�-�h��;�;��!�&�8�"3�4�4�	�	�I�B����
�
�B�h�$6�$6����5�r�{�C�C�C��R��V�V���
�Ir3�socket_path�returnc���tdtj��tj�����5}�fd�|D��cddd��S#1swxYwYdS)z9Find inodes corresponding to the unix domain socket path.z/proc/net/unix)�encodingrPc�L��g|] }�|v�|���d��!S)���)�split)�.0�linerps  �r1�
<listcomp>z$_find_uds_inodes.<locals>.<listcomp>�s0���I�I�I�T�[�D�5H�5H��
�
���R� �5H�5H�5Hr3N)�open�sys�getfilesystemencoding�getfilesystemencodeerrors)rp�files` r1�_find_uds_inodesr�s����	
���*�,�,��,�.�.�
�
�
�J�
�I�I�I�I�T�I�I�I�J�J�J�J�J�J�J�J�J�J�J�J����J�J�J�J�J�Js�A�A�Ac��	tj|j��}n#ttf$rYdSwxYw|j}d|vod|vS)zcTrue if cls.__init__ accepts the guard kwargs; legacy *_ signatures TypeError when passed limiter=.F�limiter�read_timeout)�inspect�	signature�__init__�	TypeError�
ValueError�
parameters)�protocol_cls�sigr(s   r1�_protocol_supports_guardr��s_������ 5�6�6�����z�"�����u�u�����
�^�F����;�>�V�#;�;s��1�1c�6�eZdZd�Zd�Zd�Zed���ZdS)�ConnectionLimiterc�0�||_d|_d|_dS)NrF)�max_connections�_count�saturation_logged)�selfr�s  r1r�zConnectionLimiter.__init__�s��.������!&����r3c�J�|j|jkrdS|xjdz
c_dS)NF�T)r�r��r�s r1�acquirezConnectionLimiter.acquire�s,���;�$�.�.�.��5����q�����tr3c�N�|jdkr|xjdzc_d|_dSdS)Nrr�F)r�r�r�s r1�releasezConnectionLimiter.release�s2���;��?�?��K�K�1��K�K�%*�D�"�"�"��?r3c��|jS�N)r�r�s r1�countzConnectionLimiter.count�s
���{�r3N)rCrDrEr�r�r��propertyr�rJr3r1r�r��s\������'�'�'�
���+�+�+�
����X���r3r�c�F�eZdZddd�d�Zd�Zd�Zd�Zd�Zd�Zd	�Z	d
�Z
dS)�ConnectionGuardN�r�r�c��||_||_||_||_d|_d|_d|_d|_d|_dS�NF)	�_loop�_limiter�
_read_timeout�_name�
_transport�_timeout_handle�_slot_acquired�	_peer_pid�	_peer_uid)r��loopr�r��names     r1r�zConnectionGuard.__init__�sH����
���
�)�����
����#���#����������r3c�"�|j�^|j���sE|jjs7t�d|j|jj��d|j_dS|jdu|_||_|�	��dS)Nz6%s connection limit (%d) reached; rejecting new clientTF)
r�r�r�rU�warningr�r�r�r��_schedule_timeout�r��	transports  r1�	try_admitzConnectionGuard.try_admit�s����=�$�T�]�-B�-B�-D�-D�$��=�2�
7����L��J��M�1����
37��
�/��5�"�m�4�7���#������ � � ��tr3c�"�||_||_dSr�)r�r�)r��pid�uids   r1�	note_peerzConnectionGuard.note_peer�s���������r3c�.�|���dSr�)r�r�s r1�on_datazConnectionGuard.on_data�s����� � � � � r3c��|���|jr'|j� |j���d|_d|_dSr�)�_cancel_timeoutr�r�r�r�r�s r1�on_lostzConnectionGuard.on_lost�sM����������	(�4�=�#<��M�!�!�#�#�#�"'�D������r3c��|j�dS|j�|j���|j�|j|j��|_dSr�)r�r��cancelr��
call_later�_on_timeoutr�s r1r�z!ConnectionGuard._schedule_timeout�sY����%��F���+�� �'�'�)�)�)�#�z�4�4���� 0� 
� 
����r3c�X�|j�"|j���d|_dSdSr�)r�r�r�s r1r�zConnectionGuard._cancel_timeout�s6����+�� �'�'�)�)�)�#'�D� � � �,�+r3c��d|_|j�dSt�d|j|j|j|j��|jdc}|_|���|�	��dS)Nz;Closing idle %s connection (pid=%s uid=%s, no data for %ds))
r�r�rUr�r�r�r�r��closer�r�s  r1r�zConnectionGuard._on_timeout�sx��#����?�"��F����I��J��N��N���	
�	
�	
�&*�_�d�"�	�4�?��������������r3)rCrDrEr�r�r�r�r�r�r�r�rJr3r1r�r��s�������(,�4�	�	�	�	�	�
�
�
����!�!�!����
�
�
�(�(�(�

�
�
�
�
r3r�c�P��eZdZddd�d�Z�fd�Zdefd�Zd�Zd�Zd	�Z	d
�Z
�xZS)�_RpcServerProtocolNr�c��||_||_||_d|_t	��|_t
|||d���|_dS)N�RPC)r�r�r�)r��_sinkrgr�r�_bufr��_guard)r�r��sinkrgr�r�s      r1r�z_RpcServerProtocol.__init__sL����
���
���	�����L�L��	�%��'��5�
�
�
����r3c����|j�|��s|���dS	t���|��ny#t
ttjf$rZ}t�
d|��|���d|_|j���Yd}~dSd}~wwxYw|j�
|j|j��dS)Nz5Rejected RPC connection: SO_PEERCRED unavailable (%s))r�r�r��super�connection_made�OSError�AttributeError�structrVrUr�r�r�r��_pid�_uid)r�r��exc�	__class__s   �r1r�z"_RpcServerProtocol.connection_made
s�����{�$�$�Y�/�/�	��O�O�����F�
	��G�G�#�#�I�.�.�.�.������6�	�	�	��N�N�G��
�
�
�
�O�O����"�D�O��K���!�!�!��F�F�F�F�F�����	����	
����d�i���3�3�3�3�3s�!A�C�1AC�CrNc�~�tj|��}tj���||��\}}||_|�!||dd<d|dvr|g|dd<	t
|j�����}n'#t$r}t|��g}Yd}~nd}~wwxYw||d<|S)Nr(rgrn�calling_process)r5r6r�HostingPanel�authenticatergrr��cmdliner7rZ)r�rNr-�	user_type�	user_namer�r]s       r1�preprocess_dataz"_RpcServerProtocol.preprocess_datas����*�T�"�"��,�9�;�;�H�H��'� 
� 
��	�9���	�� �(1�G�H��f�%��'�(�+�+�+�.7�[���!�'�*�	'�%�d�i�0�0�8�8�:�:�O�O���	'�	'�	'�"�1�v�v�h�O�O�O�O�O�O�����	'����%4��!�"��s�*&B�
B5�B0�0B5c��|j�dS|j���	|j�|�����nx#t$rk}t�d|j	|j
|��|j���d|_|j���Yd}~dSd}~wwxYw|jD�]�}	|�
|��}|d}|d}t�d|��t||j��}t"�|j
��}	|j�|�|||||j|j������t"�|��n#t"�|��wxYw��#t0$rO}t�d��|�t4t7|��d���Yd}~��Ld}~wt8$r]}t�dt=|����|�t4t7|��d���Yd}~���d}~wwxYwdS)Nz*Closing RPC connection (pid=%s uid=%s): %s�commandr(zData received: command=%szIncorrect token providedrOz)Something went wrong before processing %s)r�r�r�r��append�decoderrUr�r�r�r�r�r�rfrorgr#�setr��create_task�	_dispatchr��resetr�_write_responserrZr7�	exceptionr<)	r�rNr]r^rLr\r(rhr%s	         r1�
data_receivedz _RpcServerProtocol.data_received0s����?�"��F��������	��I���T�[�[�]�]�+�+�+�+��!�
	�
	�
	��N�N�<��	��	��	
�
�
�
�O�!�!�#�#�#�"�D�O��K���!�!�!��F�F�F�F�F�����
	�����9�$	L�$	L�C�#
L��-�-�c�2�2���	�*����)�����8�&�A�A�A�&�v�t�y�9�9��'�*�*�4�9�5�5��0��J�*�*����"�F�B�B�v�t�z�4�9�,M�,M������#�(�(��/�/�/�/��N�(�(��/�/�/�/����/��(�
L�
L�
L����9�:�:�:��$�$��3�q�6�6�%J�%J�K�K�K�K�K�K�K�K������
L�
L�
L�� � �?�%�c�*�*����
�$�$��3�q�6�6�%J�%J�K�K�K�K�K�K�K�K�����

L����=$	L�$	LsX�,A�
C�A C�C�A4G�	AF(�
G�(G�G�
J�AH�
J�)AJ�Jc��RK�tj�d�|����5t	||���d{V��}t
�d�||����|�|��ddd��dS#1swxYwYdS)Nzrpc_{}z Response: method - {}, data - {})r�track�taskr8r_rU�infor�)r�r\r(r[�responses     r1r�z_RpcServerProtocol._dispatchgs�����
�
�
"�
"�8�?�?�6�#:�#:�
;�
;�	+�	+�-�d�F�;�;�;�;�;�;�;�;�H��K�K�2�9�9�&�(�K�K�
�
�
�
� � ��*�*�*�
	+�	+�	+�	+�	+�	+�	+�	+�	+�	+�	+�	+����	+�	+�	+�	+�	+�	+s�AB�B �#B c�F�|j���d|_dSr�)r�r�r�r�s  r1�connection_lostz"_RpcServerProtocol.connection_lostps!�������������r3c�4�|j�t�d��dS	|j�t	j|��dz�����dS#t$r%}t�|��Yd}~dSd}~wwxYw)Nz*Cannot send RPC response: connection lost.�
)	r�rUr��writer5�dumps�encoder7r�)r�rNr]s   r1r�z"_RpcServerProtocol._write_responsets����?�"��N�N�G�H�H�H��F�
$���%�%�t�z�$�'7�'7�$�'>�&F�&F�&H�&H�I�I�I�I�I���
$�
$�
$�� � ��#�#�#�#�#�#�#�#�#�����
$���s�AA(�(
B�2B�B)rCrDrEr�r�rZr�r�r�r�r��
__classcell__)r�s@r1r�r��s��������48�t�
�
�
�
�
�4�4�4�4�4�"�C�����*5L�5L�5L�n+�+�+����$�$�$�$�$�$�$r3r�c��tj�|��}tj|d���tj|d��dS)NT)�exist_oki�)�os�path�dirname�makedirs�chmod)rp�dir_names  r1� _check_socket_folder_permissionsr�sB���w���{�+�+�H��K��4�(�(�(�(��H�X�u�����r3c�D�eZdZejZejZdZe	d���Z
dS)�	RpcServeri�c������K�t�j��tt��5t	j�j��ddd��n#1swxYwYt
tj�����	����fd��j���d{V��}t	j
�j�j��|S)Nc�J��t���j�tj���S�Nr�)r��USER�Config�READ_TIMEOUT��clsr�r�r�s����r1�<lambda>z"RpcServer.create.<locals>.<lambda>�s+���&������#�0����r3)r��SOCKET_PATHr�FileNotFoundErrorr��unlinkr�r�MAX_CONCURRENT_CONNECTIONS�create_unix_serverr��SOCKET_MODE)rr�r��serverr�s``` @r1�createzRpcServer.create�s��������(���9�9�9�
�'�
(�
(�	'�	'��I�c�o�&�&�&�	'�	'�	'�	'�	'�	'�	'�	'�	'�	'�	'����	'�	'�	'�	'�#�F�$E�F�F���.�.�
�
�
�
�
�
�
�
�O�	
�	
�	
�	
�	
�	
�	
�	
��	����#�/�2�2�2��
s�A�A�AN)rCrDrErr	r�ROOTrr�classmethodrrJr3r1r�r��sA�������$�K��=�D��K�����[���r3r�c�D�eZdZejZejZeZ	e
d���ZdS)�RpcServerAVc��~����K�d�}�j}t|��|�d��r|td��d�}t	|��}d}|D]�}	tjd��5}|D]?}	||	j��d�|��krt|	j
��}
n�@	ddd���g	ddd��nX#1swxYwY��#t$r}|}Yd}~��d}~wwxYwtd�d|z�j�j
����|�tj|
tjtjtjz��}t'�j��r"t+t,j�������fd	�}
n���fd
�}
��|
|����d{V��}|S)a�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
        c��tt��5tj|i|��cddd��S#1swxYwYdS)zReturn empty path on error.N�)rr�r��readlink)rT�kwargss  r1�
safe_readlinkz)RpcServerAV.create.<locals>.safe_readlink�s����'�"�"�
4�
4��{�D�3�F�3�3�
4�
4�
4�
4�
4�
4�
4�
4�
4�
4�
4�
4����
4�
4�
4�
4��2s�3�7�7z/var/runz/varNz
/proc/self/fdzsocket:[{}]z"[{}] Socket {!r} for {} not found.�inodec�V�������j�tj���Sr)�PROTOCOL_CLASSrrrrs����r1rz$RpcServerAV.create.<locals>.<lambda>�s0���c�0�0������#�0�1���r3c�<�������j��Sr�)rr)rr�r�s���r1rz$RpcServerAV.create.<locals>.<lambda>�s ���c�0�0��d�C�H���r3)�sock)r	r��
startswithr9rr��scandirr�r8�intr�r�rr�socket�fromfd�AF_UNIX�SOCK_STREAM�
SOCK_NONBLOCKr�rr�rrr
)rr�r�r�_socket_path�inodes�
last_errorr�it�fd�	socket_fdr]�_socket�factoryrr�s```            @r1rzRpcServerAV.create�s���������	�	�	����(��6�6�6��"�"�:�.�.�	7�'��F���
�
�6�L�!�,�/�/���
��	�	�E�
��Z��0�0�	�B� �!�!��(�=���1�1�]�5I�5I�!�6�6���),�B�G���I�!�E�	�!�	�	�	�	�	�	�	��	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	���
�
�
��
�
�
�
�
�
�����
�����4�;�;��6�z�*�C�O�S�X������	
��-���N����!5�5�
�
��
$�C�$6�7�7�	�'��(I�J�J�G��������G�G�������G��.�.�w�W�.�E�E�E�E�E�E�E�E���
sI�"C �6AC�:C �C�C �C	�C �C	�C � 
C6�*C1�1C6N)rCrDrErrrrr	r�rrrrJr3r1rr�sF�������=�D��$�K�'�N��C�C��[�C�C�Cr3rc�*�eZdZejZejZdS)�NonRootRpcServerAVN)	rCrDrEr�NON_ROOTrr�NON_ROOT_SOCKET_PATHr	rJr3r1r1r1�s��������D��-�K�K�Kr3r1c�.�eZdZejZejZdZ	dS)�NonRootRpcServeri�N)
rCrDrErr3r	rr2rrrJr3r1r5r5�s%�������-�K���D��K�K�Kr3r5c� �eZdZd�Zd�Zd�ZdS)�_RpcClientImplc��	tjtjtjtjz��|_|j�|��dS#tttf$rt���wxYwr�)
r#r%r&r'�_sock�connect�ConnectionRefusedErrorr
�BlockingIOErrorr)r�rps  r1r�z_RpcClientImpl.__init__�sy��	&������ 2�V�5I� I���D�J�
�J���{�+�+�+�+�+��&�(9�?�K�	&�	&�	&�#�%�%�%�	&���s�AA�&A?c��	|j�tj||d���dz�����n$#t
$r}t
d|�����d}~wwxYw	|�d���}n%#t$r}td|����|�d}~wwxYw	tj
|�����}n5#t$r(}td�
|����|�d}~wwxYw|S)N�r�r(r�zcommunication interrupted, �
)�terminator_bytezConnection reset: zError parsing RPC response {!r})r9�sendallr5r�r��BrokenPipeErrorr�_sock_recv_until�ConnectionResetErrorrr6r�r7r8)r�r\r(r]rNr�s      r1�dispatchz_RpcClientImpl.dispatchsJ��	A��J����J�6�V�D�D�E�E��L��&�(�(�
�
�
�
��
�	A�	A�	A��?�A�?�?�@�@�@�����	A����	A��(�(��(�?�?�D�D��#�	A�	A�	A�� 8�Q� 8� 8�9�9�q�@�����	A����	��z�$�+�+�-�-�0�0�H�H���	�	�	��1�8�8��>�>����
�����	����
�sH�AA�
A(�A#�#A(�,B�
B%�
B � B%�)&C�
D�#C=�=Dc��|j���rJ�g}|r||dv�r|j���g}tj|ggtj��}|d}|j���|vr@t
|��r"td�|�����td���|j�	tj��}t|��dkrtd���|�
|��|��||dv��d�|��S)N���rz!select() = {!r} resulted in errorzrequest timeoutz!Empty response from socket.recv()r3)r9�getblocking�fileno�selectr�CLIENT_TIMEOUTrArr8�recv�io�DEFAULT_BUFFER_SIZEr9r��join)r�r@�chunks�fdread_list�
rwx_fdlist�fdready_list�chunks       r1rCz_RpcClientImpl._sock_recv_untilsG���:�)�)�+�+�+�+�+����	!��f�R�j�@�@��:�,�,�.�.�/�K�������%�
��J�&�a�=�L��z� � �"�"�,�6�6��z�?�?�9�%�;�B�B�:�N�N����&�&7�8�8�8��J�O�O�B�$:�;�;�E��5�z�z�Q���!�"E�F�F�F��M�M�%� � � �/�	!��f�R�j�@�@�2�x�x����r3N)rCrDrEr�rErCrJr3r1r7r7�sA������&�&�&����. � � � � r3r7c��eZdZdd�Zd�ZdS)�
_NoRpcImplNc��||_ttj��5t	j��}|�t|tj����ddd��dS#1swxYwYdSr�)	r�rr�OverridingReset�asyncio�get_event_loop�run_until_completerr�)r�r�r�s   r1r�z_NoRpcImpl.__init__:s�����
��i�/�
0�
0�	L�	L��)�+�+�D��#�#�O�D�)�/�$J�$J�K�K�K�	L�	L�	L�	L�	L�	L�	L�	L�	L�	L�	L�	L����	L�	L�	L�	L�	L�	Ls�AA/�/A3�6A3c���tj��}t�d�||����||d�}t
�tj����}	t|tj���}|�t|||j��|����t
�|��S#t
�|��wxYw)NzExecuting {}, params: {}r>)rg)rYrZrUr�r8r#r�r��getuidrorrr[r_r�r�)r�r\r(r��requestr%rhs       r1rEz_NoRpcImpl.dispatchFs����%�'�'�����.�5�5�f�f�E�E�F�F�F�$��7�7���"�"�2�9�;�;�/�/��	(�"�6��
�>�>�>�B��*�*� ���G�T�Z�!8�!8�&�A�A���
� � ��'�'�'�'��N� � ��'�'�'�'���s
�3AC�C6r�)rCrDrEr�rErJr3r1rVrV9s;������
L�
L�
L�
L�(�(�(�(�(r3rVc�D�eZdZdZejddd�d�Zd�Zd�Zd�Z	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_running�reconnect_with_timeout�num_retriesc��d|_t��rtjntj|_|tjkr't��rttj
���|tj
kr+ttj
tj����|tjtj
fvr[	|r|�||��|_nt%|j��|_dS#t$r|tj
kr�YnwxYw|j�-t��s
Jd���t'��|_dSdS)Nz-_NoRpcImpl is not available for non root user)�_implrrr	r3r(r>rGrrrFrr�activate_socket_servicer
�SVC_NAMErH�_reconnect_with_timeoutr7rV)r�rarbrcs    r1r�zRpcClient.__init___se����
��~�~�
-�F����,�	
��
#�o�&=�=�=�� � �
>�$�O�$;�<�<�<�
#��'>�
>�
>��V�3�D�M�B�B�C�C�C�!����#�&
�
�
�

�)�C�!%�!=�!=�.��"�"�D�J�J�"0��0A�!B�!B�D�J����$�
�
�
�)�_�-D�D�D��E�D�
�����:�����
?�
?�>�
?�
?��#���D�J�J�J��s�7C9�9D�Dc�6�tj|j|��Sr���	functools�partialr�)r�r\s  r1�__getattr__zRpcClient.__getattr__�s��� ����8�8�8r3c�6�tj|j|��Sr�rj)r�r�s  r1�cmdz
RpcClient.cmd�s��� ����9�9�9r3c�h�|j�||��}t|ttf��rI|dt
tfvr|d|dfS|dtksJ�|d|dfS|dt
tfvrt|d���|dS)NrLrMrN)	rerEr+rbrWrr!r r)r�r\r(r�s    r1r�zRpcClient._dispatch�s����:�&�&�v�v�6�6���f�t�U�m�,�,�
	$���!�e�W�%5�5�5���)�8�J�+?�?�?���)�W�4�4�4�4���)�8�F�+;�;�;���!�e�W�%5�5�5�#�H�Z�$8�9�9�9��F�#�#r3c��		t|j��S#t$r;|r5t�d|��tj|��|dz}n�YnwxYw�^)NTz$Waiting %d second(s) before retry...r�)r7r(rrUr��time�sleep)r��timeoutrcs   r1rhz!RpcClient._reconnect_with_timeout�s���	�

�%�d�&7�8�8�8��$�
�
�
����K�K�>������J�w�'�'�'��1�$�K�K�� �K�

����	s��AA�A)rCrDrE�__doc__r>rFr�rmror�rhrJr3r1r`r`Ts��������� /�6�#��,&�,&�,&�,&�,&�\9�9�9�:�:�:�
$�
$�
$�����r3r`)]rurYrkr�rMr5r�rJr#r�r{rr�
contextlibr�contextvarsr�loggingr�typingr�psutilrrX�defence360agent.apir�defence360agent.applicationr	� defence360agent.contracts.configr
rr�-defence360agent.feature_management.exceptionsr�'defence360agent.internals.auth_protocolr
�defence360agent.modelr�$defence360agent.model.simplificationr�defence360agent.utilsrr�defence360agent.utils.bufferrr�defence360agent.subsys.panelsr�"defence360agent.subsys.panels.baser�defence360agent.subsysr�$defence360agent.rpc_tools.exceptionsrrr� defence360agent.rpc_tools.lookuprr�defence360agent.rpc_tools.utilsrr�"defence360agent.rpc_tools.validater�defence360agent.rpc_toolsrr r!rCrUr#r"�__annotations__�	frozensetr,r2r<r>r_rorZrr�r�r�r�r�r�rr1r5r7rVr`rJr3r1�<module>r�sE�����������������	�	�	�	�����	�	�	�	�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�����������"�"�"�"�"�"�����������������������*�*�*�*�*�*�+�+�+�+�+�+�F�F�F�F�F�F�F�F�������K�J�J�J�J�J�+�+�+�+�+�+�@�@�@�@�@�@�8�8�8�8�8�8�8�8�G�G�G�G�G�G�G�G�7�7�7�7�7�7�D�D�D�D�D�D�)�)�)�)�)�)�����������
A�@�@�@�@�@�@�@���������?�>�>�>�>�>�=�=�=�=�=�=�=�=�=�=�
��8�	�	��#-�*�-=�">�">��
�3��>�>�>�"�	�">�">�">�?�?��	�	�	�*�*�*���������$C�C�C�8���J�#�J�(�3�-�J�J�J�J�<�<�<���������,D�D�D�D�D�D�D�D�N}$�}$�}$�}$�}$�/�}$�}$�}$�@�����������2I�I�I�I�I�I�I�I�X.�.�.�.�.��.�.�.�
�����y����> �> �> �> �> �> �> �> �B(�(�(�(�(�(�(�(�6Z�Z�Z�Z�Z�Z�Z�Z�Z�Zr3defence360agent/simple_rpc/__pycache__/advisor.cpython-311.opt-1.pyc0000644000000000000000000000625400000000000022232 0ustar  �

_.u��q���v�ddlmZddlmZddlmZddlmZddlm	Z	ddl
mZddlm
Z
Gd�d	e��Zd
S)�)�defaultdict)�
ConfigFile)�
RootEndpoints)�
update_config)�lookup)�	EventsAPI)�config_cleanupc��eZdZejdd��d���Zejdd��d���Zd�Zed���Z	dS)	�AdvisorEndpoints�advisor�applyc��<K�|�|���d{V��S�N)�_apply��self�advicess  �W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/advisor.py�
advisor_applyzAdvisorEndpoints.advisor_applys*�����[�[��)�)�)�)�)�)�)�)�)�z	apply-allc��nK�tj���d{V��}|�|���d{V��Sr)rrrrs  r�	apply_allzAdvisorEndpoints.apply_allsJ����!�)�+�+�+�+�+�+�+�+���[�[��)�)�)�)�)�)�)�)�)rc��:K�tt��}t�����}|D]}|�|||���t|j|���d{V��dtt�������iS)N�items)r�dictr�config_to_dict�_extract_conf_from_adviser�_sinkr	)rr�target_conf�current_conf�advises     rrzAdvisorEndpoints._applys�����!�$�'�'��!�|�|�2�2�4�4���	N�	N�F��*�*�6�<��M�M�M�M��D�J��4�4�4�4�4�4�4�4�4���
���(C�(C�(E�(E�F�F�G�Grc��|d���D]3\}}|���D]\}}||||vrdS��4|d���D] \}}||�|���!dS)N�ignore�
config_action)r�update)r!r r�section_key�
section_value�	value_key�ignored_valuess       rrz*AdvisorEndpoints._extract_conf_from_advises���*0��*:�*@�*@�*B�*B�	�	�&�K��-:�-@�-@�-B�-B�
�
�)�	�>���,�Y�7�>�I�I��F�F�F�J�
�+1��*A�*G�*G�*I�*I�	;�	;�&�K����$�+�+�M�:�:�:�:�	;�	;rN)
�__name__�
__module__�__qualname__r�bindrrr�staticmethodr�rrrrs��������V�[��G�$�$�*�*�%�$�*��V�[��K�(�(�*�*�)�(�*�H�H�H��;�;��\�;�;�;rrN)�collectionsr� defence360agent.contracts.configr� defence360agent.rpc_tools.lookupr�defence360agent.utils.configr�defence360agent.rpc_toolsr�!defence360agent.api.server.eventsr�+defence360agent.feature_management.checkersr	rr/rr�<module>r7s���#�#�#�#�#�#�7�7�7�7�7�7�:�:�:�:�:�:�6�6�6�6�6�6�,�,�,�,�,�,�7�7�7�7�7�7�F�F�F�F�F�F�;�;�;�;�;�}�;�;�;�;�;rdefence360agent/simple_rpc/__pycache__/advisor.cpython-311.pyc0000644000000000000000000000625400000000000021273 0ustar  �

_.u��q���v�ddlmZddlmZddlmZddlmZddlm	Z	ddl
mZddlm
Z
Gd�d	e��Zd
S)�)�defaultdict)�
ConfigFile)�
RootEndpoints)�
update_config)�lookup)�	EventsAPI)�config_cleanupc��eZdZejdd��d���Zejdd��d���Zd�Zed���Z	dS)	�AdvisorEndpoints�advisor�applyc��<K�|�|���d{V��S�N)�_apply��self�advicess  �W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/advisor.py�
advisor_applyzAdvisorEndpoints.advisor_applys*�����[�[��)�)�)�)�)�)�)�)�)�z	apply-allc��nK�tj���d{V��}|�|���d{V��Sr)rrrrs  r�	apply_allzAdvisorEndpoints.apply_allsJ����!�)�+�+�+�+�+�+�+�+���[�[��)�)�)�)�)�)�)�)�)rc��:K�tt��}t�����}|D]}|�|||���t|j|���d{V��dtt�������iS)N�items)r�dictr�config_to_dict�_extract_conf_from_adviser�_sinkr	)rr�target_conf�current_conf�advises     rrzAdvisorEndpoints._applys�����!�$�'�'��!�|�|�2�2�4�4���	N�	N�F��*�*�6�<��M�M�M�M��D�J��4�4�4�4�4�4�4�4�4���
���(C�(C�(E�(E�F�F�G�Grc��|d���D]3\}}|���D]\}}||||vrdS��4|d���D] \}}||�|���!dS)N�ignore�
config_action)r�update)r!r r�section_key�
section_value�	value_key�ignored_valuess       rrz*AdvisorEndpoints._extract_conf_from_advises���*0��*:�*@�*@�*B�*B�	�	�&�K��-:�-@�-@�-B�-B�
�
�)�	�>���,�Y�7�>�I�I��F�F�F�J�
�+1��*A�*G�*G�*I�*I�	;�	;�&�K����$�+�+�M�:�:�:�:�	;�	;rN)
�__name__�
__module__�__qualname__r�bindrrr�staticmethodr�rrrrs��������V�[��G�$�$�*�*�%�$�*��V�[��K�(�(�*�*�)�(�*�H�H�H��;�;��\�;�;�;rrN)�collectionsr� defence360agent.contracts.configr� defence360agent.rpc_tools.lookupr�defence360agent.utils.configr�defence360agent.rpc_toolsr�!defence360agent.api.server.eventsr�+defence360agent.feature_management.checkersr	rr/rr�<module>r7s���#�#�#�#�#�#�7�7�7�7�7�7�:�:�:�:�:�:�6�6�6�6�6�6�,�,�,�,�,�,�7�7�7�7�7�7�F�F�F�F�F�F�;�;�;�;�;�}�;�;�;�;�;rdefence360agent/simple_rpc/__pycache__/analyst_cleanup.cpython-311.opt-1.pyc0000644000000000000000000002460500000000000023745 0ustar  �

��g��0���,�ddlZddlmZddlmZmZddlmZddlmZm	Z	ddl
mcmcm
ZddlmZmZmZddlmZddlmZmZee��Zd	Zd
ZdZdZd
ZdeddeedddddddeiZeded�Z dZ!e"ddeh��Z#d�Z$d�Z%Gd�de��Z&dS)�N)�	getLogger)�datetime�	timedelta)�ValidationError)�
RootEndpoints�bind)�get_ssh_port�check_ssh_connection�install_pub_key)�AnalystCleanupRequest)�NO_AGENT_TOKEN�AnalystCleanupAPIz�https://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_allowlisted�not_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.)���i�zFailed to create support ticketc��t�|�d��t�|t����S)N�message)�_TICKET_ERROR_MESSAGES�get� _TICKET_ERROR_MESSAGES_BY_STATUS�_TICKET_ERROR_DEFAULT��status�bodys  �_/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/analyst_cleanup.py�_ticket_error_messager!Ss=��!�%�%�������(�,�,�V�5J�K�K����c�f�|�d��tvrdS|duod|cxkodkncS)NrTri�)r�_CLIENT_STATE_CODESrs  r �_is_expected_client_stater%ZsK���x�x�	���1�1�1��t����5�#��"5�"5�"5�"5�#�"5�"5�"5�"5�5r"c��eZdZdeeffd�Zedd��d���Zedd��d
d
���Zedd��d���ZdS)�AnalystCleanupEndpoints�returnc��K�tj|||���d{V��\}}|�d��pi}|dkrj|�d��rU|�d��r@t�d|d����|dt|d��fSt
||��rtjntj}|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
        N�ticketr�url�idzCreated ticket on url z2Failed to create support ticket: status=%s body=%s)r�
create_ticketr�logger�info�strr%�warning�errorrr!)�self�email�subject�full_descriptionrrr*�logs        r �_create_zendesk_ticketz.AnalystCleanupEndpoints._create_zendesk_ticketas����/�<����
�
�
�
�
�
�
�
����
���(�#�#�)�r���S�=�=�V�Z�Z��.�.�=�6�:�:�d�3C�3C�=��K�K�@����@�@�A�A�A��%�=�#�f�T�l�"3�"3�3�3�)���6�6�
�F�N�N���	�
	��@�&�$�O�O�O��3�F�D�A�A�B�B�Br"zanalyst-cleanup�requestc���K�tj|��x}rtd|�����tj���d{V��stt
���tj|���d{V��}|�dd��s.t|�dd���dt�d����|�d	d��rtj
d
��t|���d{V��}t���d{V��}t|���d{V��}d}	tj������d|�d
|��}
d|�d|
�d|�d�}|s)tj
dt"��|dt$�d�z
}n!|stj
dt"��|dz
}|�||	|���d{V��\}}
tj||
|���dd|iiS)zHandle analyst cleanup requestz�You already have an active request for cleaning this user. If you have additional information, you may follow the link and provide new data here: N�resultFr�zd 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_newu�We’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_link�items�
ticket_url)r�get_active_request_linkrr�check_cleanup_allowed�NOT_ALLOWLISTED_MESSAGE�check_registeredr�ZENDESK_REGISTRATION_URL�warnings�warnrr	r
�hp�HostingPanel�
get_server_ip�Warning�PREPARE_SERVER_GUIDEr8�create_request)r3r4r@r�
active_ticket�email_status�
key_installed�ssh_port�
connection_okr5�
server_accessr6rD�	ticket_ids              r �request_cleanupz'AnalystCleanupEndpoints.request_cleanup}s�����2�I��
�
�
�=�	�"�H�8E�H�H���
�(�=�?�?�?�?�?�?�?�?�	;�!�"9�:�:�:�.�?��F�F�F�F�F�F�F�F������%�0�0�	�!��#�#�I�r�2�2�2�2�*B�2�2�2���
����H�e�,�,�	��M�'�
�
�
�.�h�7�7�7�7�7�7�7�7�
�&���'�'�'�'�'�'��2�8�<�<�<�<�<�<�<�<�
�,���� � �.�.�0�0�H�H�8�H�H�h�H�H�	�
0��
0�
0�+�
0�
0�")�
0�
0�
0�	�
�
	��M�C�W�M�M�M��$�(�$�$�$�
����	��M�6��@�@�@��@�
��'+�&A�&A����'
�'
�!
�!
�!
�!
�!
�!
��
�I�	�,�� �"�	
�	
�	
�	
�
�,�
�3�4�4r"zget-requestsN�2rc��n�K�|�tj||��}ntj|||��}|rt|��dkrgSt	j��t
d���z
�t�d������fd�|D��}t�d|����|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|j�k�|j|j|jt	tj|j����t	tj|j����|jd���~S)�	completed)r@rDr�
created_at�last_updaterA)	r�last_updatedr@rBr0r�	timestampr`rA)�.0�req�
two_weeks_agos  �r �
<listcomp>z:AnalystCleanupEndpoints.request_status.<locals>.<listcomp>�s����
�
�
���z�[�(�(�C�,<�}�,L�,L� �L�!�o��*�!�(�"4�S�^�"D�"D�E�E�"�8�#5�c�6F�#G�#G�H�H�!�n�

�
�-M�,L�,Lr"zGot requests: )	r�get_all_requests�get_user_requests�lenr�utcnowrr.r/)r3r@�limit�offset�requests�filtered_requestsrfs      @r �request_statusz&AnalystCleanupEndpoints.request_status�s��������,�=�e�V�L�L�H�H�,�>��%����H�
�	�3�x�=�=�A�-�-��I�!��)�)�I�A�,>�,>�,>�>�
����=�m�=�=�>�>�>�
�
�
�
� �
�
�
��	���8�%6�8�8�9�9�9� � r"z
is-allowedc��DK�tj���d{V��}dd|iiS)NrC�
is_allowed)rrF)r3rrs  r rrz"AnalystCleanupEndpoints.is_allowed�s6����,�B�D�D�D�D�D�D�D�D�
��,�
�3�4�4r")NrZr)	�__name__�
__module__�__qualname__r0r8rrYrprr�r"r r'r'`s�������C�
�s��C�C�C�C�8
�T�
�Y�'�'�M5�M5�(�'�M5�^
�T�
�^�,�,�&!�&!�&!�-�,�&!�P
�T�
�\�*�*�5�5�+�*�5�5�5r"r')'rJ�loggingrrr�defence360agent.rpc_toolsr� defence360agent.rpc_tools.lookuprr�+defence360agent.subsys.panels.hosting_panel�subsys�panels�
hosting_panelrL�defence360agent.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 �<module>r�s������������(�(�(�(�(�(�(�(�5�5�5�5�5�5�@�@�@�@�@�@�@�@�8�8�8�8�8�8�8�8�8�8�8�8�����������
H�G�G�G�G�G���������

��8�	�	��e��?��
2��D��C���.��	:��.��	.��	.��	7�� 9�'��.
#�	-�
$�
$�$� �:�� �i��(�.�9����
���6�6�6�Y5�Y5�Y5�Y5�Y5�m�Y5�Y5�Y5�Y5�Y5r"defence360agent/simple_rpc/__pycache__/analyst_cleanup.cpython-311.pyc0000644000000000000000000002460500000000000023006 0ustar  �

��g��0���,�ddlZddlmZddlmZmZddlmZddlmZm	Z	ddl
mcmcm
ZddlmZmZmZddlmZddlmZmZee��Zd	Zd
ZdZdZd
ZdeddeedddddddeiZeded�Z dZ!e"ddeh��Z#d�Z$d�Z%Gd�de��Z&dS)�N)�	getLogger)�datetime�	timedelta)�ValidationError)�
RootEndpoints�bind)�get_ssh_port�check_ssh_connection�install_pub_key)�AnalystCleanupRequest)�NO_AGENT_TOKEN�AnalystCleanupAPIz�https://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_allowlisted�not_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.)���i�zFailed to create support ticketc��t�|�d��t�|t����S)N�message)�_TICKET_ERROR_MESSAGES�get� _TICKET_ERROR_MESSAGES_BY_STATUS�_TICKET_ERROR_DEFAULT��status�bodys  �_/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/analyst_cleanup.py�_ticket_error_messager!Ss=��!�%�%�������(�,�,�V�5J�K�K����c�f�|�d��tvrdS|duod|cxkodkncS)NrTri�)r�_CLIENT_STATE_CODESrs  r �_is_expected_client_stater%ZsK���x�x�	���1�1�1��t����5�#��"5�"5�"5�"5�#�"5�"5�"5�"5�5r"c��eZdZdeeffd�Zedd��d���Zedd��d
d
���Zedd��d���ZdS)�AnalystCleanupEndpoints�returnc��K�tj|||���d{V��\}}|�d��pi}|dkrj|�d��rU|�d��r@t�d|d����|dt|d��fSt
||��rtjntj}|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
        N�ticketr�url�idzCreated ticket on url z2Failed to create support ticket: status=%s body=%s)r�
create_ticketr�logger�info�strr%�warning�errorrr!)�self�email�subject�full_descriptionrrr*�logs        r �_create_zendesk_ticketz.AnalystCleanupEndpoints._create_zendesk_ticketas����/�<����
�
�
�
�
�
�
�
����
���(�#�#�)�r���S�=�=�V�Z�Z��.�.�=�6�:�:�d�3C�3C�=��K�K�@����@�@�A�A�A��%�=�#�f�T�l�"3�"3�3�3�)���6�6�
�F�N�N���	�
	��@�&�$�O�O�O��3�F�D�A�A�B�B�Br"zanalyst-cleanup�requestc���K�tj|��x}rtd|�����tj���d{V��stt
���tj|���d{V��}|�dd��s.t|�dd���dt�d����|�d	d��rtj
d
��t|���d{V��}t���d{V��}t|���d{V��}d}	tj������d|�d
|��}
d|�d|
�d|�d�}|s)tj
dt"��|dt$�d�z
}n!|stj
dt"��|dz
}|�||	|���d{V��\}}
tj||
|���dd|iiS)zHandle analyst cleanup requestz�You already have an active request for cleaning this user. If you have additional information, you may follow the link and provide new data here: N�resultFr�zd 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_newu�We’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_link�items�
ticket_url)r�get_active_request_linkrr�check_cleanup_allowed�NOT_ALLOWLISTED_MESSAGE�check_registeredr�ZENDESK_REGISTRATION_URL�warnings�warnrr	r
�hp�HostingPanel�
get_server_ip�Warning�PREPARE_SERVER_GUIDEr8�create_request)r3r4r@r�
active_ticket�email_status�
key_installed�ssh_port�
connection_okr5�
server_accessr6rD�	ticket_ids              r �request_cleanupz'AnalystCleanupEndpoints.request_cleanup}s�����2�I��
�
�
�=�	�"�H�8E�H�H���
�(�=�?�?�?�?�?�?�?�?�	;�!�"9�:�:�:�.�?��F�F�F�F�F�F�F�F������%�0�0�	�!��#�#�I�r�2�2�2�2�*B�2�2�2���
����H�e�,�,�	��M�'�
�
�
�.�h�7�7�7�7�7�7�7�7�
�&���'�'�'�'�'�'��2�8�<�<�<�<�<�<�<�<�
�,���� � �.�.�0�0�H�H�8�H�H�h�H�H�	�
0��
0�
0�+�
0�
0�")�
0�
0�
0�	�
�
	��M�C�W�M�M�M��$�(�$�$�$�
����	��M�6��@�@�@��@�
��'+�&A�&A����'
�'
�!
�!
�!
�!
�!
�!
��
�I�	�,�� �"�	
�	
�	
�	
�
�,�
�3�4�4r"zget-requestsN�2rc��n�K�|�tj||��}ntj|||��}|rt|��dkrgSt	j��t
d���z
�t�d������fd�|D��}t�d|����|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|j�k�|j|j|jt	tj|j����t	tj|j����|jd���~S)�	completed)r@rDr�
created_at�last_updaterA)	r�last_updatedr@rBr0r�	timestampr`rA)�.0�req�
two_weeks_agos  �r �
<listcomp>z:AnalystCleanupEndpoints.request_status.<locals>.<listcomp>�s����
�
�
���z�[�(�(�C�,<�}�,L�,L� �L�!�o��*�!�(�"4�S�^�"D�"D�E�E�"�8�#5�c�6F�#G�#G�H�H�!�n�

�
�-M�,L�,Lr"zGot requests: )	r�get_all_requests�get_user_requests�lenr�utcnowrr.r/)r3r@�limit�offset�requests�filtered_requestsrfs      @r �request_statusz&AnalystCleanupEndpoints.request_status�s��������,�=�e�V�L�L�H�H�,�>��%����H�
�	�3�x�=�=�A�-�-��I�!��)�)�I�A�,>�,>�,>�>�
����=�m�=�=�>�>�>�
�
�
�
� �
�
�
��	���8�%6�8�8�9�9�9� � r"z
is-allowedc��DK�tj���d{V��}dd|iiS)NrC�
is_allowed)rrF)r3rrs  r rrz"AnalystCleanupEndpoints.is_allowed�s6����,�B�D�D�D�D�D�D�D�D�
��,�
�3�4�4r")NrZr)	�__name__�
__module__�__qualname__r0r8rrYrprr�r"r r'r'`s�������C�
�s��C�C�C�C�8
�T�
�Y�'�'�M5�M5�(�'�M5�^
�T�
�^�,�,�&!�&!�&!�-�,�&!�P
�T�
�\�*�*�5�5�+�*�5�5�5r"r')'rJ�loggingrrr�defence360agent.rpc_toolsr� defence360agent.rpc_tools.lookuprr�+defence360agent.subsys.panels.hosting_panel�subsys�panels�
hosting_panelrL�defence360agent.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 �<module>r�s������������(�(�(�(�(�(�(�(�5�5�5�5�5�5�@�@�@�@�@�@�@�@�8�8�8�8�8�8�8�8�8�8�8�8�����������
H�G�G�G�G�G���������

��8�	�	��e��?��
2��D��C���.��	:��.��	.��	.��	7�� 9�'��.
#�	-�
$�
$�$� �:�� �i��(�.�9����
���6�6�6�Y5�Y5�Y5�Y5�Y5�m�Y5�Y5�Y5�Y5�Y5r"defence360agent/simple_rpc/__pycache__/endpoints.cpython-311.opt-1.pyc0000644000000000000000000006234600000000000022572 0ustar  �

��h���7���2�UdZddlZddlZddlZddlmZddlmZddlm	Z	ddl
mZddlm
Z
ddlmZdd	lmZdd
lmZmZddlmZmZmZmZmZmZmZdd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;ddl<m=Z=ddl>m?Z?ddl@mAZAeeB��ZCGd�de*��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+��ZZGd1�d2e*��Z[Gd3�d4e+��Z\Gd5�d6e+��Z]Gd7�d8e+��Z^dS)9z"
Here you enumerate rpc endpoints
�N)�deque)�	getLogger)�Dict)�files)�	JWTIssuer)�NewsFeed)�PamAuth)�config�eula)�ANTIVIRUS_MODE�Core�ImmutableMerger�LocalConfig�
MutableMerger�effective_user_config�int_from_envvar)�
LicenseCLN)�CLN�CLNError�InvalidLicenseError)�!collect_billing_incompatibilities�get_license_type)�ValidationError)�CommonEndpoints�
RootEndpoints�bind)�caller_uid_var)�PanelException)�IMUNIFY_PACKAGE_NAMES�
CheckRunError�check_db�getpwnam�system_packages_info)�
update_config)�ZendeskAPIError�send_request)�sync_billing_data��get_doctor_key)�
hosting_panelc��eZdZedd��dd���Zeddd��d���Zedd��dd���Zedd	��dd
���Zedd��dd���Zedd
��dd���Z	dS)�ConfigEndpointsr
�showNc��K�tj��}|r&t|tj|����}d|iSd|���iS�N�items)r
�
ConfigFiler�config_to_dict)�self�user�	full_conf�user_conf_dicts    �Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/endpoints.py�config_showzConfigEndpoints.config_show;s_�����%�'�'�	��	9�2��6�,�T�2�2���N��^�,�,��Y�5�5�7�7�8�8��defaultsc��K�tj��}dt|�����t���d���t|�����d�iS)Nr0F)�	normalize)�mutable_config�local_config�immutable_config)r�get_layer_names�configs_to_dictrr2r)r3�layer_pathss  r7�config_show_defaultsz$ConfigEndpoints.config_show_defaultsFsu����#�3�5�5���"/��"<�"<�"L�"L�"N�"N� +�
�
� <� <�u� <� M� M�$3��%�%�!�/�#�#���
�	
r9�updatec���K�|r|d}tj|��}t�d||��t	|j||���d{V��|�|���d{V��S)Nrz#AUDIT config.update user=%r data=%r)�json�loads�logger�warningr$�_sinkr8)r3r0�datar4�new_datas     r7�
config_updatezConfigEndpoints.config_updateSs������	���8�D��:�d�#�#�����<�d�H�M�M�M���J���
�
�	
�	
�	
�	
�	
�	
�	
�
�%�%�d�+�+�+�+�+�+�+�+�+r9�patchc��K�t�d||��t|j||���d{V��|�|���d{V��S)Nz"AUDIT config.patch user=%r data=%r)rHrIr$rJr8)r3rKr4s   r7�config_update_uiz ConfigEndpoints.config_update_uibsi�������;�T�4�H�H�H��D�J��d�3�3�3�3�3�3�3�3�3��%�%�d�+�+�+�+�+�+�+�+�+r9z
patch-manyc��K�|�g}t�d||��|D]}t|j||���d{V���iS)Nz(AUDIT config.patch-many users=%r data=%r)rHrIr$rJ)r3rK�usersr4s    r7�config_update_many_uiz%ConfigEndpoints.config_update_many_uihsd�����=��E����A�5�$�O�O�O��	8�	8�D���
�D�$�7�7�7�7�7�7�7�7�7�7��	r9zget-manyc��K�|�iSdii}tj��}|D]/}t|tj|����}||d|<�0|Sr/)r
r1r)r3rR�resultr5r4r6s      r7�config_get_many_uiz"ConfigEndpoints.config_get_many_uiqsm�����=��I��2����%�'�'�	��	3�	3�D�2��6�,�T�2�2���N�%3�F�7�O�D�!�!��
r9�N)NNN�NN)
�__name__�
__module__�__qualname__rr8rCrMrPrSrV�r9r7r,r,:s������	�T�(�F���9�9�9���9�
�T�(�F�J�'�'�

�

�(�'�

�
�T�(�H���,�,�,���,�
�T�(�G���,�,�,���,�

�T�(�L�!�!����"�!��
�T�(�J���
�
�
� ��
�
�
r9r,�gN@i'�_login_pam_failures�I360_LOGIN_PAM_UID_MAXi,�_login_pam_uid_failures�username�now�returnc��t�|��}|�dS|tz
}|r.|d|kr"|���|r|d|k�"|s
t|=dSt	|��t
kS)NTr)r^�get�_LOGIN_PAM_WINDOW�popleft�len�_LOGIN_PAM_MAX)rarb�history�cutoffs    r7�_login_pam_allowedrl�s���!�%�%�h�/�/�G����t�
�$�
$�F�
��g�a�j�6�)�)���������g�a�j�6�)�)�����)��t��w�<�<�.�(�(r9c���|tz
��fd�t���D��}|D]
}t|=�dS)Nc�2��g|]\}}|d�k�|��S����r\��.0�u�hrks   �r7�
<listcomp>z$_login_pam_sweep.<locals>.<listcomp>�s&���I�I�I�4�1�a�!�B�%�&�.�.�Q�.�.�.r9)rfr^r0)rb�stalerarks   @r7�_login_pam_sweeprw�sX���
�$�
$�F�I�I�I�I�.�4�4�6�6�I�I�I�E��*�*����)�)�*�*r9c�l�|tvrptt��tkrSt|��tt��tkr'tt	tt����=t�|t�����|��dSrW)	r^rh�_LOGIN_PAM_MAX_TRACKEDrw�next�iter�
setdefaultr�append)rarbs  r7�_login_pam_record_failurer~�s����+�+�+��#�$�$�(>�>�>�������"�#�#�'=�=�=�#�D��.A�)B�)B�$C�$C�D��"�"�8�U�W�W�5�5�<�<�S�A�A�A�A�Ar9c�<�t�|d��dSrW)r^�pop)ras r7�_login_pam_resetr��s�����H�d�+�+�+�+�+r9�uidc��tdkrdSt�|��}|�dS|tz
}|r.|d|kr"|���|r|d|k�"|s
t|=dSt|��tkS)NrT)�_LOGIN_PAM_UID_MAXr`re�_LOGIN_PAM_UID_WINDOWrgrh)r�rbrjrks    r7�_login_pam_uid_allowedr��s����Q����t�%�)�)�#�.�.�G����t�
�(�
(�F�
��g�a�j�6�)�)���������g�a�j�6�)�)���#�C�(��t��w�<�<�,�,�,r9c���|tz
��fd�t���D��}|D]
}t|=�dS)Nc�2��g|]\}}|d�k�|��Sror\rqs   �r7ruz(_login_pam_uid_sweep.<locals>.<listcomp>�s&���M�M�M�4�1�a�a��e�f�n�n�Q�n�n�nr9)r�r`r0)rbrvr�rks   @r7�_login_pam_uid_sweepr��sX���
�(�
(�F�M�M�M�M�2�8�8�:�:�M�M�M�E��)�)��#�C�(�(�)�)r9c��tdkrdS|tvrptt��tkrSt	|��tt��tkr'ttt
t����=t�|t�����	|��dS)Nr)
r�r`rh�_LOGIN_PAM_UID_MAX_TRACKEDr�rzr{r|rr})r�rbs  r7�_login_pam_uid_record_failurer��s����Q������*�*�*��'�(�(�,F�F�F��S�!�!�!��&�'�'�+E�E�E�'��T�2I�-J�-J�(K�(K�L��&�&�s�E�G�G�4�4�;�;�C�@�@�@�@�@r9c�8�eZdZedd��d���ZdS)�LoginEndpoints�login�pamc��rK�tj��}	tj��}n7#t$r*t
�d��td���wxYw|dkr:t||��s*t
�	d|��td���t||��s*t
�	d|��td���t��}|�
||��}|sPt||��|dkrt||��t
�	d|��td���t!|��t
�d	|��d
t%���||�|���d{V����iS)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�LookupErrorrH�error�RuntimeErrorr�rIrrlr	�authenticater~r�r��infor�	get_token�
get_user_type)r3ra�passwordrb�
caller_uid�pam_auth�
authenticateds       r7�
login_via_pamzLoginEndpoints.login_via_pam�s������n����	O�'�+�-�-�J�J���	O�	O�	O��L�L�I�J�J�J��M�N�N�N�	O������?�?�#9�*�c�#J�#J�?��N�N�@�*�M�M�M�!�"F�G�G�G�!�(�C�0�0�	H��N�N�:�H�
�
�
�"�"F�G�G�G��9�9�� �-�-�h��A�A�
��	;�%�h��4�4�4��Q���-�j�#�>�>�>��N�N�?��J�J�J�!�"9�:�:�:���"�"�"����9�8�D�D�D��Y�[�[�*�*��� 6� 6�x� @� @�@�@�@�@�@�@���
�	
s	�+�4AN)rYrZr[rr�r\r9r7r�r��s:������	�T�'�5���
�
���
�
�
r9r�c�8�eZdZedd��d���ZdS)�RootLoginEndpointsr�rec���K�t|��std���dt���|t	���|���d{V����iS)NzUser name not foundr0)r"rrr�r	r�)r3ras  r7�	login_getzRootLoginEndpoints.login_get�ss������!�!�	9�!�"7�8�8�8�
�Y�[�[�*�*���	�	� 7� 7�� A� A�A�A�A�A�A�A���
�	
r9N)rYrZr[rr�r\r9r7r�r��s:������	�T�'�5���
�
���
�
�
r9r�c�8�eZdZed��dd���ZdS)�PackageVersionsEndpointszget-package-versionsNc��>K�dtt���d{V��iSr/)r#r)r3r4s  r7�get_package_versionsz-PackageVersionsEndpoints.get_package_versionss-�����3�4I�J�J�J�J�J�J�J�J�K�Kr9rW)rYrZr[rr�r\r9r7r�r�sD������	�T�
 �!�!�L�L�L�"�!�L�L�Lr9r�c�6�eZdZed��d���ZdS)�
NewsEndpointszget-newsc��<K�dtj���d{V��iSr/)rre�r3s r7�get_newszNewsEndpoints.get_news	s)�����x�|�~�~�-�-�-�-�-�-�.�.r9N)rYrZr[rr�r\r9r7r�r�s8������	�T�*���/�/���/�/�/r9r�c��eZdZejZed��dd���Zed��d���Zed��d���Zed��dd
���Z	ed��d���Z
ed
��d���Zed��	d d���Zedd��d���Z
edd��d���Zed��dd���Zed��d���Zedd��	d!d���ZdS)"�	Endpoints�registerNc
��HK�tj���tj��rrtj��rt
st
d���nHt�dtj��z��|�	���d{V��	tj|���d{V���n_#t$r!}t
t|�����d}~wt$�r(}t�d|��	tjt!j������d{V�����d{V��n�#t&$r7t�d��t
t|�����t($rB}t
d�t|��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_registered�is_validrrrHr��
unregisterrr�r�strrrIr*�HostingPanel�retrieve_key�NotImplementedErrorr�format)r3�regkey�e�panel_es    r7r�zEndpoints.registers������(�(�*�*�*��#�%�%�		(��"�$�$�
(�%�I�)�*G�H�H�H�I����7� �*�,�,�-�����o�o�'�'�'�'�'�'�'�'�'�	.��,�v�&�&�&�&�&�&�&�&�&�&��"�	*�	*�	*�!�#�a�&�&�)�)�)������	.�	.�	.��N�N�9��
�
�
�

.��l�'�4�6�6�C�C�E�E�E�E�E�E�E�E������������'�
.�
.�
.����I����&�c�!�f�f�-�-�-�!�
M�
M�
M�%�h�o�o�c�!�f�f�c�'�l�l�&K�&K�L�L�L������1�2�
.�
.�
.�%�c�!�f�f�-�-�-�����
.�������������	.����&�	sV�'C�
H�
C)�)H�7H�AE�H�A	H� =G�H�1H
�
H�H�Hr�c���K�tj��std���tj��rtd���t	j���d{V��iS)NzAgent is not registered yetz$Free license can not be unregistered)rr�r�is_freerr�r�s r7r�zEndpoints.unregister7sm�����'�)�)�	A�!�"?�@�@�@�����	J�!�"H�I�I�I��n�����������	r9zupdate-licensec��4K�tj��std���tj��}t	j������d{V��t_tj|���d{V��}|�td���iS)Nz(Unregistered (server-id is not assigned)z*License does not exist. Agent unregistered)	rr�rr�r*r��users_countr�
refresh_token)r3�token�	new_tokens   r7�update_licensezEndpoints.update_licenseAs������'�)�)�	N�!�"L�M�M�M��$�&�&���,�.�.�:�:�<�<�<�<�<�<�<�<�	���+�E�2�2�2�2�2�2�2�2�	���!�"N�O�O�O��	r9�rstatusFc��K�tj���tj��st	d���|r"tj��rt	d���|���S)Nz%License is invalid for current serverzFree license)rr�r�r�rr��license_info)r3�paids  r7r�zEndpoints.rstatusNst������(�(�*�*�*��"�$�$�	K�!�"I�J�J�J��	2�J�&�(�(�	2�!�.�1�1�1�� � �"�"�"r9�versionc��"K�dtjiSr/)�
CoreConfig�VERSIONr�s r7r�zEndpoints.versionWs������+�,�,r9�wakeupc��
K�iS)zBWake up the agent, so it can process the request, if it's sleepingr\r�s r7r�zEndpoints.wakeup[s
�����	r9rD�latestc��K�|rl|tjjvrY|r&tj|�����S|r.tj|���||���d{V��Sn|s|dkrtd���	tj||���d{V��dS#tj
tjf$rYdSwxYw)Nr�z9Listing and version are not supported for this files type)r
�FilesUpdate�DISABLEDr�Index�get_list�	update_torrD�asyncio�TimeoutError�UpdateError)r3�subj�force�listr�s     r7�update_fileszEndpoints.update_files`s
�����		�D�F�.�7�7�7��
4��{�4�(�(�1�1�3�3�3��
I�"�[��.�.�8�8��%�H�H�H�H�H�H�H�H�H�
I��
�w�(�*�*�%�O����	��,�t�U�+�+�+�+�+�+�+�+�+�+�+���$�e�&7�8�	�	�	��D�D�	���s�	B&�&C�Cr�acceptc��<K�tj���d{V��dSrW)rr�r�s r7�eula_acceptzEndpoints.eula_acceptss*�����k�m�m���������r9r-c��,K�tj��SrW)r�textr�s r7�	eula_showzEndpoints.eula_showws�����y�{�{�r9�checkdbc��^K�|rtj��dStj��dS)zmCheck DB consistency and repair if needed.
        If recreate_schema is set recreate schema for attached DB.N)r!�recreate_schema�check_and_repair)r3r�s  r7r�zEndpoints.checkdb{s:�����	(��$�&�&�&�&�&��%�'�'�'�'�'r9�doctorc��8K�t���d{V��}d|zS)Nz8Please, provide this key:
%s
to Imunify360 Support Team
r()r3�keys  r7r�zEndpoints.doctor�s0����"�$�$�$�$�$�$�$�$��I�C�O�	
r9�support�sendc��K�	t���d{V��}n#t$rd}YnwxYw	t||||||���d{V��}n?#t$r2}t�d|j|j|j���d}~wwxYwd|giS)Nz@Got error from Zendesk API. error=%s, description=%s, details=%sr0)r)r r&r%rHr��description�details)	r3�email�subjectr��cln�attachments�
doctor_key�
ticket_urlr�s	         r7�send_to_supportzEndpoints.send_to_support�s�����
	�-�/�/�/�/�/�/�/�/�J�J���	�	�	��J�J�J�	����
	�+��w��Z��k� � �������J�J���	�	�	��L�L�����
��	�
�
�
�
�����	�����*��&�&s#��(�(�A�
B�-A>�>BrW)F)NFFr�rX)rYrZr[rr�rr�r�r�r�r�r�r�r�r�r�r�r�r\r9r7r�r�s��������*�L�	�T�*���#�#�#���#�J
�T�,��������
�T�
���
�
���
�
�T�)�_�_�#�#�#��_�#�
�T�)�_�_�-�-��_�-�
�T�(�^�^����^��
�T�(�^�^�:B�����^��$
�T�&�(��������
�T�&�&��������
�T�)�_�_�(�(�(��_�(�
�T�(�^�^�
�
��^�
�
�T�)�V���AE�'�'�'���'�'�'r9r�c�j�eZdZdZdZedd��d���Zedd��d���ZdS)	�
WhmcsEndpointz<
    Describes all endpoints for interaction with WHMCS
    �1�billing�syncc��K�	tj|��}n"#tj$rtd���wxYwt	|j|���d{V��}d|d�S)NzInvalid JSON�success�rUrK)rFrG�JSONDecodeError�
ValueErrorr'rJ)r3rK�decoded_datarUs    r7�billing_synczWhmcsEndpoint.billing_sync�sy����	-��:�d�+�+�L�L���#�	-�	-�	-��^�,�,�,�	-����(���\�B�B�B�B�B�B�B�B��#�V�4�4�4s��8z
get-configc��zK�t|jt��t���d{V�����}d|d�S)N)r��billing_license�issuesr�r�)�dictr�rr)r3rUs  r7�billing_get_configz WhmcsEndpoint.billing_get_config�sS������L�,�.�.�:�<�<�<�<�<�<�<�<�
�
�
��
$�V�4�4�4r9N)rYrZr[�__doc__r�rrrr\r9r7r�r��ss��������
�G�	�T�)�V���5�5���5�
�T�)�\�"�"�5�5�#�"�5�5�5r9r�)_rr�rFr��collectionsr�loggingr�typingr�defence360agentr�defence360agent.api.jwt_issuerr�defence360agent.api.newsfeedr�defence360agent.api.pam_authr	�defence360agent.contractsr
r� defence360agent.contracts.configrr
r�rrrrr�!defence360agent.contracts.licenser�defence360agent.internals.clnrrr�!defence360agent.myimunify.billingrr�defence360agent.rpc_toolsr� defence360agent.rpc_tools.lookuprrr�defence360agent.simple_rpcr�"defence360agent.subsys.panels.baser�defence360agent.utilsrr r!r"r#�defence360agent.utils.configr$�defence360agent.utils.supportr%r&�defence360agent.utils.whmcsr'�defence360agent.utils.doctorr)�defence360agent.subsys.panelsr*rYrHr,rirfryr^r��__annotations__r�r�r�r`�int�float�boolrlrwr~r�r�r�r�r�r�r�r�r�r�r\r9r7�<module>r"sg�����������������������������������!�!�!�!�!�!�4�4�4�4�4�4�1�1�1�1�1�1�0�0�0�0�0�0�2�2�2�2�2�2�2�2�������������������9�8�8�8�8�8�L�L�L�L�L�L�L�L�L�L���������6�5�5�5�5�5�����������
6�5�5�5�5�5�=�=�=�=�=�=���������������7�6�6�6�6�6�G�G�G�G�G�G�G�G�9�9�9�9�9�9�7�7�7�7�7�7�7�7�7�7�7�7�	��8�	�	��B�B�B�B�B�o�B�B�B�L������(*��T�#�u�*�%�*�*�*�$�_�%=�s�C�C����#��,.���c�5�j�)�.�.�.�
)��
)�5�
)�T�
)�
)�
)�
)�*�%�*�D�*�*�*�*�B��B�%�B�D�B�B�B�B�,�s�,�t�,�,�,�,�-��-�%�-�D�-�-�-�-�)�e�)��)�)�)�)�
A�s�
A��
A�4�
A�
A�
A�
A�!
�!
�!
�!
�!
�_�!
�!
�!
�H

�

�

�

�

��

�

�

�L�L�L�L�L��L�L�L�/�/�/�/�/�M�/�/�/�W'�W'�W'�W'�W'�
�W'�W'�W'�t5�5�5�5�5�M�5�5�5�5�5r9defence360agent/simple_rpc/__pycache__/endpoints.cpython-311.pyc0000644000000000000000000006234600000000000021633 0ustar  �

��h���7���2�UdZddlZddlZddlZddlmZddlmZddlm	Z	ddl
mZddlm
Z
ddlmZdd	lmZdd
lmZmZddlmZmZmZmZmZmZmZdd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;ddl<m=Z=ddl>m?Z?ddl@mAZAeeB��ZCGd�de*��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+��ZZGd1�d2e*��Z[Gd3�d4e+��Z\Gd5�d6e+��Z]Gd7�d8e+��Z^dS)9z"
Here you enumerate rpc endpoints
�N)�deque)�	getLogger)�Dict)�files)�	JWTIssuer)�NewsFeed)�PamAuth)�config�eula)�ANTIVIRUS_MODE�Core�ImmutableMerger�LocalConfig�
MutableMerger�effective_user_config�int_from_envvar)�
LicenseCLN)�CLN�CLNError�InvalidLicenseError)�!collect_billing_incompatibilities�get_license_type)�ValidationError)�CommonEndpoints�
RootEndpoints�bind)�caller_uid_var)�PanelException)�IMUNIFY_PACKAGE_NAMES�
CheckRunError�check_db�getpwnam�system_packages_info)�
update_config)�ZendeskAPIError�send_request)�sync_billing_data��get_doctor_key)�
hosting_panelc��eZdZedd��dd���Zeddd��d���Zedd��dd���Zedd	��dd
���Zedd��dd���Zedd
��dd���Z	dS)�ConfigEndpointsr
�showNc��K�tj��}|r&t|tj|����}d|iSd|���iS�N�items)r
�
ConfigFiler�config_to_dict)�self�user�	full_conf�user_conf_dicts    �Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/endpoints.py�config_showzConfigEndpoints.config_show;s_�����%�'�'�	��	9�2��6�,�T�2�2���N��^�,�,��Y�5�5�7�7�8�8��defaultsc��K�tj��}dt|�����t���d���t|�����d�iS)Nr0F)�	normalize)�mutable_config�local_config�immutable_config)r�get_layer_names�configs_to_dictrr2r)r3�layer_pathss  r7�config_show_defaultsz$ConfigEndpoints.config_show_defaultsFsu����#�3�5�5���"/��"<�"<�"L�"L�"N�"N� +�
�
� <� <�u� <� M� M�$3��%�%�!�/�#�#���
�	
r9�updatec���K�|r|d}tj|��}t�d||��t	|j||���d{V��|�|���d{V��S)Nrz#AUDIT config.update user=%r data=%r)�json�loads�logger�warningr$�_sinkr8)r3r0�datar4�new_datas     r7�
config_updatezConfigEndpoints.config_updateSs������	���8�D��:�d�#�#�����<�d�H�M�M�M���J���
�
�	
�	
�	
�	
�	
�	
�	
�
�%�%�d�+�+�+�+�+�+�+�+�+r9�patchc��K�t�d||��t|j||���d{V��|�|���d{V��S)Nz"AUDIT config.patch user=%r data=%r)rHrIr$rJr8)r3rKr4s   r7�config_update_uiz ConfigEndpoints.config_update_uibsi�������;�T�4�H�H�H��D�J��d�3�3�3�3�3�3�3�3�3��%�%�d�+�+�+�+�+�+�+�+�+r9z
patch-manyc��K�|�g}t�d||��|D]}t|j||���d{V���iS)Nz(AUDIT config.patch-many users=%r data=%r)rHrIr$rJ)r3rK�usersr4s    r7�config_update_many_uiz%ConfigEndpoints.config_update_many_uihsd�����=��E����A�5�$�O�O�O��	8�	8�D���
�D�$�7�7�7�7�7�7�7�7�7�7��	r9zget-manyc��K�|�iSdii}tj��}|D]/}t|tj|����}||d|<�0|Sr/)r
r1r)r3rR�resultr5r4r6s      r7�config_get_many_uiz"ConfigEndpoints.config_get_many_uiqsm�����=��I��2����%�'�'�	��	3�	3�D�2��6�,�T�2�2���N�%3�F�7�O�D�!�!��
r9�N)NNN�NN)
�__name__�
__module__�__qualname__rr8rCrMrPrSrV�r9r7r,r,:s������	�T�(�F���9�9�9���9�
�T�(�F�J�'�'�

�

�(�'�

�
�T�(�H���,�,�,���,�
�T�(�G���,�,�,���,�

�T�(�L�!�!����"�!��
�T�(�J���
�
�
� ��
�
�
r9r,�gN@i'�_login_pam_failures�I360_LOGIN_PAM_UID_MAXi,�_login_pam_uid_failures�username�now�returnc��t�|��}|�dS|tz
}|r.|d|kr"|���|r|d|k�"|s
t|=dSt	|��t
kS)NTr)r^�get�_LOGIN_PAM_WINDOW�popleft�len�_LOGIN_PAM_MAX)rarb�history�cutoffs    r7�_login_pam_allowedrl�s���!�%�%�h�/�/�G����t�
�$�
$�F�
��g�a�j�6�)�)���������g�a�j�6�)�)�����)��t��w�<�<�.�(�(r9c���|tz
��fd�t���D��}|D]
}t|=�dS)Nc�2��g|]\}}|d�k�|��S����r\��.0�u�hrks   �r7�
<listcomp>z$_login_pam_sweep.<locals>.<listcomp>�s&���I�I�I�4�1�a�!�B�%�&�.�.�Q�.�.�.r9)rfr^r0)rb�stalerarks   @r7�_login_pam_sweeprw�sX���
�$�
$�F�I�I�I�I�.�4�4�6�6�I�I�I�E��*�*����)�)�*�*r9c�l�|tvrptt��tkrSt|��tt��tkr'tt	tt����=t�|t�����|��dSrW)	r^rh�_LOGIN_PAM_MAX_TRACKEDrw�next�iter�
setdefaultr�append)rarbs  r7�_login_pam_record_failurer~�s����+�+�+��#�$�$�(>�>�>�������"�#�#�'=�=�=�#�D��.A�)B�)B�$C�$C�D��"�"�8�U�W�W�5�5�<�<�S�A�A�A�A�Ar9c�<�t�|d��dSrW)r^�pop)ras r7�_login_pam_resetr��s�����H�d�+�+�+�+�+r9�uidc��tdkrdSt�|��}|�dS|tz
}|r.|d|kr"|���|r|d|k�"|s
t|=dSt|��tkS)NrT)�_LOGIN_PAM_UID_MAXr`re�_LOGIN_PAM_UID_WINDOWrgrh)r�rbrjrks    r7�_login_pam_uid_allowedr��s����Q����t�%�)�)�#�.�.�G����t�
�(�
(�F�
��g�a�j�6�)�)���������g�a�j�6�)�)���#�C�(��t��w�<�<�,�,�,r9c���|tz
��fd�t���D��}|D]
}t|=�dS)Nc�2��g|]\}}|d�k�|��Sror\rqs   �r7ruz(_login_pam_uid_sweep.<locals>.<listcomp>�s&���M�M�M�4�1�a�a��e�f�n�n�Q�n�n�nr9)r�r`r0)rbrvr�rks   @r7�_login_pam_uid_sweepr��sX���
�(�
(�F�M�M�M�M�2�8�8�:�:�M�M�M�E��)�)��#�C�(�(�)�)r9c��tdkrdS|tvrptt��tkrSt	|��tt��tkr'ttt
t����=t�|t�����	|��dS)Nr)
r�r`rh�_LOGIN_PAM_UID_MAX_TRACKEDr�rzr{r|rr})r�rbs  r7�_login_pam_uid_record_failurer��s����Q������*�*�*��'�(�(�,F�F�F��S�!�!�!��&�'�'�+E�E�E�'��T�2I�-J�-J�(K�(K�L��&�&�s�E�G�G�4�4�;�;�C�@�@�@�@�@r9c�8�eZdZedd��d���ZdS)�LoginEndpoints�login�pamc��rK�tj��}	tj��}n7#t$r*t
�d��td���wxYw|dkr:t||��s*t
�	d|��td���t||��s*t
�	d|��td���t��}|�
||��}|sPt||��|dkrt||��t
�	d|��td���t!|��t
�d	|��d
t%���||�|���d{V����iS)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�LookupErrorrH�error�RuntimeErrorr�rIrrlr	�authenticater~r�r��infor�	get_token�
get_user_type)r3ra�passwordrb�
caller_uid�pam_auth�
authenticateds       r7�
login_via_pamzLoginEndpoints.login_via_pam�s������n����	O�'�+�-�-�J�J���	O�	O�	O��L�L�I�J�J�J��M�N�N�N�	O������?�?�#9�*�c�#J�#J�?��N�N�@�*�M�M�M�!�"F�G�G�G�!�(�C�0�0�	H��N�N�:�H�
�
�
�"�"F�G�G�G��9�9�� �-�-�h��A�A�
��	;�%�h��4�4�4��Q���-�j�#�>�>�>��N�N�?��J�J�J�!�"9�:�:�:���"�"�"����9�8�D�D�D��Y�[�[�*�*��� 6� 6�x� @� @�@�@�@�@�@�@���
�	
s	�+�4AN)rYrZr[rr�r\r9r7r�r��s:������	�T�'�5���
�
���
�
�
r9r�c�8�eZdZedd��d���ZdS)�RootLoginEndpointsr�rec���K�t|��std���dt���|t	���|���d{V����iS)NzUser name not foundr0)r"rrr�r	r�)r3ras  r7�	login_getzRootLoginEndpoints.login_get�ss������!�!�	9�!�"7�8�8�8�
�Y�[�[�*�*���	�	� 7� 7�� A� A�A�A�A�A�A�A���
�	
r9N)rYrZr[rr�r\r9r7r�r��s:������	�T�'�5���
�
���
�
�
r9r�c�8�eZdZed��dd���ZdS)�PackageVersionsEndpointszget-package-versionsNc��>K�dtt���d{V��iSr/)r#r)r3r4s  r7�get_package_versionsz-PackageVersionsEndpoints.get_package_versionss-�����3�4I�J�J�J�J�J�J�J�J�K�Kr9rW)rYrZr[rr�r\r9r7r�r�sD������	�T�
 �!�!�L�L�L�"�!�L�L�Lr9r�c�6�eZdZed��d���ZdS)�
NewsEndpointszget-newsc��<K�dtj���d{V��iSr/)rre�r3s r7�get_newszNewsEndpoints.get_news	s)�����x�|�~�~�-�-�-�-�-�-�.�.r9N)rYrZr[rr�r\r9r7r�r�s8������	�T�*���/�/���/�/�/r9r�c��eZdZejZed��dd���Zed��d���Zed��d���Zed��dd
���Z	ed��d���Z
ed
��d���Zed��	d d���Zedd��d���Z
edd��d���Zed��dd���Zed��d���Zedd��	d!d���ZdS)"�	Endpoints�registerNc
��HK�tj���tj��rrtj��rt
st
d���nHt�dtj��z��|�	���d{V��	tj|���d{V���n_#t$r!}t
t|�����d}~wt$�r(}t�d|��	tjt!j������d{V�����d{V��n�#t&$r7t�d��t
t|�����t($rB}t
d�t|��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_registered�is_validrrrHr��
unregisterrr�r�strrrIr*�HostingPanel�retrieve_key�NotImplementedErrorr�format)r3�regkey�e�panel_es    r7r�zEndpoints.registers������(�(�*�*�*��#�%�%�		(��"�$�$�
(�%�I�)�*G�H�H�H�I����7� �*�,�,�-�����o�o�'�'�'�'�'�'�'�'�'�	.��,�v�&�&�&�&�&�&�&�&�&�&��"�	*�	*�	*�!�#�a�&�&�)�)�)������	.�	.�	.��N�N�9��
�
�
�

.��l�'�4�6�6�C�C�E�E�E�E�E�E�E�E������������'�
.�
.�
.����I����&�c�!�f�f�-�-�-�!�
M�
M�
M�%�h�o�o�c�!�f�f�c�'�l�l�&K�&K�L�L�L������1�2�
.�
.�
.�%�c�!�f�f�-�-�-�����
.�������������	.����&�	sV�'C�
H�
C)�)H�7H�AE�H�A	H� =G�H�1H
�
H�H�Hr�c���K�tj��std���tj��rtd���t	j���d{V��iS)NzAgent is not registered yetz$Free license can not be unregistered)rr�r�is_freerr�r�s r7r�zEndpoints.unregister7sm�����'�)�)�	A�!�"?�@�@�@�����	J�!�"H�I�I�I��n�����������	r9zupdate-licensec��4K�tj��std���tj��}t	j������d{V��t_tj|���d{V��}|�td���iS)Nz(Unregistered (server-id is not assigned)z*License does not exist. Agent unregistered)	rr�rr�r*r��users_countr�
refresh_token)r3�token�	new_tokens   r7�update_licensezEndpoints.update_licenseAs������'�)�)�	N�!�"L�M�M�M��$�&�&���,�.�.�:�:�<�<�<�<�<�<�<�<�	���+�E�2�2�2�2�2�2�2�2�	���!�"N�O�O�O��	r9�rstatusFc��K�tj���tj��st	d���|r"tj��rt	d���|���S)Nz%License is invalid for current serverzFree license)rr�r�r�rr��license_info)r3�paids  r7r�zEndpoints.rstatusNst������(�(�*�*�*��"�$�$�	K�!�"I�J�J�J��	2�J�&�(�(�	2�!�.�1�1�1�� � �"�"�"r9�versionc��"K�dtjiSr/)�
CoreConfig�VERSIONr�s r7r�zEndpoints.versionWs������+�,�,r9�wakeupc��
K�iS)zBWake up the agent, so it can process the request, if it's sleepingr\r�s r7r�zEndpoints.wakeup[s
�����	r9rD�latestc��K�|rl|tjjvrY|r&tj|�����S|r.tj|���||���d{V��Sn|s|dkrtd���	tj||���d{V��dS#tj
tjf$rYdSwxYw)Nr�z9Listing and version are not supported for this files type)r
�FilesUpdate�DISABLEDr�Index�get_list�	update_torrD�asyncio�TimeoutError�UpdateError)r3�subj�force�listr�s     r7�update_fileszEndpoints.update_files`s
�����		�D�F�.�7�7�7��
4��{�4�(�(�1�1�3�3�3��
I�"�[��.�.�8�8��%�H�H�H�H�H�H�H�H�H�
I��
�w�(�*�*�%�O����	��,�t�U�+�+�+�+�+�+�+�+�+�+�+���$�e�&7�8�	�	�	��D�D�	���s�	B&�&C�Cr�acceptc��<K�tj���d{V��dSrW)rr�r�s r7�eula_acceptzEndpoints.eula_acceptss*�����k�m�m���������r9r-c��,K�tj��SrW)r�textr�s r7�	eula_showzEndpoints.eula_showws�����y�{�{�r9�checkdbc��^K�|rtj��dStj��dS)zmCheck DB consistency and repair if needed.
        If recreate_schema is set recreate schema for attached DB.N)r!�recreate_schema�check_and_repair)r3r�s  r7r�zEndpoints.checkdb{s:�����	(��$�&�&�&�&�&��%�'�'�'�'�'r9�doctorc��8K�t���d{V��}d|zS)Nz8Please, provide this key:
%s
to Imunify360 Support Team
r()r3�keys  r7r�zEndpoints.doctor�s0����"�$�$�$�$�$�$�$�$��I�C�O�	
r9�support�sendc��K�	t���d{V��}n#t$rd}YnwxYw	t||||||���d{V��}n?#t$r2}t�d|j|j|j���d}~wwxYwd|giS)Nz@Got error from Zendesk API. error=%s, description=%s, details=%sr0)r)r r&r%rHr��description�details)	r3�email�subjectr��cln�attachments�
doctor_key�
ticket_urlr�s	         r7�send_to_supportzEndpoints.send_to_support�s�����
	�-�/�/�/�/�/�/�/�/�J�J���	�	�	��J�J�J�	����
	�+��w��Z��k� � �������J�J���	�	�	��L�L�����
��	�
�
�
�
�����	�����*��&�&s#��(�(�A�
B�-A>�>BrW)F)NFFr�rX)rYrZr[rr�rr�r�r�r�r�r�r�r�r�r�r�r�r\r9r7r�r�s��������*�L�	�T�*���#�#�#���#�J
�T�,��������
�T�
���
�
���
�
�T�)�_�_�#�#�#��_�#�
�T�)�_�_�-�-��_�-�
�T�(�^�^����^��
�T�(�^�^�:B�����^��$
�T�&�(��������
�T�&�&��������
�T�)�_�_�(�(�(��_�(�
�T�(�^�^�
�
��^�
�
�T�)�V���AE�'�'�'���'�'�'r9r�c�j�eZdZdZdZedd��d���Zedd��d���ZdS)	�
WhmcsEndpointz<
    Describes all endpoints for interaction with WHMCS
    �1�billing�syncc��K�	tj|��}n"#tj$rtd���wxYwt	|j|���d{V��}d|d�S)NzInvalid JSON�success�rUrK)rFrG�JSONDecodeError�
ValueErrorr'rJ)r3rK�decoded_datarUs    r7�billing_synczWhmcsEndpoint.billing_sync�sy����	-��:�d�+�+�L�L���#�	-�	-�	-��^�,�,�,�	-����(���\�B�B�B�B�B�B�B�B��#�V�4�4�4s��8z
get-configc��zK�t|jt��t���d{V�����}d|d�S)N)r��billing_license�issuesr�r�)�dictr�rr)r3rUs  r7�billing_get_configz WhmcsEndpoint.billing_get_config�sS������L�,�.�.�:�<�<�<�<�<�<�<�<�
�
�
��
$�V�4�4�4r9N)rYrZr[�__doc__r�rrrr\r9r7r�r��ss��������
�G�	�T�)�V���5�5���5�
�T�)�\�"�"�5�5�#�"�5�5�5r9r�)_rr�rFr��collectionsr�loggingr�typingr�defence360agentr�defence360agent.api.jwt_issuerr�defence360agent.api.newsfeedr�defence360agent.api.pam_authr	�defence360agent.contractsr
r� defence360agent.contracts.configrr
r�rrrrr�!defence360agent.contracts.licenser�defence360agent.internals.clnrrr�!defence360agent.myimunify.billingrr�defence360agent.rpc_toolsr� defence360agent.rpc_tools.lookuprrr�defence360agent.simple_rpcr�"defence360agent.subsys.panels.baser�defence360agent.utilsrr r!r"r#�defence360agent.utils.configr$�defence360agent.utils.supportr%r&�defence360agent.utils.whmcsr'�defence360agent.utils.doctorr)�defence360agent.subsys.panelsr*rYrHr,rirfryr^r��__annotations__r�r�r�r`�int�float�boolrlrwr~r�r�r�r�r�r�r�r�r�r�r\r9r7�<module>r"sg�����������������������������������!�!�!�!�!�!�4�4�4�4�4�4�1�1�1�1�1�1�0�0�0�0�0�0�2�2�2�2�2�2�2�2�������������������9�8�8�8�8�8�L�L�L�L�L�L�L�L�L�L���������6�5�5�5�5�5�����������
6�5�5�5�5�5�=�=�=�=�=�=���������������7�6�6�6�6�6�G�G�G�G�G�G�G�G�9�9�9�9�9�9�7�7�7�7�7�7�7�7�7�7�7�7�	��8�	�	��B�B�B�B�B�o�B�B�B�L������(*��T�#�u�*�%�*�*�*�$�_�%=�s�C�C����#��,.���c�5�j�)�.�.�.�
)��
)�5�
)�T�
)�
)�
)�
)�*�%�*�D�*�*�*�*�B��B�%�B�D�B�B�B�B�,�s�,�t�,�,�,�,�-��-�%�-�D�-�-�-�-�)�e�)��)�)�)�)�
A�s�
A��
A�4�
A�
A�
A�
A�!
�!
�!
�!
�!
�_�!
�!
�!
�H

�

�

�

�

��

�

�

�L�L�L�L�L��L�L�L�/�/�/�/�/�M�/�/�/�W'�W'�W'�W'�W'�
�W'�W'�W'�t5�5�5�5�5�M�5�5�5�5�5r9defence360agent/simple_rpc/__pycache__/hooks.cpython-311.opt-1.pyc0000644000000000000000000001260100000000000021677 0ustar  �

�'N�������ddlZddlZddlmZddlmZddlmZddlm	Z	ddl
mZddlm
Z
mZddlmZeje��ZGd	�d
e
��ZdS)�N)�
HookEvents)�HooksConfig)�
LicenseCLN)�	EventHook)�ValidationError)�
RootEndpoints�bind)�notifierc�@�eZdZdd�Zedd��d���Zedd��d���Zedd��d	���Zedd
��d���Zedd
��d���Z	edd��dd���Z
edd��dd���ZdS)�HooksEndpointsNc�v�|tjvr(||kr$td�|�����dSdS)Nz "{}" is not valid event for hook)r�EVENTSr�format)�self�event�extras   �U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/hooks.py�_check_eventzHooksEndpoints._check_eventsG���
�)�)�)�e�u�n�n�!�2�9�9�%�@�@���
�*�)�n�n��hook�addc��K�|�|��tj||���}|s#td�||�����d|d<d|iS)N�r�pathzUnable to add hook "{} {}"�
registered�status�items�rr�add_hookrr�rrr�results    r�hook_addzHooksEndpoints.hook_addss�������%� � � ��#�%�d�;�;�;���	�!�,�3�3�E�4�@�@���
�(��x���� � r�deletec��K�|�|��tj||���}|s#td�||�����d|d<d|iS)NrzUnable to delete hook "{} {}"�unregisteredrr)rr�delete_hookrrr s    r�hook_deletezHooksEndpoints.hook_delete!ss�������%� � � ��&�U��>�>�>���	�!�/�6�6�u�d�C�C���
�*��x���� � r�listc��bK�|�|d��tj|��}d|iS)N�allr)rr�list_events)rrr!s   r�	hook_listzHooksEndpoints.hook_list,s7�������%��'�'�'��&�u�-�-���� � rz
add-nativec��K�|�|��tj||d���}|s#td�||�����d|d<d|iS)NT)rr�nativez!Unable to add native hook "{} {}"rrrrr s    r�hook_add_nativezHooksEndpoints.hook_add_native2su�������%� � � ��#�%�d�4�H�H�H���	�!�3�:�:�5�$�G�G���
�(��x���� � rznotifications-config�showc��JK�dt�����iS)Nr)r�get)rs rr0zHooksEndpoints.show=s!��������*�*�,�,�-�-r�updatec��.K�tj��rtd���|r|d}tj|��}t���|��tj���d{V��|�	���d{V��S)N�*This action is not allowed in demo versionr)
r�is_demor�json�loadsrr3r
�config_updatedr0)rr�data�new_datas    rr3zHooksEndpoints.updateAs���������	P�!�"N�O�O�O��	���8�D��:�d�#�#���
�
���X�&�&�&��%�'�'�'�'�'�'�'�'�'��Y�Y�[�[� � � � � � � r�patchc���K�tj��rtd���t���|��tj���d{V��|����d{V��S)Nr5)rr6rrr3r
r9r0)rr:s  r�	update_uizHooksEndpoints.update_uiLs���������	P�!�"N�O�O�O��
�
���T�"�"�"��%�'�'�'�'�'�'�'�'�'��Y�Y�[�[� � � � � � � r)N)NN)�__name__�
__module__�__qualname__rr	r"r'r,r/r0r3r>�rrrrsM����������
�T�&�%���!�!���!�
�T�&�(���!�!���!�
�T�&�&���!�!���!�

�T�&�,���!�!� ��!�
�T�
 �&�)�)�.�.�*�)�.�
�T�
 �(�+�+�!�!�!�,�+�!�
�T�
 �'�*�*�!�!�!�+�*�!�!�!rr)r7�logging� defence360agent.contracts.configr�defence360agent.contracts.hooksr�!defence360agent.contracts.licenser� defence360agent.model.event_hookr�defence360agent.rpc_toolsr� defence360agent.rpc_tools.lookuprr	�defence360agent.subsysr
�	getLoggerr?�loggerrrBrr�<module>rMs�����������7�7�7�7�7�7�7�7�7�7�7�7�8�8�8�8�8�8�6�6�6�6�6�6�5�5�5�5�5�5�@�@�@�@�@�@�@�@�+�+�+�+�+�+�	��	�8�	$�	$��C!�C!�C!�C!�C!�]�C!�C!�C!�C!�C!rdefence360agent/simple_rpc/__pycache__/hooks.cpython-311.pyc0000644000000000000000000001260100000000000020740 0ustar  �

�'N�������ddlZddlZddlmZddlmZddlmZddlm	Z	ddl
mZddlm
Z
mZddlmZeje��ZGd	�d
e
��ZdS)�N)�
HookEvents)�HooksConfig)�
LicenseCLN)�	EventHook)�ValidationError)�
RootEndpoints�bind)�notifierc�@�eZdZdd�Zedd��d���Zedd��d���Zedd��d	���Zedd
��d���Zedd
��d���Z	edd��dd���Z
edd��dd���ZdS)�HooksEndpointsNc�v�|tjvr(||kr$td�|�����dSdS)Nz "{}" is not valid event for hook)r�EVENTSr�format)�self�event�extras   �U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/hooks.py�_check_eventzHooksEndpoints._check_eventsG���
�)�)�)�e�u�n�n�!�2�9�9�%�@�@���
�*�)�n�n��hook�addc��K�|�|��tj||���}|s#td�||�����d|d<d|iS)N�r�pathzUnable to add hook "{} {}"�
registered�status�items�rr�add_hookrr�rrr�results    r�hook_addzHooksEndpoints.hook_addss�������%� � � ��#�%�d�;�;�;���	�!�,�3�3�E�4�@�@���
�(��x���� � r�deletec��K�|�|��tj||���}|s#td�||�����d|d<d|iS)NrzUnable to delete hook "{} {}"�unregisteredrr)rr�delete_hookrrr s    r�hook_deletezHooksEndpoints.hook_delete!ss�������%� � � ��&�U��>�>�>���	�!�/�6�6�u�d�C�C���
�*��x���� � r�listc��bK�|�|d��tj|��}d|iS)N�allr)rr�list_events)rrr!s   r�	hook_listzHooksEndpoints.hook_list,s7�������%��'�'�'��&�u�-�-���� � rz
add-nativec��K�|�|��tj||d���}|s#td�||�����d|d<d|iS)NT)rr�nativez!Unable to add native hook "{} {}"rrrrr s    r�hook_add_nativezHooksEndpoints.hook_add_native2su�������%� � � ��#�%�d�4�H�H�H���	�!�3�:�:�5�$�G�G���
�(��x���� � rznotifications-config�showc��JK�dt�����iS)Nr)r�get)rs rr0zHooksEndpoints.show=s!��������*�*�,�,�-�-r�updatec��.K�tj��rtd���|r|d}tj|��}t���|��tj���d{V��|�	���d{V��S)N�*This action is not allowed in demo versionr)
r�is_demor�json�loadsrr3r
�config_updatedr0)rr�data�new_datas    rr3zHooksEndpoints.updateAs���������	P�!�"N�O�O�O��	���8�D��:�d�#�#���
�
���X�&�&�&��%�'�'�'�'�'�'�'�'�'��Y�Y�[�[� � � � � � � r�patchc���K�tj��rtd���t���|��tj���d{V��|����d{V��S)Nr5)rr6rrr3r
r9r0)rr:s  r�	update_uizHooksEndpoints.update_uiLs���������	P�!�"N�O�O�O��
�
���T�"�"�"��%�'�'�'�'�'�'�'�'�'��Y�Y�[�[� � � � � � � r)N)NN)�__name__�
__module__�__qualname__rr	r"r'r,r/r0r3r>�rrrrsM����������
�T�&�%���!�!���!�
�T�&�(���!�!���!�
�T�&�&���!�!���!�

�T�&�,���!�!� ��!�
�T�
 �&�)�)�.�.�*�)�.�
�T�
 �(�+�+�!�!�!�,�+�!�
�T�
 �'�*�*�!�!�!�+�*�!�!�!rr)r7�logging� defence360agent.contracts.configr�defence360agent.contracts.hooksr�!defence360agent.contracts.licenser� defence360agent.model.event_hookr�defence360agent.rpc_toolsr� defence360agent.rpc_tools.lookuprr	�defence360agent.subsysr
�	getLoggerr?�loggerrrBrr�<module>rMs�����������7�7�7�7�7�7�7�7�7�7�7�7�8�8�8�8�8�8�6�6�6�6�6�6�5�5�5�5�5�5�@�@�@�@�@�@�@�@�+�+�+�+�+�+�	��	�8�	$�	$��C!�C!�C!�C!�C!�]�C!�C!�C!�C!�C!rdefence360agent/simple_rpc/__pycache__/hosting_panel.cpython-311.opt-1.pyc0000644000000000000000000000660300000000000023413 0ustar  �

ځ�L�	"���b�ddlmZddlmZddlmZddlmZddlm	Z	m
Z
Gd�de	��ZdS)	�)�PanelException)�DirectAdmin)�HostingPanel)�ValidationError)�
RootEndpoints�bindc���eZdZed��d
d���Zed��d
d���Zed��d���Zed��d	���Zed
��d���Ze	d���Z
dS)�HostingPanelEndpointsz
enable-pluginNc��FK�|j�|���d{V��S�N)�
hosting_panel�enable_imunify_plugin��self�plugin_names  �]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/hosting_panel.py�
enable_pluginz#HostingPanelEndpoints.enable_plugin	s/�����'�=�=�k�J�J�J�J�J�J�J�J�J�zdisable-pluginc��FK�|j�|���d{V��Sr)r
�disable_imunify_pluginrs  r�disable_pluginz$HostingPanelEndpoints.disable_plugin
s/�����'�>�>�{�K�K�K�K�K�K�K�K�Krzadd-sudouserc��K�|j}t|t��std���|�|���d{V��S�Nz&Feature available only for DirectAdmin)r
�
isinstancerr�add_sudouser�r�user�hps   rrz"HostingPanelEndpoints.add_sudousersT����
�
���"�k�*�*�	L�!�"J�K�K�K��_�_�T�*�*�*�*�*�*�*�*�*rzdelete-sudouserc��K�|j}t|t��std���|�|���d{V��Sr)r
rrr�delete_sudouserrs   rr z%HostingPanelEndpoints.delete_sudousersV����
�
���"�k�*�*�	L�!�"J�K�K�K��'�'��-�-�-�-�-�-�-�-�-rz
list-docrootsc��HK�d|j����d{V��iS)N�items)r
�
list_docroots)rs r�get_docrootsz"HostingPanelEndpoints.get_docroots!s2�����t�1�?�?�A�A�A�A�A�A�A�A�B�Brc�|�	t��S#t$r!}tt|�����d}~wwxYwr)rrr�str)r�es  rr
z#HostingPanelEndpoints.hosting_panel%sD��	*��>�>�!���	*�	*�	*�!�#�a�&�&�)�)�)�����	*���s�
�
;�6�;r)�__name__�
__module__�__qualname__rrrrr r$�propertyr
�rrr
r
s������	�T�/���K�K�K���K�
�T�
���L�L�L���L�
�T�.���+�+���+�
�T�
���.�.���.�
�T�/���C�C���C��*�*��X�*�*�*rr
N)�"defence360agent.subsys.panels.baser�)defence360agent.subsys.panels.directadminr�+defence360agent.subsys.panels.hosting_panelr�defence360agent.rpc_toolsr� defence360agent.rpc_tools.lookuprrr
r,rr�<module>r2s���=�=�=�=�=�=�A�A�A�A�A�A�D�D�D�D�D�D�5�5�5�5�5�5�@�@�@�@�@�@�@�@�"*�"*�"*�"*�"*�M�"*�"*�"*�"*�"*rdefence360agent/simple_rpc/__pycache__/hosting_panel.cpython-311.pyc0000644000000000000000000000660300000000000022454 0ustar  �

ځ�L�	"���b�ddlmZddlmZddlmZddlmZddlm	Z	m
Z
Gd�de	��ZdS)	�)�PanelException)�DirectAdmin)�HostingPanel)�ValidationError)�
RootEndpoints�bindc���eZdZed��d
d���Zed��d
d���Zed��d���Zed��d	���Zed
��d���Ze	d���Z
dS)�HostingPanelEndpointsz
enable-pluginNc��FK�|j�|���d{V��S�N)�
hosting_panel�enable_imunify_plugin��self�plugin_names  �]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/hosting_panel.py�
enable_pluginz#HostingPanelEndpoints.enable_plugin	s/�����'�=�=�k�J�J�J�J�J�J�J�J�J�zdisable-pluginc��FK�|j�|���d{V��Sr)r
�disable_imunify_pluginrs  r�disable_pluginz$HostingPanelEndpoints.disable_plugin
s/�����'�>�>�{�K�K�K�K�K�K�K�K�Krzadd-sudouserc��K�|j}t|t��std���|�|���d{V��S�Nz&Feature available only for DirectAdmin)r
�
isinstancerr�add_sudouser�r�user�hps   rrz"HostingPanelEndpoints.add_sudousersT����
�
���"�k�*�*�	L�!�"J�K�K�K��_�_�T�*�*�*�*�*�*�*�*�*rzdelete-sudouserc��K�|j}t|t��std���|�|���d{V��Sr)r
rrr�delete_sudouserrs   rr z%HostingPanelEndpoints.delete_sudousersV����
�
���"�k�*�*�	L�!�"J�K�K�K��'�'��-�-�-�-�-�-�-�-�-rz
list-docrootsc��HK�d|j����d{V��iS)N�items)r
�
list_docroots)rs r�get_docrootsz"HostingPanelEndpoints.get_docroots!s2�����t�1�?�?�A�A�A�A�A�A�A�A�B�Brc�|�	t��S#t$r!}tt|�����d}~wwxYwr)rrr�str)r�es  rr
z#HostingPanelEndpoints.hosting_panel%sD��	*��>�>�!���	*�	*�	*�!�#�a�&�&�)�)�)�����	*���s�
�
;�6�;r)�__name__�
__module__�__qualname__rrrrr r$�propertyr
�rrr
r
s������	�T�/���K�K�K���K�
�T�
���L�L�L���L�
�T�.���+�+���+�
�T�
���.�.���.�
�T�/���C�C���C��*�*��X�*�*�*rr
N)�"defence360agent.subsys.panels.baser�)defence360agent.subsys.panels.directadminr�+defence360agent.subsys.panels.hosting_panelr�defence360agent.rpc_toolsr� defence360agent.rpc_tools.lookuprrr
r,rr�<module>r2s���=�=�=�=�=�=�A�A�A�A�A�A�D�D�D�D�D�D�5�5�5�5�5�5�@�@�@�@�@�@�@�@�"*�"*�"*�"*�"*�M�"*�"*�"*�"*�"*rdefence360agent/simple_rpc/__pycache__/myimunify.cpython-311.opt-1.pyc0000644000000000000000000001111500000000000022601 0ustar  �

1�ds�Jj���ddlZddlmZmZddlmcmcmZ	ddl
mZmZddl
mZmZmZddlmZddlmZGd�dej��ZGd	�d
ej��ZdS)�N)�List�Optional)�MyImunifyConfig�is_mi_freemium_license)�	MyImunify�#set_protection_status_for_all_users�update_users_protection)�lookup)�Scopec���eZdZejZejdd��dee	de	fd���Z
ejdd��d���Zejdd��d	���Zd
S)�MyImunifyEndpoints�	myimunify�update�items�
protectionc��JK�t|j||dk���d{V��iS)N�enabled)r	�_sink)�selfrrs   �Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/myimunify.pyrzMyImunifyEndpoints.updatesK����%��J��z�Y�6�
�
�	
�	
�	
�	
�	
�	
�	
��	�z
enable-allc��@K�t|jd���d{V��dS)NT�rr�rs r�
enable_allzMyImunifyEndpoints.enable_alls0����1�$�*�d�C�C�C�C�C�C�C�C�C�C�Crzdisable-allc��@K�t|jd���d{V��dS)NFrrs r�disable_allzMyImunifyEndpoints.disable_all s0����1�$�*�e�D�D�D�D�D�D�D�D�D�D�DrN)
�__name__�
__module__�__qualname__r�IM360�SCOPEr
�bindr�strrrr�rrr
r
s��������K�E��V�[��h�'�'��$�s�)������(�'���V�[��l�+�+�D�D�,�+�D��V�[��m�,�,�E�E�-�,�E�E�Err
c�t�eZdZejZejdd��ddee	de
e	fd���ZdS)�MyImunifyCommonEndpointsr�statusNr�userc
��K�tj}tj��}|��|g}tjr�|����d{V���|g��}tt|��d��}tjdztj
�ddd|||���d���z}tj���tj�|�������}tj|t'��d�|D��d�S)Nz/?�cloudlinux_advantage�provisioning�my_imunify_account_protection)�m�action�suite�username�domain�	server_ipc�0�g|]}|d|dd���S)r)r)r1rr%)�.0�items  r�
<listcomp>z3MyImunifyCommonEndpoints.status.<locals>.<listcomp>Hs9������"�&�\��l�9K�L�L���r)�myimunify_enabled�purchase_page_url�is_freemiumr)r�PURCHASE_PAGE_URL�hp�HostingPanel�ENABLED�get_domains_per_user�get�next�iter�urllib�parse�	urlencode�
get_server_ipr�select�wherer)�in_�dictsr)rrr)�purchase_url�
panel_manager�user_domainsr2�responses        rr(zMyImunifyCommonEndpoints.status(sM����&�8����)�)�
����F�E��&�
�'�<�<�>�>�>�>�>�>�>�>��#�d�B�-�-���d�<�0�0�$�7�7��#�5����l�,�,�!7�&4�%D�(,�&,�)6�)D�)D�)F�)F�
��	�	����#�%�%�+�+�I�N�,>�,>�u�,E�,E�F�F�L�L�N�N��!0�!8�!-�1�3�3���$����	
�
�	
r)N)rrr rr!r"r
r#rr$rr(r%rrr'r'%sc�������K�E��V�[��h�'�'�#
�#
�$�s�)�#
�8�C�=�#
�#
�#
�(�'�#
�#
�#
rr')�urllib.parserC�typingrr�+defence360agent.subsys.panels.hosting_panel�subsys�panels�
hosting_panelr<� defence360agent.contracts.configrr�defence360agent.myimunify.modelrrr	�defence360agent.rpc_toolsr
�defence360agent.utilsr�
RootEndpointsr
�CommonEndpointsr'r%rr�<module>r[s9������!�!�!�!�!�!�!�!�8�8�8�8�8�8�8�8�8�8�8�8�������������������
-�,�,�,�,�,�'�'�'�'�'�'�E�E�E�E�E��-�E�E�E�&'
�'
�'
�'
�'
�v�5�'
�'
�'
�'
�'
rdefence360agent/simple_rpc/__pycache__/myimunify.cpython-311.pyc0000644000000000000000000001111500000000000021642 0ustar  �

1�ds�Jj���ddlZddlmZmZddlmcmcmZ	ddl
mZmZddl
mZmZmZddlmZddlmZGd�dej��ZGd	�d
ej��ZdS)�N)�List�Optional)�MyImunifyConfig�is_mi_freemium_license)�	MyImunify�#set_protection_status_for_all_users�update_users_protection)�lookup)�Scopec���eZdZejZejdd��dee	de	fd���Z
ejdd��d���Zejdd��d	���Zd
S)�MyImunifyEndpoints�	myimunify�update�items�
protectionc��JK�t|j||dk���d{V��iS)N�enabled)r	�_sink)�selfrrs   �Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/myimunify.pyrzMyImunifyEndpoints.updatesK����%��J��z�Y�6�
�
�	
�	
�	
�	
�	
�	
�	
��	�z
enable-allc��@K�t|jd���d{V��dS)NT�rr�rs r�
enable_allzMyImunifyEndpoints.enable_alls0����1�$�*�d�C�C�C�C�C�C�C�C�C�C�Crzdisable-allc��@K�t|jd���d{V��dS)NFrrs r�disable_allzMyImunifyEndpoints.disable_all s0����1�$�*�e�D�D�D�D�D�D�D�D�D�D�DrN)
�__name__�
__module__�__qualname__r�IM360�SCOPEr
�bindr�strrrr�rrr
r
s��������K�E��V�[��h�'�'��$�s�)������(�'���V�[��l�+�+�D�D�,�+�D��V�[��m�,�,�E�E�-�,�E�E�Err
c�t�eZdZejZejdd��ddee	de
e	fd���ZdS)�MyImunifyCommonEndpointsr�statusNr�userc
��K�tj}tj��}|��|g}tjr�|����d{V���|g��}tt|��d��}tjdztj
�ddd|||���d���z}tj���tj�|�������}tj|t'��d�|D��d�S)Nz/?�cloudlinux_advantage�provisioning�my_imunify_account_protection)�m�action�suite�username�domain�	server_ipc�0�g|]}|d|dd���S)r)r)r1rr%)�.0�items  r�
<listcomp>z3MyImunifyCommonEndpoints.status.<locals>.<listcomp>Hs9������"�&�\��l�9K�L�L���r)�myimunify_enabled�purchase_page_url�is_freemiumr)r�PURCHASE_PAGE_URL�hp�HostingPanel�ENABLED�get_domains_per_user�get�next�iter�urllib�parse�	urlencode�
get_server_ipr�select�wherer)�in_�dictsr)rrr)�purchase_url�
panel_manager�user_domainsr2�responses        rr(zMyImunifyCommonEndpoints.status(sM����&�8����)�)�
����F�E��&�
�'�<�<�>�>�>�>�>�>�>�>��#�d�B�-�-���d�<�0�0�$�7�7��#�5����l�,�,�!7�&4�%D�(,�&,�)6�)D�)D�)F�)F�
��	�	����#�%�%�+�+�I�N�,>�,>�u�,E�,E�F�F�L�L�N�N��!0�!8�!-�1�3�3���$����	
�
�	
r)N)rrr rr!r"r
r#rr$rr(r%rrr'r'%sc�������K�E��V�[��h�'�'�#
�#
�$�s�)�#
�8�C�=�#
�#
�#
�(�'�#
�#
�#
rr')�urllib.parserC�typingrr�+defence360agent.subsys.panels.hosting_panel�subsys�panels�
hosting_panelr<� defence360agent.contracts.configrr�defence360agent.myimunify.modelrrr	�defence360agent.rpc_toolsr
�defence360agent.utilsr�
RootEndpointsr
�CommonEndpointsr'r%rr�<module>r[s9������!�!�!�!�!�!�!�!�8�8�8�8�8�8�8�8�8�8�8�8�������������������
-�,�,�,�,�,�'�'�'�'�'�'�E�E�E�E�E��-�E�E�E�&'
�'
�'
�'
�'
�v�5�'
�'
�'
�'
�'
rdefence360agent/simple_rpc/__pycache__/permissions.cpython-311.opt-1.pyc0000644000000000000000000000173100000000000023131 0ustar  �

q��3 �6��>�ddlmZddlmZmZGd�de��ZdS)���permissions_list)�CommonEndpoints�bindc�:�eZdZedd��dd���ZdS)�PermissionEndpoints�permissions�listNc��4K�dt|���d{V��iS)N�itemsr)�self�users  �[/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/permissions.pyrz$PermissionEndpoints.permissions_lists,�����/��5�5�5�5�5�5�5�5�6�6�)N)�__name__�
__module__�__qualname__rr�rrrrs?������	�T�-�� � �7�7�7�!� �7�7�7rrN)�%defence360agent.contracts.permissionsr� defence360agent.rpc_tools.lookuprrrrrr�<module>rsc��B�B�B�B�B�B�B�B�B�B�B�B�B�B�7�7�7�7�7�/�7�7�7�7�7rdefence360agent/simple_rpc/__pycache__/permissions.cpython-311.pyc0000644000000000000000000000173100000000000022172 0ustar  �

q��3 �6��>�ddlmZddlmZmZGd�de��ZdS)���permissions_list)�CommonEndpoints�bindc�:�eZdZedd��dd���ZdS)�PermissionEndpoints�permissions�listNc��4K�dt|���d{V��iS)N�itemsr)�self�users  �[/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/permissions.pyrz$PermissionEndpoints.permissions_lists,�����/��5�5�5�5�5�5�5�5�6�6�)N)�__name__�
__module__�__qualname__rr�rrrrs?������	�T�-�� � �7�7�7�!� �7�7�7rrN)�%defence360agent.contracts.permissionsr� defence360agent.rpc_tools.lookuprrrrrr�<module>rsc��B�B�B�B�B�B�B�B�B�B�B�B�B�B�7�7�7�7�7�/�7�7�7�7�7rdefence360agent/simple_rpc/__pycache__/plesk_stats.cpython-311.opt-1.pyc0000644000000000000000000001611500000000000023114 0ustar  �

�L��]a����ddlZddlZddlmZddlmZddlmZmZddl	m
Z
ddlmZddl
mZddlmZdd	lmZdd
lmZddlmZejdd
d���ZGd�de��ZdS)�N)�suppress)�
LicenseCLN)�
RootEndpoints�bind)�run_in_executor_decorator)�HostingPanel)�list_docroots_domains_users)�atomic_rewrite)�Plesk)�kernel_care)�importerzimav.malwarelib.model�
MalwareHit)�module�name�defaultc�f�eZdZdZed��d���Zed���Zed���Z	dS)�PleskStatsEndpoints�dzplesk-statsc��DK�t��}t|t��s
Jd���tt	t
j����������}tt
j�	|t
j
j����}|�t���d{V�����d{V��}d|dz|d�|�|����d{V���dtj��rdndi�iS)Nzonly for plesk�itemsi�)�
last_modified�last_modified_str�license�r)r�
isinstancer�int�round�datetime�now�	timestamp�str�
fromtimestamp�timezone�utc�_domains_statsr	�_get_stats_field_in_plugin_infor�is_valid)�self�panel�current_timestampr�
domains_statss     �[/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/plesk_stats.py�plesk_statszPleskStatsEndpoints.plesk_statssZ���������%��'�'�9�9�)9�9�9�'���h�&7�&;�&;�&=�&=�&G�&G�&I�&I� J� J�K�K�����+�+�!��!�%�
�
�
�
��#�1�1�-�/�/�/�/�/�/�/�/�
�
�
�
�
�
�
�
�
�
�!2�T�!9�%6��� ���=�=�?�?�?�?�?�?�?�?�	�
��!4�!6�!6�=�A�A�A���
�	
�c���K�tj������d{V��siStj������d{V��}ddd�}t	t
��5t
tjj��5}tj	|��}ddd��n#1swxYwYddd��n#1swxYwY|ddk}|d|dkr/tj
�tjj
���n4tj
�|dtjj
��}|sdn6tj
�tjj
���|z
j}t!tjjtj|d|���d���d	�
��|d|d�S)
N)�effective_kernel�first_time_update_available�
updateCode�1�effectiveKernelr0)�tzr1rF)�backup�
autoUpdate)�kernel_uptodate�outdated_since_days)r�
KernelCare�check_installed�get_plugin_infor�FileNotFoundError�open�
KC_PROPERTIES�json�loadrrr#r$r"�daysr
�dumpsr )�cls�plugin_info�previous�file�update_availabler1r9s       r,r&z3PleskStatsEndpoints._get_stats_field_in_plugin_info/s{���� �+�-�-�=�=�?�?�?�?�?�?�?�?�	��I�'�2�4�4�D�D�F�F�F�F�F�F�F�F�� $�+/�
�
���'�
(�
(�	+�	+��k�,�:�;�;�
+�t��9�T�?�?��
+�
+�
+�
+�
+�
+�
+�
+�
+�
+�
+����
+�
+�
+�
+�	+�	+�	+�	+�	+�	+�	+�	+�	+�	+�	+����	+�	+�	+�	+�'�|�4��;���,�-��:L�1M�M�M�
��!�!�X�%6�%:�!�;�;�;��"�0�0��6�7��9J�9N���	$�$�
�A�A��!�%�%��):�)>�%�?�?�-�.��
	�	��"�0��J�(3�4E�(F�3N�3X�3X�3Z�3Z���
�
��		
�		
�		
�		
� +�<�8�#6�
�
�	
s6�4C�B4�(C�4B8	�8C�;B8	�<C�C�Cc	��	�t�gdd�Stt�tj���t����������}td�tt�tj���t������	�������D�����	tt�	fd�|����}g}|D]9\}}}|D]0\}|�|��r|�|��n�1�:|d|j
�t|��d�S)Nr)�infected_sites�wsites_infectedc3�&K�|]}|dV��
dS)rN�)�.0�datas  r,�	<genexpr>z5PleskStatsEndpoints._domains_stats.<locals>.<genexpr>is:����
�
��
��G�
�
�
�
�
�
r.c���|d�vS)N�rM)rO�infected_userss �r,�<lambda>z4PleskStatsEndpoints._domains_stats.<locals>.<lambda>ts���T�!�W��6�r.)r�list�select�	orig_file�where�is_infected�tuples�set�user�distinct�filter�
startswith�append�MAX_DOMAINS_COUNT�len)
r(�plesk_response�
file_names�infected_plesk_responserJ�docroot�domainr\�filenamerSs
         @r,r%z"PleskStatsEndpoints._domains_statsZs������"$�#$���
�����j�2�3�3�
�U�:�)�)�+�+�
,�
,�
�V�X�X�
�
�
��
�
���!�!�*�/�2�2���z�-�-�/�/�0�0���������	��
�
�
�
�
��#'��6�6�6�6��
�
�#
�#
����%<�	�	�!�G�V�T�)�
�
����&�&�w�/�/��"�)�)�&�1�1�1��E���
-�-E�t�/E�-E�F�"�>�2�2�
�
�	
r.N)
�__name__�
__module__�__qualname__rarr-�classmethodr&rr%rMr.r,rrsv��������	�T�-���
�
���
�.�(
�(
��[�(
�T�(
�(
���(
�(
�(
r.r)rr@�
contextlibr�!defence360agent.contracts.licenser� defence360agent.rpc_tools.lookuprr�defence360agent.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,�<module>rws.����������������8�8�8�8�8�8�@�@�@�@�@�@�@�@�E�E�E�E�E�E�D�D�D�D�D�D�O�O�O�O�O�O�0�0�0�0�0�0�5�5�5�5�5�5�7�7�7�7�7�7�*�*�*�*�*�*�
�X�\�"��t����
�
o
�o
�o
�o
�o
�-�o
�o
�o
�o
�o
r.defence360agent/simple_rpc/__pycache__/plesk_stats.cpython-311.pyc0000644000000000000000000001611500000000000022155 0ustar  �

�L��]a����ddlZddlZddlmZddlmZddlmZmZddl	m
Z
ddlmZddl
mZddlmZdd	lmZdd
lmZddlmZejdd
d���ZGd�de��ZdS)�N)�suppress)�
LicenseCLN)�
RootEndpoints�bind)�run_in_executor_decorator)�HostingPanel)�list_docroots_domains_users)�atomic_rewrite)�Plesk)�kernel_care)�importerzimav.malwarelib.model�
MalwareHit)�module�name�defaultc�f�eZdZdZed��d���Zed���Zed���Z	dS)�PleskStatsEndpoints�dzplesk-statsc��DK�t��}t|t��s
Jd���tt	t
j����������}tt
j�	|t
j
j����}|�t���d{V�����d{V��}d|dz|d�|�|����d{V���dtj��rdndi�iS)Nzonly for plesk�itemsi�)�
last_modified�last_modified_str�license�r)r�
isinstancer�int�round�datetime�now�	timestamp�str�
fromtimestamp�timezone�utc�_domains_statsr	�_get_stats_field_in_plugin_infor�is_valid)�self�panel�current_timestampr�
domains_statss     �[/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/plesk_stats.py�plesk_statszPleskStatsEndpoints.plesk_statssZ���������%��'�'�9�9�)9�9�9�'���h�&7�&;�&;�&=�&=�&G�&G�&I�&I� J� J�K�K�����+�+�!��!�%�
�
�
�
��#�1�1�-�/�/�/�/�/�/�/�/�
�
�
�
�
�
�
�
�
�
�!2�T�!9�%6��� ���=�=�?�?�?�?�?�?�?�?�	�
��!4�!6�!6�=�A�A�A���
�	
�c���K�tj������d{V��siStj������d{V��}ddd�}t	t
��5t
tjj��5}tj	|��}ddd��n#1swxYwYddd��n#1swxYwY|ddk}|d|dkr/tj
�tjj
���n4tj
�|dtjj
��}|sdn6tj
�tjj
���|z
j}t!tjjtj|d|���d���d	�
��|d|d�S)
N)�effective_kernel�first_time_update_available�
updateCode�1�effectiveKernelr0)�tzr1rF)�backup�
autoUpdate)�kernel_uptodate�outdated_since_days)r�
KernelCare�check_installed�get_plugin_infor�FileNotFoundError�open�
KC_PROPERTIES�json�loadrrr#r$r"�daysr
�dumpsr )�cls�plugin_info�previous�file�update_availabler1r9s       r,r&z3PleskStatsEndpoints._get_stats_field_in_plugin_info/s{���� �+�-�-�=�=�?�?�?�?�?�?�?�?�	��I�'�2�4�4�D�D�F�F�F�F�F�F�F�F�� $�+/�
�
���'�
(�
(�	+�	+��k�,�:�;�;�
+�t��9�T�?�?��
+�
+�
+�
+�
+�
+�
+�
+�
+�
+�
+����
+�
+�
+�
+�	+�	+�	+�	+�	+�	+�	+�	+�	+�	+�	+����	+�	+�	+�	+�'�|�4��;���,�-��:L�1M�M�M�
��!�!�X�%6�%:�!�;�;�;��"�0�0��6�7��9J�9N���	$�$�
�A�A��!�%�%��):�)>�%�?�?�-�.��
	�	��"�0��J�(3�4E�(F�3N�3X�3X�3Z�3Z���
�
��		
�		
�		
�		
� +�<�8�#6�
�
�	
s6�4C�B4�(C�4B8	�8C�;B8	�<C�C�Cc	��	�t�gdd�Stt�tj���t����������}td�tt�tj���t������	�������D�����	tt�	fd�|����}g}|D]9\}}}|D]0\}|�|��r|�|��n�1�:|d|j
�t|��d�S)Nr)�infected_sites�wsites_infectedc3�&K�|]}|dV��
dS)rN�)�.0�datas  r,�	<genexpr>z5PleskStatsEndpoints._domains_stats.<locals>.<genexpr>is:����
�
��
��G�
�
�
�
�
�
r.c���|d�vS)N�rM)rO�infected_userss �r,�<lambda>z4PleskStatsEndpoints._domains_stats.<locals>.<lambda>ts���T�!�W��6�r.)r�list�select�	orig_file�where�is_infected�tuples�set�user�distinct�filter�
startswith�append�MAX_DOMAINS_COUNT�len)
r(�plesk_response�
file_names�infected_plesk_responserJ�docroot�domainr\�filenamerSs
         @r,r%z"PleskStatsEndpoints._domains_statsZs������"$�#$���
�����j�2�3�3�
�U�:�)�)�+�+�
,�
,�
�V�X�X�
�
�
��
�
���!�!�*�/�2�2���z�-�-�/�/�0�0���������	��
�
�
�
�
��#'��6�6�6�6��
�
�#
�#
����%<�	�	�!�G�V�T�)�
�
����&�&�w�/�/��"�)�)�&�1�1�1��E���
-�-E�t�/E�-E�F�"�>�2�2�
�
�	
r.N)
�__name__�
__module__�__qualname__rarr-�classmethodr&rr%rMr.r,rrsv��������	�T�-���
�
���
�.�(
�(
��[�(
�T�(
�(
���(
�(
�(
r.r)rr@�
contextlibr�!defence360agent.contracts.licenser� defence360agent.rpc_tools.lookuprr�defence360agent.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,�<module>rws.����������������8�8�8�8�8�8�@�@�@�@�@�@�@�@�E�E�E�E�E�E�D�D�D�D�D�D�O�O�O�O�O�O�0�0�0�0�0�0�5�5�5�5�5�5�7�7�7�7�7�7�*�*�*�*�*�*�
�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  �

����T�~���ddlZddlmZddlmZmZddlmZddlm	Z	ddl
mZddlm
Z
ddlmZeje��ZGd	�d
ej��ZdS)�N)�lookup)�ValidationError�validate_av_plus_license)�PanelException)�InfectedDomainList)�
hosting_panel)�
ReputationAPI)�run_in_executorc��eZdZejd��ed�����Zejd��ed�����ZdS)�ReputationManagementEndpointszinfected-domainsc��K�ttj������d{V����}t	j|||���\}}||d�S)N)�offset�limit)�items�	max_count)�setr�HostingPanel�	get_usersr�get_by_user)�selfrr�existing_usersrrs      �e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/reputation_management.py�list_domainsz*ReputationManagementEndpoints.list_domainssv�����=�#=�#?�#?�#I�#I�#K�#K�K�K�K�K�K�K�L�L��-�9��6��
�
�
���y��"�
�
�	
�z
check-domainsc�����K�tj��}|���std���	|����d{V��}n.#t
$r!}tt
|�����d}~wwxYw|std���tj|���d{V���tj���	���d{V���td��fd����d{V��dS)Nz!No avaliable control panel found!zDomains not foundc�.��tj����S)N)r�refresh_domains)�domain_to_user�reputation_datas��r�<lambda>z=ReputationManagementEndpoints.check_domains.<locals>.<lambda>6s���&�6�����r)rr�is_installedr�get_user_domainsr�strr	�check�get_domain_to_ownerr
)r�hp�domains�errs    @@r�
check_domainsz+ReputationManagementEndpoints.check_domainssY�������
'�
)�
)����� � �	G�!�"E�F�F�F�	*��/�/�1�1�1�1�1�1�1�1�G�G���	*�	*�	*�!�#�a�&�&�)�)�)�����	*�����	7�!�"5�6�6�6� -� 3�G� <� <�<�<�<�<�<�<���,�.�.�B�B�D�D�D�D�D�D�D�D�	���
�
�
�
�
�
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
s�A�
B�!A=�=BN)�__name__�
__module__�__qualname__r�bindrrr)�rrrrsy�������V�[�#�$�$��
�
���%�$�
��V�[��!�!��
�
���"�!�
�
�
rr)�logging�defence360agent.rpc_toolsr�"defence360agent.rpc_tools.validaterr�"defence360agent.subsys.panels.baser�%defence360agent.model.infected_domainr�defence360agent.subsys.panelsr�%defence360agent.api.server.reputationr	�$defence360agent.model.simplificationr
�	getLoggerr*�logger�
RootEndpointsrr.rr�<module>r:s�������,�,�,�,�,�,���������>�=�=�=�=�=�D�D�D�D�D�D�7�7�7�7�7�7�?�?�?�?�?�?�@�@�@�@�@�@�	��	�8�	$�	$��)
�)
�)
�)
�)
�F�$8�)
�)
�)
�)
�)
rdefence360agent/simple_rpc/__pycache__/reputation_management.cpython-311.pyc0000644000000000000000000000671200000000000024211 0ustar  �

����T�~���ddlZddlmZddlmZmZddlmZddlm	Z	ddl
mZddlm
Z
ddlmZeje��ZGd	�d
ej��ZdS)�N)�lookup)�ValidationError�validate_av_plus_license)�PanelException)�InfectedDomainList)�
hosting_panel)�
ReputationAPI)�run_in_executorc��eZdZejd��ed�����Zejd��ed�����ZdS)�ReputationManagementEndpointszinfected-domainsc��K�ttj������d{V����}t	j|||���\}}||d�S)N)�offset�limit)�items�	max_count)�setr�HostingPanel�	get_usersr�get_by_user)�selfrr�existing_usersrrs      �e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/reputation_management.py�list_domainsz*ReputationManagementEndpoints.list_domainssv�����=�#=�#?�#?�#I�#I�#K�#K�K�K�K�K�K�K�L�L��-�9��6��
�
�
���y��"�
�
�	
�z
check-domainsc�����K�tj��}|���std���	|����d{V��}n.#t
$r!}tt
|�����d}~wwxYw|std���tj|���d{V���tj���	���d{V���td��fd����d{V��dS)Nz!No avaliable control panel found!zDomains not foundc�.��tj����S)N)r�refresh_domains)�domain_to_user�reputation_datas��r�<lambda>z=ReputationManagementEndpoints.check_domains.<locals>.<lambda>6s���&�6�����r)rr�is_installedr�get_user_domainsr�strr	�check�get_domain_to_ownerr
)r�hp�domains�errs    @@r�
check_domainsz+ReputationManagementEndpoints.check_domainssY�������
'�
)�
)����� � �	G�!�"E�F�F�F�	*��/�/�1�1�1�1�1�1�1�1�G�G���	*�	*�	*�!�#�a�&�&�)�)�)�����	*�����	7�!�"5�6�6�6� -� 3�G� <� <�<�<�<�<�<�<���,�.�.�B�B�D�D�D�D�D�D�D�D�	���
�
�
�
�
�
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
s�A�
B�!A=�=BN)�__name__�
__module__�__qualname__r�bindrrr)�rrrrsy�������V�[�#�$�$��
�
���%�$�
��V�[��!�!��
�
���"�!�
�
�
rr)�logging�defence360agent.rpc_toolsr�"defence360agent.rpc_tools.validaterr�"defence360agent.subsys.panels.baser�%defence360agent.model.infected_domainr�defence360agent.subsys.panelsr�%defence360agent.api.server.reputationr	�$defence360agent.model.simplificationr
�	getLoggerr*�logger�
RootEndpointsrr.rr�<module>r:s�������,�,�,�,�,�,���������>�=�=�=�=�=�D�D�D�D�D�D�7�7�7�7�7�7�?�?�?�?�?�?�@�@�@�@�@�@�	��	�8�	$�	$��)
�)
�)
�)
�)
�F�$8�)
�)
�)
�)
�)
rdefence360agent/simple_rpc/__pycache__/schema.cpython-311.opt-1.pyc0000644000000000000000000002045000000000000022015 0ustar  �

��[�:C�)���ddlmZmZddlmZddlmZmZmZm	Z	m
Z
mZmZm
Z
mZmZmZddlmZGd�de��Zd�ZdS)	�)�BaseErrorHandler�BasicErrorHandler)�UserType)�add_eula�add_license�add_license_user�add_version�collect_warnings�counts�default_to_items�	max_count�preserve_remote_addr�send_command_invoke_message�set_caller_type_context)�prepare_schemac�L�eZdZej���Zd�Zd�ZdS)�ErrorHandlerc#�*K�|jr'|jD]}|�|��Ed{V���dSd�|j|j|j�|jd��j|j|j	|j|jd����V�dS)Nz#field: '{}', value: '{}', error: {}�)�
constraint�field�value)
�child_errors�collect_errors�formatrr�messages�get�code�infor)�self�error�errs   �V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/schema.pyrzErrorHandler.collect_errorss�������	��)�
4�
4���.�.�s�3�3�3�3�3�3�3�3�3�3�
4�
4�8�>�>�����8��
�!�!�%�*�b�1�1�8��Z�$�/��+��+�	���	�	�	
�	
�	
�	
�	
�c�n�g}|D]/}|�|��D]}|�|����0|S)N)r�append)r �errors�string_representationr!rs     r#�__call__zErrorHandler.__call__(sX�� "���	3�	3�E��+�+�E�2�2�
3�
3��%�,�,�T�2�2�2�2�
3�%�$r$N)�__name__�
__module__�__qualname__rr�copyrr)�r$r#rrsF������ �)�.�.�0�0�H����"%�%�%�%�%r$rc��|t|��t���}idttjtjffttjtjff||��tjtjffttjffttjffttjffttjtjffttjtjffttjtjffg	�dttjtjffg�dttjtjffg�dttjtjffg�dttjtjffg�dttjtjffg�dttjtjffg�dttjtjffg�d	ttjtjffg�d
ttjtjffg�dttjtjffg�dttjtjffg�d
ttjtjffg�dttjtjffg�dttjtjffg�dttjtjffg�dttjtjffg�ttjtjffgttjtjffgttjtjffgttjtjffgttjtjffgttjtjffgd��}idtg�dtg�dtg�dtg�dtg�dtg�dtg�dtg�dtg�dtg�dtg�dtg�dtg�d tg�d!tg�d"tg�d#tg�id$tg�d%tg�d&tg�d'tg�d(tg�d)tg�d*tg�d+tg�d,tg�d-tg�d.tg�d/tg�d0tg�d1tg�d2tg�d3tg�d4tg��tgtgtgtgtgtgtgtgtgtgtgtgtgtgd5��}|||fS)6N)�
error_handler)�	whitelist�ip�list)�	blacklistr2r3)�graylistr2r3)r1r2�add)r4r2r6)r1�countryr3)r4r7r3)r5r7r3)r4)r1)zwhitelisted-crawlersr3)zblocked-portr3)zblocked-port-ipr3)�rules�
list-disabled)�wordpress-pluginr8r9)r:z
list-sites))�	proactive�ignorer3)�feature-management�show)�ip-list�synced)r?�localr3)r?rAr6)r?rA�delete)z
enable-plugin)zdisable-plugin)zswitch-max-webserver)zinstall-vendors)zuninstall-vendors)zadd-sudouser)zdelete-sudouser)�doctor)�captchazupdate-localizations)rDzcompile-localizations)�update)�kcarectlzdisable-auto-update)rFzenable-auto-update)rFzplugin-info)�register)�
unregister)�rstatus)zupdate-license)�3rdpartyr3)zadmin-emails)z
list-docroots)�featuresr3)rK�status)rK�install)rK�remove)r=�native�enable)r=rO�disable)r=rOrL)�import�wblist)r8zupdate-app-specific-rules)�support�send)rJ�	conflicts)�
smtp-blocking�reset)rW�sync))�malwarez	on-demandzcheck-detached)�checkdb)zrestore-configs)�patchman�users)r\rG)r\rM)r\�migrate)r\�	uninstall)r\rL)r\rM�realtime)r\r_r`)�analyst-cleanup�request)razget-requests)raz
is-allowed)rrrr�ROOT�NON_ROOTrrrrr	r
rrr
r)�schema_validator�validate_middleware�schema_paths�
_validator�_middleware�_middleware_excludes      r#�init_validatorrk1s��!�!��|�$�$�"����J�T��%�x�}�h�6G�&H�I�
(�8�=�(�:K�*L�M�$�#�J�/�/���� 1�2�
�
�8�=�*�+�
�� 1�3�4�
��
�'�(�
�8�=�(�*;�<�=�
��
�x�/@�A�B�
��
�x�/@�A�B�+
�T�0	$�
�h�m�X�%6�7�8�&
�1T�6	$�
����(9�:�;�&
�7T�<	#�
�h�m�X�%6�7�8�%
�=T�B	#�
!�H�M�8�3D�#E�F�%
�CT�H	#�
!�H�M�8�3D�#E�F�%
�IT�N	)�
����(9�:�;�+
�OT�T	)�
����(9�:�;�+
�UT�Z	(�
����(9�:�;�*
�[T�`	�&�8�=�(�2C�"D�E�F�aT�b	�&�8�=�(�2C�"D�E�F�cT�d	)�
����(9�:�;�+
�eT�j	!�
�h�m�X�%6�7�8�#
�kT�p	$�
����(9�:�;�&
�qT�v	#�
����(9�:�;�%
�wT�|	7�
����(9�:�;�9
�}T�B	+�
����(9�:�;�-
�CT�J����(9�:�;�*
�����(9�:�;�)
�"(�(�-��9J�)K� L�M�
�h�m�X�%6�7�8�'
�"�H�M�8�3D�#E�F�&
�"�H�M�8�3D�#E�F�)
�cT�T�T�K�l1��X�J�1��h�Z�1�	"�H�:�1�	�x�j�	1�
	��
�1�	�H�:�
1�	�x�j�1�	�h�Z�1�	,�h�Z�1�	-�x�j�1�	�h�Z�1�	,�h�Z�1�	+�X�J�1�	$�h�Z�1�	��z�1� 	�(��!1�"	�x�j�#1�1�$	�h�Z�%1�&	�x�j�'1�(	�H�:�)1�*	�X�J�+1�,	�x�j�-1�.	��
�/1�0	 �(��11�2	��
�31�4	3�X�J�51�6	4�h�Z�71�8	3�X�J�91�:	�x�j�;1�<	/��
�=1�>	�h�Z�?1�@	"�H�:�A1�B	#�X�J�C1�D	"�H�:�E1�1�F6>�J��j�'�j� (�z�#+�*�"*��"*��$,�:�!)�
�.6�Z�08�z�)1�
�.6�Z�,4�:�a1�1�1��f�{�$7�7�7r$N)�cerberus.errorsrr� defence360agent.contracts.configr�$defence360agent.rpc_tools.middlewarerrrr	r
rrr
rrr�defence360agent.rpc_tools.utilsrrrkr.r$r#�<module>rps��?�?�?�?�?�?�?�?�5�5�5�5�5�5���������������������������;�:�:�:�:�:�%�%�%�%�%�#�%�%�%�:P8�P8�P8�P8�P8r$defence360agent/simple_rpc/__pycache__/schema.cpython-311.pyc0000644000000000000000000002045000000000000021056 0ustar  �

��[�:C�)���ddlmZmZddlmZddlmZmZmZm	Z	m
Z
mZmZm
Z
mZmZmZddlmZGd�de��Zd�ZdS)	�)�BaseErrorHandler�BasicErrorHandler)�UserType)�add_eula�add_license�add_license_user�add_version�collect_warnings�counts�default_to_items�	max_count�preserve_remote_addr�send_command_invoke_message�set_caller_type_context)�prepare_schemac�L�eZdZej���Zd�Zd�ZdS)�ErrorHandlerc#�*K�|jr'|jD]}|�|��Ed{V���dSd�|j|j|j�|jd��j|j|j	|j|jd����V�dS)Nz#field: '{}', value: '{}', error: {}�)�
constraint�field�value)
�child_errors�collect_errors�formatrr�messages�get�code�infor)�self�error�errs   �V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/schema.pyrzErrorHandler.collect_errorss�������	��)�
4�
4���.�.�s�3�3�3�3�3�3�3�3�3�3�
4�
4�8�>�>�����8��
�!�!�%�*�b�1�1�8��Z�$�/��+��+�	���	�	�	
�	
�	
�	
�	
�c�n�g}|D]/}|�|��D]}|�|����0|S)N)r�append)r �errors�string_representationr!rs     r#�__call__zErrorHandler.__call__(sX�� "���	3�	3�E��+�+�E�2�2�
3�
3��%�,�,�T�2�2�2�2�
3�%�$r$N)�__name__�
__module__�__qualname__rr�copyrr)�r$r#rrsF������ �)�.�.�0�0�H����"%�%�%�%�%r$rc��|t|��t���}idttjtjffttjtjff||��tjtjffttjffttjffttjffttjtjffttjtjffttjtjffg	�dttjtjffg�dttjtjffg�dttjtjffg�dttjtjffg�dttjtjffg�dttjtjffg�dttjtjffg�d	ttjtjffg�d
ttjtjffg�dttjtjffg�dttjtjffg�d
ttjtjffg�dttjtjffg�dttjtjffg�dttjtjffg�dttjtjffg�ttjtjffgttjtjffgttjtjffgttjtjffgttjtjffgttjtjffgd��}idtg�dtg�dtg�dtg�dtg�dtg�dtg�dtg�dtg�dtg�dtg�dtg�dtg�d tg�d!tg�d"tg�d#tg�id$tg�d%tg�d&tg�d'tg�d(tg�d)tg�d*tg�d+tg�d,tg�d-tg�d.tg�d/tg�d0tg�d1tg�d2tg�d3tg�d4tg��tgtgtgtgtgtgtgtgtgtgtgtgtgtgd5��}|||fS)6N)�
error_handler)�	whitelist�ip�list)�	blacklistr2r3)�graylistr2r3)r1r2�add)r4r2r6)r1�countryr3)r4r7r3)r5r7r3)r4)r1)zwhitelisted-crawlersr3)zblocked-portr3)zblocked-port-ipr3)�rules�
list-disabled)�wordpress-pluginr8r9)r:z
list-sites))�	proactive�ignorer3)�feature-management�show)�ip-list�synced)r?�localr3)r?rAr6)r?rA�delete)z
enable-plugin)zdisable-plugin)zswitch-max-webserver)zinstall-vendors)zuninstall-vendors)zadd-sudouser)zdelete-sudouser)�doctor)�captchazupdate-localizations)rDzcompile-localizations)�update)�kcarectlzdisable-auto-update)rFzenable-auto-update)rFzplugin-info)�register)�
unregister)�rstatus)zupdate-license)�3rdpartyr3)zadmin-emails)z
list-docroots)�featuresr3)rK�status)rK�install)rK�remove)r=�native�enable)r=rO�disable)r=rOrL)�import�wblist)r8zupdate-app-specific-rules)�support�send)rJ�	conflicts)�
smtp-blocking�reset)rW�sync))�malwarez	on-demandzcheck-detached)�checkdb)zrestore-configs)�patchman�users)r\rG)r\rM)r\�migrate)r\�	uninstall)r\rL)r\rM�realtime)r\r_r`)�analyst-cleanup�request)razget-requests)raz
is-allowed)rrrr�ROOT�NON_ROOTrrrrr	r
rrr
r)�schema_validator�validate_middleware�schema_paths�
_validator�_middleware�_middleware_excludes      r#�init_validatorrk1s��!�!��|�$�$�"����J�T��%�x�}�h�6G�&H�I�
(�8�=�(�:K�*L�M�$�#�J�/�/���� 1�2�
�
�8�=�*�+�
�� 1�3�4�
��
�'�(�
�8�=�(�*;�<�=�
��
�x�/@�A�B�
��
�x�/@�A�B�+
�T�0	$�
�h�m�X�%6�7�8�&
�1T�6	$�
����(9�:�;�&
�7T�<	#�
�h�m�X�%6�7�8�%
�=T�B	#�
!�H�M�8�3D�#E�F�%
�CT�H	#�
!�H�M�8�3D�#E�F�%
�IT�N	)�
����(9�:�;�+
�OT�T	)�
����(9�:�;�+
�UT�Z	(�
����(9�:�;�*
�[T�`	�&�8�=�(�2C�"D�E�F�aT�b	�&�8�=�(�2C�"D�E�F�cT�d	)�
����(9�:�;�+
�eT�j	!�
�h�m�X�%6�7�8�#
�kT�p	$�
����(9�:�;�&
�qT�v	#�
����(9�:�;�%
�wT�|	7�
����(9�:�;�9
�}T�B	+�
����(9�:�;�-
�CT�J����(9�:�;�*
�����(9�:�;�)
�"(�(�-��9J�)K� L�M�
�h�m�X�%6�7�8�'
�"�H�M�8�3D�#E�F�&
�"�H�M�8�3D�#E�F�)
�cT�T�T�K�l1��X�J�1��h�Z�1�	"�H�:�1�	�x�j�	1�
	��
�1�	�H�:�
1�	�x�j�1�	�h�Z�1�	,�h�Z�1�	-�x�j�1�	�h�Z�1�	,�h�Z�1�	+�X�J�1�	$�h�Z�1�	��z�1� 	�(��!1�"	�x�j�#1�1�$	�h�Z�%1�&	�x�j�'1�(	�H�:�)1�*	�X�J�+1�,	�x�j�-1�.	��
�/1�0	 �(��11�2	��
�31�4	3�X�J�51�6	4�h�Z�71�8	3�X�J�91�:	�x�j�;1�<	/��
�=1�>	�h�Z�?1�@	"�H�:�A1�B	#�X�J�C1�D	"�H�:�E1�1�F6>�J��j�'�j� (�z�#+�*�"*��"*��$,�:�!)�
�.6�Z�08�z�)1�
�.6�Z�,4�:�a1�1�1��f�{�$7�7�7r$N)�cerberus.errorsrr� defence360agent.contracts.configr�$defence360agent.rpc_tools.middlewarerrrr	r
rrr
rrr�defence360agent.rpc_tools.utilsrrrkr.r$r#�<module>rps��?�?�?�?�?�?�?�?�5�5�5�5�5�5���������������������������;�:�:�:�:�:�%�%�%�%�%�#�%�%�%�:P8�P8�P8�P8�P8r$defence360agent/simple_rpc/__pycache__/wordpress_security_plugin.cpython-311.opt-1.pyc0000644000000000000000000002263600000000000026122 0ustar  �

�9�V.�`�
�"�ddlZddlZddlZddlmZddlmZmZmZddl	m
Z
mZddlm
Z
ddlmZddlmZddlmZdd	lmZeje��Z	dd
edzdedzdeedzedzffd
�ZGd�de��ZGd�de��ZdS)�N)�ValidationError)�CommonEndpoints�
RootEndpoints�bind)�Scope�is_root_user)�MessageType)�get_wordpress_incidents)�$enrich_incidents_with_disabled_state)�get_installed_sites_paginated)�get_domain_paths�user�site_search�returnc�x�tj��}t��r�t�d|��d}|�s	tj|��j}t�d||��n<#t$r/t�	d|��td|�d����wxYw||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)
�os�getuidr�logger�debug�pwd�getpwnam�pw_uid�KeyError�warning)rr�current_uid�user_ids    �i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/wordpress_security_plugin.py�get_user_id_and_site_for_queryrs���,�)�+�+�K��~�~�$����D�d�K�K�K�����
;��,�t�,�,�3�����>��g�������
;�
;�
;����3�T�:�:�:��9��9�9�9�:�:�:�
;������#�#���#�#s�5A8�89B1c���eZdZejZedd��d���Zedd��d���Zedd��d���Z	edd��d	���Z
d
S)�WordpressEndpoints�wordpress-pluginzinstall-on-new-sitesc��pK�|j�tjd������d{V��dS)N�install_on_new_sites��action��_sink�process_messager	�WordpressPluginAction��selfs r�wordpress_plugin_installz+WordpressEndpoints.wordpress_plugin_installGsZ�����j�(�(��-�5K�L�L�L�
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
�ztidy-upc��pK�|j�tjd������d{V��dS)N�tidy_upr$r&r*s r�wordpress_plugin_tidy_upz+WordpressEndpoints.wordpress_plugin_tidy_upMsY�����j�(�(��-�Y�?�?�?�
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
r-�updatec��pK�|j�tjd������d{V��dS)N�update_existingr$r&r*s r�wordpress_plugin_updatez*WordpressEndpoints.wordpress_plugin_updateSsZ�����j�(�(��-�5F�G�G�G�
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
r-zinstall-and-updatec��pK�|j�tjd������d{V��dS)N�install_and_updater$r&r*s r�#wordpress_plugin_install_and_updatez6WordpressEndpoints.wordpress_plugin_install_and_updateYsZ�����j�(�(��-�5I�J�J�J�
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
r-N)�__name__�
__module__�__qualname__r�AV_IM360�SCOPErr,r0r4r7�r-rr r Ds��������N�E�	�T�
�4�5�5�
�
�6�5�
�

�T�
�i�(�(�
�
�)�(�
�

�T�
�h�'�'�
�
�(�'�
�

�T�
�2�3�3�
�
�4�3�
�
�
r-r c��eZdZejZedd��												ddedzdedzd	ed
ededzdedzd
edzdedzdedzdedzde	dzde
de	efd���Zedd��dd���Z
dS)�WordpressCommonEndpointsr!zlist-incidentsN�2rFrr�limit�offset�by_abuser_ip�by_country_code�	by_domain�search�since�to�order_by�include_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)rArBrrCrDrErFrrGrHrIrJ�retries�times�country�code)rrr�strr
�popr)r+rrrArBrCrDrErFrGrHrIrJr�	site_path�e�	incidents�incidentrNs                   r�wordpress_plugin_list_incidentsz8WordpressCommonEndpoints.wordpress_plugin_list_incidentscs����V	1�!?��k�"�"��G�Y�Y���	1�	1�	1�!�#�a�&�&�)�)�q�0�����	1����,����%�+���!����)�

�

�

�	� "�	�	�H� (���Y� 7� 7�H�W���l�l�9�-�-�G�%,�%8���!�!�d�
�Y���	-�Y�7�7�7��s��
A�?�Az
list-sitesc��fK�d}|r/	tj|��j}n#t$rdgfcYSwxYwt	|||���\}}t���d{V��}g}|D]K}	|�|	jg��}
|
r|
dn|	j}|�	||	jd����L||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)�domain�docroot)
rrrrrr
�getrZrY�append)r+rArBrrX�	max_count�sites�docroot_domains�items�site�domains�primary_domains            r�
list_sitesz#WordpressCommonEndpoints.list_sites�s�������	�
��l�4�(�(�/�����
�
�
��"�u����
����9��5��
�
�
��	�5�
!1� 2� 2�2�2�2�2�2�2�����
	�
	�D�%�)�)�$�,��;�;�G�+2�C�W�Q�Z�Z���N��L�L�,�#�|���
�
�
�
��%��s�"�3�3)NNr@rNNNNNNNF)r@rN)r8r9r:rr;r<rrP�int�list�bool�dictrVrdr=r-rr?r?`s\�������N�E�	�T�
�.�/�/� �"&���#'�&*� $�!� �� $�$�J�J��D�j�J��4�Z�J��	J�
�J��D�j�
J��t��J���:�J��d�
�J��T�z�J�
�$�J�J���+�J��J�
�d��J�J�J�0�/�J�X
�T�
�l�+�+�# �# �# �,�+�# �# �# r-r?)NN)�loggingrr�defence360agent.rpc_toolsr� defence360agent.rpc_tools.lookuprrr�defence360agent.utilsrr�"defence360agent.contracts.messagesr	�(defence360agent.model.wordpress_incidentr
�&defence360agent.model.wp_disabled_ruler�)defence360agent.wordpress.site_repositoryr�defence360agent.wordpress.utilsr
�	getLoggerr8rrP�tuplererr r?r=r-r�<module>rts�������	�	�	�	�
�
�
�
�5�5�5�5�5�5�����������
6�5�5�5�5�5�5�5�:�:�:�:�:�:�L�L�L�L�L�L�������������=�<�<�<�<�<�	��	�8�	$�	$��8<�($�($�

��*�($�*-��*�($�
�3��:�s�T�z�!�"�($�($�($�($�V
�
�
�
�
��
�
�
�8t �t �t �t �t ��t �t �t �t �t r-defence360agent/simple_rpc/__pycache__/wordpress_security_plugin.cpython-311.pyc0000644000000000000000000002263600000000000025163 0ustar  �

�9�V.�`�
�"�ddlZddlZddlZddlmZddlmZmZmZddl	m
Z
mZddlm
Z
ddlmZddlmZddlmZdd	lmZeje��Z	dd
edzdedzdeedzedzffd
�ZGd�de��ZGd�de��ZdS)�N)�ValidationError)�CommonEndpoints�
RootEndpoints�bind)�Scope�is_root_user)�MessageType)�get_wordpress_incidents)�$enrich_incidents_with_disabled_state)�get_installed_sites_paginated)�get_domain_paths�user�site_search�returnc�x�tj��}t��r�t�d|��d}|�s	tj|��j}t�d||��n<#t$r/t�	d|��td|�d����wxYw||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)
�os�getuidr�logger�debug�pwd�getpwnam�pw_uid�KeyError�warning)rr�current_uid�user_ids    �i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/wordpress_security_plugin.py�get_user_id_and_site_for_queryrs���,�)�+�+�K��~�~�$����D�d�K�K�K�����
;��,�t�,�,�3�����>��g�������
;�
;�
;����3�T�:�:�:��9��9�9�9�:�:�:�
;������#�#���#�#s�5A8�89B1c���eZdZejZedd��d���Zedd��d���Zedd��d���Z	edd��d	���Z
d
S)�WordpressEndpoints�wordpress-pluginzinstall-on-new-sitesc��pK�|j�tjd������d{V��dS)N�install_on_new_sites��action��_sink�process_messager	�WordpressPluginAction��selfs r�wordpress_plugin_installz+WordpressEndpoints.wordpress_plugin_installGsZ�����j�(�(��-�5K�L�L�L�
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
�ztidy-upc��pK�|j�tjd������d{V��dS)N�tidy_upr$r&r*s r�wordpress_plugin_tidy_upz+WordpressEndpoints.wordpress_plugin_tidy_upMsY�����j�(�(��-�Y�?�?�?�
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
r-�updatec��pK�|j�tjd������d{V��dS)N�update_existingr$r&r*s r�wordpress_plugin_updatez*WordpressEndpoints.wordpress_plugin_updateSsZ�����j�(�(��-�5F�G�G�G�
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
r-zinstall-and-updatec��pK�|j�tjd������d{V��dS)N�install_and_updater$r&r*s r�#wordpress_plugin_install_and_updatez6WordpressEndpoints.wordpress_plugin_install_and_updateYsZ�����j�(�(��-�5I�J�J�J�
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
r-N)�__name__�
__module__�__qualname__r�AV_IM360�SCOPErr,r0r4r7�r-rr r Ds��������N�E�	�T�
�4�5�5�
�
�6�5�
�

�T�
�i�(�(�
�
�)�(�
�

�T�
�h�'�'�
�
�(�'�
�

�T�
�2�3�3�
�
�4�3�
�
�
r-r c��eZdZejZedd��												ddedzdedzd	ed
ededzdedzd
edzdedzdedzdedzde	dzde
de	efd���Zedd��dd���Z
dS)�WordpressCommonEndpointsr!zlist-incidentsN�2rFrr�limit�offset�by_abuser_ip�by_country_code�	by_domain�search�since�to�order_by�include_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)rArBrrCrDrErFrrGrHrIrJ�retries�times�country�code)rrr�strr
�popr)r+rrrArBrCrDrErFrGrHrIrJr�	site_path�e�	incidents�incidentrNs                   r�wordpress_plugin_list_incidentsz8WordpressCommonEndpoints.wordpress_plugin_list_incidentscs����V	1�!?��k�"�"��G�Y�Y���	1�	1�	1�!�#�a�&�&�)�)�q�0�����	1����,����%�+���!����)�

�

�

�	� "�	�	�H� (���Y� 7� 7�H�W���l�l�9�-�-�G�%,�%8���!�!�d�
�Y���	-�Y�7�7�7��s��
A�?�Az
list-sitesc��fK�d}|r/	tj|��j}n#t$rdgfcYSwxYwt	|||���\}}t���d{V��}g}|D]K}	|�|	jg��}
|
r|
dn|	j}|�	||	jd����L||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)�domain�docroot)
rrrrrr
�getrZrY�append)r+rArBrrX�	max_count�sites�docroot_domains�items�site�domains�primary_domains            r�
list_sitesz#WordpressCommonEndpoints.list_sites�s�������	�
��l�4�(�(�/�����
�
�
��"�u����
����9��5��
�
�
��	�5�
!1� 2� 2�2�2�2�2�2�2�����
	�
	�D�%�)�)�$�,��;�;�G�+2�C�W�Q�Z�Z���N��L�L�,�#�|���
�
�
�
��%��s�"�3�3)NNr@rNNNNNNNF)r@rN)r8r9r:rr;r<rrP�int�list�bool�dictrVrdr=r-rr?r?`s\�������N�E�	�T�
�.�/�/� �"&���#'�&*� $�!� �� $�$�J�J��D�j�J��4�Z�J��	J�
�J��D�j�
J��t��J���:�J��d�
�J��T�z�J�
�$�J�J���+�J��J�
�d��J�J�J�0�/�J�X
�T�
�l�+�+�# �# �# �,�+�# �# �# r-r?)NN)�loggingrr�defence360agent.rpc_toolsr� defence360agent.rpc_tools.lookuprrr�defence360agent.utilsrr�"defence360agent.contracts.messagesr	�(defence360agent.model.wordpress_incidentr
�&defence360agent.model.wp_disabled_ruler�)defence360agent.wordpress.site_repositoryr�defence360agent.wordpress.utilsr
�	getLoggerr8rrP�tuplererr r?r=r-r�<module>rts�������	�	�	�	�
�
�
�
�5�5�5�5�5�5�����������
6�5�5�5�5�5�5�5�:�:�:�:�:�:�L�L�L�L�L�L�������������=�<�<�<�<�<�	��	�8�	$�	$��8<�($�($�

��*�($�*-��*�($�
�3��:�s�T�z�!�"�($�($�($�($�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  �

8��J������dZddlZddlZddlZddlZddlmZddlmZm	Z	ddl
mZddlm
Z
mZddlmZddlmZdd	lmZmZdd
lmZddlmZmZddlmZdd
lmZmZddl m!Z!ddl"m#Z#ej$e%��Z&de'de(e'fd�Z)de'de(e'dzde(e'fd�Z*de(e+de+dzde(e+fd�Z,	dde(e'dedzddfd�Z-Gd�de��Z.dS)z6RPC endpoints for WordPress disabled protection rules.�N)�MessageType)�WP_WAF_RULES_EDIT�check_permission)�MessageSink)�Index�WP_RULES)�WPDisabledRule)�ValidationError)�CommonEndpoints�bind)�
hosting_panel)�Scope�log_future_errors)�ChangelogProcessor)�redeploy_rules_php�update_disabled_rules_on_sites)�get_installed_sites_by_domains)�get_wp_rules_data�user�returnc��K�	tj��}|����d{V��}|�|g��S#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
�HostingPanel�get_domains_per_user�get�	Exception�logger�warning)r�hp�domains_per_user�es    �a/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/wp_disabled_rules.py�_get_user_domainsr"#s������
�
'�
)�
)��!#�!8�!8�!:�!:�:�:�:�:�:�:���#�#�D�"�-�-�-���������>��a�H�H�H��	�	�	�	�	�	��������s�AA�
A9�A4�.A9�4A9�domainsc���K�t|���d{V���|s�std����S�fd�|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 userc���g|]}|�v�|��	S�r&��.0�d�user_domainss  �r!�
<listcomp>z*_validate_user_domains.<locals>.<listcomp>Ls#���B�B�B���\�0A�0A�!�0A�0A�0A�z5You don't have access to any of the specified domains)r"r
)rr#�authorized_domainsr*s   @r!�_validate_user_domainsr.3s������&+�4�0�0�0�0�0�0�0�0�L����	?�!�"=�>�>�>���B�B�B�B�W�B�B�B���
��C�
�
�	
��r,�disabled_rules�
wp_rules_datac���g}|D]f}|d}|r|�|i��ni}|�i|�|�d��|�d��d�����g|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_id�target�versions)�	componentr4)r�append)r/r0�enriched�ruler2�metadatas      r!�_enrich_with_metadatar:Ts����H��

�

���y�/��5B�J�=�$�$�W�b�1�1�1������
��
�%�\�\�(�3�3�$�L�L��4�4�
�
�
�	
�	
�	
�	
��Or,�sinkc���K�	t|��}|sdSt���||����d{V��dS#t$r(}t�d|d���Yd}~dSd}~wwxYw)a�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.
    N)r;zJIT changelog sync failed: %sT)�exc_info)rr�process_changelogs_for_sitesrrr)r#r;�sitesr s    r!�_jit_sync_changelogsr@qs�����J�.�w�7�7���	��F� �"�"�?�?���@�
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
���J�J�J����6��D��I�I�I�I�I�I�I�I�I�����J���s�A�)A�
A4�A/�/A4c��eZdZdZejZeddd��				dded	ed
e	e
dzde
dzdeee	eff
d
���Z
de
de
d
e	e
dzde
dzdef
d�Zeddd��		dde
d
e	e
dzde
dzdefd���Zeddd��		dde
d
e	e
dzde
dzdefd���ZdS)�WPDisabledRulesEndpointsz:Endpoints for listing disabled WordPress protection rules.zwordpress-plugin�rulesz
list-disabled�2rN�limit�offsetr#rrc���K�|r.t|���d{V���|s�}n�fd�|D��}|sdgfStj||||du���\}}	ttd���}t|��}n4#t$r'}	t�d|	��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)
        Nc���g|]}|�v�|��	Sr&r&r's  �r!r+z@WPDisabledRulesEndpoints.list_disabled_rules.<locals>.<listcomp>�s#���C�C�C���l�1B�1B�1�1B�1B�1Br,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_rules�s�����0�		!�!2�4�!8�!8�8�8�8�8�8�8�L��
!�&���C�C�C�C�g�C�C�C���!��b�5�L�'5�&:��� ��4�<�	'
�'
�'
�#��^�	!�"�8�U�C�C�C�N�-�n�=�=�M�M���	!�	!�	!��N�N�=�q�A�A�A� �M�M�M�M�M�M�����	!����
/�~�}�M�M���N�*�*s�%A8�8
B)�B$�$B)�actionr8c
��K�tt|���d{V��|�d}n<	tj|��j}n!#t
$rt
d|�d����wxYw|rt||���d{V��}|rt||j	���d{V��|dkr/tj||tj|���tj}n"tj||���tj}	|j	�|d||pgt%j��|tj�	�����d{V��n4#t&$r'}t(�d
|||��Yd}~nd}~wwxYw|r#t-jt1|�����}n t-jt3����}|�t6��iS)z8Shared implementation for disable/enable rule endpoints.NrzUser 'z' not found�disable)r2r#�source�user_id)r2r#�	wordpress)�	plugin_idr8r#�	timestamprUrTz#Failed to report rule %s for %s: %s)r#)rr�pwd�getpwnam�pw_uid�KeyErrorr
r.r@�_sinkr	�store�SOURCE_AGENTr�WPRuleDisabled�remove�
WPRuleEnabled�process_message�timerr�error�asyncio�create_taskrr�add_done_callbackr)	rLrQr8r#rrU�message_clsr �tasks	         r!�_toggle_rulez%WPDisabledRulesEndpoints._toggle_rule�so�����0�$�7�7�7�7�7�7�7�7�7��<��G�G�
B��,�t�,�,�3�����
B�
B�
B�%�&@�t�&@�&@�&@�A�A�A�
B�����	B�2�4��A�A�A�A�A�A�A�A�G�
�	<�&�w��
�;�;�;�;�;�;�;�;�;��Y���� ���%�2��	
�
�
�
�&�4�K�K��!�$��@�@�@�@�%�3�K�	��*�,�,���)��#�M�r�"�i�k�k�#�)�6�
���	�	�	
�	
�	
�	
�	
�	
�	
�	
���	�	�	��L�L�5�v�t�Q�
�
�
�
�
�
�
�
�����	����
�	=��&�.�w�?�?�?���D�D�
�&�'9�';�';�<�<�D����0�1�1�1��	s"�>�A�,AD9�9
E*�E%�%E*rSc��BK�|�d|||���d{V��S)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.
        rSN�rk�rLr8r#rs    r!�disable_rulez%WPDisabledRulesEndpoints.disable_rules4����.�&�&�y�$���F�F�F�F�F�F�F�F�Fr,�enablec��BK�|�d|||���d{V��S)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_rules4����4�&�&�x��w��E�E�E�E�E�E�E�E�Er,)rDrNN)NN)�__name__�
__module__�__qualname__�__doc__r�AV_IM360�SCOPEr�int�list�str�tuple�dictrPrkrorrr&r,r!rBrB�s�������D�D��N�E�	�T�
�g��7�7���$(��6+�6+��6+��6+��c��T�!�	6+�
�D�j�6+�
�s�D��J��	�
6+�6+�6+�8�7�6+�p@��@��@��c��T�!�	@�
�D�j�@�
�
@�@�@�@�D
�T�
�g�y�1�1�%)��	G�G��G��c��T�!�G��D�j�	G�

�G�G�G�2�1�G�0
�T�
�g�x�0�0�%)��	F�F��F��c��T�!�F��D�j�	F�

�F�F�F�1�0�F�F�Fr,rB)N)/rvrf�loggingrYrd�"defence360agent.contracts.messagesr�%defence360agent.contracts.permissionsrr�!defence360agent.contracts.pluginsr�defence360agent.filesrr�&defence360agent.model.wp_disabled_ruler	�defence360agent.rpc_toolsr
� defence360agent.rpc_tools.lookuprr�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!�<module>r�s���<�<���������
�
�
�
�����:�:�:�:�:�:���������:�9�9�9�9�9�1�1�1�1�1�1�1�1�A�A�A�A�A�A�5�5�5�5�5�5�B�B�B�B�B�B�B�B�7�7�7�7�7�7�:�:�:�:�:�:�:�:���������������������A�@�@�@�@�@�	��	�8�	$�	$��
�#�
�$�s�)�
�
�
�
� �

���S�	�D�(��	�#�Y�����B���J��/3�d�{��	�$�Z�����<48�J�J�
�#�Y�J�)�D�0�J�	�J�J�J�J�*sF�sF�sF�sF�sF��sF�sF�sF�sF�sFr,defence360agent/simple_rpc/__pycache__/wp_disabled_rules.cpython-311.pyc0000644000000000000000000003232000000000000023304 0ustar  �

8��J������dZddlZddlZddlZddlZddlmZddlmZm	Z	ddl
mZddlm
Z
mZddlmZddlmZdd	lmZmZdd
lmZddlmZmZddlmZdd
lmZmZddl m!Z!ddl"m#Z#ej$e%��Z&de'de(e'fd�Z)de'de(e'dzde(e'fd�Z*de(e+de+dzde(e+fd�Z,	dde(e'dedzddfd�Z-Gd�de��Z.dS)z6RPC endpoints for WordPress disabled protection rules.�N)�MessageType)�WP_WAF_RULES_EDIT�check_permission)�MessageSink)�Index�WP_RULES)�WPDisabledRule)�ValidationError)�CommonEndpoints�bind)�
hosting_panel)�Scope�log_future_errors)�ChangelogProcessor)�redeploy_rules_php�update_disabled_rules_on_sites)�get_installed_sites_by_domains)�get_wp_rules_data�user�returnc��K�	tj��}|����d{V��}|�|g��S#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
�HostingPanel�get_domains_per_user�get�	Exception�logger�warning)r�hp�domains_per_user�es    �a/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/wp_disabled_rules.py�_get_user_domainsr"#s������
�
'�
)�
)��!#�!8�!8�!:�!:�:�:�:�:�:�:���#�#�D�"�-�-�-���������>��a�H�H�H��	�	�	�	�	�	��������s�AA�
A9�A4�.A9�4A9�domainsc���K�t|���d{V���|s�std����S�fd�|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 userc���g|]}|�v�|��	S�r&��.0�d�user_domainss  �r!�
<listcomp>z*_validate_user_domains.<locals>.<listcomp>Ls#���B�B�B���\�0A�0A�!�0A�0A�0A�z5You don't have access to any of the specified domains)r"r
)rr#�authorized_domainsr*s   @r!�_validate_user_domainsr.3s������&+�4�0�0�0�0�0�0�0�0�L����	?�!�"=�>�>�>���B�B�B�B�W�B�B�B���
��C�
�
�	
��r,�disabled_rules�
wp_rules_datac���g}|D]f}|d}|r|�|i��ni}|�i|�|�d��|�d��d�����g|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_id�target�versions)�	componentr4)r�append)r/r0�enriched�ruler2�metadatas      r!�_enrich_with_metadatar:Ts����H��

�

���y�/��5B�J�=�$�$�W�b�1�1�1������
��
�%�\�\�(�3�3�$�L�L��4�4�
�
�
�	
�	
�	
�	
��Or,�sinkc���K�	t|��}|sdSt���||����d{V��dS#t$r(}t�d|d���Yd}~dSd}~wwxYw)a�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.
    N)r;zJIT changelog sync failed: %sT)�exc_info)rr�process_changelogs_for_sitesrrr)r#r;�sitesr s    r!�_jit_sync_changelogsr@qs�����J�.�w�7�7���	��F� �"�"�?�?���@�
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
���J�J�J����6��D��I�I�I�I�I�I�I�I�I�����J���s�A�)A�
A4�A/�/A4c��eZdZdZejZeddd��				dded	ed
e	e
dzde
dzdeee	eff
d
���Z
de
de
d
e	e
dzde
dzdef
d�Zeddd��		dde
d
e	e
dzde
dzdefd���Zeddd��		dde
d
e	e
dzde
dzdefd���ZdS)�WPDisabledRulesEndpointsz:Endpoints for listing disabled WordPress protection rules.zwordpress-plugin�rulesz
list-disabled�2rN�limit�offsetr#rrc���K�|r.t|���d{V���|s�}n�fd�|D��}|sdgfStj||||du���\}}	ttd���}t|��}n4#t$r'}	t�d|	��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)
        Nc���g|]}|�v�|��	Sr&r&r's  �r!r+z@WPDisabledRulesEndpoints.list_disabled_rules.<locals>.<listcomp>�s#���C�C�C���l�1B�1B�1�1B�1B�1Br,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_rules�s�����0�		!�!2�4�!8�!8�8�8�8�8�8�8�L��
!�&���C�C�C�C�g�C�C�C���!��b�5�L�'5�&:��� ��4�<�	'
�'
�'
�#��^�	!�"�8�U�C�C�C�N�-�n�=�=�M�M���	!�	!�	!��N�N�=�q�A�A�A� �M�M�M�M�M�M�����	!����
/�~�}�M�M���N�*�*s�%A8�8
B)�B$�$B)�actionr8c
��K�tt|���d{V��|�d}n<	tj|��j}n!#t
$rt
d|�d����wxYw|rt||���d{V��}|rt||j	���d{V��|dkr/tj||tj|���tj}n"tj||���tj}	|j	�|d||pgt%j��|tj�	�����d{V��n4#t&$r'}t(�d
|||��Yd}~nd}~wwxYw|r#t-jt1|�����}n t-jt3����}|�t6��iS)z8Shared implementation for disable/enable rule endpoints.NrzUser 'z' not found�disable)r2r#�source�user_id)r2r#�	wordpress)�	plugin_idr8r#�	timestamprUrTz#Failed to report rule %s for %s: %s)r#)rr�pwd�getpwnam�pw_uid�KeyErrorr
r.r@�_sinkr	�store�SOURCE_AGENTr�WPRuleDisabled�remove�
WPRuleEnabled�process_message�timerr�error�asyncio�create_taskrr�add_done_callbackr)	rLrQr8r#rrU�message_clsr �tasks	         r!�_toggle_rulez%WPDisabledRulesEndpoints._toggle_rule�so�����0�$�7�7�7�7�7�7�7�7�7��<��G�G�
B��,�t�,�,�3�����
B�
B�
B�%�&@�t�&@�&@�&@�A�A�A�
B�����	B�2�4��A�A�A�A�A�A�A�A�G�
�	<�&�w��
�;�;�;�;�;�;�;�;�;��Y���� ���%�2��	
�
�
�
�&�4�K�K��!�$��@�@�@�@�%�3�K�	��*�,�,���)��#�M�r�"�i�k�k�#�)�6�
���	�	�	
�	
�	
�	
�	
�	
�	
�	
���	�	�	��L�L�5�v�t�Q�
�
�
�
�
�
�
�
�����	����
�	=��&�.�w�?�?�?���D�D�
�&�'9�';�';�<�<�D����0�1�1�1��	s"�>�A�,AD9�9
E*�E%�%E*rSc��BK�|�d|||���d{V��S)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.
        rSN�rk�rLr8r#rs    r!�disable_rulez%WPDisabledRulesEndpoints.disable_rules4����.�&�&�y�$���F�F�F�F�F�F�F�F�Fr,�enablec��BK�|�d|||���d{V��S)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_rules4����4�&�&�x��w��E�E�E�E�E�E�E�E�Er,)rDrNN)NN)�__name__�
__module__�__qualname__�__doc__r�AV_IM360�SCOPEr�int�list�str�tuple�dictrPrkrorrr&r,r!rBrB�s�������D�D��N�E�	�T�
�g��7�7���$(��6+�6+��6+��6+��c��T�!�	6+�
�D�j�6+�
�s�D��J��	�
6+�6+�6+�8�7�6+�p@��@��@��c��T�!�	@�
�D�j�@�
�
@�@�@�@�D
�T�
�g�y�1�1�%)��	G�G��G��c��T�!�G��D�j�	G�

�G�G�G�2�1�G�0
�T�
�g�x�0�0�%)��	F�F��F��c��T�!�F��D�j�	F�

�F�F�F�1�0�F�F�Fr,rB)N)/rvrf�loggingrYrd�"defence360agent.contracts.messagesr�%defence360agent.contracts.permissionsrr�!defence360agent.contracts.pluginsr�defence360agent.filesrr�&defence360agent.model.wp_disabled_ruler	�defence360agent.rpc_toolsr
� defence360agent.rpc_tools.lookuprr�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!�<module>r�s���<�<���������
�
�
�
�����:�:�:�:�:�:���������:�9�9�9�9�9�1�1�1�1�1�1�1�1�A�A�A�A�A�A�5�5�5�5�5�5�B�B�B�B�B�B�B�B�7�7�7�7�7�7�:�:�:�:�:�:�:�:���������������������A�@�@�@�@�@�	��	�8�	$�	$��
�#�
�$�s�)�
�
�
�
� �

���S�	�D�(��	�#�Y�����B���J��/3�d�{��	�$�Z�����<48�J�J�
�#�Y�J�)�D�0�J�	�J�J�J�J�*sF�sF�sF�sF�sF��sF�sF�sF�sF�sFr,defence360agent/simple_rpc/__pycache__/wp_waf_bulk.cpython-311.opt-1.pyc0000644000000000000000000002577300000000000023072 0ustar  �

�,-�稈m�	��dZddlZddlZddlZddlmZddlmZddlm	Z	m
Z
ddlmZddl
mZddlmZdd	lmZmZdd
lmZeje��ZdZdZd
ZdZdeedeeee dze!effd�Z"deee dze!efde#e e fde#fd�Z$deee dze!efdedzdedzde!fd�Z%Gd�de	��Z&dS)z Bulk WAF set + status endpoints.�N)�	Wordpress)�ValidationError)�
RootEndpoints�bind)�
hosting_panel)�Scope)�
update_config)�waf_global_snapshot�#waf_status_and_source_for_user_sync)�count_installed_sites_by_uid�
�enabled�disabledi��users�returnc���g}|D]Z}	tj|��j}n#t$rd}YnwxYwt	|��\}}|�||||f���[|S)N)�pwd�getpwnam�pw_uid�KeyErrorr�append)r�rows�name�uidr�sources      �[/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/wp_waf_bulk.py�_resolve_accounts_syncr"s����D��2�2��	��,�t�$�$�+�C�C���	�	�	��C�C�C�	����=�d�C�C�������T�3���0�1�1�1�1��Ks�"�1�1�row�site_countsc�b�|\}}}}||rtnt||�|d��d�S)Nr)r�
waf_statusr�wp_sites)�_STATUS_ENABLED�_STATUS_DISABLED�get)rrrrrrs      r�_status_itemr&0sE��"%��D�#�w���)0�F�o�o�6F���O�O�C��+�+�	����statusrc�\�|\}}}}|rtnt}|�||krdS|�||krdSdS)NFT)r#r$)rr(r�_r�srcr!s       r�_matchesr,<sN��
��A�q�'�3�$+�A���1A�J�
��j�F�2�2��u�
��c�V�m�m��u��4r'c���eZdZejZeddd��		ddedede	edzd	e
fd
���Zeddd��					ddedzdedzd
edzdedzded	e
fd���Z
dS)�WordpressWafBulkEndpointszwordpress-plugin�waf�setFNr(�	all_usersrrc��X���K�|r|�td���|s|�td���|�|std���tjstd���t�d|||��	tt
j������d{V����}n%#t$r}td|����|�d}~wwxYwg}g}g}|rt|��}	nQg}	t�|��D]4}
|
|vr|	�
|
���|�
|
dd����5|d	k�d
tdtttdzff��fd��t!d
t#|	��t$��D]l}�fd�|	||t$z�D��}t'j|��d{V��}
|
D]5\}
}|�|�
|
���|�
|
|d����6�mgd�|D���d�|D���d�|D���}||||d�S)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)�user�reasonr�urc���K�	t�jdd�ii|����d{V��|dfS#t$r}|t|��fcYd}~Sd}~wwxYw)N�	WORDPRESS�waf_enabled)r4)r	�_sink�	Exception�str)r6�e�self�	waf_values  ��r�_apply_to_userz9WordpressWafBulkEndpoints.waf_set.<locals>._apply_to_user�s������
!�#��J� �=�)�"<�=������������
�$�w����
!�
!�
!��#�a�&�&�y� � � � � � �����
!���s�$*�
A�A
�A�
Arc�&��g|]
}�|����S�rB)�.0r6r@s  �r�
<listcomp>z5WordpressWafBulkEndpoints.waf_set.<locals>.<listcomp>�s0������&'���q�!�!���r'c��g|]}|ddd���	S)�	succeeded��r4r(r5rB)rCr6s  rrDz5WordpressWafBulkEndpoints.waf_set.<locals>.<listcomp>�s2�������k�R�@�@���r'c�2�g|]}|dd|dd���S)r4�skippedr5rHrB)rC�ss  rrDz5WordpressWafBulkEndpoints.waf_set.<locals>.<listcomp>�s:�������6��i�1�X�;�O�O���r'c�2�g|]}|dd|dd���S)r4�failedr5rHrB)rC�fs  rrDz5WordpressWafBulkEndpoints.waf_set.<locals>.<listcomp>�s:�������6��h�!�H�+�N�N���r')�itemsrFrJrM)rr�SECURITY_PLUGIN_ENABLED�logger�warningr0r�HostingPanel�	get_usersr;�list�dict�fromkeysrr<�tuple�range�len�_MAX_CONCURRENT�asyncio�gather)r>r(r1r�panel_usersr=rFrJrM�valid_usersr6�i�batch�results�errrOr@r?s`               @@r�waf_setz!WordpressWafBulkEndpoints.waf_setMsL��������	��*�!�A���
��	K�U�]�!�"I�J�J�J���U��!�"=�>�>�>��0�	�!�;���
�
	���L����		
�	
�	
�	��M�$>�$@�$@�$J�$J�$L�$L�L�L�L�L�L�L�M�M�K�K���	�	�	�!�9�a�9�9����
�����	����
 "�	� �����	P��{�+�+�K�K��K��]�]�5�)�)�
P�
P����#�#��&�&�q�)�)�)�)��N�N�A�9M�#N�#N�O�O�O�O��i�'�	�		!�C�		!�E�#�s�T�z�/�,B�		!�		!�		!�		!�		!�		!�		!��q�#�k�*�*�O�<�<�		>�		>�A�����+6�q�1��;N�7N�+O����E�$�N�E�2�2�2�2�2�2�2�G�!�
>�
>���3��;��$�$�Q�'�'�'�'��M�M�1��"<�"<�=�=�=�=�	
>�

���"����

�
�� ����

�������

�� �"���	
�
�	
s�88B1�1
C�;C�Crr4r�limit�offsetc��8���K�|�|dkrtd���|dkrtd���tj��}t��\}}}		t	t
�tj���	���d{V������}
n%#t$r}td|����|�d}~wwxYw|�&|t|
��vrt|�d����|g}
|�dt���d{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 userc�0��g|]}t|�����SrB)r&)rCrrs  �rrDz8WordpressWafBulkEndpoints.waf_status.<locals>.<listcomp>�s#���D�D�D��\�#�{�3�3�D�D�Dr'c�R��g|]#}t|�����t|�����$SrB)r,r&)rCrrrr(s  ���rrDz8WordpressWafBulkEndpoints.waf_status.<locals>.<listcomp>�sE��������C���0�0���S�+�.�.���r'c��|dS)NrrB)r`s r�<lambda>z6WordpressWafBulkEndpoints.waf_status.<locals>.<lambda>�s
��Q�v�Y�r')�key)�security_plugin_enabled�
global_waf�global_waf_default�total_countrO)rr\�get_running_loopr
rUrVrWrrSrTr;r0�run_in_executorr�_SAFETY_CAP�minrZ�sortedr�sortr#r$)r>r4r(rrerf�looprm�global_waf_enabledror^r=�	page_sizerp�pagerrOrs  ``             @rr!z$WordpressWafBulkEndpoints.waf_status�s��������������!�"8�9�9�9��A�:�:�!�"9�:�:�:��'�)�)��
 �!�!�		
�#���	���
�
�M�$>�$@�$@�$J�$J�$L�$L�L�L�L�L�L�L�M�M���K�K���	�	�	�!�9�a�9�9����
�����	����
���3�{�+�+�+�+�%��&E�&E�&E�F�F�F��&�K� �0�0��.�
�
�
�
�
�
�
�
��$)�=�K�K�c�%��6M�6M�	��>�f�n�
�k�*�*�K��+�&�&�v���0B�'B�C�D��-�-��,�d���������D�E�D�D�D�t�D�D�D�E�E��-�-��,�k���������D�����������E�

�J�J�.�.�J�/�/�/��e�*�*�K��&�6�I�#5�5�6�E�(?�#5�K���;K�$6�K���;K�&��

�

�
	
s�AB)�)
C�3C�C)FN)NNNNr)�__name__�
__module__�__qualname__r�AV_IM360�SCOPErr<�boolrUrVrd�intr!rBr'rr.r.Js,�������N�E�	�T�
�e�U�+�+� �"&�	]
�]
��]
��]
��C�y�4��	]
�

�]
�]
�]
�,�+�]
�~
�T�
�e�X�.�.� �!�!� ��
L
�L
��D�j�L
��d�
�L
��d�
�	L
�
�T�z�L
��
L
�
�L
�L
�L
�/�.�L
�L
�L
r'r.)'�__doc__r\�loggingr� defence360agent.contracts.configr�defence360agent.rpc_toolsr� defence360agent.rpc_tools.lookuprr�defence360agent.subsys.panelsr�defence360agent.utilsr�defence360agent.utils.configr	� defence360agent.wordpress.pluginr
r�)defence360agent.wordpress.site_repositoryr�	getLoggerr{rQr[r#r$rsrUr<rXr�r�rrVr&r,r.rBr'r�<module>r�s��&�&���������
�
�
�
�6�6�6�6�6�6�5�5�5�5�5�5�@�@�@�@�@�@�@�@�7�7�7�7�7�7�'�'�'�'�'�'�6�6�6�6�6�6���������������
��	�8�	$�	$��������
�����9��	�%��S�4�Z��s�*�
+�,�����	�	�s�C�$�J��c�)�	*�	�9=�c�3�h��	�	�	�	�	�	��	�s�C�$�J��c�)�	*���$�J��
�$�J��
�	����p
�p
�p
�p
�p
�
�p
�p
�p
�p
�p
r'defence360agent/simple_rpc/__pycache__/wp_waf_bulk.cpython-311.pyc0000644000000000000000000002577300000000000022133 0ustar  �

�,-�稈m�	��dZddlZddlZddlZddlmZddlmZddlm	Z	m
Z
ddlmZddl
mZddlmZdd	lmZmZdd
lmZeje��ZdZdZd
ZdZdeedeeee dze!effd�Z"deee dze!efde#e e fde#fd�Z$deee dze!efdedzdedzde!fd�Z%Gd�de	��Z&dS)z Bulk WAF set + status endpoints.�N)�	Wordpress)�ValidationError)�
RootEndpoints�bind)�
hosting_panel)�Scope)�
update_config)�waf_global_snapshot�#waf_status_and_source_for_user_sync)�count_installed_sites_by_uid�
�enabled�disabledi��users�returnc���g}|D]Z}	tj|��j}n#t$rd}YnwxYwt	|��\}}|�||||f���[|S)N)�pwd�getpwnam�pw_uid�KeyErrorr�append)r�rows�name�uidr�sources      �[/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/wp_waf_bulk.py�_resolve_accounts_syncr"s����D��2�2��	��,�t�$�$�+�C�C���	�	�	��C�C�C�	����=�d�C�C�������T�3���0�1�1�1�1��Ks�"�1�1�row�site_countsc�b�|\}}}}||rtnt||�|d��d�S)Nr)r�
waf_statusr�wp_sites)�_STATUS_ENABLED�_STATUS_DISABLED�get)rrrrrrs      r�_status_itemr&0sE��"%��D�#�w���)0�F�o�o�6F���O�O�C��+�+�	����statusrc�\�|\}}}}|rtnt}|�||krdS|�||krdSdS)NFT)r#r$)rr(r�_r�srcr!s       r�_matchesr,<sN��
��A�q�'�3�$+�A���1A�J�
��j�F�2�2��u�
��c�V�m�m��u��4r'c���eZdZejZeddd��		ddedede	edzd	e
fd
���Zeddd��					ddedzdedzd
edzdedzded	e
fd���Z
dS)�WordpressWafBulkEndpointszwordpress-plugin�waf�setFNr(�	all_usersrrc��X���K�|r|�td���|s|�td���|�|std���tjstd���t�d|||��	tt
j������d{V����}n%#t$r}td|����|�d}~wwxYwg}g}g}|rt|��}	nQg}	t�|��D]4}
|
|vr|	�
|
���|�
|
dd����5|d	k�d
tdtttdzff��fd��t!d
t#|	��t$��D]l}�fd�|	||t$z�D��}t'j|��d{V��}
|
D]5\}
}|�|�
|
���|�
|
|d����6�mgd�|D���d�|D���d�|D���}||||d�S)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)�user�reasonr�urc���K�	t�jdd�ii|����d{V��|dfS#t$r}|t|��fcYd}~Sd}~wwxYw)N�	WORDPRESS�waf_enabled)r4)r	�_sink�	Exception�str)r6�e�self�	waf_values  ��r�_apply_to_userz9WordpressWafBulkEndpoints.waf_set.<locals>._apply_to_user�s������
!�#��J� �=�)�"<�=������������
�$�w����
!�
!�
!��#�a�&�&�y� � � � � � �����
!���s�$*�
A�A
�A�
Arc�&��g|]
}�|����S�rB)�.0r6r@s  �r�
<listcomp>z5WordpressWafBulkEndpoints.waf_set.<locals>.<listcomp>�s0������&'���q�!�!���r'c��g|]}|ddd���	S)�	succeeded��r4r(r5rB)rCr6s  rrDz5WordpressWafBulkEndpoints.waf_set.<locals>.<listcomp>�s2�������k�R�@�@���r'c�2�g|]}|dd|dd���S)r4�skippedr5rHrB)rC�ss  rrDz5WordpressWafBulkEndpoints.waf_set.<locals>.<listcomp>�s:�������6��i�1�X�;�O�O���r'c�2�g|]}|dd|dd���S)r4�failedr5rHrB)rC�fs  rrDz5WordpressWafBulkEndpoints.waf_set.<locals>.<listcomp>�s:�������6��h�!�H�+�N�N���r')�itemsrFrJrM)rr�SECURITY_PLUGIN_ENABLED�logger�warningr0r�HostingPanel�	get_usersr;�list�dict�fromkeysrr<�tuple�range�len�_MAX_CONCURRENT�asyncio�gather)r>r(r1r�panel_usersr=rFrJrM�valid_usersr6�i�batch�results�errrOr@r?s`               @@r�waf_setz!WordpressWafBulkEndpoints.waf_setMsL��������	��*�!�A���
��	K�U�]�!�"I�J�J�J���U��!�"=�>�>�>��0�	�!�;���
�
	���L����		
�	
�	
�	��M�$>�$@�$@�$J�$J�$L�$L�L�L�L�L�L�L�M�M�K�K���	�	�	�!�9�a�9�9����
�����	����
 "�	� �����	P��{�+�+�K�K��K��]�]�5�)�)�
P�
P����#�#��&�&�q�)�)�)�)��N�N�A�9M�#N�#N�O�O�O�O��i�'�	�		!�C�		!�E�#�s�T�z�/�,B�		!�		!�		!�		!�		!�		!�		!��q�#�k�*�*�O�<�<�		>�		>�A�����+6�q�1��;N�7N�+O����E�$�N�E�2�2�2�2�2�2�2�G�!�
>�
>���3��;��$�$�Q�'�'�'�'��M�M�1��"<�"<�=�=�=�=�	
>�

���"����

�
�� ����

�������

�� �"���	
�
�	
s�88B1�1
C�;C�Crr4r�limit�offsetc��8���K�|�|dkrtd���|dkrtd���tj��}t��\}}}		t	t
�tj���	���d{V������}
n%#t$r}td|����|�d}~wwxYw|�&|t|
��vrt|�d����|g}
|�dt���d{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 userc�0��g|]}t|�����SrB)r&)rCrrs  �rrDz8WordpressWafBulkEndpoints.waf_status.<locals>.<listcomp>�s#���D�D�D��\�#�{�3�3�D�D�Dr'c�R��g|]#}t|�����t|�����$SrB)r,r&)rCrrrr(s  ���rrDz8WordpressWafBulkEndpoints.waf_status.<locals>.<listcomp>�sE��������C���0�0���S�+�.�.���r'c��|dS)NrrB)r`s r�<lambda>z6WordpressWafBulkEndpoints.waf_status.<locals>.<lambda>�s
��Q�v�Y�r')�key)�security_plugin_enabled�
global_waf�global_waf_default�total_countrO)rr\�get_running_loopr
rUrVrWrrSrTr;r0�run_in_executorr�_SAFETY_CAP�minrZ�sortedr�sortr#r$)r>r4r(rrerf�looprm�global_waf_enabledror^r=�	page_sizerp�pagerrOrs  ``             @rr!z$WordpressWafBulkEndpoints.waf_status�s��������������!�"8�9�9�9��A�:�:�!�"9�:�:�:��'�)�)��
 �!�!�		
�#���	���
�
�M�$>�$@�$@�$J�$J�$L�$L�L�L�L�L�L�L�M�M���K�K���	�	�	�!�9�a�9�9����
�����	����
���3�{�+�+�+�+�%��&E�&E�&E�F�F�F��&�K� �0�0��.�
�
�
�
�
�
�
�
��$)�=�K�K�c�%��6M�6M�	��>�f�n�
�k�*�*�K��+�&�&�v���0B�'B�C�D��-�-��,�d���������D�E�D�D�D�t�D�D�D�E�E��-�-��,�k���������D�����������E�

�J�J�.�.�J�/�/�/��e�*�*�K��&�6�I�#5�5�6�E�(?�#5�K���;K�$6�K���;K�&��

�

�
	
s�AB)�)
C�3C�C)FN)NNNNr)�__name__�
__module__�__qualname__r�AV_IM360�SCOPErr<�boolrUrVrd�intr!rBr'rr.r.Js,�������N�E�	�T�
�e�U�+�+� �"&�	]
�]
��]
��]
��C�y�4��	]
�

�]
�]
�]
�,�+�]
�~
�T�
�e�X�.�.� �!�!� ��
L
�L
��D�j�L
��d�
�L
��d�
�	L
�
�T�z�L
��
L
�
�L
�L
�L
�/�.�L
�L
�L
r'r.)'�__doc__r\�loggingr� defence360agent.contracts.configr�defence360agent.rpc_toolsr� defence360agent.rpc_tools.lookuprr�defence360agent.subsys.panelsr�defence360agent.utilsr�defence360agent.utils.configr	� defence360agent.wordpress.pluginr
r�)defence360agent.wordpress.site_repositoryr�	getLoggerr{rQr[r#r$rsrUr<rXr�r�rrVr&r,r.rBr'r�<module>r�s��&�&���������
�
�
�
�6�6�6�6�6�6�5�5�5�5�5�5�@�@�@�@�@�@�@�@�7�7�7�7�7�7�'�'�'�'�'�'�6�6�6�6�6�6���������������
��	�8�	$�	$��������
�����9��	�%��S�4�Z��s�*�
+�,�����	�	�s�C�$�J��c�)�	*�	�9=�c�3�h��	�	�	�	�	�	��	�s�C�$�J��c�)�	*���$�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.py0000644000000000000000000001645700000000000014532 0ustar  from cerberus.errors import BaseErrorHandler, BasicErrorHandler

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,
    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):
    _validator = schema_validator(
        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)),
            (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�]��root�asu�
advisor apply�}�(�help��
(internal)��cli�}��users�]��root�asuu.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�}�(�help��6Send request to malware remediation team of imunify360��return_type��AnalystCleanupRequestResponse��type��dict��cli�}�(�users�]��root�a�require_rpc��any�u�schema�}�(�email�}�(�type��string��regex��[^@]+@[^@]+\.[^@]+$��required��u�username�}�(�type��string��required���empty��u�message�}�(�type��string��required���empty��uuu�analyst-cleanup get-requests�}�(�help��TGet analyst-cleanup requests for provided username or all if username isn't provided��return_type��!AnalystCleanupGetRequestsResponse��type��dict��cli�}�(�users�]��root�a�require_rpc��any�u�schema�}�(�username�}�(�type��string��required���empty��u�limit�}�(�type��integer��coerce��int��default�K2u�offset�}�(�type��integer��coerce��int��default�Kuuu�analyst-cleanup is-allowed�}�(�help��@Send request imunify360 API and shows is analyst-cleanup allowed��return_type��AnalystCleanupAllowedResponse��cli�}�(�users�]��root�a�require_rpc��any�uuu.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_type��TokenAgentResponse��help��Get independent agent ID token��cli�}��users�]��root�asu�auth-cloud-refresh-token�}�(�help��&Refresh the independent agent ID token��cli�}��users�]��root�asuu.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_type��WhmcsUpdateResponse��cli_only���help��1(internal) For communication with whmcs updates.
��cli�}��users�]��root�as�type��dict��schema�}��data�}�(�type��string��nullable���
positional���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"}}`
�usu�billing get-config�}�(�cli_only���help��1(internal) For communication with whmcs updates.
��cli�}��users�]��root�as�type��dict�uu.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�]��root�a�require_rpc��stopped�u�type��dict��schema�}��recreate_schema�}�(�type��boolean��default���required��usus.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_type��ConfigAgentResponse��help��k(internal) Update Imunify configuration (`/etc/sysconfig/imunify360/imunify360.config.d/90-local.config`).
��cli�}��users�]��root�as�type��dict��schema�}�(�items�}�(�type��list��schema�}��type��string�s�help��
(internal)�u�data�}�(�type��string��nullable���
positional���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}}`
�u�user�}�(�type��string��nullable���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.
�uuu�config patch�}�(�return_type��ConfigAgentResponse��help��`Update Imunify configuration (`/etc/sysconfig/imunify360/imunify360.config.d/90-local.config`).
��cli�}��users�]��root�as�type��dict��schema�}�(�data�}�(�type��dict��nullable���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}}`
�u�user�}�(�type��string��nullable���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.
�uuu�config patch-many�}�(�help��1Update Imunify configuration for multiple users.
��cli�}��users�]��root�as�type��dict��schema�}�(�data�}�(�type��dict��nullable���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}}`
�u�users�}�(�type��list��schema�}��type��string�s�nullable���help��NList of users to update the config for.
Example: `["user1", "user2", "root"]`
�uuu�config get-many�}�(�return_type��ConfigAgentResponse��help��.Get Imunify configuration for multiple users.
��cli�}��users�]��root�as�type��dict��schema�}��users�}�(�type��list��schema�}��type��string�s�nullable���help��IList of users to get the config for.
Example: `"user1", "user2", "root"`
�usu�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�as�type��dict��schema�}��user�}�(�type��string��nullable���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.
�usu�config show defaults�}�(�help�X�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�as�type��dict�uu.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�]��root�a�require_rpc��any�u�help��#Shows conflicts with other software�us.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�]��root�a�require_rpc��direct�uus.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_type��NullAgentResponse��cli�}��users�]��root�asu�	eula show�}�(�help��Get EULA��cli�}��users�]��root�asuu.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�]��root�a�require_rpc��any�u�type��dict��schema�}�(�subj�}�(�type��string��
positional��u�force�}�(�type��boolean��default��u�list�}�(�type��boolean��default��u�version�}�(�type��string��default��latest�uuus.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�]��root�as�type��dict��return_type��GetNewsAgentResponse�us.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�}�(�type��dict��return_type��ReputationAgentResponse��cli�}��users�]��root�as�help��Returns infected domain list��schema�}�(�limit�}�(�type��integer��default�K2�coerce��int��help��offset for pagination�u�offset�}�(�type��integer��default�K�coerce��int��help��limit for pagination�uuu�
check-domains�}�(�type��dict��cli�}��users�]��root�as�help��Send domain list check�uu.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�}�(�type��dict��cli�}��users�]��root�as�schema�}��event�}�(�type��string��required��usu�hook add�}�(�type��dict��cli�}��users�]��root�as�schema�}�(�event�}�(�type��string��required��u�path�}�(�type��string��required��uuu�hook delete�}�(�type��dict��cli�}��users�]��root�as�schema�}�(�event�}�(�type��string��required��u�path�}�(�type��string��required��uuu�hook add-native�}�(�type��dict��cli�}��users�]��root�as�schema�}�(�event�}�(�type��string��required��u�path�}�(�type��string��required��uuuu.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_type��ConfigAgentResponse��help��S(internal) https://docs.imunify360.com/command_line_interface/#notifications-config��cli�}��users�]��root�as�type��dict��schema�}�(�items�}�(�type��list��schema�}��type��string�su�data�}�(�type��string��nullable���
positional��uuu�notifications-config patch�}�(�return_type��NotificationConfigAgentResponse��help��S(internal) https://docs.imunify360.com/command_line_interface/#notifications-config��cli�}��users�]��root�as�type��dict��schema�}��data�}�(�type��dict��nullable��usu�notifications-config show�}�(�return_type��NotificationConfigAgentResponse��help��S(internal) https://docs.imunify360.com/command_line_interface/#notifications-config��cli�}��users�]��root�as�type��dict�uu.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�}�(�help��4(internal) Enable hosting panel plugin (if detected)��type��dict��cli�}�(�users�]��root�a�require_rpc��direct�u�schema�}��plugin_name�}�(�type��string��nullable��usu�disable-plugin�}�(�help��'(internal) Disable hosting panel plugin��type��dict��cli�}�(�users�]��root�a�require_rpc��direct�u�schema�}��plugin_name�}�(�type��string��nullable��usu�add-sudouser�}�(�help��
(internal)��type��dict��cli�}�(�users�]��root�a�require_rpc��direct�u�schema�}��user�}�(�type��string��required��usu�delete-sudouser�}�(�help��
(internal)��type��dict��cli�}�(�users�]��root�a�require_rpc��direct�u�schema�}��user�}�(�type��string��required��usu�
list-docroots�}�(�help��'(internal) Get docroots for all domains��cli�}�(�users�]��root�a�require_rpc��any�uuu.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�}�(�help��aUses PAM to check the provided credential and returns a token for USERNAME if PASSWORD is correct��cli�}��users�]��root�as�schema�}�(�username�}�(�type��string��required���empty��u�password�}�(�type��string��required���empty���envvar��PASSWORD�uu�return_type��TokenAgentResponse�u�	login get�}�(�help��8Returns a token for USERNAME (must be executed by admin)��cli�}��users�]��root�as�schema�}��username�}�(�type��string��required���empty��us�return_type��TokenAgentResponse�uu.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_type��GetPackageVersionsAgentResponse��help��
(internal)��cli�}��users�]��root�asus.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)��type��dict��cli�}��users�]��root�as�schema�}��user�}�(�type��string��required��usus.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�]��root�as�help��)Return stats, required by plesk extension�us.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�]��root�a�require_rpc��any�u�help��Get registration status��schema�}��paid�}�(�type��boolean��default��usu�register�}�(�return_type��NoItemsAndEulaAgentResponse��cli�}�(�users�]��root�a�require_rpc��any�u�type��dict��help��Register the agent��schema�}��regkey�}�(�envvar��REG_KEY��type��string��default��IPL��isascii���
positional���help��8Registration key or 'IPL' word (if you registered by IP)�usu�
unregister�}�(�cli�}�(�users�]��root�a�require_rpc��any�u�help��Unregister the agent�u�update-license�}�(�cli�}�(�users�]��root�a�require_rpc��any�u�help��Force update license�uu.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 imunify360��type��dict��cli�}�(�users�]��root�a�require_rpc��any�u�schema�}�(�email�}�(�type��string��regex��[^@]+@[^@]+\.[^@]+$��required��u�subject�}�(�type��string��required���empty��u�description�}�(�type��string��required���empty��u�cln�}��type��string�s�attachments�}�(�type��list��schema�}�(�type��string��default�]��is_absolute_path��uuuus.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�}�(�help��Get Imunify Agent version��cli�}��users�]��root�asu�wakeup�}�(�help��Wake up Imunify Agent��cli�}��users�]��root�asuu.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�}�(�help��EInstall Imunify Security plugin for WordPress on new WordPress sites.��cli�}��users�]��root�asu�wordpress-plugin tidy-up�}�(�help��`Tidy-up on WordPress sites where the Imunify Security plugin for WordPress was manually removed.��cli�}��users�]��root�asu�wordpress-plugin update�}�(�help��xUpdates Imunify Security plugin for WordPress to the latest version on all WordPress sites where it's already installed.��cli�}��users�]��root�asu�#wordpress-plugin install-and-update�}�(�help���Install 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�]��root�asu�wordpress-plugin list-incidents�}�(�help��List WordPress incidents��type��dict��return_type��#WordpressIncidentsListAgentResponse��cli�}��users�]��root�as�schema�}�(�user�}�(�type��string��nullable��u�site_search�}�(�type��string��nullable���help��Filter by site path
�u�by_abuser_ip�}�(�type��string��nullable���help��Filter by abuser IP address
�u�by_country_code�}�(�type��string��nullable���help��Filter by country code
�u�	by_domain�}�(�type��string��nullable���help��Filter by domain
�u�search�}�(�type��string��nullable���help��(Search by IP address, name, description
�u�since�}�(�type��integer��coerce��int��nullable���
check_with�]��	timestamp�a�help��5Show incidents after this unix timestamp (inclusive)
�u�to�}�(�type��integer��coerce��int��nullable���
check_with�]��	timestamp�a�help��6Show incidents before this unix timestamp (inclusive)
�u�include_hidden�}�(�type��boolean��default���help��xInclude incidents whose rule has the internal TEST- prefix
(hidden from the WordPress plugin admin UI). Default: false.
�u�order_by�}�(�type��list��nullable���schema�}�(�type��order_by��coerce��order_by�u�help���List 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.
�u�limit�}�(�type��integer��coerce��int��default�K2u�offset�}�(�type��integer��coerce��int��default�Kuuu�wordpress-plugin list-sites�}�(�help��<List WordPress sites with Imunify Security plugin installed.��cli�}��users�]��root�as�type��dict��return_type��WordpressDomainsResponse��schema�}�(�user�}�(�type��string��nullable���help��RAdmins can filter results by user.
Users can only see the sites relevant to them.
�u�limit�}�(�type��integer��default�K2�coerce��int��help��"Maximum number of items to return.�u�offset�}�(�type��integer��default�K�coerce��int��help��Number of items to skip.�uuuu.defence360agent/simple_rpc/schema/wordpress.yaml0000644000000000000000000000620700000000000017064 0ustar  wordpress-plugin install-on-new-sites :
  help: Install Imunify Security plugin for WordPress on new WordPress sites.
  cli:
    users:
      - root

wordpress-plugin tidy-up :
  help: Tidy-up on WordPress sites where the Imunify Security plugin for WordPress was manually removed.
  cli:
    users:
      - root

wordpress-plugin update :
  help: Updates Imunify Security plugin for WordPress to the latest version on all WordPress sites where it's already installed.
  cli:
    users:
      - root

wordpress-plugin install-and-update :
  help: Install 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:
      - root

wordpress-plugin list-incidents:
  help: "List WordPress incidents"
  type: dict
  return_type: WordpressIncidentsListAgentResponse
  cli:
    users:
      - root
  schema:
    user:
      type: string
      nullable: true
    site_search:
      type: string
      nullable: true
      help: |
        Filter by site path
    by_abuser_ip:
      type: string
      nullable: true
      help: |
        Filter by abuser IP address
    by_country_code:
      type: string
      nullable: true
      help: |
        Filter by country code
    by_domain:
      type: string
      nullable: true
      help: |
        Filter by domain
    search:
      type: string
      nullable: true
      help: |
        Search by IP address, name, description
    since:
      type: integer
      coerce: int
      nullable: true
      check_with:
        - timestamp
      help: |
        Show incidents after this unix timestamp (inclusive)
    to:
      type: integer
      coerce: int
      nullable: true
      check_with:
        - timestamp
      help: |
        Show incidents before this unix timestamp (inclusive)
    include_hidden:
      type: boolean
      default: false
      help: |
        Include incidents whose rule has the internal TEST- prefix
        (hidden from the WordPress plugin admin UI). Default: false.
    order_by:
      type: list
      nullable: true
      schema:
        type: order_by
        coerce: order_by
      help: |
        List 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.
    limit:
      type: integer
      coerce: int
      default: 50
    offset:
      type: integer
      coerce: int
      default: 0


wordpress-plugin list-sites:
  help: List WordPress sites with Imunify Security plugin installed.
  cli:
    users:
      - root
  type: dict
  return_type: WordpressDomainsResponse
  schema:
    user:
      type: string
      nullable: true
      help: |
        Admins can filter results by user.
        Users can only see the sites relevant to them.
    limit:
      type: integer
      default: 50
      coerce: int
      help: Maximum number of items to return.
    offset:
      type: integer
      default: 0
      coerce: int
      help: Number of items to skip.
defence360agent/simple_rpc/schema/wp-disabled-rules.pickle0000644000000000000000000000277700000000000020674 0ustar  ���}�(�$wordpress-plugin rules list-disabled�}�(�help��7List disabled WordPress protection rules with metadata.��type��dict��cli�}��users�]��root�as�schema�}�(�limit�}�(�type��integer��coerce��int��default�K2�help��"Maximum number of rules to return.�u�offset�}�(�type��integer��coerce��int��default�K�help��Number of rules to skip.�u�domains�}�(�type��list��nullable���schema�}��type��string�s�help��Filter by specific domains.�u�user�}�(�type��string��nullable���help��HFilter rules visible to this user (shows only rules for user's domains).�uuu�wordpress-plugin rules disable�}�(�help��EDisable a WordPress protection rule globally or for specific domains.��type��dict��cli�}��users�]��root�as�schema�}�(�rule�}�(�type��string��required���help��,The rule ID to disable (e.g., CVE-2025-001).�u�domains�}�(�type��list��nullable���schema�}��type��string�s�help��GList of domains to disable the rule for. If omitted, disables globally.�u�user�}�(�type��string��nullable���help��
(internal)�uuu�wordpress-plugin rules enable�}�(�help��GRe-enable a WordPress protection rule globally or for specific domains.��type��dict��cli�}��users�]��root�as�schema�}�(�rule�}�(�type��string��required���help��+The rule ID to enable (e.g., CVE-2025-001).�u�domains�}�(�type��list��nullable���schema�}��type��string�s�help��EList of domains to enable the rule for. If omitted, enables globally.�u�user�}�(�type��string��nullable���help��
(internal)�uuuu.defence360agent/simple_rpc/schema/wp-disabled-rules.yaml0000644000000000000000000000317700000000000020362 0ustar  wordpress-plugin rules list-disabled:
  help: List disabled WordPress protection rules with metadata.
  type: dict
  cli:
    users:
      - root
  schema:
    limit:
      type: integer
      coerce: int
      default: 50
      help: Maximum number of rules to return.
    offset:
      type: integer
      coerce: int
      default: 0
      help: Number of rules to skip.
    domains:
      type: list
      nullable: true
      schema:
        type: string
      help: Filter by specific domains.
    user:
      type: string
      nullable: true
      help: Filter rules visible to this user (shows only rules for user's domains).

wordpress-plugin rules disable:
  help: Disable a WordPress protection rule globally or for specific domains.
  type: dict
  cli:
    users:
      - root
  schema:
    rule:
      type: string
      required: true
      help: The rule ID to disable (e.g., CVE-2025-001).
    domains:
      type: list
      nullable: true
      schema:
        type: string
      help: List of domains to disable the rule for. If omitted, disables globally.
    user:
      type: string
      nullable: true
      help: (internal)

wordpress-plugin rules enable:
  help: Re-enable a WordPress protection rule globally or for specific domains.
  type: dict
  cli:
    users:
      - root
  schema:
    rule:
      type: string
      required: true
      help: The rule ID to enable (e.g., CVE-2025-001).
    domains:
      type: list
      nullable: true
      schema:
        type: string
      help: List of domains to enable the rule for. If omitted, enables globally.
    user:
      type: string
      nullable: true
      help: (internal)
defence360agent/simple_rpc/schema/wp-waf.pickle0000644000000000000000000000312400000000000016535 0ustar  ��I}�(�wordpress-plugin waf set�}�(�help���Bulk enable or disable WAF for hosting users.

Examples:
  imunify360-agent wordpress-plugin waf set --status enabled --all-users
  imunify360-agent wordpress-plugin waf set --status disabled --users alice bob carol
��type��dict��cli�}��users�]��root�as�schema�}�(�status�}�(�type��string��required���allowed�]�(�enabled��disabled�e�help��(Target WAF state for the selected users.�u�	all_users�}�(�type��boolean��default���help��*Apply to every hosting user on the server.�u�users�}�(�type��list��nullable���schema�}��type��string�s�help��PApply to a space-separated list of hosting users (e.g. --users alice bob carol).�uuu�wordpress-plugin waf status�}�(�help�XReport effective WAF status and its source for every hosting account.

Examples:
  imunify360-agent wordpress-plugin waf status
  imunify360-agent wordpress-plugin waf status --status disabled --json
  imunify360-agent wordpress-plugin waf status --user alice
��type��dict��cli�}��users�]��root�as�schema�}�(�user�}�(�type��string��nullable���help��Show only this hosting account.�u�status�}�(�type��string��nullable���allowed�]�(�enabled��disabled�e�help��Filter by effective WAF status.�u�source�}�(�type��string��nullable���allowed�]�(�default��override�e�help��:Filter by status source (default or per-account override).�u�limit�}�(�type��integer��coerce��int��nullable���help��5Maximum number of accounts to return (capped at 500).�u�offset�}�(�type��integer��coerce��int��default�K�help��Number of accounts to skip.�uuuu.defence360agent/simple_rpc/schema/wp-waf.yaml0000644000000000000000000000332000000000000016226 0ustar  wordpress-plugin waf set:
  help: |
    Bulk enable or disable WAF for hosting users.

    Examples:
      imunify360-agent wordpress-plugin waf set --status enabled --all-users
      imunify360-agent wordpress-plugin waf set --status disabled --users alice bob carol
  type: dict
  cli:
    users:
      - root
  schema:
    status:
      type: string
      required: true
      allowed: [enabled, disabled]
      help: Target WAF state for the selected users.
    all_users:
      type: boolean
      default: false
      help: Apply to every hosting user on the server.
    users:
      type: list
      nullable: true
      schema: {type: string}
      help: Apply to a space-separated list of hosting users (e.g. --users alice bob carol).

wordpress-plugin waf status:
  help: |
    Report effective WAF status and its source for every hosting account.

    Examples:
      imunify360-agent wordpress-plugin waf status
      imunify360-agent wordpress-plugin waf status --status disabled --json
      imunify360-agent wordpress-plugin waf status --user alice
  type: dict
  cli:
    users:
      - root
  schema:
    user:
      type: string
      nullable: true
      help: Show only this hosting account.
    status:
      type: string
      nullable: true
      allowed: [enabled, disabled]
      help: Filter by effective WAF status.
    source:
      type: string
      nullable: true
      allowed: [default, override]
      help: Filter by status source (default or per-account override).
    limit:
      type: integer
      coerce: int
      nullable: true
      help: Maximum number of accounts to return (capped at 500).
    offset:
      type: integer
      coerce: int
      default: 0
      help: Number of accounts to skip.
defence360agent/simple_rpc/schema_responses/0000755000000000000000000000000000000000000016244 5ustar  defence360agent/simple_rpc/schema_responses/AnalystCleanupAllowedResponse.json0000644000000000000000000000710400000000000025113 0ustar  {"type":"object","properties":{"data":{"$ref":"#/definitions/AgentResponseData<IAnalystCleanupAllowedResult>"},"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<IAnalystCleanupAllowedResult>":{"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<IAnalystCleanupGetRequestsItem>"},"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<IAnalystCleanupGetRequestsItem>":{"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<IAnalystCleanupRequestResult>"},"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<IAnalystCleanupRequestResult>":{"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<I360ConfigDataItems>"},"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<I360ConfigDataItems>":{"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<FeaturesStatus>"},"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<FeaturesStatus>":{"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<ClientFeatures>"},"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<ClientFeatures>":{"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<NativeFeaturesManagementStatus>"},"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<NativeFeaturesManagementStatus>":{"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<FeaturesManagementResponseItem>"},"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<FeaturesManagementResponseItem>":{"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<NewsItem[]>"},"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<NewsItem[]>":{"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<PackageVersions|null>"},"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<PackageVersions|null>":{"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<NotificationConfigType>"},"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<NotificationConfigType>":{"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<null>"},"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<null>":{"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<ReputationBackendItem>"},"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<ReputationBackendItem>":{"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<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"],"definitions":{"AgentResponseData<string>":{"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<IUserDomain>"},"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<IUserDomain>":{"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<IWordpressIncident>"},"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<IWordpressIncident>":{"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<ICountry>"},{"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<ICountry>":{"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  �

s�����s���dS)N�r��T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/__init__.py�<module>rs���rdefence360agent/subsys/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000030100000000000020541 0ustar  �

s�����s���dS)N�r��T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/__init__.py�<module>rs���rdefence360agent/subsys/__pycache__/ainotify.cpython-311.opt-1.pyc0000644000000000000000000002752600000000000021605 0ustar  �

��"bc�8w����ddlmZddlZddlZddlZddlZddlZddlZddlZddl	m
Z
edd��Zeje
��ZGd�d��ZGd�d	��ZdS)
�)�
namedtupleN)�sysctl�Event)�path�flags�cookie�name�wdc��eZdZdZdZdZdZdZdZdZ	dZ
d	Zd
ZdZ
dZd
ZdZdZdZdZdZdZdZdZdZd�ej��dkrdnd��Zejed���Ze j!d��Z"e#d���Z$e#d���Z%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
    ������ �@��i�ii i@i�iiii i@lzlibc.{}�Darwinzso.6�dylibT)�	use_errno�iIIIc��ttj|��|�}|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�_libc�ctypes�	get_errno�OSError�os�strerror)�method�args�ret�errnos    �T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/ainotify.py�_callz
Inotify._call4sR��-�g�g�m�V�,�,�d�3���"�9�9��$�&�&�E��%���U�!3�!3�4�4�4��
�c�6�t�d��S)z�
        Initialize an inotify instance.
        See `man inotify_init` for details
        :return: a file descriptor of new inotify instance
        �inotify_init�rr)�r*r(�initzInotify.initCs���}�}�^�,�,�,r*c�<�t�d|||��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-)�fdr�masks   r(�	add_watchzInotify.add_watchLs���}�}�0�"�d�D�A�A�Ar*c�:�t�d||��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���}�}�/��R�8�8�8r*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_prefix�unpack��datas r(�
unpack_prefixzInotify.unpack_prefixds���#�*�*�4�0�0�0r*c�~�tjdt|��z|��d�d��S)z�
        Unpack name field of inotify event struct
        See `man inotify` for details
        :param data: struct bytestring
        :return: name string
        z%dsr�)�structr:�len�rstripr;s r(�unpack_namezInotify.unpack_namens4���}�U�S��Y�Y�.��5�5�a�8�?�?��H�H�Hr*N)*�__name__�
__module__�__qualname__�__doc__�ACCESS�MODIFY�ATTRIB�CLOSE_WRITE�
CLOSE_NOWRITE�OPEN�
MOVED_FROM�MOVED_TO�CREATE�DELETE�DELETE_SELF�	MOVE_SELF�UNMOUNT�
Q_OVERFLOW�IGNORED�ONLYDIR�DONT_FOLLOW�EXCL_UNLINK�MASK_ADD�ISDIR�ONESHOT�format�platform�system�_nr�CDLLrr@�Structr9�staticmethodr)r/r4r7r=rCr.r*r(rrs����������F�
�F�
�F��K��M��D��J��H�
�F�
�F��K��I��G��J��G��G��K��K��H��E��G�	�	�	�O�H�O�$5�$5��$A�$A�&�&�w�	O�	O�B��F�K��d�+�+�+�E� �6�=��(�(�L�����\���-�-��\�-��B�B��\�B��9�9��\�9��1�1��\�1��I�I��\�I�I�Ir*rc�Z�eZdZdZdZdZdZdZdd�Zd�Z	d	�Z
d
�Zd�Zd�Z
d
�Zd�Zd�ZdS)�Watcherz1
    Asynchronous watcher for inotify events
    r�g�?zfs.inotify.max_user_watchesNc��||_t���|_t	j��|_|p|jj|_|j�	|j|j
��|���dS�N)�_looprr/�_fd�asyncio�Queue�_queue�put�	_callback�
add_reader�_read�_reset_state)�self�loop�
coro_callbacks   r(�__init__zWatcher.__init__�si����
��<�<�>�>����m�o�o���&�9�$�+�/����
���d�h��
�3�3�3��������r*c�0�i|_i|_d|_dS)Nr*)�paths�descriptors�buf�rss r(rrzWatcher._reset_state�s����
��������r*c�F�|xjtj|j|j��z
c_t
jj}t|j��|k�rIt
�	|jd|���\}}}}||z}t
�
|j||���}|j|d�|_||jvr��|j|}|t
jzr1t�d|��|�|����|t
jzrt�d����t%|||||��}	|j�|�|	����t|j��|k��GdSdS)Nz(Got IGNORED event for %s, cleaning watchzInotify queue overflow)rzr"�readrj�_CHUNK_SIZErr9�sizerAr=rCrxrV�logger�warning�_cleanup_watchrU�errorrri�create_taskro)
rs�struct_sizer
rr�length�
struct_endr	r�evs
          r(rqz
Watcher._read�s������B�G�D�H�d�&6�7�7�7����*�/���$�(�m�m�{�*�*�(/�(=�(=����+��&�)�)�%�B��v�v�%�v�-�J��&�&�t�x��J�0F�'G�H�H�D��x�
���,�D�H����#�#���:�b�>�D��w��&�
����>������#�#�D�)�)�)���w�)�)�
����5�6�6�6���t�U�F�D�"�5�5�B��J�"�"�4�>�>�"�#5�#5�6�6�6�/�$�(�m�m�{�*�*�*�*�*�*r*c���tj|j��}|t||jz��z}t
�d|j|��tj|j|��dS)NzRaising %s to %s)rr}�_MAX_USER_WATCHES�int�_WATCHERS_RAISE_COEFFr��info�write)rs�current_max_watches�new_max_watcherss   r(�_raise_user_watcheszWatcher._raise_user_watches�sx��$�k�$�*@�A�A��.���$�"<�<�2
�2
�
��	����� 6�8H�	
�	
�	
�	��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(r�z
Watcher.close�st��
	
�
� � ���*�*�*�	��H�T�X�����������D�H�H�H��
�������D�H�O�O�O�Os�A�A4c��t|t��s
Jd���t�d|��d}		t�|j||��}||j|<||j|<dS#t$rz}||j
krN|jtjkr9|�
��|dz
}t�d|��Yd}~��t�d|���d}~wwxYw)	z�
        Add file to watch
        :param path: file or directory to watch
        :param mask: events mask for this watch
        zPath must be byteszWatching %rrTr
z-Inotify: not enough watches (%r), retrying...Nz Inotify failed while watching %r)�
isinstance�bytesr�r�rr4rjrxryr!�_MAX_WATCH_RETRIESr'�ENOSPCr�r�r�)rsrr3�retriesr
�es      r(�watchz
Watcher.watch�s���$��&�&�<�<�(<�<�<�&����M�4�(�(�(���	�
��&�&�t�x��t�<�<��!%��
�2��)+�� ��&�����
�
�
��d�5�5�5���5�<�/�/��,�,�.�.�.��q�L�G��N�N�G������H�H�H�H����?��F�F�F������
���s�5A6�6
C:�AC5�C5�5C:c�z�|j�|d��}|�|j�|d��dSdSrh)ry�poprx)rsr�
descriptors   r(r�zWatcher._cleanup_watch�sD���%�)�)�$��5�5�
��!��J�N�N�:�t�,�,�,�,�,�"�!r*c��||jvrdSt�d|��	t�|j|j|��|�|��dS#|�|��wxYw)zq
        Remove file or directory from watch
        :param path: file or directory to remove watch from
        NzStop watching %r)ryr�r�rr7rjr�)rsrs  r(�unwatchzWatcher.unwatch�s���
�t�'�'�'��F����&��-�-�-�	&����T�X�t�'7��'=�>�>�>�����%�%�%�%�%��D����%�%�%�%���s�+A*�*Bc��~K�|j����d{V��}t�d|��|S)zF
        Get watch event
        :return: `Event` named tuple
        NzInotify event: %s)rm�getr��debug)rs�events  r(�	get_eventzWatcher.get_event�sE����
�k�o�o�'�'�'�'�'�'�'�'�����(�%�0�0�0��r*rh)rDrErFrGr~r�r�r�rvrrrqr�r�r�r�r�r�r.r*r(rereys����������K�����5���������
7�7�7�:?�?�?�
�
�
����:-�-�-�
&�&�&�����r*re)�collectionsrrkrr'�loggingr"r@r^�defence360agent.subsysrr�	getLoggerrDr�rrer.r*r(�<module>r�s��"�"�"�"�"�"�����
�
�
�
���������	�	�	�	�
�
�
�
�����)�)�)�)�)�)��
�7�E�F�F��
��	�8�	$�	$��dI�dI�dI�dI�dI�dI�dI�dI�N@�@�@�@�@�@�@�@�@�@r*defence360agent/subsys/__pycache__/ainotify.cpython-311.pyc0000644000000000000000000002752600000000000020646 0ustar  �

��"bc�8w����ddlmZddlZddlZddlZddlZddlZddlZddlZddl	m
Z
edd��Zeje
��ZGd�d��ZGd�d	��ZdS)
�)�
namedtupleN)�sysctl�Event)�path�flags�cookie�name�wdc��eZdZdZdZdZdZdZdZdZ	dZ
d	Zd
ZdZ
dZd
ZdZdZdZdZdZdZdZdZdZd�ej��dkrdnd��Zejed���Ze j!d��Z"e#d���Z$e#d���Z%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
    ������ �@��i�ii i@i�iiii i@lzlibc.{}�Darwinzso.6�dylibT)�	use_errno�iIIIc��ttj|��|�}|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�_libc�ctypes�	get_errno�OSError�os�strerror)�method�args�ret�errnos    �T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/ainotify.py�_callz
Inotify._call4sR��-�g�g�m�V�,�,�d�3���"�9�9��$�&�&�E��%���U�!3�!3�4�4�4��
�c�6�t�d��S)z�
        Initialize an inotify instance.
        See `man inotify_init` for details
        :return: a file descriptor of new inotify instance
        �inotify_init�rr)�r*r(�initzInotify.initCs���}�}�^�,�,�,r*c�<�t�d|||��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-)�fdr�masks   r(�	add_watchzInotify.add_watchLs���}�}�0�"�d�D�A�A�Ar*c�:�t�d||��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���}�}�/��R�8�8�8r*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_prefix�unpack��datas r(�
unpack_prefixzInotify.unpack_prefixds���#�*�*�4�0�0�0r*c�~�tjdt|��z|��d�d��S)z�
        Unpack name field of inotify event struct
        See `man inotify` for details
        :param data: struct bytestring
        :return: name string
        z%dsr�)�structr:�len�rstripr;s r(�unpack_namezInotify.unpack_namens4���}�U�S��Y�Y�.��5�5�a�8�?�?��H�H�Hr*N)*�__name__�
__module__�__qualname__�__doc__�ACCESS�MODIFY�ATTRIB�CLOSE_WRITE�
CLOSE_NOWRITE�OPEN�
MOVED_FROM�MOVED_TO�CREATE�DELETE�DELETE_SELF�	MOVE_SELF�UNMOUNT�
Q_OVERFLOW�IGNORED�ONLYDIR�DONT_FOLLOW�EXCL_UNLINK�MASK_ADD�ISDIR�ONESHOT�format�platform�system�_nr�CDLLrr@�Structr9�staticmethodr)r/r4r7r=rCr.r*r(rrs����������F�
�F�
�F��K��M��D��J��H�
�F�
�F��K��I��G��J��G��G��K��K��H��E��G�	�	�	�O�H�O�$5�$5��$A�$A�&�&�w�	O�	O�B��F�K��d�+�+�+�E� �6�=��(�(�L�����\���-�-��\�-��B�B��\�B��9�9��\�9��1�1��\�1��I�I��\�I�I�Ir*rc�Z�eZdZdZdZdZdZdZdd�Zd�Z	d	�Z
d
�Zd�Zd�Z
d
�Zd�Zd�ZdS)�Watcherz1
    Asynchronous watcher for inotify events
    r�g�?zfs.inotify.max_user_watchesNc��||_t���|_t	j��|_|p|jj|_|j�	|j|j
��|���dS�N)�_looprr/�_fd�asyncio�Queue�_queue�put�	_callback�
add_reader�_read�_reset_state)�self�loop�
coro_callbacks   r(�__init__zWatcher.__init__�si����
��<�<�>�>����m�o�o���&�9�$�+�/����
���d�h��
�3�3�3��������r*c�0�i|_i|_d|_dS)Nr*)�paths�descriptors�buf�rss r(rrzWatcher._reset_state�s����
��������r*c�F�|xjtj|j|j��z
c_t
jj}t|j��|k�rIt
�	|jd|���\}}}}||z}t
�
|j||���}|j|d�|_||jvr��|j|}|t
jzr1t�d|��|�|����|t
jzrt�d����t%|||||��}	|j�|�|	����t|j��|k��GdSdS)Nz(Got IGNORED event for %s, cleaning watchzInotify queue overflow)rzr"�readrj�_CHUNK_SIZErr9�sizerAr=rCrxrV�logger�warning�_cleanup_watchrU�errorrri�create_taskro)
rs�struct_sizer
rr�length�
struct_endr	r�evs
          r(rqz
Watcher._read�s������B�G�D�H�d�&6�7�7�7����*�/���$�(�m�m�{�*�*�(/�(=�(=����+��&�)�)�%�B��v�v�%�v�-�J��&�&�t�x��J�0F�'G�H�H�D��x�
���,�D�H����#�#���:�b�>�D��w��&�
����>������#�#�D�)�)�)���w�)�)�
����5�6�6�6���t�U�F�D�"�5�5�B��J�"�"�4�>�>�"�#5�#5�6�6�6�/�$�(�m�m�{�*�*�*�*�*�*r*c���tj|j��}|t||jz��z}t
�d|j|��tj|j|��dS)NzRaising %s to %s)rr}�_MAX_USER_WATCHES�int�_WATCHERS_RAISE_COEFFr��info�write)rs�current_max_watches�new_max_watcherss   r(�_raise_user_watcheszWatcher._raise_user_watches�sx��$�k�$�*@�A�A��.���$�"<�<�2
�2
�
��	����� 6�8H�	
�	
�	
�	��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(r�z
Watcher.close�st��
	
�
� � ���*�*�*�	��H�T�X�����������D�H�H�H��
�������D�H�O�O�O�Os�A�A4c��t|t��s
Jd���t�d|��d}		t�|j||��}||j|<||j|<dS#t$rz}||j
krN|jtjkr9|�
��|dz
}t�d|��Yd}~��t�d|���d}~wwxYw)	z�
        Add file to watch
        :param path: file or directory to watch
        :param mask: events mask for this watch
        zPath must be byteszWatching %rrTr
z-Inotify: not enough watches (%r), retrying...Nz Inotify failed while watching %r)�
isinstance�bytesr�r�rr4rjrxryr!�_MAX_WATCH_RETRIESr'�ENOSPCr�r�r�)rsrr3�retriesr
�es      r(�watchz
Watcher.watch�s���$��&�&�<�<�(<�<�<�&����M�4�(�(�(���	�
��&�&�t�x��t�<�<��!%��
�2��)+�� ��&�����
�
�
��d�5�5�5���5�<�/�/��,�,�.�.�.��q�L�G��N�N�G������H�H�H�H����?��F�F�F������
���s�5A6�6
C:�AC5�C5�5C:c�z�|j�|d��}|�|j�|d��dSdSrh)ry�poprx)rsr�
descriptors   r(r�zWatcher._cleanup_watch�sD���%�)�)�$��5�5�
��!��J�N�N�:�t�,�,�,�,�,�"�!r*c��||jvrdSt�d|��	t�|j|j|��|�|��dS#|�|��wxYw)zq
        Remove file or directory from watch
        :param path: file or directory to remove watch from
        NzStop watching %r)ryr�r�rr7rjr�)rsrs  r(�unwatchzWatcher.unwatch�s���
�t�'�'�'��F����&��-�-�-�	&����T�X�t�'7��'=�>�>�>�����%�%�%�%�%��D����%�%�%�%���s�+A*�*Bc��~K�|j����d{V��}t�d|��|S)zF
        Get watch event
        :return: `Event` named tuple
        NzInotify event: %s)rm�getr��debug)rs�events  r(�	get_eventzWatcher.get_event�sE����
�k�o�o�'�'�'�'�'�'�'�'�����(�%�0�0�0��r*rh)rDrErFrGr~r�r�r�rvrrrqr�r�r�r�r�r�r.r*r(rereys����������K�����5���������
7�7�7�:?�?�?�
�
�
����:-�-�-�
&�&�&�����r*re)�collectionsrrkrr'�loggingr"r@r^�defence360agent.subsysrr�	getLoggerrDr�rrer.r*r(�<module>r�s��"�"�"�"�"�"�����
�
�
�
���������	�	�	�	�
�
�
�
�����)�)�)�)�)�)��
�7�E�F�F��
��	�8�	$�	$��dI�dI�dI�dI�dI�dI�dI�dI�N@�@�@�@�@�@�@�@�@�@r*defence360agent/subsys/__pycache__/backup_systems.cpython-311.opt-1.pyc0000644000000000000000000006204000000000000023005 0ustar  �

�'0T�sY����ddlZddlZddlZddlmZddlmZmZmZm	Z	ddl
mZmZm
ZmZmZmZmZmZmZmZmZmZmZmZddlmZddlmZmZddlm Z ddl!m"Z"dd	l#m$Z$esdd
l%m&Z&ddl'm(Z(ddl)m*Z*m+Z+ej,e-��Z.d
�Z/dee0fd�Z1		d.dee0effd�Z2de	e0fd�Z3de	e4fd�Z5d�Z6Gd�de7��Z8Gd�d��Z9Gd�de9��Z:Gd�de9��Z;Gd�de9��Z<Gd �d!e9��Z=Gd"�d#e9��Z>Gd$�d%e9��Z?Gd&�d'e9��Z@Gd(�d)e@��ZAGd*�d+eA��ZBGd,�d-eA��ZCdS)/�N)�timezone)�Callable�Dict�List�Optional)�ACRONIS�ANTIVIRUS_MODE�
AcronisBackup�BackupConfig�
BackupRestore�
CLOUDLINUX�CLOUDLINUX_ON_PREMISE�
CLUSTERLOGICS�CPANEL�Core�DIRECTADMIN�PLESK�R1SOFT�SAMPLE_BACKEND)�
LicenseCLN)�BackupNotFound�RestCLN)�cPanel)�DirectAdmin)�Plesk)�backup_backends)�BackupFailed)�BackendNonApplicableError�BackendNotAuthorizedErrorc��	td���|��S#ttf$r#td�|�����wxYw)NT)�include_samplez"Backup system is not available: {})�_get_avalible_backends�KeyErrorr�
ValueError�format)�names �Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/backup_systems.py�get_backendr((si��L�@�%�T�:�:�:�4�@�B�B�B���/�0�L�L�L��=�D�D�T�J�J�K�K�K�L���s	� �4A�returnc��g}td������D]5\}}	|��|�|���&#t$rY�2wxYw|S)NF)�
include_cl)r"�items�appendr)�namesr&�clss   r'�get_available_backends_namesr0/s����E�+�u�=�=�=�C�C�E�E���	��c�	��C�E�E�E�
�L�L�������)�	�	�	��D�	����
�Ls�
A�
A�AFTc��tttti}tjr|rt|t<tjrt|t<tj��rt|t<nEtj��rt |t"<n"t%j��rt&|t(<|rt*|t,<|S�N)r�Acronisr�R1Softr�CL_BACKUP_ALLOWED�
CloudLinuxr
�CL_ON_PREMISE_BACKUP_ALLOWED�CloudLinuxOnPremiserr�is_installed�cPanelBackuprr�PleskBackuprr�DirectAdminBackupr�Sampler)r!r+�backendss   r'r"r"=s���
	�����H��&�*�:�*�)�����1�>�*=��&�'�
����2�'�����	�	�	�	�2�%�����	�	!�	#�	#�2� 1�����*�#)��� ��O�c��t������di��}|�d��o|�d��S�N�
BACKUP_SYSTEM�enabled�
backup_system)r�config_to_dict�get)�confs r'�get_current_backendrHWsJ���>�>�(�(�*�*�.�.���C�C�D��8�8�I���<�4�8�8�O�#<�#<�<r?c��|K�t��}|sdSt|��}|����d{V��Sr2)rHr(�get_last_backup_timestamp)�backend�backend_instances  r'rJrJ\sP����!�#�#�G����t�"�7�+�+��!�;�;�=�=�=�=�=�=�=�=�=r?c����fd�}|S)Nc���K�d}	�|g|�Ri|���d{V��}d}|�|���n#|�|���wxYw|S)NFT�rC��_update_backups_config)r/�args�kwargs�ok�rv�fs     �r'�wrapperztransactional.<locals>.wrapperfs������
��	3��q��.�t�.�.�.�v�.�.�.�.�.�.�.�.�B��B��&�&�r�&�2�2�2�2��C�&�&�r�&�2�2�2�2�����	s	�2�A
�)rVrWs` r'�
transactionalrYes#���������Nr?c��eZdZdS)�BackupExceptionN)�__name__�
__module__�__qualname__rXr?r'r[r[rs�������Dr?r[c�`�eZdZd
d�Zd�Zd�Zdd�Zd�Zd�Zd	�Z	d
e
fd�Zd
ee
fd�ZdS)�BackupSystemNc�"�||_||_dSr2)r&�log_path)�selfr&rbs   r'�__init__zBackupSystem.__init__ws����	� ��
�
�
r?c�n�d||r|jndd�i}t���|dd���dS)NrB)rCrDT)�	overwrite�validate)r&r�dict_to_config)rcrC�new_confs   r'rQz#BackupSystem._update_backups_config{sN���"�.5�!?����4���
��	���%�%�h�$��%�N�N�N�N�Nr?c��6K�|�d���dS)NTrOrP)rcrRrSs   r'�initzBackupSystem.init�s#�����#�#�D�#�1�1�1�1�1r?Fc��6K�|�d���dS)NFrOrP)rc�delete_backupss  r'�disablezBackupSystem.disable�s#�����#�#�E�#�2�2�2�2�2r?c��
K�iSr2rX�rcs r'�checkzBackupSystem.check�������	r?c��
K�iSr2rXrps r'�showzBackupSystem.show�rrr?c��
K�dSr2rXrps r'�make_backupzBackupSystem.make_backup�s�����r?r)c���K�t������di��}|�d��o|�d��|jkSrA)rrErFr&)rcrGs  r'�check_statezBackupSystem.check_state�sU�����~�~�,�,�.�.�2�2�?�B�G�G���x�x�	�"�"�M�t�x�x��'@�'@�D�I�'M�Mr?c��
K�dSr2rXrps r'rJz&BackupSystem.get_last_backup_timestamp�s�����tr?r2�F)r\r]r^rdrQrkrnrqrtrv�boolrxr�intrJrXr?r'r`r`vs�������!�!�!�!�O�O�O�2�2�2�3�3�3�3�������
�
�
�N�4�N�N�N�N���#�������r?r`c���eZdZ�fd�Z�xZS)r;c�T��t���t��dSr2)�superrdr�rc�	__class__s �r'rdzPleskBackup.__init__�s!���
����������r?�r\r]r^rd�
__classcell__�r�s@r'r;r;�s8������� � � � � � � � � r?r;c���eZdZ�fd�Z�xZS)r:c�T��t���t��dSr2)rrdrr�s �r'rdzcPanelBackup.__init__�s!���
������ � � � � r?r�r�s@r'r:r:�s8�������!�!�!�!�!�!�!�!�!r?r:c���eZdZ�fd�Z�xZS)r<c�T��t���t��dSr2)rrdrr�s �r'rdzDirectAdminBackup.__init__�s!���
������%�%�%�%�%r?r�r�s@r'r<r<�s8�������&�&�&�&�&�&�&�&�&r?r<c�@��eZdZ�fd�Zdefd�Zed���Z�xZS)r4c���t���t��tjdd���|_dS)N�r1softT��async_)rrdrrrKr�s �r'rdzR1Soft.__init__�s6���
������ � � �&�.�x��E�E�E����r?r)c��K�|j����d{V��}d�|���D��S)Nc�"�i|]\}}|dv�	||��
S))�username�	timestamp�iprX��.0�k�vs   r'�
<dictcomp>zR1Soft.show.<locals>.<dictcomp>�s4��
�
�
���1��3�3�3�
�q�3�3�3r?�rK�infor,�rc�	info_datas  r'rtzR1Soft.show��V�����,�+�+�-�-�-�-�-�-�-�-�	�
�
�!���)�)�
�
�
�	
r?c��PK�|j�||||���d{V��dSr2�rKrk)rcr�r��password�encryption_keyrSs      r'rkzR1Soft.init�s:�����l����H�h��G�G�G�G�G�G�G�G�G�G�Gr?�	r\r]r^rd�dictrtrYrkr�r�s@r'r4r4�sz�������F�F�F�F�F�
�D�
�
�
�
��H�H��]�H�H�H�H�Hr?r4c�@��eZdZ�fd�Zdefd�Zed���Z�xZS)�
ClusterLogicsc���t���t��tjtd���|_dS�NTr�)rrdrrrKr�s �r'rdzClusterLogics.__init__�s6���
������'�'�'�&�.�}�T�J�J�J����r?r)c��K�|j����d{V��}d�|���D��S)Nc�"�i|]\}}|dv�	||��
S))r��url�apikeyrXr�s   r'r�z&ClusterLogics.show.<locals>.<dictcomp>�s4��
�
�
���1��1�1�1�
�q�1�1�1r?r�r�s  r'rtzClusterLogics.show�r�r?c��@K�|d=|jjdi|���d{V��dS)N�forcerXr�)rcrSs  r'rkzClusterLogics.init�sB����

�7�O��d�l��)�)�&�)�)�)�)�)�)�)�)�)�)�)r?r�r�s@r'r�r��ss�������K�K�K�K�K�
�D�
�
�
�
��*�*��]�*�*�*�*�*r?r�c���eZdZ�fd�Z�xZS)r=c���t���t��tj|jd���|_dSr�)rrdrrrKr&r�s �r'rdzSample.__init__�s8���
������(�(�(�&�.�t�y��F�F�F����r?r�r�s@r'r=r=�sA�������G�G�G�G�G�G�G�G�Gr?r=c�n��eZdZ�fd�Zdefd�Zed
d���Zdd�Zde	e
fd�Zdefd	�Z
�xZS)r3c����t���tdtj�dt
j����tj|j	d���|_dS)Nz	/var/log/�/Tr�)
rrdrr�PRODUCT�AcronisBackupConfig�LOG_NAMErrKr&r�s �r'rdzAcronis.__init__�sX���
������G� $����.A�.J�.J�K�	
�	
�	
�'�.�t�y��F�F�F����r?r)c��K�|j����d{V��}d�|���D��S)Nc�"�i|]\}}|dv�	||��
S))r�r�rXr�s   r'r�z Acronis.show.<locals>.<dictcomp>�s4��
�
�
���1��-�-�-�
�q�-�-�-r?r�r�s  r'rtzAcronis.show�r�r?Fc��K�|j����d{V��}|j�||||tj����d{V��dS)N��	provisionr��tmp_dir)rK�is_agent_installedrkr�TMPDIR�rcr�r�r�rSr�s      r'rkzAcronis.init�s�����"�l�=�=�?�?�?�?�?�?�?�?�?�	��l��������K� �
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
r?Nc��FK�|j�|���d{V��Sr2)rK�backups)rc�untils  r'�
_list_backupszAcronis._list_backups�s.�����\�)�)�%�0�0�0�0�0�0�0�0�0r?c��K�|����d{V��}|r&ttd�|D������SdS)Nc3�K�|];}|j�tj������V��<dS))�tzinfoN)�created�replacer�utcr�)r��backups  r'�	<genexpr>z4Acronis.get_last_backup_timestamp.<locals>.<genexpr>�sW��������N�*�*�(�,�*�?�?�I�I�K�K������r?)r�r|�max)rcr�s  r'rJz!Acronis.get_last_backup_timestamp�ss�����*�*�,�,�,�,�,�,�,�,���	�����")��������
��tr?c���K�	t|����d{V����S#tjtf$r�t
$rt�d��YdSwxYw)zif backup exists, than state OKNzError during checking stateF)r{r��asyncio�CancelledErrorr�	Exception�logger�	exceptionrps r'rxzAcronis.check_states�����	��d�0�0�2�2�2�2�2�2�2�2�3�3�3���&�(A�B�	�	�	���	�	�	����:�;�;�;��5�5�	���s�&+�:A)�(A)rzr2)r\r]r^rdr�rtrYrkr�rr|rJr{rxr�r�s@r'r3r3�s��������G�G�G�G�G�
�D�
�
�
�
��
�
�
��]�
�1�1�1�1�	��#��	�	�	�	��4��������r?r3c�@�eZdZdefd�Zd�Zdeefd�Zdd�Z	dS)	�CloudLinuxBaser)c��K�|j����d{V��}|�d��|d<|j����d{V��|d<|S)N�usage�backup_space_used_bytes�	login_url)rKr��popr�r�s  r'rtzCloudLinuxBase.show
sr�����,�+�+�-�-�-�-�-�-�-�-�	�/8�}�}�W�/E�/E�	�+�,�'+�|�'=�'=�'?�'?�!?�!?�!?�!?�!?�!?�	�+���r?c��HK�t�d��	|j����d{V��dS#t$rX}tjd��tt|j	��r|j	drt|��nd���d}~wwxYw)Nz
Making backupzCloudLinux backup failedrr)r�r�rK�make_initial_backup_strictr�loggingr�r[�lenrR�str)rc�es  r'rvzCloudLinuxBase.make_backups��������O�$�$�$�	��,�9�9�;�;�;�;�;�;�;�;�;�;�;���	�	�	���8�9�9�9�!��a�f�+�+�G�!�&��)�G��A�������
�����	���s�?�
B!�	AB�B!c��DK�|j����d{V��Sr2)rK�get_backup_progressrps r'r�z"CloudLinuxBase.get_backup_progresss,�����\�5�5�7�7�7�7�7�7�7�7�7r?Fc���K�t�d|jz��|j����d{V��}|j�||||tj����d{V��dS)NzStarting %s initr�)r�r�r&rKr�rkrr�r�s      r'rkzCloudLinuxBase.init s��������&���2�3�3�3�"�l�=�=�?�?�?�?�?�?�?�?�?�	��l��������K� �
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
r?Nrz)
r\r]r^r�rtrvrr|r�rkrXr?r'r�r�sq�������D��������8�8�C�=�8�8�8�8�	
�	
�	
�	
�	
�	
r?r�c�Z��eZdZd\ZZ�fd�Zed�fd�	��ZGd�d��Zej	de
f�fd���Zej	�fd	���Zej	de
ef�fd
���Zej	de
ef�fd���Zej	def�fd���Zde
fd
�Zd�fd�	Z�xZS)r6)�paid�unpaidc�`��t�����t|_dSr2)rrdr
r&r�s �r'rdzCloudLinux.__init__/s$���
����������	�	�	r?Fc����K�tjtj������d{V��}t	���|d|d|����d{V��dS)N��	server_id�loginr��r�)r�acronis_credentialsr�
get_server_idrrk)rcr�rS�credentialsr�s    �r'rkzCloudLinux.init3s������#�7� �.�0�0�
�
�
�
�
�
�
�
�
���g�g�l�l��� ��
�#���
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
r?c�$�eZdZed���ZdS)�CloudLinux.Decoratorsc�F��tj����fd���}|S)Nc���K�	�|g|�Ri|���d{V��S#t$r1|�d����d{V���|g|�Ri|���d{V��cYSwxYw)NTr�)rrk)rcrRrSrVs   �r'�wrappedzOCloudLinux.Decorators.update_credentials_on_unauthorized_error.<locals>.wrappedAs������:�!"��4�!9�$�!9�!9�!9�&�!9�!9�9�9�9�9�9�9�9��0�:�:�:��)�)�$�)�/�/�/�/�/�/�/�/�/�!"��4�!9�$�!9�!9�!9�&�!9�!9�9�9�9�9�9�9�9�9�9�:���s��8A�A)�	functools�wraps)rVr�s` r'�(update_credentials_on_unauthorized_errorz>CloudLinux.Decorators.update_credentials_on_unauthorized_error?s8���
�_�Q�
�
�
:�
:�
:�
:� �
�
:��Nr?N)r\r]r^�staticmethodr�rXr?r'�
Decoratorsr�>s-������	�		�		�
��		�		�		r?r�r)c���K�t������d{V��}tjt	j������d{V��}|�dd��}|�dd��}||d<||d<|S)Nr��sizerr��purchased_backup_gb�
resize_url)rrtr�
acronis_checkrr�rF)rcr��responser�r�r�s     �r'rtzCloudLinux.showKs�������'�'�,�,�.�.�(�(�(�(�(�(�	� �.� �.�0�0�
�
�
�
�
�
�
�
�
��'�l�l�6�1�5�5���\�\�%��.�.�
�+>�	�'�(�",�	�,���r?c��X�K�t������d{V��dSr2)rrvr�s �r'rvzCloudLinux.make_backupZs5������g�g�!�!�#�#�#�#�#�#�#�#�#�#�#r?c��T�K�t������d{V��Sr2)rr�r�s �r'r�zCloudLinux.get_backup_progress^s/������W�W�0�0�2�2�2�2�2�2�2�2�2r?c��T�K�t������d{V��Sr2)rrJr�s �r'rJz$CloudLinux.get_last_backup_timestampbs/������W�W�6�6�8�8�8�8�8�8�8�8�8r?c��T�K�t������d{V��Sr2)rrxr�s �r'rxzCloudLinux.check_statefs/������W�W�(�(�*�*�*�*�*�*�*�*�*r?c��K�	tjtj������d{V��}n3#t$r&}|j|���d�cYd}~Sd}~wwxYw|j|�d��d�S)Nr�)�statusr�r�)rr�)	rr�rr�r�UNPAID�add_used_space�PAIDrF)rc�contentr�s   r'rqzCloudLinux.checkjs�����	F�#�1�$�2�4�4����������G�G���	F�	F�	F�"�k�!�2B�2B�2D�2D�E�E�E�E�E�E�E�E�����	F�����)�W�[�[��-@�-@�A�A�As�,1�
A!�A�A!�A!c���K�t������d{V��|r.tjt	j������d{V��dSdS)Nr�)rrnr�acronis_removerr�)rcrmr�s  �r'rnzCloudLinux.disabletsw������g�g�o�o�����������	O��(�:�3K�3M�3M�N�N�N�N�N�N�N�N�N�N�N�N�	O�	Or?rz)r\r]r^rrrdrYrkr�r�r�rtrvrr|r�rJr{rxrqrnr�r�s@r'r6r6,s��������#�L�D�&�������
�
�
�
�
��]�
����������8��D������9�8���8�$�$�$�$�9�8�$��8�3�8�C�=�3�3�3�3�3�9�8�3��8�9��#��9�9�9�9�9�9�8�9��8�+�4�+�+�+�+�+�9�8�+�B�T�B�B�B�B�O�O�O�O�O�O�O�O�O�Or?r6c�8��eZdZ�fd�Ze�fd���Z�xZS)r8c�`��t�����t|_dSr2)rrdrr&r�s �r'rdzCloudLinuxOnPremise.__init__{s$���
��������)��	�	�	r?c��J�K�t��j|i|���d{V��dSr2)rrk)rcrRrSr�s   �r'rkzCloudLinuxOnPremise.inits:������e�g�g�l�D�+�F�+�+�+�+�+�+�+�+�+�+�+r?)r\r]r^rdrYrkr�r�s@r'r8r8zs]�������*�*�*�*�*��,�,�,�,��]�,�,�,�,�,r?r8)FT)Dr�r�r��datetimer�typingrrrr� defence360agent.contracts.configrr	r
r�rrr
rrrrrrrr�!defence360agent.contracts.licenser�defence360agent.internals.clnrr�*defence360agent.subsys.panels.cpanel.panelr�/defence360agent.subsys.panels.directadmin.panelr�)defence360agent.subsys.panels.plesk.panelr�restore_infectedr�(restore_infected.backup_backends.acronisr�$restore_infected.backup_backends_librr�	getLoggerr\r�r(r�r0r"rHr|rJrYr�r[r`r;r:r<r4r�r=r3r�r6r8rXr?r'�<module>rs���������������������1�1�1�1�1�1�1�1�1�1�1�1��������������������������������� 9�8�8�8�8�8�A�A�A�A�A�A�A�A�=�=�=�=�=�=�G�G�G�G�G�G�;�;�;�;�;�;���0�0�0�0�0�0�E�E�E�E�E�E���������

��	�8�	$�	$��L�L�L��d�3�i���������
�#�x�-������4=�X�c�]�=�=�=�=�
>��#��>�>�>�>�
�
�
�	�	�	�	�	�i�	�	�	�"�"�"�"�"�"�"�"�J � � � � �,� � � �
!�!�!�!�!�<�!�!�!�
&�&�&�&�&��&�&�&�
H�H�H�H�H�\�H�H�H�$*�*�*�*�*�L�*�*�*�,G�G�G�G�G�\�G�G�G�1�1�1�1�1�l�1�1�1�h
�
�
�
�
�W�
�
�
�@KO�KO�KO�KO�KO��KO�KO�KO�\,�,�,�,�,�.�,�,�,�,�,r?defence360agent/subsys/__pycache__/backup_systems.cpython-311.pyc0000644000000000000000000006204000000000000022046 0ustar  �

�'0T�sY����ddlZddlZddlZddlmZddlmZmZmZm	Z	ddl
mZmZm
ZmZmZmZmZmZmZmZmZmZmZmZddlmZddlmZmZddlm Z ddl!m"Z"dd	l#m$Z$esdd
l%m&Z&ddl'm(Z(ddl)m*Z*m+Z+ej,e-��Z.d
�Z/dee0fd�Z1		d.dee0effd�Z2de	e0fd�Z3de	e4fd�Z5d�Z6Gd�de7��Z8Gd�d��Z9Gd�de9��Z:Gd�de9��Z;Gd�de9��Z<Gd �d!e9��Z=Gd"�d#e9��Z>Gd$�d%e9��Z?Gd&�d'e9��Z@Gd(�d)e@��ZAGd*�d+eA��ZBGd,�d-eA��ZCdS)/�N)�timezone)�Callable�Dict�List�Optional)�ACRONIS�ANTIVIRUS_MODE�
AcronisBackup�BackupConfig�
BackupRestore�
CLOUDLINUX�CLOUDLINUX_ON_PREMISE�
CLUSTERLOGICS�CPANEL�Core�DIRECTADMIN�PLESK�R1SOFT�SAMPLE_BACKEND)�
LicenseCLN)�BackupNotFound�RestCLN)�cPanel)�DirectAdmin)�Plesk)�backup_backends)�BackupFailed)�BackendNonApplicableError�BackendNotAuthorizedErrorc��	td���|��S#ttf$r#td�|�����wxYw)NT)�include_samplez"Backup system is not available: {})�_get_avalible_backends�KeyErrorr�
ValueError�format)�names �Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/backup_systems.py�get_backendr((si��L�@�%�T�:�:�:�4�@�B�B�B���/�0�L�L�L��=�D�D�T�J�J�K�K�K�L���s	� �4A�returnc��g}td������D]5\}}	|��|�|���&#t$rY�2wxYw|S)NF)�
include_cl)r"�items�appendr)�namesr&�clss   r'�get_available_backends_namesr0/s����E�+�u�=�=�=�C�C�E�E���	��c�	��C�E�E�E�
�L�L�������)�	�	�	��D�	����
�Ls�
A�
A�AFTc��tttti}tjr|rt|t<tjrt|t<tj��rt|t<nEtj��rt |t"<n"t%j��rt&|t(<|rt*|t,<|S�N)r�Acronisr�R1Softr�CL_BACKUP_ALLOWED�
CloudLinuxr
�CL_ON_PREMISE_BACKUP_ALLOWED�CloudLinuxOnPremiserr�is_installed�cPanelBackuprr�PleskBackuprr�DirectAdminBackupr�Sampler)r!r+�backendss   r'r"r"=s���
	�����H��&�*�:�*�)�����1�>�*=��&�'�
����2�'�����	�	�	�	�2�%�����	�	!�	#�	#�2� 1�����*�#)��� ��O�c��t������di��}|�d��o|�d��S�N�
BACKUP_SYSTEM�enabled�
backup_system)r�config_to_dict�get)�confs r'�get_current_backendrHWsJ���>�>�(�(�*�*�.�.���C�C�D��8�8�I���<�4�8�8�O�#<�#<�<r?c��|K�t��}|sdSt|��}|����d{V��Sr2)rHr(�get_last_backup_timestamp)�backend�backend_instances  r'rJrJ\sP����!�#�#�G����t�"�7�+�+��!�;�;�=�=�=�=�=�=�=�=�=r?c����fd�}|S)Nc���K�d}	�|g|�Ri|���d{V��}d}|�|���n#|�|���wxYw|S)NFT�rC��_update_backups_config)r/�args�kwargs�ok�rv�fs     �r'�wrapperztransactional.<locals>.wrapperfs������
��	3��q��.�t�.�.�.�v�.�.�.�.�.�.�.�.�B��B��&�&�r�&�2�2�2�2��C�&�&�r�&�2�2�2�2�����	s	�2�A
�)rVrWs` r'�
transactionalrYes#���������Nr?c��eZdZdS)�BackupExceptionN)�__name__�
__module__�__qualname__rXr?r'r[r[rs�������Dr?r[c�`�eZdZd
d�Zd�Zd�Zdd�Zd�Zd�Zd	�Z	d
e
fd�Zd
ee
fd�ZdS)�BackupSystemNc�"�||_||_dSr2)r&�log_path)�selfr&rbs   r'�__init__zBackupSystem.__init__ws����	� ��
�
�
r?c�n�d||r|jndd�i}t���|dd���dS)NrB)rCrDT)�	overwrite�validate)r&r�dict_to_config)rcrC�new_confs   r'rQz#BackupSystem._update_backups_config{sN���"�.5�!?����4���
��	���%�%�h�$��%�N�N�N�N�Nr?c��6K�|�d���dS)NTrOrP)rcrRrSs   r'�initzBackupSystem.init�s#�����#�#�D�#�1�1�1�1�1r?Fc��6K�|�d���dS)NFrOrP)rc�delete_backupss  r'�disablezBackupSystem.disable�s#�����#�#�E�#�2�2�2�2�2r?c��
K�iSr2rX�rcs r'�checkzBackupSystem.check�������	r?c��
K�iSr2rXrps r'�showzBackupSystem.show�rrr?c��
K�dSr2rXrps r'�make_backupzBackupSystem.make_backup�s�����r?r)c���K�t������di��}|�d��o|�d��|jkSrA)rrErFr&)rcrGs  r'�check_statezBackupSystem.check_state�sU�����~�~�,�,�.�.�2�2�?�B�G�G���x�x�	�"�"�M�t�x�x��'@�'@�D�I�'M�Mr?c��
K�dSr2rXrps r'rJz&BackupSystem.get_last_backup_timestamp�s�����tr?r2�F)r\r]r^rdrQrkrnrqrtrv�boolrxr�intrJrXr?r'r`r`vs�������!�!�!�!�O�O�O�2�2�2�3�3�3�3�������
�
�
�N�4�N�N�N�N���#�������r?r`c���eZdZ�fd�Z�xZS)r;c�T��t���t��dSr2)�superrdr�rc�	__class__s �r'rdzPleskBackup.__init__�s!���
����������r?�r\r]r^rd�
__classcell__�r�s@r'r;r;�s8������� � � � � � � � � r?r;c���eZdZ�fd�Z�xZS)r:c�T��t���t��dSr2)rrdrr�s �r'rdzcPanelBackup.__init__�s!���
������ � � � � r?r�r�s@r'r:r:�s8�������!�!�!�!�!�!�!�!�!r?r:c���eZdZ�fd�Z�xZS)r<c�T��t���t��dSr2)rrdrr�s �r'rdzDirectAdminBackup.__init__�s!���
������%�%�%�%�%r?r�r�s@r'r<r<�s8�������&�&�&�&�&�&�&�&�&r?r<c�@��eZdZ�fd�Zdefd�Zed���Z�xZS)r4c���t���t��tjdd���|_dS)N�r1softT��async_)rrdrrrKr�s �r'rdzR1Soft.__init__�s6���
������ � � �&�.�x��E�E�E����r?r)c��K�|j����d{V��}d�|���D��S)Nc�"�i|]\}}|dv�	||��
S))�username�	timestamp�iprX��.0�k�vs   r'�
<dictcomp>zR1Soft.show.<locals>.<dictcomp>�s4��
�
�
���1��3�3�3�
�q�3�3�3r?�rK�infor,�rc�	info_datas  r'rtzR1Soft.show��V�����,�+�+�-�-�-�-�-�-�-�-�	�
�
�!���)�)�
�
�
�	
r?c��PK�|j�||||���d{V��dSr2�rKrk)rcr�r��password�encryption_keyrSs      r'rkzR1Soft.init�s:�����l����H�h��G�G�G�G�G�G�G�G�G�G�Gr?�	r\r]r^rd�dictrtrYrkr�r�s@r'r4r4�sz�������F�F�F�F�F�
�D�
�
�
�
��H�H��]�H�H�H�H�Hr?r4c�@��eZdZ�fd�Zdefd�Zed���Z�xZS)�
ClusterLogicsc���t���t��tjtd���|_dS�NTr�)rrdrrrKr�s �r'rdzClusterLogics.__init__�s6���
������'�'�'�&�.�}�T�J�J�J����r?r)c��K�|j����d{V��}d�|���D��S)Nc�"�i|]\}}|dv�	||��
S))r��url�apikeyrXr�s   r'r�z&ClusterLogics.show.<locals>.<dictcomp>�s4��
�
�
���1��1�1�1�
�q�1�1�1r?r�r�s  r'rtzClusterLogics.show�r�r?c��@K�|d=|jjdi|���d{V��dS)N�forcerXr�)rcrSs  r'rkzClusterLogics.init�sB����

�7�O��d�l��)�)�&�)�)�)�)�)�)�)�)�)�)�)r?r�r�s@r'r�r��ss�������K�K�K�K�K�
�D�
�
�
�
��*�*��]�*�*�*�*�*r?r�c���eZdZ�fd�Z�xZS)r=c���t���t��tj|jd���|_dSr�)rrdrrrKr&r�s �r'rdzSample.__init__�s8���
������(�(�(�&�.�t�y��F�F�F����r?r�r�s@r'r=r=�sA�������G�G�G�G�G�G�G�G�Gr?r=c�n��eZdZ�fd�Zdefd�Zed
d���Zdd�Zde	e
fd�Zdefd	�Z
�xZS)r3c����t���tdtj�dt
j����tj|j	d���|_dS)Nz	/var/log/�/Tr�)
rrdrr�PRODUCT�AcronisBackupConfig�LOG_NAMErrKr&r�s �r'rdzAcronis.__init__�sX���
������G� $����.A�.J�.J�K�	
�	
�	
�'�.�t�y��F�F�F����r?r)c��K�|j����d{V��}d�|���D��S)Nc�"�i|]\}}|dv�	||��
S))r�r�rXr�s   r'r�z Acronis.show.<locals>.<dictcomp>�s4��
�
�
���1��-�-�-�
�q�-�-�-r?r�r�s  r'rtzAcronis.show�r�r?Fc��K�|j����d{V��}|j�||||tj����d{V��dS)N��	provisionr��tmp_dir)rK�is_agent_installedrkr�TMPDIR�rcr�r�r�rSr�s      r'rkzAcronis.init�s�����"�l�=�=�?�?�?�?�?�?�?�?�?�	��l��������K� �
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
r?Nc��FK�|j�|���d{V��Sr2)rK�backups)rc�untils  r'�
_list_backupszAcronis._list_backups�s.�����\�)�)�%�0�0�0�0�0�0�0�0�0r?c��K�|����d{V��}|r&ttd�|D������SdS)Nc3�K�|];}|j�tj������V��<dS))�tzinfoN)�created�replacer�utcr�)r��backups  r'�	<genexpr>z4Acronis.get_last_backup_timestamp.<locals>.<genexpr>�sW��������N�*�*�(�,�*�?�?�I�I�K�K������r?)r�r|�max)rcr�s  r'rJz!Acronis.get_last_backup_timestamp�ss�����*�*�,�,�,�,�,�,�,�,���	�����")��������
��tr?c���K�	t|����d{V����S#tjtf$r�t
$rt�d��YdSwxYw)zif backup exists, than state OKNzError during checking stateF)r{r��asyncio�CancelledErrorr�	Exception�logger�	exceptionrps r'rxzAcronis.check_states�����	��d�0�0�2�2�2�2�2�2�2�2�3�3�3���&�(A�B�	�	�	���	�	�	����:�;�;�;��5�5�	���s�&+�:A)�(A)rzr2)r\r]r^rdr�rtrYrkr�rr|rJr{rxr�r�s@r'r3r3�s��������G�G�G�G�G�
�D�
�
�
�
��
�
�
��]�
�1�1�1�1�	��#��	�	�	�	��4��������r?r3c�@�eZdZdefd�Zd�Zdeefd�Zdd�Z	dS)	�CloudLinuxBaser)c��K�|j����d{V��}|�d��|d<|j����d{V��|d<|S)N�usage�backup_space_used_bytes�	login_url)rKr��popr�r�s  r'rtzCloudLinuxBase.show
sr�����,�+�+�-�-�-�-�-�-�-�-�	�/8�}�}�W�/E�/E�	�+�,�'+�|�'=�'=�'?�'?�!?�!?�!?�!?�!?�!?�	�+���r?c��HK�t�d��	|j����d{V��dS#t$rX}tjd��tt|j	��r|j	drt|��nd���d}~wwxYw)Nz
Making backupzCloudLinux backup failedrr)r�r�rK�make_initial_backup_strictr�loggingr�r[�lenrR�str)rc�es  r'rvzCloudLinuxBase.make_backups��������O�$�$�$�	��,�9�9�;�;�;�;�;�;�;�;�;�;�;���	�	�	���8�9�9�9�!��a�f�+�+�G�!�&��)�G��A�������
�����	���s�?�
B!�	AB�B!c��DK�|j����d{V��Sr2)rK�get_backup_progressrps r'r�z"CloudLinuxBase.get_backup_progresss,�����\�5�5�7�7�7�7�7�7�7�7�7r?Fc���K�t�d|jz��|j����d{V��}|j�||||tj����d{V��dS)NzStarting %s initr�)r�r�r&rKr�rkrr�r�s      r'rkzCloudLinuxBase.init s��������&���2�3�3�3�"�l�=�=�?�?�?�?�?�?�?�?�?�	��l��������K� �
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
r?Nrz)
r\r]r^r�rtrvrr|r�rkrXr?r'r�r�sq�������D��������8�8�C�=�8�8�8�8�	
�	
�	
�	
�	
�	
r?r�c�Z��eZdZd\ZZ�fd�Zed�fd�	��ZGd�d��Zej	de
f�fd���Zej	�fd	���Zej	de
ef�fd
���Zej	de
ef�fd���Zej	def�fd���Zde
fd
�Zd�fd�	Z�xZS)r6)�paid�unpaidc�`��t�����t|_dSr2)rrdr
r&r�s �r'rdzCloudLinux.__init__/s$���
����������	�	�	r?Fc����K�tjtj������d{V��}t	���|d|d|����d{V��dS)N��	server_id�loginr��r�)r�acronis_credentialsr�
get_server_idrrk)rcr�rS�credentialsr�s    �r'rkzCloudLinux.init3s������#�7� �.�0�0�
�
�
�
�
�
�
�
�
���g�g�l�l��� ��
�#���
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
r?c�$�eZdZed���ZdS)�CloudLinux.Decoratorsc�F��tj����fd���}|S)Nc���K�	�|g|�Ri|���d{V��S#t$r1|�d����d{V���|g|�Ri|���d{V��cYSwxYw)NTr�)rrk)rcrRrSrVs   �r'�wrappedzOCloudLinux.Decorators.update_credentials_on_unauthorized_error.<locals>.wrappedAs������:�!"��4�!9�$�!9�!9�!9�&�!9�!9�9�9�9�9�9�9�9��0�:�:�:��)�)�$�)�/�/�/�/�/�/�/�/�/�!"��4�!9�$�!9�!9�!9�&�!9�!9�9�9�9�9�9�9�9�9�9�:���s��8A�A)�	functools�wraps)rVr�s` r'�(update_credentials_on_unauthorized_errorz>CloudLinux.Decorators.update_credentials_on_unauthorized_error?s8���
�_�Q�
�
�
:�
:�
:�
:� �
�
:��Nr?N)r\r]r^�staticmethodr�rXr?r'�
Decoratorsr�>s-������	�		�		�
��		�		�		r?r�r)c���K�t������d{V��}tjt	j������d{V��}|�dd��}|�dd��}||d<||d<|S)Nr��sizerr��purchased_backup_gb�
resize_url)rrtr�
acronis_checkrr�rF)rcr��responser�r�r�s     �r'rtzCloudLinux.showKs�������'�'�,�,�.�.�(�(�(�(�(�(�	� �.� �.�0�0�
�
�
�
�
�
�
�
�
��'�l�l�6�1�5�5���\�\�%��.�.�
�+>�	�'�(�",�	�,���r?c��X�K�t������d{V��dSr2)rrvr�s �r'rvzCloudLinux.make_backupZs5������g�g�!�!�#�#�#�#�#�#�#�#�#�#�#r?c��T�K�t������d{V��Sr2)rr�r�s �r'r�zCloudLinux.get_backup_progress^s/������W�W�0�0�2�2�2�2�2�2�2�2�2r?c��T�K�t������d{V��Sr2)rrJr�s �r'rJz$CloudLinux.get_last_backup_timestampbs/������W�W�6�6�8�8�8�8�8�8�8�8�8r?c��T�K�t������d{V��Sr2)rrxr�s �r'rxzCloudLinux.check_statefs/������W�W�(�(�*�*�*�*�*�*�*�*�*r?c��K�	tjtj������d{V��}n3#t$r&}|j|���d�cYd}~Sd}~wwxYw|j|�d��d�S)Nr�)�statusr�r�)rr�)	rr�rr�r�UNPAID�add_used_space�PAIDrF)rc�contentr�s   r'rqzCloudLinux.checkjs�����	F�#�1�$�2�4�4����������G�G���	F�	F�	F�"�k�!�2B�2B�2D�2D�E�E�E�E�E�E�E�E�����	F�����)�W�[�[��-@�-@�A�A�As�,1�
A!�A�A!�A!c���K�t������d{V��|r.tjt	j������d{V��dSdS)Nr�)rrnr�acronis_removerr�)rcrmr�s  �r'rnzCloudLinux.disabletsw������g�g�o�o�����������	O��(�:�3K�3M�3M�N�N�N�N�N�N�N�N�N�N�N�N�	O�	Or?rz)r\r]r^rrrdrYrkr�r�r�rtrvrr|r�rJr{rxrqrnr�r�s@r'r6r6,s��������#�L�D�&�������
�
�
�
�
��]�
����������8��D������9�8���8�$�$�$�$�9�8�$��8�3�8�C�=�3�3�3�3�3�9�8�3��8�9��#��9�9�9�9�9�9�8�9��8�+�4�+�+�+�+�+�9�8�+�B�T�B�B�B�B�O�O�O�O�O�O�O�O�O�Or?r6c�8��eZdZ�fd�Ze�fd���Z�xZS)r8c�`��t�����t|_dSr2)rrdrr&r�s �r'rdzCloudLinuxOnPremise.__init__{s$���
��������)��	�	�	r?c��J�K�t��j|i|���d{V��dSr2)rrk)rcrRrSr�s   �r'rkzCloudLinuxOnPremise.inits:������e�g�g�l�D�+�F�+�+�+�+�+�+�+�+�+�+�+r?)r\r]r^rdrYrkr�r�s@r'r8r8zs]�������*�*�*�*�*��,�,�,�,��]�,�,�,�,�,r?r8)FT)Dr�r�r��datetimer�typingrrrr� defence360agent.contracts.configrr	r
r�rrr
rrrrrrrr�!defence360agent.contracts.licenser�defence360agent.internals.clnrr�*defence360agent.subsys.panels.cpanel.panelr�/defence360agent.subsys.panels.directadmin.panelr�)defence360agent.subsys.panels.plesk.panelr�restore_infectedr�(restore_infected.backup_backends.acronisr�$restore_infected.backup_backends_librr�	getLoggerr\r�r(r�r0r"rHr|rJrYr�r[r`r;r:r<r4r�r=r3r�r6r8rXr?r'�<module>rs���������������������1�1�1�1�1�1�1�1�1�1�1�1��������������������������������� 9�8�8�8�8�8�A�A�A�A�A�A�A�A�=�=�=�=�=�=�G�G�G�G�G�G�;�;�;�;�;�;���0�0�0�0�0�0�E�E�E�E�E�E���������

��	�8�	$�	$��L�L�L��d�3�i���������
�#�x�-������4=�X�c�]�=�=�=�=�
>��#��>�>�>�>�
�
�
�	�	�	�	�	�i�	�	�	�"�"�"�"�"�"�"�"�J � � � � �,� � � �
!�!�!�!�!�<�!�!�!�
&�&�&�&�&��&�&�&�
H�H�H�H�H�\�H�H�H�$*�*�*�*�*�L�*�*�*�,G�G�G�G�G�\�G�G�G�1�1�1�1�1�l�1�1�1�h
�
�
�
�
�W�
�
�
�@KO�KO�KO�KO�KO��KO�KO�KO�\,�,�,�,�,�.�,�,�,�,�,r?defence360agent/subsys/__pycache__/clcagefs.cpython-311.opt-1.pyc0000644000000000000000000003122600000000000021522 0ustar  �

91�\hO7_���ddlZddlZddlZdZdZGd�de��ZGd�d��Zd�Zdd
�Z						dd
�Z
d�Zd�Zdd�Z
dS)�Nz/etc/cagefs/cagefs.mpz/usr/sbin/cagefsctlc��eZdZd�Zd�ZdS)�CagefsMpConflictc�2�d|�dt�d|�d�|_dS)NzConflict in adding 'z' to z5 because of pre-existing alternative specification: '�')�CAGEFS_MP_FILENAME�_msg)�self�new_item�
existing_items   �T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/clcagefs.py�__init__zCagefsMpConflict.__init__s,����x�x�+�+�+�]�]�]�
<�	
�	�	�	�c��|jS�N)r�r	s r�__str__zCagefsMpConflict.__str__s
���y�rN)�__name__�
__module__�__qualname__r
r�rrrrs2������
�
�
�����rrc��eZdZdZdZdZd�Zd�Zd�Ze	d���Z
d�Zd	�Zd
�Z
e	d���Ze	d���Ze	d
���Zd�Zd�Zd�ZdS)�CagefsMpItems@!%r�!c��|dd�dkr	d|_dS|���dkr	d|_dS||_dS)z�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 ctorN��#r)�
_path_spec�strip)r	�args  rr
zCagefsMpItem.__init__#sI���r��r�7�d�?�?�"�D�O�O�O�
�Y�Y�[�[�C�
�
�"�D�O�O�O�!�D�O�O�Orc�\�|���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�(�D�O�T�+B�B�D�O��rc�4�tj|j��Sr)�os�fsdecoderrs rrzCagefsMpItem.__str__:s���{�4�?�+�+�+rc�8�|dkrdS|ddkr|dzS|S)Nr�/����/r)�paths r�
_add_slashzCagefsMpItem._add_slash=s.���3�;�;��4���8�w����$�;���rc�d�t�|��}|���s|���rdSt�|�����}t�|�����}|�|��S)NF)r�_adopt�is_dummyr,r+�
startswith)r	�another�adopted�	this_path�test_preexist_in_paths     r�pre_exist_inzCagefsMpItem.pre_exist_inEs����%�%�g�.�.���=�=�?�?�	�g�.�.�0�0�	��5� �+�+�D�I�I�K�K�8�8�	� ,� 7� 7������ G� G���#�#�$9�:�:�:rc��t�|��}|���s|���rdS|���|���krdStjtjgi}g}|���|�|���|��vS)NFT)rr.r/r"�_PREFIX_MOUNT_RW�_PREFIX_MOUNT_RO�get)r	�existingr2�prefix_compatibility_map�null_optionss     r�is_compatible_by_prefix_withz)CagefsMpItem.is_compatible_by_prefix_withPs����%�%�h�/�/���=�=�?�?�	�g�.�.�0�0�	��5��;�;�=�=�G�N�N�,�,�,�,��4�
�)�L�,I�+J�$
� ����{�{�}�}� 8� <� <��N�N���l�!
�!
�
�	
rc��|jduSr�rrs rr/zCagefsMpItem.is_dummycs����$�&�&rc�N�t|t��r|St|��Sr)�
isinstancer)�xs rr.zCagefsMpItem._adoptfs%���a��&�&�	#��H���?�?�"rc�8�|�d��dS)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�@�|�tj��Sr)�lstripr�PREFIX_LISTrFs r�_cut_off_prefixzCagefsMpItem._cut_off_prefixus������ 8�9�9�9rc�p�t�t�|j����Sr)rrLrHrrs rr+zCagefsMpItem.pathys-���+�+��&�&�t��7�7�
�
�	
rc�^�|j|���kr|jdd�SdS)Nrrr)rr+rs rr"zCagefsMpItem.prefix~s-���?�d�i�i�k�k�)�)��?�1�Q�3�'�'��3rc��|jSrr?rs r�speczCagefsMpItem.spec�s
����rN)rrrrKr7r8r
r#r�staticmethodr,r5r=r/r.rHrLr+r"rPrrrrrs�������K�����
"�
"�
"����,�,�,�����\��	;�	;�	;�
�
�
�&'�'�'��#�#��\�#��(�(��\�(��:�:��\�:�
�
�
�
�������rrc�J�tj�t��Sr)r%r+�exists�CAGEFSCTL_TOOLrrr�is_cagefs_presentrU�s��
�7�>�>�.�)�)�)r��c���|�d}|�d}tj�|��stj|��|�tj||��tj|||��dS)Nr))r%r+�isdir�mkdir�chmod�chown)r+r#�owner_id�group_ids    r�_mk_mount_dir_setup_permr^�sm����������
�7�=�=�����
��������
���t�����H�T�8�X�&�&�&�&�&rrTc��
�t||||��tj�t��stjtdg��tjtdg��ttd��}	t||z���
|���
d�|D��}�
fd�|D��}	|	s�|�dd��|�dd	��}|�
d
|�d��zdz��|�
�
���dz��|���|rtjtd
g��n1�
�|	d��st%�
|	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�|]}|���V��dSr)�rstrip)�.0�	file_lines  r�	<genexpr>z)setup_mount_dir_cagefs.<locals>.<genexpr>�s.����F�F�y�	�(�(�*�*�F�F�F�F�F�Frc�>��g|]}��|���|��Sr)r5)rbrBr
s  �r�
<listcomp>z*setup_mount_dir_cagefs.<locals>.<listcomp>�s<���
�
�
��x�'<�'<�Q�'?�'?�
�
�
�
�
rr��
� s# next line is added by zutf-8�
�
--remount-allr)N)r^r%r+rSr�
subprocess�callrT�openrr#�seek�replace�write�encoderP�closer=r)r+�added_byr#r\r]r"�remount_cagefs�	cagefs_mp�trim_nl_iter�pre_exist_optionr
s          @r�setup_mount_dir_cagefsry�s����^�T�4��8�<�<�<��7�>�>�,�-�-�9�����7�8�8�8��O�^�\�2�3�3�3�
�'��/�/�I�����
�.�.�3�3�D�9�9��F�F�I�F�F�F��
�
�
�
�#�
�
�
�� �	C��N�N�1�a� � � � �'�'��c�2�2�H��O�O�+�h�o�o�g�.F�.F�F��N�
�
�
�
�O�O�H�M�M�O�O�e�3�4�4�4��O�O�����
C����� A�B�B�B���6�6�7G��7K�L�L�	C�"�8�-=�b�-A�B�B�B�	���������	���������s
�D(G�Gc��ttd��5}|���cddd��S#1swxYwYdS)N�rb)rnr�	readlines)�fs r�_get_cagefs_mp_linesr~�s��	
� �$�	'�	'��1��{�{�}�}���������������������s�7�;�;c��ttd��5}|�|��cddd��S#1swxYwYdS)N�wb)rnr�
writelines)�linesr}s  r�_write_cagefs_mp_linesr��s���	
� �$�	'�	'�#�1��|�|�E�"�"�#�#�#�#�#�#�#�#�#�#�#�#����#�#�#�#�#�#s�8�<�<c���t��}tjdtjtj|��fz����fd�|D��}t
|��|rtjtdg��dSdS)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+)?$c3�F�K�|]}��|���|V��dSr)�match)rb�line�rs  �rrdz*remove_mount_dir_cagefs.<locals>.<genexpr>s2�����L�L��a�g�g�d�m�m�L��L�L�L�L�L�LrrkN)
r~�re�compilerrK�escaper�rlrmrT)r+rur��lines_with_excluded_pathr�s    @r�remove_mount_dir_cagefsr��s����
!�"�"�E�
�
��� 8�"�)�D�/�/�J�J�	�	�A� M�L�L�L��L�L�L���3�4�4�4��;�����9�:�:�:�:�:�;�;r)rVNN)rVNNrT)T)r%r�rlrrT�	ExceptionrrrUr^ryr~r�r�rrr�<module>r�s��
�	�	�	�	�	�	�	�����,��&��	�	�	�	�	�y�	�	�	�g�g�g�g�g�g�g�g�T*�*�*�
'�
'�
'�
'�&
�
�
���V�V�V�V�r���
#�#�#�
;�;�;�;�;�;rdefence360agent/subsys/__pycache__/clcagefs.cpython-311.pyc0000644000000000000000000003122600000000000020563 0ustar  �

91�\hO7_���ddlZddlZddlZdZdZGd�de��ZGd�d��Zd�Zdd
�Z						dd
�Z
d�Zd�Zdd�Z
dS)�Nz/etc/cagefs/cagefs.mpz/usr/sbin/cagefsctlc��eZdZd�Zd�ZdS)�CagefsMpConflictc�2�d|�dt�d|�d�|_dS)NzConflict in adding 'z' to z5 because of pre-existing alternative specification: '�')�CAGEFS_MP_FILENAME�_msg)�self�new_item�
existing_items   �T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/clcagefs.py�__init__zCagefsMpConflict.__init__s,����x�x�+�+�+�]�]�]�
<�	
�	�	�	�c��|jS�N)r�r	s r�__str__zCagefsMpConflict.__str__s
���y�rN)�__name__�
__module__�__qualname__r
r�rrrrs2������
�
�
�����rrc��eZdZdZdZdZd�Zd�Zd�Ze	d���Z
d�Zd	�Zd
�Z
e	d���Ze	d���Ze	d
���Zd�Zd�Zd�ZdS)�CagefsMpItems@!%r�!c��|dd�dkr	d|_dS|���dkr	d|_dS||_dS)z�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 ctorN��#r)�
_path_spec�strip)r	�args  rr
zCagefsMpItem.__init__#sI���r��r�7�d�?�?�"�D�O�O�O�
�Y�Y�[�[�C�
�
�"�D�O�O�O�!�D�O�O�Orc�\�|���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�(�D�O�T�+B�B�D�O��rc�4�tj|j��Sr)�os�fsdecoderrs rrzCagefsMpItem.__str__:s���{�4�?�+�+�+rc�8�|dkrdS|ddkr|dzS|S)Nr�/����/r)�paths r�
_add_slashzCagefsMpItem._add_slash=s.���3�;�;��4���8�w����$�;���rc�d�t�|��}|���s|���rdSt�|�����}t�|�����}|�|��S)NF)r�_adopt�is_dummyr,r+�
startswith)r	�another�adopted�	this_path�test_preexist_in_paths     r�pre_exist_inzCagefsMpItem.pre_exist_inEs����%�%�g�.�.���=�=�?�?�	�g�.�.�0�0�	��5� �+�+�D�I�I�K�K�8�8�	� ,� 7� 7������ G� G���#�#�$9�:�:�:rc��t�|��}|���s|���rdS|���|���krdStjtjgi}g}|���|�|���|��vS)NFT)rr.r/r"�_PREFIX_MOUNT_RW�_PREFIX_MOUNT_RO�get)r	�existingr2�prefix_compatibility_map�null_optionss     r�is_compatible_by_prefix_withz)CagefsMpItem.is_compatible_by_prefix_withPs����%�%�h�/�/���=�=�?�?�	�g�.�.�0�0�	��5��;�;�=�=�G�N�N�,�,�,�,��4�
�)�L�,I�+J�$
� ����{�{�}�}� 8� <� <��N�N���l�!
�!
�
�	
rc��|jduSr�rrs rr/zCagefsMpItem.is_dummycs����$�&�&rc�N�t|t��r|St|��Sr)�
isinstancer)�xs rr.zCagefsMpItem._adoptfs%���a��&�&�	#��H���?�?�"rc�8�|�d��dS)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�@�|�tj��Sr)�lstripr�PREFIX_LISTrFs r�_cut_off_prefixzCagefsMpItem._cut_off_prefixus������ 8�9�9�9rc�p�t�t�|j����Sr)rrLrHrrs rr+zCagefsMpItem.pathys-���+�+��&�&�t��7�7�
�
�	
rc�^�|j|���kr|jdd�SdS)Nrrr)rr+rs rr"zCagefsMpItem.prefix~s-���?�d�i�i�k�k�)�)��?�1�Q�3�'�'��3rc��|jSrr?rs r�speczCagefsMpItem.spec�s
����rN)rrrrKr7r8r
r#r�staticmethodr,r5r=r/r.rHrLr+r"rPrrrrrs�������K�����
"�
"�
"����,�,�,�����\��	;�	;�	;�
�
�
�&'�'�'��#�#��\�#��(�(��\�(��:�:��\�:�
�
�
�
�������rrc�J�tj�t��Sr)r%r+�exists�CAGEFSCTL_TOOLrrr�is_cagefs_presentrU�s��
�7�>�>�.�)�)�)r��c���|�d}|�d}tj�|��stj|��|�tj||��tj|||��dS)Nr))r%r+�isdir�mkdir�chmod�chown)r+r#�owner_id�group_ids    r�_mk_mount_dir_setup_permr^�sm����������
�7�=�=�����
��������
���t�����H�T�8�X�&�&�&�&�&rrTc��
�t||||��tj�t��stjtdg��tjtdg��ttd��}	t||z���
|���
d�|D��}�
fd�|D��}	|	s�|�dd��|�dd	��}|�
d
|�d��zdz��|�
�
���dz��|���|rtjtd
g��n1�
�|	d��st%�
|	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�|]}|���V��dSr)�rstrip)�.0�	file_lines  r�	<genexpr>z)setup_mount_dir_cagefs.<locals>.<genexpr>�s.����F�F�y�	�(�(�*�*�F�F�F�F�F�Frc�>��g|]}��|���|��Sr)r5)rbrBr
s  �r�
<listcomp>z*setup_mount_dir_cagefs.<locals>.<listcomp>�s<���
�
�
��x�'<�'<�Q�'?�'?�
�
�
�
�
rr��
� s# next line is added by zutf-8�
�
--remount-allr)N)r^r%r+rSr�
subprocess�callrT�openrr#�seek�replace�write�encoderP�closer=r)r+�added_byr#r\r]r"�remount_cagefs�	cagefs_mp�trim_nl_iter�pre_exist_optionr
s          @r�setup_mount_dir_cagefsry�s����^�T�4��8�<�<�<��7�>�>�,�-�-�9�����7�8�8�8��O�^�\�2�3�3�3�
�'��/�/�I�����
�.�.�3�3�D�9�9��F�F�I�F�F�F��
�
�
�
�#�
�
�
�� �	C��N�N�1�a� � � � �'�'��c�2�2�H��O�O�+�h�o�o�g�.F�.F�F��N�
�
�
�
�O�O�H�M�M�O�O�e�3�4�4�4��O�O�����
C����� A�B�B�B���6�6�7G��7K�L�L�	C�"�8�-=�b�-A�B�B�B�	���������	���������s
�D(G�Gc��ttd��5}|���cddd��S#1swxYwYdS)N�rb)rnr�	readlines)�fs r�_get_cagefs_mp_linesr~�s��	
� �$�	'�	'��1��{�{�}�}���������������������s�7�;�;c��ttd��5}|�|��cddd��S#1swxYwYdS)N�wb)rnr�
writelines)�linesr}s  r�_write_cagefs_mp_linesr��s���	
� �$�	'�	'�#�1��|�|�E�"�"�#�#�#�#�#�#�#�#�#�#�#�#����#�#�#�#�#�#s�8�<�<c���t��}tjdtjtj|��fz����fd�|D��}t
|��|rtjtdg��dSdS)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+)?$c3�F�K�|]}��|���|V��dSr)�match)rb�line�rs  �rrdz*remove_mount_dir_cagefs.<locals>.<genexpr>s2�����L�L��a�g�g�d�m�m�L��L�L�L�L�L�LrrkN)
r~�re�compilerrK�escaper�rlrmrT)r+rur��lines_with_excluded_pathr�s    @r�remove_mount_dir_cagefsr��s����
!�"�"�E�
�
��� 8�"�)�D�/�/�J�J�	�	�A� M�L�L�L��L�L�L���3�4�4�4��;�����9�:�:�:�:�:�;�;r)rVNN)rVNNrT)T)r%r�rlrrT�	ExceptionrrrUr^ryr~r�r�rrr�<module>r�s��
�	�	�	�	�	�	�	�����,��&��	�	�	�	�	�y�	�	�	�g�g�g�g�g�g�g�g�T*�*�*�
'�
'�
'�
'�&
�
�
���V�V�V�V�r���
#�#�#�
;�;�;�;�;�;rdefence360agent/subsys/__pycache__/notifier.cpython-311.opt-1.pyc0000644000000000000000000000664100000000000021575 0ustar  �

����~����dZddlZddlZddlZdZdZdZdZdZdZ	d	Z
d
ZdZdZ
d
ZdZdedededefd�Zdeddfd�Zdedededdfd�Zdd�ZdS)z$Send events via Notification service�Nz/opt/imunify360/lib/event.sockg$@�i�CONFIG_UPDATED�USER_SCAN_STARTED�USER_SCAN_FINISHED�USER_SCAN_MALWARE_FOUND�CUSTOM_SCAN_STARTED�CUSTOM_SCAN_FINISHED�CUSTOM_SCAN_MALWARE_FOUND�SCRIPT_BLOCKED�event_id�user�body�returnc	���tj||tjtj|���d�����d��d���}|�d��}t
|��tkr5td�	t
|��t�����t
|���
td���|zS)Nzutf-8)rr
rz#message size {} exceeds limit of {}�big)�	byteorder)�json�dumps�base64�	b64encode�encode�decode�len�	_MAX_SIZE�	Exception�format�to_bytes�
_LEN_BYTES)rr
r�event�binarys     �T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/notifier.py�_prepare_eventr"s����J� ���$�T�Z��%5�%5�%<�%<�W�%E�%E�F�F�M�M����	
�	
�
�
�E��\�\�'�
"�
"�F�
�6�{�{�Y����1�8�8��F���Y�
�
�
�
�	
�
�v�;�;���
�e��<�<�v�E�E�rc��K�tjt���d{V��\}}	|�|��|����d{V��|���dS#|���wxYw)N)�asyncio�open_unix_connection�SOCKET_PATH�write�drain�close)r�_�writers   r!�_send_eventr-*s������2�;�?�?�?�?�?�?�?�?�I�A�v�����U�����l�l�n�n��������������������������s�/A+�+Bc��K�t|||��}tjt|��t���d{V��dS)z>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�����8�T�4�0�0�E�
�
�;�u�-�-�~�
>�
>�>�>�>�>�>�>�>�>�>r#c��BK�ttdi���d{V��dS)zRSend CONFIG_UPDATED event.

    This forces imunify-notifier to reread its config.�N)r1�CONFIG_UPDATED_EVENT_ID�r#r!�config_updatedr69s3�����/��R�
8�
8�8�8�8�8�8�8�8�8�8r#)rN)�__doc__r%rrr'r0rrr4�USER_SCAN_STARTED_EVENT_ID�USER_SCAN_FINISHED_EVENT_ID� USER_SCAN_MALWARE_FOUND_EVENT_ID�CUSTOM_SCAN_STARTED_EVENT_ID�CUSTOM_SCAN_FINISHED_EVENT_ID�"CUSTOM_SCAN_MALWARE_FOUND_EVENT_ID�SCRIPT_BLOCKED_EVENT_ID�str�dict�bytesr"r-r1r6r5r#r!�<module>rBs��*�*�����
�
�
�
�����.����
�
��	�*��0��2��#<� �4�� 6��%@�"�*��F�S�F��F�4�F�E�F�F�F�F�(�U��t�����?�#�?�S�?��?��?�?�?�?�9�9�9�9�9�9r#defence360agent/subsys/__pycache__/notifier.cpython-311.pyc0000644000000000000000000000664100000000000020636 0ustar  �

����~����dZddlZddlZddlZdZdZdZdZdZdZ	d	Z
d
ZdZdZ
d
ZdZdedededefd�Zdeddfd�Zdedededdfd�Zdd�ZdS)z$Send events via Notification service�Nz/opt/imunify360/lib/event.sockg$@�i�CONFIG_UPDATED�USER_SCAN_STARTED�USER_SCAN_FINISHED�USER_SCAN_MALWARE_FOUND�CUSTOM_SCAN_STARTED�CUSTOM_SCAN_FINISHED�CUSTOM_SCAN_MALWARE_FOUND�SCRIPT_BLOCKED�event_id�user�body�returnc	���tj||tjtj|���d�����d��d���}|�d��}t
|��tkr5td�	t
|��t�����t
|���
td���|zS)Nzutf-8)rr
rz#message size {} exceeds limit of {}�big)�	byteorder)�json�dumps�base64�	b64encode�encode�decode�len�	_MAX_SIZE�	Exception�format�to_bytes�
_LEN_BYTES)rr
r�event�binarys     �T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/notifier.py�_prepare_eventr"s����J� ���$�T�Z��%5�%5�%<�%<�W�%E�%E�F�F�M�M����	
�	
�
�
�E��\�\�'�
"�
"�F�
�6�{�{�Y����1�8�8��F���Y�
�
�
�
�	
�
�v�;�;���
�e��<�<�v�E�E�rc��K�tjt���d{V��\}}	|�|��|����d{V��|���dS#|���wxYw)N)�asyncio�open_unix_connection�SOCKET_PATH�write�drain�close)r�_�writers   r!�_send_eventr-*s������2�;�?�?�?�?�?�?�?�?�I�A�v�����U�����l�l�n�n��������������������������s�/A+�+Bc��K�t|||��}tjt|��t���d{V��dS)z>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�����8�T�4�0�0�E�
�
�;�u�-�-�~�
>�
>�>�>�>�>�>�>�>�>�>r#c��BK�ttdi���d{V��dS)zRSend CONFIG_UPDATED event.

    This forces imunify-notifier to reread its config.�N)r1�CONFIG_UPDATED_EVENT_ID�r#r!�config_updatedr69s3�����/��R�
8�
8�8�8�8�8�8�8�8�8�8r#)rN)�__doc__r%rrr'r0rrr4�USER_SCAN_STARTED_EVENT_ID�USER_SCAN_FINISHED_EVENT_ID� USER_SCAN_MALWARE_FOUND_EVENT_ID�CUSTOM_SCAN_STARTED_EVENT_ID�CUSTOM_SCAN_FINISHED_EVENT_ID�"CUSTOM_SCAN_MALWARE_FOUND_EVENT_ID�SCRIPT_BLOCKED_EVENT_ID�str�dict�bytesr"r-r1r6r5r#r!�<module>rBs��*�*�����
�
�
�
�����.����
�
��	�*��0��2��#<� �4�� 6��%@�"�*��F�S�F��F�4�F�E�F�F�F�F�(�U��t�����?�#�?�S�?��?��?�?�?�?�9�9�9�9�9�9r#defence360agent/subsys/__pycache__/persistent_state.cpython-311.opt-1.pyc0000644000000000000000000000722100000000000023351 0ustar  �

`��(�v���ddlZddlmZddlmZddlmZddlmZddl	m
Z
ee��Zed��Z
e
dzZe��Zd	ed
ee
je
je
jfdefd�Zd
edefd�Zdefd�Zd�ZdS)�N)�	getLogger)�Path)�Literal)�ANTIVIRUS_MODE)�Scopez/var/imunify360z.persistent_state�	lock_file�scope�returnc�>�td|�d�z}|tjkrt�|��nc|tjkr"trt�|��n1|tjkr!tst�|��|S)z%Register lock file for further usage.�.z.lock)�PERSISTENT_STATE_DIRr�AV_IM360�
LOCK_FILES�add�AVr�IM360)rr	�
_lock_files   �\/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/persistent_state.py�register_lock_filers���&�(<�I�(<�(<�(<�<�J���������z�"�"�"�"�	�%�(�	�	�~�	����z�"�"�"�"�	�%�+�	�	�n�	����z�"�"�"����
class_name�valuesc��t}	|�dd���||�d�z}tj||�d����dS#t
tf$r'}t�d||��Yd}~dSd}~wwxYw)z1Save state to a file in .persistent_state folder.T)�parents�exist_ok�.state�wzFailed to save state: %s %sN)	r
�mkdir�json�dump�open�AttributeError�OSError�logger�error)rr�folder_path�	file_path�es     r�
save_stater)s���'�K�C����$���6�6�6��Z�"7�"7�"7�7�	��	�&�)�.�.��-�-�.�.�.�.�.���G�$�C�C�C����2�J��B�B�B�B�B�B�B�B�B�����C���s�AA�B
�#B�B
c�>�t}||�d�z}|���rm	tj|�d����S#tjttf$r&}t�	d||��Yd}~nd}~wwxYwt��S)z3Load state from a file in .persistent_state folder.r�rzFailed to load state: %s %sN)r
�existsr�loadr!�JSONDecodeErrorr#�UnicodeDecodeErrorr$r%�dict)rr&r'r(s    r�
load_stater1*s���'�K���3�3�3�3�I������G�	G��9�Y�^�^�C�0�0�1�1�1���$�g�/A�B�	G�	G�	G��L�L�6�
�A�F�F�F�F�F�F�F�F�����	G�����6�6�Ms�&A�B�(B	�	Bc�z�t�d��D]}|tvr|���� dS)z;Remove all unused lock files from .persistent_state folder.z*.lockN)r
�globr�unlink)rs r�remove_unused_locksr58sG��)�.�.�x�8�8���	��J�&�&���������r)r�loggingr�pathlibr�typingr� defence360agent.contracts.configr�!defence360agent.contracts.pluginsr�__name__r$�BASE_DIRr
�setr�strrrrrr0r)r1r5�rr�<module>r@s:������������������������;�;�;�;�;�;�3�3�3�3�3�3�
��8�	�	���4�!�"�"���"5�5��
�S�U�U�
����"�5�8�U�[�%�.�#H�I��	�����	C�3�	C��	C�	C�	C�	C��d���������rdefence360agent/subsys/__pycache__/persistent_state.cpython-311.pyc0000644000000000000000000000722100000000000022412 0ustar  �

`��(�v���ddlZddlmZddlmZddlmZddlmZddl	m
Z
ee��Zed��Z
e
dzZe��Zd	ed
ee
je
je
jfdefd�Zd
edefd�Zdefd�Zd�ZdS)�N)�	getLogger)�Path)�Literal)�ANTIVIRUS_MODE)�Scopez/var/imunify360z.persistent_state�	lock_file�scope�returnc�>�td|�d�z}|tjkrt�|��nc|tjkr"trt�|��n1|tjkr!tst�|��|S)z%Register lock file for further usage.�.z.lock)�PERSISTENT_STATE_DIRr�AV_IM360�
LOCK_FILES�add�AVr�IM360)rr	�
_lock_files   �\/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/persistent_state.py�register_lock_filers���&�(<�I�(<�(<�(<�<�J���������z�"�"�"�"�	�%�(�	�	�~�	����z�"�"�"�"�	�%�+�	�	�n�	����z�"�"�"����
class_name�valuesc��t}	|�dd���||�d�z}tj||�d����dS#t
tf$r'}t�d||��Yd}~dSd}~wwxYw)z1Save state to a file in .persistent_state folder.T)�parents�exist_ok�.state�wzFailed to save state: %s %sN)	r
�mkdir�json�dump�open�AttributeError�OSError�logger�error)rr�folder_path�	file_path�es     r�
save_stater)s���'�K�C����$���6�6�6��Z�"7�"7�"7�7�	��	�&�)�.�.��-�-�.�.�.�.�.���G�$�C�C�C����2�J��B�B�B�B�B�B�B�B�B�����C���s�AA�B
�#B�B
c�>�t}||�d�z}|���rm	tj|�d����S#tjttf$r&}t�	d||��Yd}~nd}~wwxYwt��S)z3Load state from a file in .persistent_state folder.r�rzFailed to load state: %s %sN)r
�existsr�loadr!�JSONDecodeErrorr#�UnicodeDecodeErrorr$r%�dict)rr&r'r(s    r�
load_stater1*s���'�K���3�3�3�3�I������G�	G��9�Y�^�^�C�0�0�1�1�1���$�g�/A�B�	G�	G�	G��L�L�6�
�A�F�F�F�F�F�F�F�F�����	G�����6�6�Ms�&A�B�(B	�	Bc�z�t�d��D]}|tvr|���� dS)z;Remove all unused lock files from .persistent_state folder.z*.lockN)r
�globr�unlink)rs r�remove_unused_locksr58sG��)�.�.�x�8�8���	��J�&�&���������r)r�loggingr�pathlibr�typingr� defence360agent.contracts.configr�!defence360agent.contracts.pluginsr�__name__r$�BASE_DIRr
�setr�strrrrrr0r)r1r5�rr�<module>r@s:������������������������;�;�;�;�;�;�3�3�3�3�3�3�
��8�	�	���4�!�"�"���"5�5��
�S�U�U�
����"�5�8�U�[�%�.�#H�I��	�����	C�3�	C��	C�	C�	C�	C��d���������rdefence360agent/subsys/__pycache__/svcctl.cpython-311.opt-1.pyc0000644000000000000000000002605200000000000021252 0ustar  �

��'�����4�ddlZddlZddlZddlZddlmZddlmZddl	m
Z
mZmZm
Z
eje��ZdZdZdZdZd	Zd
Zd�Zded
fd�ZGd�d
��ZGd�de��ZGd�de��Zd�Zd�Zd�Zd�Zd�Z d�Z!d�Z"d�Z#d�Z$dS)�N)�Iterable)�Core)�	check_run�
CheckRunError�run�
OsReleaseInfozimunify360-dos-protectionz imunify360-unified-access-loggerzimunify360-pamzimunify-auditd-log-readerzimunify360-scanlogdzimunify360-agentc����fd�}|S)Nc��|�K��|i|��}t�d|��t|���d{V��dS)Nzcheck_call(%r))�logger�debugr)�args�kwargs�cmd�funcs   ��R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/svcctl.py�wrapperz_apply_cmd.<locals>.wrappersT������d�D�#�F�#�#�����%�s�+�+�+���n�n�����������)rrs` r�
_apply_cmdrs#��������
�Nr�services�_SystemctlBasedc��K�|D]�}	|����d{V��|����d{V��n5#t$r(}t�d||��Yd}~dSd}~wwxYwtd��D]T}|����d{V��rn7t�d|d��tjd���d{V���U��dS)Nz/Failed to reset failed state for service %s: %s�
z4Service %s is still not active, sleep for %s seconds�)	�reset_failed�restartrr�warning�range�	is_active�asyncio�sleep)r�s�e�_s    r�_reset_failed_stater%s;�����#�#��	��.�.�"�"�"�"�"�"�"�"�"��)�)�+�+�����������	�	�	��N�N�A�1�a�
�
�
�
�F�F�F�F�F�F�����		����
�r���	#�	#�A��[�[�]�]�"�"�"�"�"�"�
����N�N�F��1�
�
�
��-��"�"�"�"�"�"�"�"�"�"��#�#s�4=�
A/�A*�*A/c���eZdZdZd�Zed���Zed���Zed���Zede	fd���Z
de	fd�Zd	�Zd
�Z
ede	fd���Zed���Zd
�Zed���Zd�ZdS)r�	systemctlc��||_dS�N)�
_service_name)�self�service_names  r�__init__z_SystemctlBased.__init__5s��)����rc� �|jd|jgS)N�start��SVC_CTL_BINr*�r+s rr/z_SystemctlBased.start8s��� �'�4�+=�>�>rc� �|jd|jgS)N�stopr0r2s rr4z_SystemctlBased.stop<s��� �&�$�*<�=�=rc� �|jd|jgS)Nrr0r2s rrz_SystemctlBased.restart@s��� �)�T�-?�@�@r�nowc�0�|jdg|rdgng�|j�S)N�enable�--nowr0�r+r6s  r�_enable_nowz_SystemctlBased._enable_nowDs9��
���
��&�w�i�i�B�
�
��	
�	
rc��jK�|�|����d{V��i}	tj|��n#ttf$rYdSwxYw|�dd�����dkrdS|�dd��dkr|����d{V��dSdS)N)r6�ID��ubuntu�
VERSION_IDz16.04)r;r�dict_from_file�FileNotFoundError�PermissionError�get�lowerr)r+r6�osinfos   rr8z_SystemctlBased.enableMs��������3��'�'�'�'�'�'�'�'�'���	��(��0�0�0�0��!�?�3�	�	�	��F�F�	�����:�:�d�B���%�%�'�'�8�3�3��F��:�:�l�B�'�'�7�2�2��,�,�.�.� � � � � � � � � �3�2s�7�A�Ac���K�|jd|jg}tj|tjtjd���d{V��}|����d{V��|����d{V��}|dkS�Nz
is-enabled��stdout�stderrr)r1r*r �create_subprocess_exec�su�DEVNULL�communicate�wait)r+r�proc�rcs    r�
is_enabledz_SystemctlBased.is_enabled^s��������t�/A�B���3�
���B�J�
�
�
�
�
�
�
�
�
����� � � � � � � � � ��9�9�;�;�
�
�
�
�
�
���Q�w�rc��|jd|jg}tj|tjtj���}|dkSrH)r1r*rM�callrN)r+rrRs   r�is_enabled_syncz_SystemctlBased.is_enabled_syncgs7�����t�/A�B��
�W�S���B�J�
?�
?�
?���Q�w�rc�0�|jdg|rdgng�|j�S)N�disabler9r0r:s  rrXz_SystemctlBased.disablels9��
���
��&�w�i�i�B�
�
��	
�	
rc� �|jd|jgS)N�reloadr0r2s rrZz_SystemctlBased.reloadus��� �(�D�,>�?�?rc��bK�|jd|jg}t|���d{V��\}}}|dkS)Nz	is-activer)r1r*r)r+r�	exit_coder$s    rrz_SystemctlBased.is_activeys?�������d�.@�A�� #�C���.�.�.�.�.�.��	�1�a��A�~�rc� �|jd|jgS)Nzreset-failedr0r2s rrz_SystemctlBased.reset_failed~s��� �.�$�2D�E�Erc��tj|jd|jgtjtj���}|jdkS)N�catrIr)rMrr1r*rN�
returncode)r+�cps  r�unit_existsz_SystemctlBased.unit_exists�sA��
�V�
�
�u�d�&8�9��:��:�
�
�
��
�}��!�!rN)�__name__�
__module__�__qualname__r1r-rr/r4r�boolr;r8rSrVrXrZrrrbrrrrr2s]�������K�*�*�*��?�?��Z�?��>�>��Z�>��A�A��Z�A��
�$�
�
�
��Z�
�!�4�!�!�!�!�"������
�
�d�
�
�
��Z�
��@�@��Z�@����
�F�F��Z�F�"�"�"�"�"rc��eZdZdZdS)�_CentOs7z/usr/bin/systemctlN�rcrdrer1rrrrhrh�s������&�K�K�Krrhc��eZdZdZdS)�
_DebianUbuntuz/bin/systemctlNrirrrrkrk�s������"�K�K�Krrkc��ttfD]3}tj�|j��r
||��cS�4t
d���)Nz'Cannot instantiate appropriate adaptor.)rkrh�os�path�existsr1�RuntimeError)r,�as  r�adaptorrr�sV���X�
&�#�#��
�7�>�>�!�-�(�(�	#��1�\�?�?�"�"�"�	#�
�@�
A�
A�Arc���K�t|��}t|�d���}|����d{V��r�|����d{V��s�|����d{V��t	|f���d{V��td��D]9}t
jd���d{V��|����d{V��rdS�:t�	d|�d|����dSdSdS)Nz.socket�rzFailed to await active z.socket after reseting )
rrrSrrr%rr r!r�error)r,�
agent_service�agent_service_socketr$s    r�activate_socket_servicerx�s������L�)�)�M�"�l�#;�#;�#;�<�<��#�-�-�/�/�/�/�/�/�/�/�
�*�4�4�6�6�6�6�6�6�6�6�
�
#�/�/�1�1�1�1�1�1�1�1�1�!�=�"2�3�3�3�3�3�3�3�3�3��q���	�	�A��-��"�"�"�"�"�"�"�"�"�)�3�3�5�5�5�5�5�5�5�5�
����
�	���
�l�
�
��
�
�	
�	
�	
�	
�	
�
�
�
�
rc�4�ttj��Sr))rrr�SVC_NAMErrr�imunify360_servicer{�s���4�=�!�!�!rc��	tt��S#t$rt�d��YdSwxYw)Nz5DOS Protector service is not available on this system)rr�DOS_PROTECTOR_SERVICE_NAMErpr�inforrr� imunify360_dos_protector_servicer�sK����1�2�2�2���������K�L�L�L��t�t����s��$>�>c�*�tt��Sr))rr�UAL_SERVICE_NAMErrr�imunify360_ual_servicer������#�$�$�$rc�*�tt��Sr))rr�PAM_SERVICE_NAMErrr�imunify360_pam_servicer��r�rc�*�tt��Sr))rr�SCANLOGD_SERVICE_NAMErrr�imunify360_scanlogd_servicer��s���(�)�)�)rc�*�tt��Sr))rr�AGENT_SERVICE_NAMErrr�imunify360_agent_servicer��s���%�&�&�&rc��tt��}|���rtt��St�d��dS)Nz9Auditd-log-reader service is not available on this system)rr�AUDITD_SERVICE_NAMErbrr~)�units r�imunify360_auditd_servicer��sK���&�'�'�D������,��*�+�+�+�
�K�K�K�L�L�L��4r)%r �loggingrm�
subprocessrM�typingr� defence360agent.contracts.configr�defence360agent.utilsrrrr�	getLoggerrcrr}r�r�r�r�r�rr%rrhrkrrrxr{rr�r�r�r�r�rrr�<module>r�s�����������	�	�	�	�����������1�1�1�1�1�1�N�N�N�N�N�N�N�N�N�N�N�N�	��	�8�	$�	$��8��5��#��1��-��'�����#��(�)�#�#�#�#�*V"�V"�V"�V"�V"�V"�V"�V"�r'�'�'�'�'��'�'�'�#�#�#�#�#�O�#�#�#�B�B�B�
�
�
�0"�"�"����%�%�%�%�%�%�*�*�*�'�'�'�����rdefence360agent/subsys/__pycache__/svcctl.cpython-311.pyc0000644000000000000000000002605200000000000020313 0ustar  �

��'�����4�ddlZddlZddlZddlZddlmZddlmZddl	m
Z
mZmZm
Z
eje��ZdZdZdZdZd	Zd
Zd�Zded
fd�ZGd�d
��ZGd�de��ZGd�de��Zd�Zd�Zd�Zd�Zd�Z d�Z!d�Z"d�Z#d�Z$dS)�N)�Iterable)�Core)�	check_run�
CheckRunError�run�
OsReleaseInfozimunify360-dos-protectionz imunify360-unified-access-loggerzimunify360-pamzimunify-auditd-log-readerzimunify360-scanlogdzimunify360-agentc����fd�}|S)Nc��|�K��|i|��}t�d|��t|���d{V��dS)Nzcheck_call(%r))�logger�debugr)�args�kwargs�cmd�funcs   ��R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/svcctl.py�wrapperz_apply_cmd.<locals>.wrappersT������d�D�#�F�#�#�����%�s�+�+�+���n�n�����������)rrs` r�
_apply_cmdrs#��������
�Nr�services�_SystemctlBasedc��K�|D]�}	|����d{V��|����d{V��n5#t$r(}t�d||��Yd}~dSd}~wwxYwtd��D]T}|����d{V��rn7t�d|d��tjd���d{V���U��dS)Nz/Failed to reset failed state for service %s: %s�
z4Service %s is still not active, sleep for %s seconds�)	�reset_failed�restartrr�warning�range�	is_active�asyncio�sleep)r�s�e�_s    r�_reset_failed_stater%s;�����#�#��	��.�.�"�"�"�"�"�"�"�"�"��)�)�+�+�����������	�	�	��N�N�A�1�a�
�
�
�
�F�F�F�F�F�F�����		����
�r���	#�	#�A��[�[�]�]�"�"�"�"�"�"�
����N�N�F��1�
�
�
��-��"�"�"�"�"�"�"�"�"�"��#�#s�4=�
A/�A*�*A/c���eZdZdZd�Zed���Zed���Zed���Zede	fd���Z
de	fd�Zd	�Zd
�Z
ede	fd���Zed���Zd
�Zed���Zd�ZdS)r�	systemctlc��||_dS�N)�
_service_name)�self�service_names  r�__init__z_SystemctlBased.__init__5s��)����rc� �|jd|jgS)N�start��SVC_CTL_BINr*�r+s rr/z_SystemctlBased.start8s��� �'�4�+=�>�>rc� �|jd|jgS)N�stopr0r2s rr4z_SystemctlBased.stop<s��� �&�$�*<�=�=rc� �|jd|jgS)Nrr0r2s rrz_SystemctlBased.restart@s��� �)�T�-?�@�@r�nowc�0�|jdg|rdgng�|j�S)N�enable�--nowr0�r+r6s  r�_enable_nowz_SystemctlBased._enable_nowDs9��
���
��&�w�i�i�B�
�
��	
�	
rc��jK�|�|����d{V��i}	tj|��n#ttf$rYdSwxYw|�dd�����dkrdS|�dd��dkr|����d{V��dSdS)N)r6�ID��ubuntu�
VERSION_IDz16.04)r;r�dict_from_file�FileNotFoundError�PermissionError�get�lowerr)r+r6�osinfos   rr8z_SystemctlBased.enableMs��������3��'�'�'�'�'�'�'�'�'���	��(��0�0�0�0��!�?�3�	�	�	��F�F�	�����:�:�d�B���%�%�'�'�8�3�3��F��:�:�l�B�'�'�7�2�2��,�,�.�.� � � � � � � � � �3�2s�7�A�Ac���K�|jd|jg}tj|tjtjd���d{V��}|����d{V��|����d{V��}|dkS�Nz
is-enabled��stdout�stderrr)r1r*r �create_subprocess_exec�su�DEVNULL�communicate�wait)r+r�proc�rcs    r�
is_enabledz_SystemctlBased.is_enabled^s��������t�/A�B���3�
���B�J�
�
�
�
�
�
�
�
�
����� � � � � � � � � ��9�9�;�;�
�
�
�
�
�
���Q�w�rc��|jd|jg}tj|tjtj���}|dkSrH)r1r*rM�callrN)r+rrRs   r�is_enabled_syncz_SystemctlBased.is_enabled_syncgs7�����t�/A�B��
�W�S���B�J�
?�
?�
?���Q�w�rc�0�|jdg|rdgng�|j�S)N�disabler9r0r:s  rrXz_SystemctlBased.disablels9��
���
��&�w�i�i�B�
�
��	
�	
rc� �|jd|jgS)N�reloadr0r2s rrZz_SystemctlBased.reloadus��� �(�D�,>�?�?rc��bK�|jd|jg}t|���d{V��\}}}|dkS)Nz	is-activer)r1r*r)r+r�	exit_coder$s    rrz_SystemctlBased.is_activeys?�������d�.@�A�� #�C���.�.�.�.�.�.��	�1�a��A�~�rc� �|jd|jgS)Nzreset-failedr0r2s rrz_SystemctlBased.reset_failed~s��� �.�$�2D�E�Erc��tj|jd|jgtjtj���}|jdkS)N�catrIr)rMrr1r*rN�
returncode)r+�cps  r�unit_existsz_SystemctlBased.unit_exists�sA��
�V�
�
�u�d�&8�9��:��:�
�
�
��
�}��!�!rN)�__name__�
__module__�__qualname__r1r-rr/r4r�boolr;r8rSrVrXrZrrrbrrrrr2s]�������K�*�*�*��?�?��Z�?��>�>��Z�>��A�A��Z�A��
�$�
�
�
��Z�
�!�4�!�!�!�!�"������
�
�d�
�
�
��Z�
��@�@��Z�@����
�F�F��Z�F�"�"�"�"�"rc��eZdZdZdS)�_CentOs7z/usr/bin/systemctlN�rcrdrer1rrrrhrh�s������&�K�K�Krrhc��eZdZdZdS)�
_DebianUbuntuz/bin/systemctlNrirrrrkrk�s������"�K�K�Krrkc��ttfD]3}tj�|j��r
||��cS�4t
d���)Nz'Cannot instantiate appropriate adaptor.)rkrh�os�path�existsr1�RuntimeError)r,�as  r�adaptorrr�sV���X�
&�#�#��
�7�>�>�!�-�(�(�	#��1�\�?�?�"�"�"�	#�
�@�
A�
A�Arc���K�t|��}t|�d���}|����d{V��r�|����d{V��s�|����d{V��t	|f���d{V��td��D]9}t
jd���d{V��|����d{V��rdS�:t�	d|�d|����dSdSdS)Nz.socket�rzFailed to await active z.socket after reseting )
rrrSrrr%rr r!r�error)r,�
agent_service�agent_service_socketr$s    r�activate_socket_servicerx�s������L�)�)�M�"�l�#;�#;�#;�<�<��#�-�-�/�/�/�/�/�/�/�/�
�*�4�4�6�6�6�6�6�6�6�6�
�
#�/�/�1�1�1�1�1�1�1�1�1�!�=�"2�3�3�3�3�3�3�3�3�3��q���	�	�A��-��"�"�"�"�"�"�"�"�"�)�3�3�5�5�5�5�5�5�5�5�
����
�	���
�l�
�
��
�
�	
�	
�	
�	
�	
�
�
�
�
rc�4�ttj��Sr))rrr�SVC_NAMErrr�imunify360_servicer{�s���4�=�!�!�!rc��	tt��S#t$rt�d��YdSwxYw)Nz5DOS Protector service is not available on this system)rr�DOS_PROTECTOR_SERVICE_NAMErpr�inforrr� imunify360_dos_protector_servicer�sK����1�2�2�2���������K�L�L�L��t�t����s��$>�>c�*�tt��Sr))rr�UAL_SERVICE_NAMErrr�imunify360_ual_servicer������#�$�$�$rc�*�tt��Sr))rr�PAM_SERVICE_NAMErrr�imunify360_pam_servicer��r�rc�*�tt��Sr))rr�SCANLOGD_SERVICE_NAMErrr�imunify360_scanlogd_servicer��s���(�)�)�)rc�*�tt��Sr))rr�AGENT_SERVICE_NAMErrr�imunify360_agent_servicer��s���%�&�&�&rc��tt��}|���rtt��St�d��dS)Nz9Auditd-log-reader service is not available on this system)rr�AUDITD_SERVICE_NAMErbrr~)�units r�imunify360_auditd_servicer��sK���&�'�'�D������,��*�+�+�+�
�K�K�K�L�L�L��4r)%r �loggingrm�
subprocessrM�typingr� defence360agent.contracts.configr�defence360agent.utilsrrrr�	getLoggerrcrr}r�r�r�r�r�rr%rrhrkrrrxr{rr�r�r�r�r�rrr�<module>r�s�����������	�	�	�	�����������1�1�1�1�1�1�N�N�N�N�N�N�N�N�N�N�N�N�	��	�8�	$�	$��8��5��#��1��-��'�����#��(�)�#�#�#�#�*V"�V"�V"�V"�V"�V"�V"�V"�r'�'�'�'�'��'�'�'�#�#�#�#�#�O�#�#�#�B�B�B�
�
�
�0"�"�"����%�%�%�%�%�%�*�*�*�'�'�'�����rdefence360agent/subsys/__pycache__/sysctl.cpython-311.opt-1.pyc0000644000000000000000000000315300000000000021272 0ustar  �

��pz0sA�� �ddlZd�Zd�Zd�ZdS)�Nc�n�tjjtjddg|�d���R�S)N�proc�sys�.)�os�path�join�sep�split)�names �R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/sysctl.py�_build_pathrs,��
�7�<�����@��
�
�3���@�@�@�@�c��tt|����5}|������}|jrt|��cddd��S|cddd��S#1swxYwYdS)N)�openr�read�strip�isdigit�int)r�f�datas   r
rrs���	
�k�$���	 �	 ��A��v�v�x�x�~�~�����<�	��t�9�9���������
���������������������s�<A4�&A4�4A8�;A8c��tt|��d��5}|�t|����ddd��dS#1swxYwYdS)N�w)rr�write�str)r�valuers   r
rrs���	
�k�$����	%�	%���	����E�
�
������������������������s�#A�A�A)rrrr�rr
�<module>rsH��	�	�	�	�A�A�A��������rdefence360agent/subsys/__pycache__/sysctl.cpython-311.pyc0000644000000000000000000000315300000000000020333 0ustar  �

��pz0sA�� �ddlZd�Zd�Zd�ZdS)�Nc�n�tjjtjddg|�d���R�S)N�proc�sys�.)�os�path�join�sep�split)�names �R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/sysctl.py�_build_pathrs,��
�7�<�����@��
�
�3���@�@�@�@�c��tt|����5}|������}|jrt|��cddd��S|cddd��S#1swxYwYdS)N)�openr�read�strip�isdigit�int)r�f�datas   r
rrs���	
�k�$���	 �	 ��A��v�v�x�x�~�~�����<�	��t�9�9���������
���������������������s�<A4�&A4�4A8�;A8c��tt|��d��5}|�t|����ddd��dS#1swxYwYdS)N�w)rr�write�str)r�valuers   r
rrs���	
�k�$����	%�	%���	����E�
�
������������������������s�#A�A�A)rrrr�rr
�<module>rsH��	�	�	�	�A�A�A��������rdefence360agent/subsys/__pycache__/systemd_notifier.cpython-311.opt-1.pyc0000644000000000000000000000531600000000000023343 0ustar  �

���=u(G0��~�dZddlZddlZddlZddlmZeje��Zda	da
Gd�de��Zd�Z
d�ZdS)	z"Notify systemd about process state�N)�ANTIVIRUS_MODEFc�"�eZdZdZdZdZdZdZdS)�
AgentStatez*Allowed agent state for notifying systemd.zREADY=1zSTATUS=Starting main processz#STATUS=Applying database migrationszSTATUS=DemonizedN)�__name__�
__module__�__qualname__�__doc__�READY�STARTING�	MIGRATING�
DAEMONIZED���\/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/systemd_notifier.pyrrs(������4�4��E�-�H�5�I�#�J�J�Jrrc�b�ts"tj�dd��adatS)N�
NOTIFY_SOCKETT)�_socket_detached�os�environ�pop�_notify_socket_addrrrr�_take_notify_socketrs.��
� � �j�n�n�_�d�C�C�����rc��trdSt��}|sdS|�d��r
d|dd�zn|}	tjtjtjtjz��5}|�|��|�|�	����ddd��dS#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�
startswith�socket�AF_UNIX�
SOCK_DGRAM�SOCK_CLOEXEC�connect�sendall�encode�OSError�logger�	exception)�state�addr�connect_addr�sock�es     r�notifyr-#se������� � �D�����'+�o�o�c�&:�&:�D�4�$�q�r�r�(�?�?��L�

�
�]��N�F�-��0C�C�
�
�	)�
��L�L��&�&�&��L�L������(�(�(�		)�	)�	)�	)�	)�	)�	)�	)�	)�	)�	)�	)����	)�	)�	)�	)�	)�	)��
�
�
�
����G�
�	
�	
�	
�	
�	
�	
�	
�	
�	
�����
���s<�6C�7=C�4C�C�C�C�	C�
C>�C9�9C>)r	�loggingrr� defence360agent.contracts.configr�	getLoggerrr&rr�objectrrr-rrr�<module>r2s���(�(�����	�	�	�	�
�
�
�
�;�;�;�;�;�;�
��	�8�	$�	$������$�$�$�$�$��$�$�$����
�
�
�
�
rdefence360agent/subsys/__pycache__/systemd_notifier.cpython-311.pyc0000644000000000000000000000531600000000000022404 0ustar  �

���=u(G0��~�dZddlZddlZddlZddlmZeje��Zda	da
Gd�de��Zd�Z
d�ZdS)	z"Notify systemd about process state�N)�ANTIVIRUS_MODEFc�"�eZdZdZdZdZdZdZdS)�
AgentStatez*Allowed agent state for notifying systemd.zREADY=1zSTATUS=Starting main processz#STATUS=Applying database migrationszSTATUS=DemonizedN)�__name__�
__module__�__qualname__�__doc__�READY�STARTING�	MIGRATING�
DAEMONIZED���\/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/systemd_notifier.pyrrs(������4�4��E�-�H�5�I�#�J�J�Jrrc�b�ts"tj�dd��adatS)N�
NOTIFY_SOCKETT)�_socket_detached�os�environ�pop�_notify_socket_addrrrr�_take_notify_socketrs.��
� � �j�n�n�_�d�C�C�����rc��trdSt��}|sdS|�d��r
d|dd�zn|}	tjtjtjtjz��5}|�|��|�|�	����ddd��dS#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�
startswith�socket�AF_UNIX�
SOCK_DGRAM�SOCK_CLOEXEC�connect�sendall�encode�OSError�logger�	exception)�state�addr�connect_addr�sock�es     r�notifyr-#se������� � �D�����'+�o�o�c�&:�&:�D�4�$�q�r�r�(�?�?��L�

�
�]��N�F�-��0C�C�
�
�	)�
��L�L��&�&�&��L�L������(�(�(�		)�	)�	)�	)�	)�	)�	)�	)�	)�	)�	)�	)����	)�	)�	)�	)�	)�	)��
�
�
�
����G�
�	
�	
�	
�	
�	
�	
�	
�	
�	
�����
���s<�6C�7=C�4C�C�C�C�	C�
C>�C9�9C>)r	�loggingrr� defence360agent.contracts.configr�	getLoggerrr&rr�objectrrr-rrr�<module>r2s���(�(�����	�	�	�	�
�
�
�
�;�;�;�;�;�;�
��	�8�	$�	$������$�$�$�$�$��$�$�$����
�
�
�
�
rdefence360agent/subsys/__pycache__/web_server.cpython-311.opt-1.pyc0000644000000000000000000012405000000000000022114 0ustar  �

O��#�)�
���ddlZddlZddlZddlZddlZddlZddlZddlZddlZddl	Z	ddl
mcmZ
ddlmZddlmZddlmZddlmZddlmZddlmZmZmZmZddlmZmZm Z m!Z!m"Z"m#Z#m$Z$ddl%Z%dd	l&m'Z'dd
l(m)Z)m*Z*ddl+m,Z,ddl-m.Z.m/Z/m0Z0m1Z1m2Z2m3Z3m4Z4m5Z5dd
l6m7Z7e8ej9�:dd����Z;	dZ<ed��Z=dZ>dZ?dZ@dZAdZBdZCejDd��ZEeFd�eGe	jH��D����ZIdZJejKeL��ZMGd�deN��ZOGd�deN��ZPGd�d ��ZQd!�ZRd"�ZSd#eTfd$�ZUd#e!eTfd%�ZVd#eWfd&�ZXeY��d'�d(�ZZd)�Z[d*�Z\d+�Z]		dXd-egefd.e8fd/�Z^d0�Z_d#e eTfd1�Z`d#e!e$eTfd2�Zad3Zbejcd4�5��d#edfd6���Zed7edd#e eTfd8�ZfdYd7edd#e$eTfd:�Zgd#edfd;�Zhd#e!eTfd<�ZidZd7edd#e$eTfd>�Zjd#e$eTfd?�Zked@��ZldAeTd#edfdB�Zmd[dC�Zne7joe;��d[dD���Zpd[dE�ZqdF�ZrdG�Zsd#edfdH�Ztd#edfdI�Zud\dJ�Zvd#edfdK�ZwdYdL�ZxdM�ZydNeWd#e eWfdO�ZzdP�Z{dQ�Z|e.d4�5��dR���Z}e4ee8ej9�:dSdT�����U���V��dW���Z~dS)]�N)�suppress)�
ContextVar)�	timedelta)�Version)�Path)�CalledProcessError�
check_call�check_output�DEVNULL)�Any�Callable�List�Optional�Set�Tuple�Iterable)�IntegrationConfig)�is_generic_panel_installed�is_plesk_installed)�g)�async_lru_cache�atomic_rewrite�	check_run�get_system_user_names�
OsReleaseInfo�
CheckRunError�
TimedCache�BACKUP_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�|]}|���V��dS�N)�encode)�.0�xs  �V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/web_server.py�	<genexpr>r*:s*����@�@�1�A�H�H�J�J�@�@�@�@�@�@��apachec��eZdZdZdS)�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.@s���������r+r.c��eZdZdZdS)�ConfigInvalidErrorzO
    Error used to indicate that the web server config is having error(s).
    Nr/r4r+r)r6r6Hs���������r+r6c��eZdZdZdZdZdZdZdZdZ	dZ
d	�Zd
efd�Z
defd
�Zd
eeeeffd�Zd�Zd
efd�ZdS)�LiteSpeedConfig�useIpInProxyHeader�security�
accessControl�allow�denyr��c�8�tj|��|_dSr%)�ET�
fromstring�config)�self�contents  r)�__init__zLiteSpeedConfig.__init__Xs���m�G�,�,����r+�returnc��|j�|j��}|�|js|jSt|j��Sr%)rC�find�CLIENT_IP_IN_HEADER_TAG�text�CLIENT_IP_IN_HEADER_DISABLED�int�rD�elements  r)�client_ip_in_headerz#LiteSpeedConfig.client_ip_in_header[s>���+�"�"�4�#?�@�@���?�'�,�?��4�4��7�<� � � r+�valuec���|j�|j��}|�3tj|j��}|j�|��t
|��|_dSr%)rCrIrJrA�Element�append�strrK)rDrQrOs   r)�set_client_ip_in_headerz'LiteSpeedConfig.set_client_ip_in_headerasX���+�"�"�4�#?�@�@���?��j��!=�>�>�G��K���w�'�'�'��5�z�z����r+c��|j�d�d|j|j|jg����}|�*|jr#d�|j���D��St��S)N�/�.c��h|]R}|�d��D]:}|�|�d��r
|dd�n||�d��f��;�SS)�,�TN���)�split�endswith)r'�s�items   r)�	<setcomp>z>LiteSpeedConfig.access_control_allowed_list.<locals>.<setcomp>ts}�������G�G�C�L�L�����	�"�m�m�C�0�0�:��c�r�c���d�D�M�M�#�<N�<N�O����r+)	rCrI�join�SECURITY_TAG�ACCESS_CONTROL_TAG�ACCESS_CONTROL_ALLOWED_TAGrKr^�setrNs  r)�access_control_allowed_listz+LiteSpeedConfig.access_control_allowed_lisths����+�"�"��H�H���%��+��3�	�
�
�	
�	
����7�<���� ��+�+�-�-����
��u�u�r+c��d�|D��}d�|��}|j�d�d|j|j|jg����}|��t
j|j��}|j�d�d|j|jg����}|��t
j|j��}|j�|j��}|�3t
j|j��}|j�|��|�|��|�|��||_	dS)Nc�D�g|]}|dr|ddzn|d��S)r>rr\r4)r'ras  r)�
<listcomp>zCLiteSpeedConfig.set_access_control_allowed_list.<locals>.<listcomp>}s1��K�K�K�4�$�q�'�6��a��3���t�A�w�K�K�Kr+r[rXrY)
rcrCrIrdrerfrArSrTrK)rD�allowed�itemsrQrO�access_controlr:s       r)�set_access_control_allowed_listz/LiteSpeedConfig.set_access_control_allowed_list|sL��K�K�7�K�K�K����������+�"�"��H�H���%��+��3�	�
�
�	
�	
���?��j��!@�A�A�G�!�[�-�-������)��/������N��%�!#��D�,C�!D�!D���;�+�+�D�,=�>�>���#�!�z�$�*;�<�<�H��K�&�&�x�0�0�0�����/�/�/��!�!�'�*�*�*�����r+c��tj��}tj|j��}|�|dd���|���S)Nzutf-8T)�encoding�xml_declaration)�io�BytesIOrA�ElementTreerC�write�getvalue)rD�buf�trees   r)�tostringzLiteSpeedConfig.tostring�sD���j�l�l���~�d�k�*�*���
�
�3��$�
�?�?�?��|�|�~�~�r+N)r0r1r2rJrdrerf�ACCESS_CONTROL_DENIED_TAGrL�CLIENT_IP_IN_HEADER_ENABLED�#CLIENT_IP_IN_HEADER_TRUSTED_IP_ONLYrFrMrPrVrrrU�boolrhro�bytesrzr4r+r)r8r8Ns�������2���L�(��!(�� &��#$� �"#��*+�'�-�-�-�!�S�!�!�!�!�"�S�"�"�"�"��S��s�D�y�1A�-B�����( � � �D�%������r+r8c��ttt��5tt�����cddd��S#1swxYwYdS)z3Return LiteSpeed's pid or None if it can't be read.N)r�OSError�
ValueErrorrM�LITESPEED_PID_FILE_PATH�
read_bytesr4r+r)�_get_litespeed_pidr��s���	�'�:�	&�	&�9�9��*�5�5�7�7�8�8�9�9�9�9�9�9�9�9�9�9�9�9����9�9�9�9�9�9s�&A�A�Ac��t��}	t|otj|����S#t$rYdSwxYw)zb
    Litespeed use constant PID file path, so using it to determinate status
    :return bool
    F)r�r~�psutil�
pid_exists�
OverflowError)�pids r)�litespeed_runningr��sT��
�
�
�C���C�2�F�-�c�2�2�3�3�3�������u�u����s�"3�
A�ArGc�8�tjd��ptS)N�	litespeed)�shutil�which�LITESPEED_BIN_PATHr4r+r)�_litespeed_binr��s���<��$�$�:�(:�:r+c�6�t��}|r|dndS)z�
    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
    �	httpd_binN)�_apache_running_process)�infos r)�apache_runningr��s$��#�$�$�D� $�.�4����$�.r+c���K�t��}|std���	tj��tjzrqtd�����rPtd�tj
|��tj|����d����d{V��}nt|g|����d{V��}n+#t$rt�d��YdSwxYw|S)N�Apache is not runningz/etc/apache2/envvarsz. /etc/apache2/envvars && {} {}T)�shellzApache doesn't work properlyr+)r�r.r�id_like�DEBIANr�existsr�format�shlex�quotercr�logger�warning)�argsr��stdouts   r)�apache_binary_callr��s0����� � �I��7��5�6�6�6���!�#�#�m�&:�:�
	9��+�,�,�3�3�5�5�
	9�%�1�8�8��K�	�*�*�E�J�t�,<�,<����	���������F�F�%�i�%7�$�%7�8�8�8�8�8�8�8�8�F����������5�6�6�6��s�s������Ms�B)C
�
$C5�4C5��
exclude_usersc���d�}tj��tjzr t��s
|��rt}nt
}t
t����|z
}t|��}|rm|d�J�||d<	|d}tj
�||��r|Sn2#t$r%}t�d|��Yd}~nd}~wwxYwdS)z�
    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
    c�^�t��rtjdd��tkSdS)N�
web_server�server_typeF)rr�get�APACHEr4r+r)�is_generic_panel_on_apachez;_apache_running_process.<locals>.is_generic_panel_on_apache�s.��%�'�'�	P�$�(��}�E�E��O�O��ur+�exeNr�z#Can't determine apache bin path: %s)rr�r�r�APACHE2_BIN_PATH�HTTPD_BIN_PATHrgr�_apache_running_process_info�os�path�samefiler�r�r�)r�r�r��	sys_usersr��httpd_process_exe�excs       r)r�r��s#��"���
	����-�"6�6�#����#� :� :� <� <�#�%�	�	�"�	��)�+�+�,�,�}�<�I�'�	�2�2�D��D��E�{�&�&�&�%��[��	D� $�U����w���	�+<�=�=�
���
���	D�	D�	D��K�K�=�s�C�C�C�C�C�C�C�C�����	D�����4s�
)B5�5
C$�?C�C$c	����td��D]a}tt��5t�fd�t	jgd����D��d��cddd��cS#1swxYwY�bdS)z#Retry process_iter() on IndexError.r?c3��K�|]G}|jd�8|jd�d��r|jd�v�>|jV��HdS)r�N)z/httpdz/apache2�username�r�r_)r'�pr�s  �r)r*z/_apache_running_process_info.<locals>.<genexpr>sp�����
�
����u�
�1��F�5�M�2�2�3I�J�J�2��F�:�.�)�;�;��F�<�;�;�;�
�
r+)�namer�r��uids�gids��attrsN)�ranger�
IndexError�nextr��process_iter)r��_s` r)r�r�
s����
�1�X�X����
�j�
!�
!�	�	��
�
�
�
�#�0�I�I�I����
�
�
��
�
�	�	�	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	��s�1A'�'A+	�.A+	c��tdh���}|std���tj||dd|dd��dS)z&Make web server user/group own *path*.�rootr�z5Can't find running apache process without root owner.r�rr�N)r�r.r��chown)r�r�s  r)r�r�s]��"�&��:�:�:�D��
��C�
�
�	
��H�T�4��<��?�D��L��O�4�4�4�4�4r+c�`�td�tjgd����D��d��S)z;Return path to a running nginx binary or None if not found.c3��K�|]i}|jd�Z|jd�d��r:|jd�-d|jdvr|jddv�Z|jdV��jdS)r�N�nginxr�r�)r�zwww-datar�)r'r�s  r)r*z%find_running_nginx.<locals>.<genexpr>+s�����
	
�
	
����v��*��F�6�N�+�+�G�4�4�+��F�5�M�-��q�v�e�}�,�,��F�:�&�*?�?�?�
�F�5�M�@�?�?�?�
	
�
	
r+)r�r�r�r�N)r�r�r�r4r+r)�find_running_nginxr�(sJ���
	
�
	
��(�/J�/J�/J�K�K�K�
	
�
	
�
	
�	
�
�
�
r+�
�webserver_running_cb�granularityc��K�|dksJ�t|��D]/}|��}|r|cStj||z���d{V���0|S)Nr)r��asyncio�sleep)r��timeout_secr�r��results     r)�check_with_timeoutr�:sz����
��?�?�?�?�
�;�
�
����%�%�'�'���	��M�M�M��m�K�+�5�6�6�6�6�6�6�6�6�6�6��
r+c�@�tj�d��S)z8
    though, available != running
    :return bool:
    z/etc/cpanel/ea4/is_ea4)r�r��isfiler4r+r)�is_EA4_availabler�Js��
�7�>�>�2�3�3�3r+c���tjt��}|r|gStj��tjzr#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.
    �	systemctl�reloadz--job-mode=replace-irreversiblyz-k�graceful)	r�r��CPANEL_RESTART_APACHE_SCRIPTrr�r�r�r��basename)�	apachectl�restartsrv_httpds  r)�_apache_graceful_restart_cmdr�Rsv���|�$@�A�A���"� �!�!������!5�5�	-�
��-��G���Y�'�'�	
�	
��4��,�,r+c��tj��r�	tj��dd}|st�d��dS|���}tj�|d��r|St�d|��n*#t$rt�d��YnwxYwdS)Nr��graceful_restart_scriptz'graceful_restart_script option is emptyrz,Web server restart script does not exist: %sz;Integration config is missing graceful_restart_script field)	rr��to_dictr�r�r^r�r��KeyError)�restart_script�cmds  r)�+_graceful_restart_cmd_from_integration_confr�ls�����!�!��	�.�6�8�8��F�)��N�"�
����=�����t� �&�&�(�(�C��w�~�~�c�!�f�%�%�
��
��N�N�>��
�
�
�
���	�	�	��N�N�M�
�
�
�
�
�	����"�4s�B)�)$C�C��r>)�maxsizec�B�tjd��}|sdS	t|dgt������}n#t
tf$rYdSwxYwtjd|��}|duo*t|�
d����tkS)N�systemd-runFz	--version)�stderrzsystemd\s+(\d+)r>)r�r�r
r�decoder�r�re�searchrM�group�_SYSTEMD_RUN_WAIT_MIN_VERSION)�systemd_run�out�matchs   r)�_systemd_run_supports_waitr��s����,�}�-�-�K����u���K��5�g�F�F�F�M�M�O�O�����'�(�����u�u������I�(�#�.�.�E������E�K�K��N�N���<�<�s�*A�A�A�waitc��g}tjd��x}rC||dddgz
}|r#t��r|�d��|�d��|S)Nr�z-pzSendSIGKILL=noz--slice=graceful_restartz--waitz--)r�r�r�rT)r��prefixr�s   r)�_systemd_run_prefixr��s|���F��l�=�1�1�1�{�	�����&�	
�	
���	$�.�0�0�	$��M�M�(�#�#�#��
�
�d�����Mr+Fc��t|��}t��}|�|t|��zSt��r|tt��zSt��x}r|t
|��zStd���)z Gracefully restart a web server.N�Could not detect a web server)r�r��listr��LITESPEED_RESTART_CMDr�r��RuntimeError)r�r�r�r�s    r)�_graceful_restart_cmdr�s���
 ��
&�
&�F�
5�
7�
7�C�
����S�	�	�!�!����4���2�3�3�3�3�"�$�$�$�y�@��4�Y�?�?�?�?�
�6�
7�
7�7r+c�J�tj�t��Sr%)r�r�r��LITESPEED_CONF_PATHr4r+r)�_litespeed_installedr�s��
�7�>�>�-�.�.�.r+c��d}t��r7	tjdd��}n#t$rYdSwxYw|tkrdSd}tj��t
jzr4t��s|r$tj
�t��Stj
�t��S)u�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.Fr�r�NT)rrr�r�r�rr�r�rr�r�r�r�r�)�on_generic_apacher�s  r)�_apache_systemd_unitr�s�����!�#�#�!�	�+�/��m�L�L�K�K���	�	�	��4�4�	�����&� � ��4� ������-�"6�6�2����2� 1�2��w��� 0�1�1�1�
�7���N�+�+�+s�(�
6�6Tc��t|��}t��r|tt��zSt	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 serverr�r#)	r�rr��LITESPEED_HARD_RESTART_CMDr�r�r�rr)r�r�r��units    r)�_hard_restart_cmdr�s���!��
&�
&�F����9���7�8�8�8�8�!�<�(D�E�E�E��8��)�;�7�7�7��!�!�D��|��M�N�N�N��[�)�T�2�2�2r+c��t��r=	tjdd��}|r|���Sn#t$rYnwxYwt��x}r(t
j��tjzrddgS|dgSt��rt��dgSt��x}r|dgStd���)Nr��config_test_scriptr��
configtest�-tr�)
rrr�r^r�r�rr�r�r�r�r�r)r��
apache_bin�	nginx_bins   r)�_configtest_cmdr�s���!�#�#��	�#�'��6J�K�K�C��
#��y�y�{�{�"�
#���	�	�	��D�	����$�%�%�%�z�!�� �"�"�]�%9�9�	/���.�.��D�!�!�	�	�	�!�� � �$�'�'�(�*�*�	*��!��4� � �
�6�
7�
7�7s�*<�
A	�A	�graceful_restart_caller�
new_configc������	�
��K�tj���tz��fd��
tj����}t�||���sdS��fd��	t
d����d{V��	t���n=#t$r0}t�
d|�����Yd}~dSd}~wwxYwtj���	�	�
��fd	�}tjt|�
��t ��}t#j��d}t&�|j��}	|����d{V��t&�|��n#t&�|��wxYwt�d��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.
    c���tt��5tj���ddd��dS#1swxYwYdSr%)r�FileNotFoundErrorr��unlink)�config_backup_paths�r)�
remove_backupz)safe_update_config.<locals>.remove_backups����
�'�
(�
(�	*�	*��I�(�)�)�)�	*�	*�	*�	*�	*�	*�	*�	*�	*�	*�	*�	*����	*�	*�	*�	*�	*�	*s�8�<�<)�backupTc���	tj����dS#t$r&t�d�����YdSwxYw)N�w)r��renamer�open�close)r�config_paths��r)�revertz"safe_update_config.<locals>.revert"sa���	+��I�(�+�6�6�6�6�6�� �	+�	+�	+���c�"�"�(�(�*�*�*�*�*�*�	+���s��,A
�	A
��raise_exceptionNz*Failed to get graceful restart command: %sFc����d�}d�}|���s�|�����t�d|����������td�����}|�|����t�����}|�|��dS���dS)Nc��|���sD|����2t�d|������dSdSdS)Nz'The reverted config seems to be invalid��exc_info��	cancelled�	exceptionr��critical��futs r)�log_config_errorzFsafe_update_config.<locals>.restart_callback.<locals>.log_config_error9sb���}�}����3�=�=�?�?�+F��O�O�A�!$�����$��������+F�+Fr+c��|���sD|����2t�d|������dSdSdS)Nzuncaught exceptionr'r)r-s r)�log_uncaught_exceptionzLsafe_update_config.<locals>.restart_callback.<locals>.log_uncaught_exception@sa���}�}����3�=�=�?�?�+F��O�O�,�s�}�}���$��������+F�+Fr+z7Web server failed to start... Revert changes back. (%s)Tr#)r*r+r��error�create_taskr�add_done_callback�_graceful_restart)�taskr/r1�loopr�restart_cmdr"s   ����r)�restart_callbackz,safe_update_config.<locals>.restart_callback8s����
�
�
�
�
�
��>�>�#�#�
 ����(8�(8�(D����M��N�N�$�$����������'�'�
�4�(H�(H�(H�I�I���&�&�'7�8�8�8��'�'�(9�+�(F�(F�G�G���&�&�'=�>�>�>�>�>��
�����r+)�
done_callbackr>z)Successfully scheduled web server restartz Web server config is invalid: %s)r��fspathrr�r�rrrrr�r2r��get_running_loopr�coalesce_calls�GRACEFUL_RESTART_MIN_PERIODr5�inspect�stack�_graceful_restart_callerrg�function�resetr�r6)
r!r�make_backup�er9�graceful_restart�caller_frame�
context_tokenrr7rr8r"s
`       @@@@@r)�safe_update_configrIsh����������*��;�/�/�2B�B��*�*�*�*�*��'�.�.��-�-�K��+�z�+�F�F�F���t�+�+�+�+�+�+�6���.�.�.�.�.�.�.�.�.�.�
	�/�1�1�K�K���	�	�	��L�L�E�q�I�I�I��F�H�H�H��5�5�5�5�5�����	����
�'�)�)��	 �	 �	 �	 �	 �	 �	 �	 �8
�6�E�'�7G�
�
�
�
�����}���q�)��0�4�4�\�5J�K�K�
�	:�"�"�;�/�/�/�/�/�/�/�/�/�$�*�*�=�9�9�9�9��$�*�*�=�9�9�9�9�������?�@�@�@��t��i�������7��;�;�;�������������������j�5s<�$F+�;B
�

C�%B?�?C�E0�0F�+
G$�5%G�G$c��K�t��	t|p
t�����d{V��t�d��dS#t
$r&}t�d|��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_startrrr�r�rr�)r8�errs  r)r5r5cs����� �!�!�!�9���>�'<�'>�'>�?�?�?�?�?�?�?�?�?�	���7�8�8�8�8�8���B�B�B����;�S�A�A�A�A�A�A�A�A�A�����B���s�#A�
B�A<�<Bc��K�t|��}|t_	|�d{V��	tjd��S#tjd��wxYw)N�web_server_restart_task)r5rrQ�pop)r8r6s  r)�_graceful_restart_coalescedrSrs]�����[�)�)�D� $�A��)��z�z�z�z�z�z��	��'�(�(�(�(����'�(�(�(�(���s	�<�Ac��K�tj��d}t�|j��}	t|���d{V��}t�|��n#t�|��wxYw|S)rKr>N)r?r@rArgrBrSrC)r8rGrHr�s    r)rFrF|s������=�?�?�1�%�L�,�0�0��1F�G�G�M�6�2�;�?�?�?�?�?�?�?�?�� �&�&�}�5�5�5�5�� �&�&�}�5�5�5�5�����Ms�A,�,Bc�p�t�d��}t�d|��dS)N�unknownz/Performing web server graceful restart, from %s)rAr�r�r�)�callers r)rNrN�s0��
%�
)�
)�)�
4�
4�F�
�K�K�A�6�J�J�J�J�Jr+c���tj��d}t�|j��}	t��t�|��n#t�|��wxYw	tt��tt���t�d��dS#t$r&}t�
d|��Yd}~dSd}~wwxYw)zk
    Gracefully restart a web server synchronously.

    If web server cannot be detected, do nothing.
    r>)r�r�rLrMN)r?r@rArgrBrNrCr	rrr�r�rr�)rGrHrOs   r)�graceful_restart_syncrY�s����=�?�?�1�%�L�,�0�0��1F�G�G�M�6�#�%�%�%� �&�&�}�5�5�5�5�� �&�&�}�5�5�5�5����9��(�*�*�7�7�K�K�K�K�	���7�8�8�8�8�8���B�B�B����;�S�A�A�A�A�A�A�A�A�A�����B���s#�A#�#A?�(C�
C7�C2�2C7c���K�tj��d}t�|j��}	t��t�|��n#t�|��wxYw	td���}n3#t$r&}t�
d|��Yd}~dSd}~wwxYwt|���d{V��rt�d��dSt�
d��t���d{V��t|���d{V��rt�d	��dSt���d{V��S)
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>T�r�rMNFrLzLWeb server reload after update did not complete cleanly; attempting recoveryz-Web server recovered on graceful reload retry)r?r@rArgrBrNrCrrr�r��_reload_confirmedr�r2�_log_failed_configtest�
_hard_restart)rGrHr�rOs    r)�graceful_restart_confirmedr_�s������=�?�?�1�%�L�,�0�0��1F�G�G�M�6�#�%�%�%� �&�&�}�5�5�5�5�� �&�&�}�5�5�5�5�����#��.�.�.�����������;�S�A�A�A��u�u�u�u�u����������s�
#�
#�#�#�#�#�#�#�����7�8�8�8��t�
�L�L�	����!�
"�
"�"�"�"�"�"�"�"�
�s�
#�
#�#�#�#�#�#�#�����C�D�D�D��t���� � � � � � � s#�A%�%B�B�
C� C�Cc��K�	t|���d{V��n:#ttf$r&}t�d|��Yd}~dSd}~wwxYwt��rdS	t
d����d{V��n#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#)rrrr�r�r�rr6�r�rOs  r)r\r\�s��������n�n�����������<�(�������@�#�F�F�F��u�u�u�u�u���������"�#�#���t����.�.�.�.�.�.�.�.�.�.�.�������u�u������4s&��A�A�A�%A<�<
B
�	B
c��K�	td����d{V��dS#t$r&}t�d|��Yd}~dSd}~wwxYw)NTr#z.Web server config test failed after update: %s)rr6r�r2)rOs r)r]r]�s����L���.�.�.�.�.�.�.�.�.�.�.�.���L�L�L����E�s�K�K�K�K�K�K�K�K�K�����L���s��
A�A�Ac��jK�	td���}n3#t$r&}t�d|��Yd}~dSd}~wwxYw	t	|���d{V��n:#t
tf$r&}t�d|��Yd}~dSd}~wwxYwt�d��dS)NTr[zCannot recover web server: %sFz"Web server hard restart failed: %sz-Web server hard-restarted after failed reload)rrr�r2rrr�ras  r)r^r^�s�������T�*�*�*�����������4�c�:�:�:��u�u�u�u�u������������n�n�����������<�(�������9�3�?�?�?��u�u�u�u�u����������K�K�?�@�@�@��4s,��
A�A�A�	A�B�0B�Bc��K�t�d��	tt��t����d{V��dS#t
$r8}t�d|��|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)r�r�rrr6rr�)r$rOs  r)rr�s������K�K�3�4�4�4�H���)�)�5G�H�H�H�H�H�H�H�H�H�H�H�H���H�H�H����5�s�;�;�;��	H�$�%=�>�>�C�G�	H�	H�	H�	H�	H�	H�����H���s�(A�
B
�-B�B
c���t�|��}|�"t|�d����St	d�|�����)Nr>z)Failed to parse apache version string: {})�apache_version_regexpr�rr�r�r�)�outputr�s  r)�_parse_apache_version_outputrisV��!�(�(��0�0�E����u�{�{�1�~�~�&�&�&��7�>�>�v�F�F�
�
�	
r+rhc�>�d�|���D��S)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
    c��g|]H}|�t���|������d��IS)r)�
startswith�BYTE_SPACES�stripr^)r'�lines  r)rkz-_parse_apache_module_list.<locals>.<listcomp>!sS�������?�?�;�'�'���
�
�������Q����r+)�
splitlines)rhs r)�_parse_apache_module_listrqs/�����%�%�'�'����r+c���g}|����d��D]L}|�d��}|dkr/|�||d�������M|S)N�
rXr)r�r^rIrTrn)�dump�includesro�indexs    r)�_parse_includesrw(sp���H����
�
�#�#�D�)�)�2�2���	�	�#�����1�9�9��O�O�D����L�.�.�0�0�1�1�1���Or+c��tK�	ttgd����d{V����S#t$rgcYSwxYw)N)r�rz-D�
DUMP_INCLUDES)rwrrr4r+r)�
dump_includesrz1sg�������F�F�F�G�G�G�G�G�G�G�G�
�
�	
�������	�	�	����s�#(�7�7c���K�t��}|�td���t|dg���d{V��}t|�����}t
�d|��|S)Nr�z-vzApache %s version detected)r�r.rrir�r�r�)rr��versions   r)�apache_versionr}:sy�����!�!�J����5�6�6�6��:�t�,�-�-�
-�
-�
-�
-�
-�
-�C�*�3�:�:�<�<�8�8�G�
�K�K�,�g�6�6�6��Nr+�'IMUNIFY360_APACHE_MODULES_CACHE_TIMEOUTiX)�seconds)�
expirationc��NK�td���d{V��}t|��S)Nz-M)r�rq)r�s r)�apache_modulesr�Es5����&�d�+�+�
+�
+�
+�
+�
+�
+�F�$�V�,�,�,r+)r�r�)F)Tr%)rGN)r��	functoolsr?rs�loggingr�r�r�r��string�xml.etree.ElementTree�etreerurA�
contextlibr�contextvarsr�datetimer�packaging.versionr�pathlibr�
subprocessrr	r
r�typingrr
rrrrrr��$defence360agent.api.integration_confr�3defence360agent.application.determine_hosting_panelrr�&defence360agent.internals.global_scoper�defence360agent.utilsrrrrrrrr�defence360agent.utils.commonrrM�environr�r>r�r�r�r	rr�r�r��compilerg�tupler��
whitespacermr��	getLoggerr0r�rr.r6r8r�r�rUr�r�rr��	frozensetr�r�r�r�r�r�r�r�r��	lru_cacher~r�r�rrrrrrArIr5r=rSrFrNrYr_r\r]r^rrirqrwrzr}r�r4r+r)�<module>r�s���������������	�	�	�	�����	�	�	�	�	�	�	�	�����
�
�
�
�
�
�
�
�"�"�"�"�"�"�"�"�"�������"�"�"�"�"�"�������%�%�%�%�%�%�������L�L�L�L�L�L�L�L�L�L�L�L�F�F�F�F�F�F�F�F�F�F�F�F�F�F�F�F�F�F�
�
�
�
�B�B�B�B�B�B���������5�4�4�4�4�4�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�E�D�D�D�D�D�!�c��J�N�N�6��?�?����� L���$�9�:�:��G��I��=��4��&��"��"��
�#F�G�G���e�@�@���V�->�(?�(?�@�@�@�@�@��	��	��	�8�	$�	$�������l�������������T�T�T�T�T�T�T�T�n9�9�9�	�	�	�;��;�;�;�;�/���
�/�/�/�/��u�����2.7�Y�[�[�'�'�'�'�'�T���(5�5�5����(��
�
�"�2�s�7�+�
��
�
�
�
� 4�4�4�-�t�C�y�-�-�-�-�4�X�h�s�m�5L�����>!$�����Q�����D���� ����d��t�C�y�����"8�8��8��#��8�8�8�8�"/�d�/�/�/�/�,�h�s�m�,�,�,�,�(3�3�D�3�H�S�M�3�3�3�3�(8��#��8�8�8�8�(&�:�&?�@�@��^�c�^�d�^�^�^�^�B9�9�9�9�,��+�,G�H�H�)�)�)�I�H�)�
�
�
�
� K�K�K�
9�9�9�*$!�$�$!�$!�$!�$!�N�D�����.L�L�L�L��T�����H�H�H�H�
�
�
��e���U������&��������������������y����J�N�N�D�c�J�J�
�
�������-�-���-�-�-r+defence360agent/subsys/__pycache__/web_server.cpython-311.pyc0000644000000000000000000012405000000000000021155 0ustar  �

O��#�)�
���ddlZddlZddlZddlZddlZddlZddlZddlZddlZddl	Z	ddl
mcmZ
ddlmZddlmZddlmZddlmZddlmZddlmZmZmZmZddlmZmZm Z m!Z!m"Z"m#Z#m$Z$ddl%Z%dd	l&m'Z'dd
l(m)Z)m*Z*ddl+m,Z,ddl-m.Z.m/Z/m0Z0m1Z1m2Z2m3Z3m4Z4m5Z5dd
l6m7Z7e8ej9�:dd����Z;	dZ<ed��Z=dZ>dZ?dZ@dZAdZBdZCejDd��ZEeFd�eGe	jH��D����ZIdZJejKeL��ZMGd�deN��ZOGd�deN��ZPGd�d ��ZQd!�ZRd"�ZSd#eTfd$�ZUd#e!eTfd%�ZVd#eWfd&�ZXeY��d'�d(�ZZd)�Z[d*�Z\d+�Z]		dXd-egefd.e8fd/�Z^d0�Z_d#e eTfd1�Z`d#e!e$eTfd2�Zad3Zbejcd4�5��d#edfd6���Zed7edd#e eTfd8�ZfdYd7edd#e$eTfd:�Zgd#edfd;�Zhd#e!eTfd<�ZidZd7edd#e$eTfd>�Zjd#e$eTfd?�Zked@��ZldAeTd#edfdB�Zmd[dC�Zne7joe;��d[dD���Zpd[dE�ZqdF�ZrdG�Zsd#edfdH�Ztd#edfdI�Zud\dJ�Zvd#edfdK�ZwdYdL�ZxdM�ZydNeWd#e eWfdO�ZzdP�Z{dQ�Z|e.d4�5��dR���Z}e4ee8ej9�:dSdT�����U���V��dW���Z~dS)]�N)�suppress)�
ContextVar)�	timedelta)�Version)�Path)�CalledProcessError�
check_call�check_output�DEVNULL)�Any�Callable�List�Optional�Set�Tuple�Iterable)�IntegrationConfig)�is_generic_panel_installed�is_plesk_installed)�g)�async_lru_cache�atomic_rewrite�	check_run�get_system_user_names�
OsReleaseInfo�
CheckRunError�
TimedCache�BACKUP_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�|]}|���V��dS�N)�encode)�.0�xs  �V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/web_server.py�	<genexpr>r*:s*����@�@�1�A�H�H�J�J�@�@�@�@�@�@��apachec��eZdZdZdS)�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.@s���������r+r.c��eZdZdZdS)�ConfigInvalidErrorzO
    Error used to indicate that the web server config is having error(s).
    Nr/r4r+r)r6r6Hs���������r+r6c��eZdZdZdZdZdZdZdZdZ	dZ
d	�Zd
efd�Z
defd
�Zd
eeeeffd�Zd�Zd
efd�ZdS)�LiteSpeedConfig�useIpInProxyHeader�security�
accessControl�allow�denyr��c�8�tj|��|_dSr%)�ET�
fromstring�config)�self�contents  r)�__init__zLiteSpeedConfig.__init__Xs���m�G�,�,����r+�returnc��|j�|j��}|�|js|jSt|j��Sr%)rC�find�CLIENT_IP_IN_HEADER_TAG�text�CLIENT_IP_IN_HEADER_DISABLED�int�rD�elements  r)�client_ip_in_headerz#LiteSpeedConfig.client_ip_in_header[s>���+�"�"�4�#?�@�@���?�'�,�?��4�4��7�<� � � r+�valuec���|j�|j��}|�3tj|j��}|j�|��t
|��|_dSr%)rCrIrJrA�Element�append�strrK)rDrQrOs   r)�set_client_ip_in_headerz'LiteSpeedConfig.set_client_ip_in_headerasX���+�"�"�4�#?�@�@���?��j��!=�>�>�G��K���w�'�'�'��5�z�z����r+c��|j�d�d|j|j|jg����}|�*|jr#d�|j���D��St��S)N�/�.c��h|]R}|�d��D]:}|�|�d��r
|dd�n||�d��f��;�SS)�,�TN���)�split�endswith)r'�s�items   r)�	<setcomp>z>LiteSpeedConfig.access_control_allowed_list.<locals>.<setcomp>ts}�������G�G�C�L�L�����	�"�m�m�C�0�0�:��c�r�c���d�D�M�M�#�<N�<N�O����r+)	rCrI�join�SECURITY_TAG�ACCESS_CONTROL_TAG�ACCESS_CONTROL_ALLOWED_TAGrKr^�setrNs  r)�access_control_allowed_listz+LiteSpeedConfig.access_control_allowed_lisths����+�"�"��H�H���%��+��3�	�
�
�	
�	
����7�<���� ��+�+�-�-����
��u�u�r+c��d�|D��}d�|��}|j�d�d|j|j|jg����}|��t
j|j��}|j�d�d|j|jg����}|��t
j|j��}|j�|j��}|�3t
j|j��}|j�|��|�|��|�|��||_	dS)Nc�D�g|]}|dr|ddzn|d��S)r>rr\r4)r'ras  r)�
<listcomp>zCLiteSpeedConfig.set_access_control_allowed_list.<locals>.<listcomp>}s1��K�K�K�4�$�q�'�6��a��3���t�A�w�K�K�Kr+r[rXrY)
rcrCrIrdrerfrArSrTrK)rD�allowed�itemsrQrO�access_controlr:s       r)�set_access_control_allowed_listz/LiteSpeedConfig.set_access_control_allowed_list|sL��K�K�7�K�K�K����������+�"�"��H�H���%��+��3�	�
�
�	
�	
���?��j��!@�A�A�G�!�[�-�-������)��/������N��%�!#��D�,C�!D�!D���;�+�+�D�,=�>�>���#�!�z�$�*;�<�<�H��K�&�&�x�0�0�0�����/�/�/��!�!�'�*�*�*�����r+c��tj��}tj|j��}|�|dd���|���S)Nzutf-8T)�encoding�xml_declaration)�io�BytesIOrA�ElementTreerC�write�getvalue)rD�buf�trees   r)�tostringzLiteSpeedConfig.tostring�sD���j�l�l���~�d�k�*�*���
�
�3��$�
�?�?�?��|�|�~�~�r+N)r0r1r2rJrdrerf�ACCESS_CONTROL_DENIED_TAGrL�CLIENT_IP_IN_HEADER_ENABLED�#CLIENT_IP_IN_HEADER_TRUSTED_IP_ONLYrFrMrPrVrrrU�boolrhro�bytesrzr4r+r)r8r8Ns�������2���L�(��!(�� &��#$� �"#��*+�'�-�-�-�!�S�!�!�!�!�"�S�"�"�"�"��S��s�D�y�1A�-B�����( � � �D�%������r+r8c��ttt��5tt�����cddd��S#1swxYwYdS)z3Return LiteSpeed's pid or None if it can't be read.N)r�OSError�
ValueErrorrM�LITESPEED_PID_FILE_PATH�
read_bytesr4r+r)�_get_litespeed_pidr��s���	�'�:�	&�	&�9�9��*�5�5�7�7�8�8�9�9�9�9�9�9�9�9�9�9�9�9����9�9�9�9�9�9s�&A�A�Ac��t��}	t|otj|����S#t$rYdSwxYw)zb
    Litespeed use constant PID file path, so using it to determinate status
    :return bool
    F)r�r~�psutil�
pid_exists�
OverflowError)�pids r)�litespeed_runningr��sT��
�
�
�C���C�2�F�-�c�2�2�3�3�3�������u�u����s�"3�
A�ArGc�8�tjd��ptS)N�	litespeed)�shutil�which�LITESPEED_BIN_PATHr4r+r)�_litespeed_binr��s���<��$�$�:�(:�:r+c�6�t��}|r|dndS)z�
    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
    �	httpd_binN)�_apache_running_process)�infos r)�apache_runningr��s$��#�$�$�D� $�.�4����$�.r+c���K�t��}|std���	tj��tjzrqtd�����rPtd�tj
|��tj|����d����d{V��}nt|g|����d{V��}n+#t$rt�d��YdSwxYw|S)N�Apache is not runningz/etc/apache2/envvarsz. /etc/apache2/envvars && {} {}T)�shellzApache doesn't work properlyr+)r�r.r�id_like�DEBIANr�existsr�format�shlex�quotercr�logger�warning)�argsr��stdouts   r)�apache_binary_callr��s0����� � �I��7��5�6�6�6���!�#�#�m�&:�:�
	9��+�,�,�3�3�5�5�
	9�%�1�8�8��K�	�*�*�E�J�t�,<�,<����	���������F�F�%�i�%7�$�%7�8�8�8�8�8�8�8�8�F����������5�6�6�6��s�s������Ms�B)C
�
$C5�4C5��
exclude_usersc���d�}tj��tjzr t��s
|��rt}nt
}t
t����|z
}t|��}|rm|d�J�||d<	|d}tj
�||��r|Sn2#t$r%}t�d|��Yd}~nd}~wwxYwdS)z�
    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
    c�^�t��rtjdd��tkSdS)N�
web_server�server_typeF)rr�get�APACHEr4r+r)�is_generic_panel_on_apachez;_apache_running_process.<locals>.is_generic_panel_on_apache�s.��%�'�'�	P�$�(��}�E�E��O�O��ur+�exeNr�z#Can't determine apache bin path: %s)rr�r�r�APACHE2_BIN_PATH�HTTPD_BIN_PATHrgr�_apache_running_process_info�os�path�samefiler�r�r�)r�r�r��	sys_usersr��httpd_process_exe�excs       r)r�r��s#��"���
	����-�"6�6�#����#� :� :� <� <�#�%�	�	�"�	��)�+�+�,�,�}�<�I�'�	�2�2�D��D��E�{�&�&�&�%��[��	D� $�U����w���	�+<�=�=�
���
���	D�	D�	D��K�K�=�s�C�C�C�C�C�C�C�C�����	D�����4s�
)B5�5
C$�?C�C$c	����td��D]a}tt��5t�fd�t	jgd����D��d��cddd��cS#1swxYwY�bdS)z#Retry process_iter() on IndexError.r?c3��K�|]G}|jd�8|jd�d��r|jd�v�>|jV��HdS)r�N)z/httpdz/apache2�username�r�r_)r'�pr�s  �r)r*z/_apache_running_process_info.<locals>.<genexpr>sp�����
�
����u�
�1��F�5�M�2�2�3I�J�J�2��F�:�.�)�;�;��F�<�;�;�;�
�
r+)�namer�r��uids�gids��attrsN)�ranger�
IndexError�nextr��process_iter)r��_s` r)r�r�
s����
�1�X�X����
�j�
!�
!�	�	��
�
�
�
�#�0�I�I�I����
�
�
��
�
�	�	�	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	��s�1A'�'A+	�.A+	c��tdh���}|std���tj||dd|dd��dS)z&Make web server user/group own *path*.�rootr�z5Can't find running apache process without root owner.r�rr�N)r�r.r��chown)r�r�s  r)r�r�s]��"�&��:�:�:�D��
��C�
�
�	
��H�T�4��<��?�D��L��O�4�4�4�4�4r+c�`�td�tjgd����D��d��S)z;Return path to a running nginx binary or None if not found.c3��K�|]i}|jd�Z|jd�d��r:|jd�-d|jdvr|jddv�Z|jdV��jdS)r�N�nginxr�r�)r�zwww-datar�)r'r�s  r)r*z%find_running_nginx.<locals>.<genexpr>+s�����
	
�
	
����v��*��F�6�N�+�+�G�4�4�+��F�5�M�-��q�v�e�}�,�,��F�:�&�*?�?�?�
�F�5�M�@�?�?�?�
	
�
	
r+)r�r�r�r�N)r�r�r�r4r+r)�find_running_nginxr�(sJ���
	
�
	
��(�/J�/J�/J�K�K�K�
	
�
	
�
	
�	
�
�
�
r+�
�webserver_running_cb�granularityc��K�|dksJ�t|��D]/}|��}|r|cStj||z���d{V���0|S)Nr)r��asyncio�sleep)r��timeout_secr�r��results     r)�check_with_timeoutr�:sz����
��?�?�?�?�
�;�
�
����%�%�'�'���	��M�M�M��m�K�+�5�6�6�6�6�6�6�6�6�6�6��
r+c�@�tj�d��S)z8
    though, available != running
    :return bool:
    z/etc/cpanel/ea4/is_ea4)r�r��isfiler4r+r)�is_EA4_availabler�Js��
�7�>�>�2�3�3�3r+c���tjt��}|r|gStj��tjzr#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.
    �	systemctl�reloadz--job-mode=replace-irreversiblyz-k�graceful)	r�r��CPANEL_RESTART_APACHE_SCRIPTrr�r�r�r��basename)�	apachectl�restartsrv_httpds  r)�_apache_graceful_restart_cmdr�Rsv���|�$@�A�A���"� �!�!������!5�5�	-�
��-��G���Y�'�'�	
�	
��4��,�,r+c��tj��r�	tj��dd}|st�d��dS|���}tj�|d��r|St�d|��n*#t$rt�d��YnwxYwdS)Nr��graceful_restart_scriptz'graceful_restart_script option is emptyrz,Web server restart script does not exist: %sz;Integration config is missing graceful_restart_script field)	rr��to_dictr�r�r^r�r��KeyError)�restart_script�cmds  r)�+_graceful_restart_cmd_from_integration_confr�ls�����!�!��	�.�6�8�8��F�)��N�"�
����=�����t� �&�&�(�(�C��w�~�~�c�!�f�%�%�
��
��N�N�>��
�
�
�
���	�	�	��N�N�M�
�
�
�
�
�	����"�4s�B)�)$C�C��r>)�maxsizec�B�tjd��}|sdS	t|dgt������}n#t
tf$rYdSwxYwtjd|��}|duo*t|�
d����tkS)N�systemd-runFz	--version)�stderrzsystemd\s+(\d+)r>)r�r�r
r�decoder�r�re�searchrM�group�_SYSTEMD_RUN_WAIT_MIN_VERSION)�systemd_run�out�matchs   r)�_systemd_run_supports_waitr��s����,�}�-�-�K����u���K��5�g�F�F�F�M�M�O�O�����'�(�����u�u������I�(�#�.�.�E������E�K�K��N�N���<�<�s�*A�A�A�waitc��g}tjd��x}rC||dddgz
}|r#t��r|�d��|�d��|S)Nr�z-pzSendSIGKILL=noz--slice=graceful_restartz--waitz--)r�r�r�rT)r��prefixr�s   r)�_systemd_run_prefixr��s|���F��l�=�1�1�1�{�	�����&�	
�	
���	$�.�0�0�	$��M�M�(�#�#�#��
�
�d�����Mr+Fc��t|��}t��}|�|t|��zSt��r|tt��zSt��x}r|t
|��zStd���)z Gracefully restart a web server.N�Could not detect a web server)r�r��listr��LITESPEED_RESTART_CMDr�r��RuntimeError)r�r�r�r�s    r)�_graceful_restart_cmdr�s���
 ��
&�
&�F�
5�
7�
7�C�
����S�	�	�!�!����4���2�3�3�3�3�"�$�$�$�y�@��4�Y�?�?�?�?�
�6�
7�
7�7r+c�J�tj�t��Sr%)r�r�r��LITESPEED_CONF_PATHr4r+r)�_litespeed_installedr�s��
�7�>�>�-�.�.�.r+c��d}t��r7	tjdd��}n#t$rYdSwxYw|tkrdSd}tj��t
jzr4t��s|r$tj
�t��Stj
�t��S)u�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.Fr�r�NT)rrr�r�r�rr�r�rr�r�r�r�r�)�on_generic_apacher�s  r)�_apache_systemd_unitr�s�����!�#�#�!�	�+�/��m�L�L�K�K���	�	�	��4�4�	�����&� � ��4� ������-�"6�6�2����2� 1�2��w��� 0�1�1�1�
�7���N�+�+�+s�(�
6�6Tc��t|��}t��r|tt��zSt	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 serverr�r#)	r�rr��LITESPEED_HARD_RESTART_CMDr�r�r�rr)r�r�r��units    r)�_hard_restart_cmdr�s���!��
&�
&�F����9���7�8�8�8�8�!�<�(D�E�E�E��8��)�;�7�7�7��!�!�D��|��M�N�N�N��[�)�T�2�2�2r+c��t��r=	tjdd��}|r|���Sn#t$rYnwxYwt��x}r(t
j��tjzrddgS|dgSt��rt��dgSt��x}r|dgStd���)Nr��config_test_scriptr��
configtest�-tr�)
rrr�r^r�r�rr�r�r�r�r�r)r��
apache_bin�	nginx_bins   r)�_configtest_cmdr�s���!�#�#��	�#�'��6J�K�K�C��
#��y�y�{�{�"�
#���	�	�	��D�	����$�%�%�%�z�!�� �"�"�]�%9�9�	/���.�.��D�!�!�	�	�	�!�� � �$�'�'�(�*�*�	*��!��4� � �
�6�
7�
7�7s�*<�
A	�A	�graceful_restart_caller�
new_configc������	�
��K�tj���tz��fd��
tj����}t�||���sdS��fd��	t
d����d{V��	t���n=#t$r0}t�
d|�����Yd}~dSd}~wwxYwtj���	�	�
��fd	�}tjt|�
��t ��}t#j��d}t&�|j��}	|����d{V��t&�|��n#t&�|��wxYwt�d��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.
    c���tt��5tj���ddd��dS#1swxYwYdSr%)r�FileNotFoundErrorr��unlink)�config_backup_paths�r)�
remove_backupz)safe_update_config.<locals>.remove_backups����
�'�
(�
(�	*�	*��I�(�)�)�)�	*�	*�	*�	*�	*�	*�	*�	*�	*�	*�	*�	*����	*�	*�	*�	*�	*�	*s�8�<�<)�backupTc���	tj����dS#t$r&t�d�����YdSwxYw)N�w)r��renamer�open�close)r�config_paths��r)�revertz"safe_update_config.<locals>.revert"sa���	+��I�(�+�6�6�6�6�6�� �	+�	+�	+���c�"�"�(�(�*�*�*�*�*�*�	+���s��,A
�	A
��raise_exceptionNz*Failed to get graceful restart command: %sFc����d�}d�}|���s�|�����t�d|����������td�����}|�|����t�����}|�|��dS���dS)Nc��|���sD|����2t�d|������dSdSdS)Nz'The reverted config seems to be invalid��exc_info��	cancelled�	exceptionr��critical��futs r)�log_config_errorzFsafe_update_config.<locals>.restart_callback.<locals>.log_config_error9sb���}�}����3�=�=�?�?�+F��O�O�A�!$�����$��������+F�+Fr+c��|���sD|����2t�d|������dSdSdS)Nzuncaught exceptionr'r)r-s r)�log_uncaught_exceptionzLsafe_update_config.<locals>.restart_callback.<locals>.log_uncaught_exception@sa���}�}����3�=�=�?�?�+F��O�O�,�s�}�}���$��������+F�+Fr+z7Web server failed to start... Revert changes back. (%s)Tr#)r*r+r��error�create_taskr�add_done_callback�_graceful_restart)�taskr/r1�loopr�restart_cmdr"s   ����r)�restart_callbackz,safe_update_config.<locals>.restart_callback8s����
�
�
�
�
�
��>�>�#�#�
 ����(8�(8�(D����M��N�N�$�$����������'�'�
�4�(H�(H�(H�I�I���&�&�'7�8�8�8��'�'�(9�+�(F�(F�G�G���&�&�'=�>�>�>�>�>��
�����r+)�
done_callbackr>z)Successfully scheduled web server restartz Web server config is invalid: %s)r��fspathrr�r�rrrrr�r2r��get_running_loopr�coalesce_calls�GRACEFUL_RESTART_MIN_PERIODr5�inspect�stack�_graceful_restart_callerrg�function�resetr�r6)
r!r�make_backup�er9�graceful_restart�caller_frame�
context_tokenrr7rr8r"s
`       @@@@@r)�safe_update_configrIsh����������*��;�/�/�2B�B��*�*�*�*�*��'�.�.��-�-�K��+�z�+�F�F�F���t�+�+�+�+�+�+�6���.�.�.�.�.�.�.�.�.�.�
	�/�1�1�K�K���	�	�	��L�L�E�q�I�I�I��F�H�H�H��5�5�5�5�5�����	����
�'�)�)��	 �	 �	 �	 �	 �	 �	 �	 �8
�6�E�'�7G�
�
�
�
�����}���q�)��0�4�4�\�5J�K�K�
�	:�"�"�;�/�/�/�/�/�/�/�/�/�$�*�*�=�9�9�9�9��$�*�*�=�9�9�9�9�������?�@�@�@��t��i�������7��;�;�;�������������������j�5s<�$F+�;B
�

C�%B?�?C�E0�0F�+
G$�5%G�G$c��K�t��	t|p
t�����d{V��t�d��dS#t
$r&}t�d|��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_startrrr�r�rr�)r8�errs  r)r5r5cs����� �!�!�!�9���>�'<�'>�'>�?�?�?�?�?�?�?�?�?�	���7�8�8�8�8�8���B�B�B����;�S�A�A�A�A�A�A�A�A�A�����B���s�#A�
B�A<�<Bc��K�t|��}|t_	|�d{V��	tjd��S#tjd��wxYw)N�web_server_restart_task)r5rrQ�pop)r8r6s  r)�_graceful_restart_coalescedrSrs]�����[�)�)�D� $�A��)��z�z�z�z�z�z��	��'�(�(�(�(����'�(�(�(�(���s	�<�Ac��K�tj��d}t�|j��}	t|���d{V��}t�|��n#t�|��wxYw|S)rKr>N)r?r@rArgrBrSrC)r8rGrHr�s    r)rFrF|s������=�?�?�1�%�L�,�0�0��1F�G�G�M�6�2�;�?�?�?�?�?�?�?�?�� �&�&�}�5�5�5�5�� �&�&�}�5�5�5�5�����Ms�A,�,Bc�p�t�d��}t�d|��dS)N�unknownz/Performing web server graceful restart, from %s)rAr�r�r�)�callers r)rNrN�s0��
%�
)�
)�)�
4�
4�F�
�K�K�A�6�J�J�J�J�Jr+c���tj��d}t�|j��}	t��t�|��n#t�|��wxYw	tt��tt���t�d��dS#t$r&}t�
d|��Yd}~dSd}~wwxYw)zk
    Gracefully restart a web server synchronously.

    If web server cannot be detected, do nothing.
    r>)r�r�rLrMN)r?r@rArgrBrNrCr	rrr�r�rr�)rGrHrOs   r)�graceful_restart_syncrY�s����=�?�?�1�%�L�,�0�0��1F�G�G�M�6�#�%�%�%� �&�&�}�5�5�5�5�� �&�&�}�5�5�5�5����9��(�*�*�7�7�K�K�K�K�	���7�8�8�8�8�8���B�B�B����;�S�A�A�A�A�A�A�A�A�A�����B���s#�A#�#A?�(C�
C7�C2�2C7c���K�tj��d}t�|j��}	t��t�|��n#t�|��wxYw	td���}n3#t$r&}t�
d|��Yd}~dSd}~wwxYwt|���d{V��rt�d��dSt�
d��t���d{V��t|���d{V��rt�d	��dSt���d{V��S)
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>T�r�rMNFrLzLWeb server reload after update did not complete cleanly; attempting recoveryz-Web server recovered on graceful reload retry)r?r@rArgrBrNrCrrr�r��_reload_confirmedr�r2�_log_failed_configtest�
_hard_restart)rGrHr�rOs    r)�graceful_restart_confirmedr_�s������=�?�?�1�%�L�,�0�0��1F�G�G�M�6�#�%�%�%� �&�&�}�5�5�5�5�� �&�&�}�5�5�5�5�����#��.�.�.�����������;�S�A�A�A��u�u�u�u�u����������s�
#�
#�#�#�#�#�#�#�����7�8�8�8��t�
�L�L�	����!�
"�
"�"�"�"�"�"�"�"�
�s�
#�
#�#�#�#�#�#�#�����C�D�D�D��t���� � � � � � � s#�A%�%B�B�
C� C�Cc��K�	t|���d{V��n:#ttf$r&}t�d|��Yd}~dSd}~wwxYwt��rdS	t
d����d{V��n#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#)rrrr�r�r�rr6�r�rOs  r)r\r\�s��������n�n�����������<�(�������@�#�F�F�F��u�u�u�u�u���������"�#�#���t����.�.�.�.�.�.�.�.�.�.�.�������u�u������4s&��A�A�A�%A<�<
B
�	B
c��K�	td����d{V��dS#t$r&}t�d|��Yd}~dSd}~wwxYw)NTr#z.Web server config test failed after update: %s)rr6r�r2)rOs r)r]r]�s����L���.�.�.�.�.�.�.�.�.�.�.�.���L�L�L����E�s�K�K�K�K�K�K�K�K�K�����L���s��
A�A�Ac��jK�	td���}n3#t$r&}t�d|��Yd}~dSd}~wwxYw	t	|���d{V��n:#t
tf$r&}t�d|��Yd}~dSd}~wwxYwt�d��dS)NTr[zCannot recover web server: %sFz"Web server hard restart failed: %sz-Web server hard-restarted after failed reload)rrr�r2rrr�ras  r)r^r^�s�������T�*�*�*�����������4�c�:�:�:��u�u�u�u�u������������n�n�����������<�(�������9�3�?�?�?��u�u�u�u�u����������K�K�?�@�@�@��4s,��
A�A�A�	A�B�0B�Bc��K�t�d��	tt��t����d{V��dS#t
$r8}t�d|��|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)r�r�rrr6rr�)r$rOs  r)rr�s������K�K�3�4�4�4�H���)�)�5G�H�H�H�H�H�H�H�H�H�H�H�H���H�H�H����5�s�;�;�;��	H�$�%=�>�>�C�G�	H�	H�	H�	H�	H�	H�����H���s�(A�
B
�-B�B
c���t�|��}|�"t|�d����St	d�|�����)Nr>z)Failed to parse apache version string: {})�apache_version_regexpr�rr�r�r�)�outputr�s  r)�_parse_apache_version_outputrisV��!�(�(��0�0�E����u�{�{�1�~�~�&�&�&��7�>�>�v�F�F�
�
�	
r+rhc�>�d�|���D��S)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
    c��g|]H}|�t���|������d��IS)r)�
startswith�BYTE_SPACES�stripr^)r'�lines  r)rkz-_parse_apache_module_list.<locals>.<listcomp>!sS�������?�?�;�'�'���
�
�������Q����r+)�
splitlines)rhs r)�_parse_apache_module_listrqs/�����%�%�'�'����r+c���g}|����d��D]L}|�d��}|dkr/|�||d�������M|S)N�
rXr)r�r^rIrTrn)�dump�includesro�indexs    r)�_parse_includesrw(sp���H����
�
�#�#�D�)�)�2�2���	�	�#�����1�9�9��O�O�D����L�.�.�0�0�1�1�1���Or+c��tK�	ttgd����d{V����S#t$rgcYSwxYw)N)r�rz-D�
DUMP_INCLUDES)rwrrr4r+r)�
dump_includesrz1sg�������F�F�F�G�G�G�G�G�G�G�G�
�
�	
�������	�	�	����s�#(�7�7c���K�t��}|�td���t|dg���d{V��}t|�����}t
�d|��|S)Nr�z-vzApache %s version detected)r�r.rrir�r�r�)rr��versions   r)�apache_versionr}:sy�����!�!�J����5�6�6�6��:�t�,�-�-�
-�
-�
-�
-�
-�
-�C�*�3�:�:�<�<�8�8�G�
�K�K�,�g�6�6�6��Nr+�'IMUNIFY360_APACHE_MODULES_CACHE_TIMEOUTiX)�seconds)�
expirationc��NK�td���d{V��}t|��S)Nz-M)r�rq)r�s r)�apache_modulesr�Es5����&�d�+�+�
+�
+�
+�
+�
+�
+�F�$�V�,�,�,r+)r�r�)F)Tr%)rGN)r��	functoolsr?rs�loggingr�r�r�r��string�xml.etree.ElementTree�etreerurA�
contextlibr�contextvarsr�datetimer�packaging.versionr�pathlibr�
subprocessrr	r
r�typingrr
rrrrrr��$defence360agent.api.integration_confr�3defence360agent.application.determine_hosting_panelrr�&defence360agent.internals.global_scoper�defence360agent.utilsrrrrrrrr�defence360agent.utils.commonrrM�environr�r>r�r�r�r	rr�r�r��compilerg�tupler��
whitespacermr��	getLoggerr0r�rr.r6r8r�r�rUr�r�rr��	frozensetr�r�r�r�r�r�r�r�r��	lru_cacher~r�r�rrrrrrArIr5r=rSrFrNrYr_r\r]r^rrirqrwrzr}r�r4r+r)�<module>r�s���������������	�	�	�	�����	�	�	�	�	�	�	�	�����
�
�
�
�
�
�
�
�"�"�"�"�"�"�"�"�"�������"�"�"�"�"�"�������%�%�%�%�%�%�������L�L�L�L�L�L�L�L�L�L�L�L�F�F�F�F�F�F�F�F�F�F�F�F�F�F�F�F�F�F�
�
�
�
�B�B�B�B�B�B���������5�4�4�4�4�4�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�E�D�D�D�D�D�!�c��J�N�N�6��?�?����� L���$�9�:�:��G��I��=��4��&��"��"��
�#F�G�G���e�@�@���V�->�(?�(?�@�@�@�@�@��	��	��	�8�	$�	$�������l�������������T�T�T�T�T�T�T�T�n9�9�9�	�	�	�;��;�;�;�;�/���
�/�/�/�/��u�����2.7�Y�[�[�'�'�'�'�'�T���(5�5�5����(��
�
�"�2�s�7�+�
��
�
�
�
� 4�4�4�-�t�C�y�-�-�-�-�4�X�h�s�m�5L�����>!$�����Q�����D���� ����d��t�C�y�����"8�8��8��#��8�8�8�8�"/�d�/�/�/�/�,�h�s�m�,�,�,�,�(3�3�D�3�H�S�M�3�3�3�3�(8��#��8�8�8�8�(&�:�&?�@�@��^�c�^�d�^�^�^�^�B9�9�9�9�,��+�,G�H�H�)�)�)�I�H�)�
�
�
�
� K�K�K�
9�9�9�*$!�$�$!�$!�$!�$!�N�D�����.L�L�L�L��T�����H�H�H�H�
�
�
��e���U������&��������������������y����J�N�N�D�c�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  �

s�����s���dS)N�r��]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/features/__init__.py�<module>rs���rdefence360agent/subsys/features/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000031200000000000022361 0ustar  �

s�����s���dS)N�r��]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/features/__init__.py�<module>rs���rdefence360agent/subsys/features/__pycache__/abstract_feature.cpython-311.opt-1.pyc0000644000000000000000000002421500000000000025107 0ustar  �

�B

��������ddlZddlZddlZddlmZmZddlmZddlZej	e
��ZGd�d��Zd�Z
Gd�de��ZGd	�d
e��ZGd�de�
��ZdS)�N)�ABCMeta�abstractmethod)�isclosec�*�eZdZdZdZdZdZdZdZdZ	dS)	�
FeatureStatus�error�	installed�
installing�removing�
not_installed�managed_by_lveznot-supported-by-cl-soloN)
�__name__�
__module__�__qualname__�ERROR�	INSTALLED�
INSTALLING�REMOVING�
NOT_INSTALLED�MANAGED_BY_LVE�NOT_SUPPORTED_BY_CL_SOLO���e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/features/abstract_feature.pyrrs4�������E��I��J��H�#�M�%�N�9���rrc����fd�}|S)z�
    If Easy Apache 4 not installed, then raising an error
    :raises FeatureError
    :param func: install or remove func
    :return func:
    c���K�tj�d��std����|i|���d{V��S)Nz/etc/cpanel/ea4/is_ea4z3Hardened PHP is compatible only with Easy Apache 4!)�os�path�isfile�FeatureError)�args�kwargs�funcs  �r�wrapperzea4_only.<locals>.wrappers\������w�~�~�6�7�7�	��E���
��T�4�*�6�*�*�*�*�*�*�*�*�*rr�r#r$s` r�ea4_onlyr&s#���+�+�+�+�+��Nrc��eZdZdZdS)r z*Feature operation can't be performed errorN�rrr�__doc__rrrr r (s������4�4��Drr c��eZdZdZdS)�
FeatureNoticez+Feature operation can't be performed noticeNr(rrrr+r+.s������5�5��Drr+c�,�eZdZdZdZdZgZdd�Zd�Ze	d���Z
e	d���Zed���Z
ed���Zed���Zd	efd
�Zed	efd���Zed	efd���Zed	efd
���Zed���Zed���Zd�ZdS)�AbstractFeatureNc�X�|js
Jd���|js
Jd���||_dS)Nzvariable isn't set!)�INSTALL_LOG_FILE_MASK�REMOVE_LOG_FILE_MASK�_sink)�self�sinks  r�__init__zAbstractFeature.__init__:s=���)�@�@�+@�@�@�)��(�?�?�*?�?�?�(���
�
�
rc��HK�|����d{V��|_|S�N)�check_installed�is_installed�r2s r�initzAbstractFeature.init@s1����"&�"6�"6�"8�"8�8�8�8�8�8�8����rc�6�|�|j��Sr6)�
_get_live_logr/r9s r�installation_live_logz%AbstractFeature.installation_live_logDs���!�!�$�"<�=�=�=rc�6�|�|j��Sr6)r<r0r9s r�removal_live_logz AbstractFeature.removal_live_logHs���!�!�$�";�<�<�<rc���	t|dz��5}|���������\}}t	tjt|�������t�
|��d���cddd��S#1swxYwYdS#ttt
j
f$rYdSwxYw)z+Checks if any processes are using log file.z.pidg�-���q=)�rel_tolNF)�open�read�strip�splitr�psutil�Process�int�create_time�float�fromhex�OSError�
ValueError�
NoSuchProcess)�cls�log_file�pf�pid�
creation_times     r�_log_still_usedzAbstractFeature._log_still_usedLs��		��h��'�(�(�
�B�%'�W�W�Y�Y�_�_�%6�%6�%<�%<�%>�%>�"��]���N�3�s�8�8�,�,�8�8�:�:��M�M�-�0�0�!����
�
�
�
�
�
�
�
�
�
�
�
����
�
�
�
�
�
����V�%9�:�	�	�	��5�5�	���s5�C�BB7�*C�7B;�;C�>B;�?C�C$�#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��"�"�"rc�n�tt|j|�|����d��S)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)�next�filterrTrX)rO�	file_masks  rr<zAbstractFeature._get_live_logbs-���F�3�.����Y�0G�0G�H�H�$�O�O�Or�returnc��^K�|jrdS|jrdS|����d{V��S)NFT)r=r?�_check_installed_implr9s rr7zAbstractFeature.check_installednsJ�����%�	��5�� �	��4��/�/�1�1�1�1�1�1�1�1�1rc��
K�dS)NFrr9s rr_z%AbstractFeature._check_installed_implus�����urc��"K�t���)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 r�installzAbstractFeature.installys����"�#�#�#rc��"K�t���r6rbr9s r�removezAbstractFeature.remove�s����!�#�#�#rc����fd�}|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
        c���K�|jrtd���|jr'td�|j�����|jp�|���d{V��S)Nz$Wait until uninstalling is finished!z{} is already installed)r?r r8r+�format�NAMEr=�r2r#s �rr$z>AbstractFeature.raise_if_shouldnt_install_now.<locals>.wrapper�s{������$�
�"�#I�J�J�J��"�
�#�-�4�4�T�Y�?�?�����-�A�t�t�D�z�z�1A�1A�1A�1A�1A�1A�Arrr%s` r�raise_if_shouldnt_install_nowz-AbstractFeature.raise_if_shouldnt_install_now�s(���		B�		B�		B�		B�		B��rc����fd�}|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
        c���K�|jrtd���|js'td�|j�����|jp�|���d{V��S)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.<locals>.wrapper�s�������)�
�"�#I�J�J�J��&�
�#�A�H�H��	�������(�<�$�$�t�*�*�,<�,<�,<�,<�,<�,<�<rrr%s` r�raise_if_shouldnt_remove_nowz,AbstractFeature.raise_if_shouldnt_remove_now�s#���	=�	=�	=�	=�	=��rc��K�|jr'd�|j��}tj}n�|jr'd�|j��}tj}ng|����d{V��r'd�|j��}tj}n&d�|j��}tj	}d||d�iS)Nz{} is installingz{} is removingz{} is installedz{} is not installed�items)�message�status)
r=rirjrrr?rr7rr)r2�msgrss   rrszAbstractFeature.status�s������%�	1�$�+�+�D�I�6�6�C�"�-�F�F�
�
"�	1�"�)�)�$�)�4�4�C�"�+�F�F��'�'�)�)�
)�
)�
)�
)�
)�
)�	1�#�*�*�4�9�5�5�C�"�,�F�F�'�.�.�t�y�9�9�C�"�0�F��� ���
�	
rr6)rrrrjr/r0�	_CMD_LISTr4r:�propertyr=r?�classmethodrT�staticmethodrXr<�boolr7rr_�strrdrfrlrorsrrrr-r-4s��������D� �����I���������>�>��X�>��=�=��X�=�����[���#�#��\�#��	P�	P��[�	P�2�t�2�2�2�2���T�����^���$�s�$�$�$��^�$��$�c�$�$�$��^�$�����\��0����\��,
�
�
�
�
rr-)�	metaclass)rV�loggingr�abcrr�mathrrF�	getLoggerr�loggerrr&�	Exceptionr r+r-rrr�<module>r�s4����������	�	�	�	�'�'�'�'�'�'�'�'�������
�
�
�
�	��	�8�	$�	$��:�:�:�:�:�:�:�:����$	�	�	�	�	�9�	�	�	�	�	�	�	�	�L�	�	�	�U
�U
�U
�U
�U
��U
�U
�U
�U
�U
�U
rdefence360agent/subsys/features/__pycache__/abstract_feature.cpython-311.pyc0000644000000000000000000002421500000000000024150 0ustar  �

�B

��������ddlZddlZddlZddlmZmZddlmZddlZej	e
��ZGd�d��Zd�Z
Gd�de��ZGd	�d
e��ZGd�de�
��ZdS)�N)�ABCMeta�abstractmethod)�isclosec�*�eZdZdZdZdZdZdZdZdZ	dS)	�
FeatureStatus�error�	installed�
installing�removing�
not_installed�managed_by_lveznot-supported-by-cl-soloN)
�__name__�
__module__�__qualname__�ERROR�	INSTALLED�
INSTALLING�REMOVING�
NOT_INSTALLED�MANAGED_BY_LVE�NOT_SUPPORTED_BY_CL_SOLO���e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/features/abstract_feature.pyrrs4�������E��I��J��H�#�M�%�N�9���rrc����fd�}|S)z�
    If Easy Apache 4 not installed, then raising an error
    :raises FeatureError
    :param func: install or remove func
    :return func:
    c���K�tj�d��std����|i|���d{V��S)Nz/etc/cpanel/ea4/is_ea4z3Hardened PHP is compatible only with Easy Apache 4!)�os�path�isfile�FeatureError)�args�kwargs�funcs  �r�wrapperzea4_only.<locals>.wrappers\������w�~�~�6�7�7�	��E���
��T�4�*�6�*�*�*�*�*�*�*�*�*rr�r#r$s` r�ea4_onlyr&s#���+�+�+�+�+��Nrc��eZdZdZdS)r z*Feature operation can't be performed errorN�rrr�__doc__rrrr r (s������4�4��Drr c��eZdZdZdS)�
FeatureNoticez+Feature operation can't be performed noticeNr(rrrr+r+.s������5�5��Drr+c�,�eZdZdZdZdZgZdd�Zd�Ze	d���Z
e	d���Zed���Z
ed���Zed���Zd	efd
�Zed	efd���Zed	efd���Zed	efd
���Zed���Zed���Zd�ZdS)�AbstractFeatureNc�X�|js
Jd���|js
Jd���||_dS)Nzvariable isn't set!)�INSTALL_LOG_FILE_MASK�REMOVE_LOG_FILE_MASK�_sink)�self�sinks  r�__init__zAbstractFeature.__init__:s=���)�@�@�+@�@�@�)��(�?�?�*?�?�?�(���
�
�
rc��HK�|����d{V��|_|S�N)�check_installed�is_installed�r2s r�initzAbstractFeature.init@s1����"&�"6�"6�"8�"8�8�8�8�8�8�8����rc�6�|�|j��Sr6)�
_get_live_logr/r9s r�installation_live_logz%AbstractFeature.installation_live_logDs���!�!�$�"<�=�=�=rc�6�|�|j��Sr6)r<r0r9s r�removal_live_logz AbstractFeature.removal_live_logHs���!�!�$�";�<�<�<rc���	t|dz��5}|���������\}}t	tjt|�������t�
|��d���cddd��S#1swxYwYdS#ttt
j
f$rYdSwxYw)z+Checks if any processes are using log file.z.pidg�-���q=)�rel_tolNF)�open�read�strip�splitr�psutil�Process�int�create_time�float�fromhex�OSError�
ValueError�
NoSuchProcess)�cls�log_file�pf�pid�
creation_times     r�_log_still_usedzAbstractFeature._log_still_usedLs��		��h��'�(�(�
�B�%'�W�W�Y�Y�_�_�%6�%6�%<�%<�%>�%>�"��]���N�3�s�8�8�,�,�8�8�:�:��M�M�-�0�0�!����
�
�
�
�
�
�
�
�
�
�
�
����
�
�
�
�
�
����V�%9�:�	�	�	��5�5�	���s5�C�BB7�*C�7B;�;C�>B;�?C�C$�#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��"�"�"rc�n�tt|j|�|����d��S)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)�next�filterrTrX)rO�	file_masks  rr<zAbstractFeature._get_live_logbs-���F�3�.����Y�0G�0G�H�H�$�O�O�Or�returnc��^K�|jrdS|jrdS|����d{V��S)NFT)r=r?�_check_installed_implr9s rr7zAbstractFeature.check_installednsJ�����%�	��5�� �	��4��/�/�1�1�1�1�1�1�1�1�1rc��
K�dS)NFrr9s rr_z%AbstractFeature._check_installed_implus�����urc��"K�t���)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 r�installzAbstractFeature.installys����"�#�#�#rc��"K�t���r6rbr9s r�removezAbstractFeature.remove�s����!�#�#�#rc����fd�}|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
        c���K�|jrtd���|jr'td�|j�����|jp�|���d{V��S)Nz$Wait until uninstalling is finished!z{} is already installed)r?r r8r+�format�NAMEr=�r2r#s �rr$z>AbstractFeature.raise_if_shouldnt_install_now.<locals>.wrapper�s{������$�
�"�#I�J�J�J��"�
�#�-�4�4�T�Y�?�?�����-�A�t�t�D�z�z�1A�1A�1A�1A�1A�1A�Arrr%s` r�raise_if_shouldnt_install_nowz-AbstractFeature.raise_if_shouldnt_install_now�s(���		B�		B�		B�		B�		B��rc����fd�}|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
        c���K�|jrtd���|js'td�|j�����|jp�|���d{V��S)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.<locals>.wrapper�s�������)�
�"�#I�J�J�J��&�
�#�A�H�H��	�������(�<�$�$�t�*�*�,<�,<�,<�,<�,<�,<�<rrr%s` r�raise_if_shouldnt_remove_nowz,AbstractFeature.raise_if_shouldnt_remove_now�s#���	=�	=�	=�	=�	=��rc��K�|jr'd�|j��}tj}n�|jr'd�|j��}tj}ng|����d{V��r'd�|j��}tj}n&d�|j��}tj	}d||d�iS)Nz{} is installingz{} is removingz{} is installedz{} is not installed�items)�message�status)
r=rirjrrr?rr7rr)r2�msgrss   rrszAbstractFeature.status�s������%�	1�$�+�+�D�I�6�6�C�"�-�F�F�
�
"�	1�"�)�)�$�)�4�4�C�"�+�F�F��'�'�)�)�
)�
)�
)�
)�
)�
)�	1�#�*�*�4�9�5�5�C�"�,�F�F�'�.�.�t�y�9�9�C�"�0�F��� ���
�	
rr6)rrrrjr/r0�	_CMD_LISTr4r:�propertyr=r?�classmethodrT�staticmethodrXr<�boolr7rr_�strrdrfrlrorsrrrr-r-4s��������D� �����I���������>�>��X�>��=�=��X�=�����[���#�#��\�#��	P�	P��[�	P�2�t�2�2�2�2���T�����^���$�s�$�$�$��^�$��$�c�$�$�$��^�$�����\��0����\��,
�
�
�
�
rr-)�	metaclass)rV�loggingr�abcrr�mathrrF�	getLoggerr�loggerrr&�	Exceptionr r+r-rrr�<module>r�s4����������	�	�	�	�'�'�'�'�'�'�'�'�������
�
�
�
�	��	�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  �

���������ddlZddlZddlZddlmZddlmZmZddlm	Z	m
Z
mZddlm
Z
ddlmZeje��ZGd�de	��ZdS)	�N)�Core)�
OsReleaseInfo�run_cmd_and_log_in_own_cgroup)�AbstractFeature�FeatureError�
FeatureStatus)�utils)�
exceptionsc����eZdZdZdZdejzZdZdZ	dezZ
dezZdezZdZ
d	Zee
egZd
ddd
d�Zdefd�Z�fd�Zejd���Zejd���Zd�Zd�Zd�Z�xZS)�
KernelCarez4/var/imunify360/plesk-previous-kernelcare-stats.jsonz<https://repo.cloudlinux.com/kernelcare/kernelcare_install.shz/var/log/%sz/usr/bin/kcarectlz%s/install-kernelcare.log.*z%s/remove-kernelcare.log.*zcurl -s %s | bashzyum remove -y kernelcarezapt-get -y remove kernelcarez)Host is updated to the latest patch levelzThere are no applied patchesz!There are new not applied patcheszKernel is unsupported)r����returnc��NK�tj�|j��S�N)�os�path�exists�BIN_PATH��selfs �`/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/features/kernel_care.py�_check_installed_implz KernelCare._check_installed_impl-s�����w�~�~�d�m�,�,�,�c
��j�K�t������d{V��}|ddtjk}|s|S|�d���d{V��\}}}	d|dd<|j||dd<n'#t$rtd|�d	|�d
|�d����wxYw|S)z}
        :raises FeatureError: if kernelcare returns unexpected error
        :return: str: feature's current status
        N�items�statusz--statusF�
edf_supported�messagezCUnknown error occured while getting status from kcarectl. stdout: [z], stderr: [z], return code: [�])�superrr�	INSTALLED�get_output_kcarectl�STATUS_MESSAGE�KeyErrorr)rr�is_feature_installed�ret�out�err�	__class__s      �rrzKernelCare.status0s�����
�w�w�~�~�'�'�'�'�'�'�'�'���7�O�H�%��)@�@�	�$�	��M�"�6�6�z�B�B�B�B�B�B�B�B�
��S�#�	�/4�F�7�O�O�,�)-�)<�S�)A�F�7�O�I�&�&���	�	�	��J��J�J�-0�J�J�CF�J�J�J���
�	�����
s�*!B�$B0c��NK�t|j|jddi����d{V��S)N�DEBIAN_FRONTEND�noninteractive)�env)r�INSTALL_CMD�INSTALL_LOG_FILE_MASKrs r�installzKernelCare.installIsR����
3����&�"�$4�5�
�
�
�
�
�
�
�
�
�	
rc��K�tj��tjzr|j}n|j}t||j���d{V��Sr)r�id_like�DEBIAN�REMOVE_CMD_DEBIAN�REMOVE_CMD_REDHATr�REMOVE_LOG_FILE_MASK)r�commands  r�removezKernelCare.removeTsi����� �"�"�]�%9�9�	-��,�G�G��,�G�2��T�.�
�
�
�
�
�
�
�
�	
rc���K�	|�dd���d{V��}n]#t$rtjd���tj$r.}|jdkr	d|jvs�tjd���d}~wwxYw	tj	|�
���d��d��}n"#t$rtjd	���wxYw|S)
Nz
--plugin-infoz--jsonzkcarectl not foundrs--jsonzbYour kcarectl version doesn't support --json option. Please, update to kernelcare-2.15-2 or newer.z	--START--���zTCan't decode kcarectl output as json. Try updating to the latest kernelcare version.)
�run_kcarectl�FileNotFoundErrorr
�RpcErrorr	�
CheckRunError�
returncode�stderr�json�loads�decode�	partition�
ValueError)r�output�e�resultss    r�get_plugin_infozKernelCare.get_plugin_info^s!����	��,�,�_�h�G�G�G�G�G�G�G�G�F�F�� �	<�	<�	<��%�&:�;�;�;��"�	�	�	��L�A�%�%�)�q�x�*?�*?��!�)�E��������	����	��j������!:�!:�;�!G�!G��!K�L�L�G�G���	�	�	��%�B���
�	����
�s!�!�,A;�
)A6�6A;�??B?�?Cc��LK�tj|jf|z���d{V��Sr)r	�	check_runr)r�optionss  rr=zKernelCare.run_kcarectlus2�����_�d�m�%5��%?�@�@�@�@�@�@�@�@�@rc��K�tj|jg|����d{V��\}}}||���|���fSr)r	�runrrE)rrNr(r)r*s     rr$zKernelCare.get_output_kcarectlxsT����#�i���(A��(A�B�B�B�B�B�B�B�B�
��S�#��C�J�J�L�L�#�*�*�,�,�.�.r)�__name__�
__module__�__qualname__�
KC_PROPERTIES�
KC_SCRIPT_URLr�PRODUCT�LOG_DIR�NAMErr1r8r0r7r6�	_CMD_LISTr%�boolrrr�raise_if_shouldnt_install_nowr2�raise_if_shouldnt_remove_nowr:rKr=r$�
__classcell__)r+s@rrrs:�������J�M�F���d�l�*�G��D�"�H�9�G�C��7�'�A��%�
�5�K�2��6���/�1B�C�I�7�)�.�"�	��N�-�T�-�-�-�-������2�2�
�
�3�2�
��1�
�
�2�1�
����.A�A�A�/�/�/�/�/�/�/rr)�loggingrrC� defence360agent.contracts.configr�defence360agent.utilsrr�0defence360agent.subsys.features.abstract_featurerrr�defence360agentr	�defence360agent.rpc_toolsr
�	getLoggerrQ�loggerr�rr�<module>rgs������	�	�	�	�����1�1�1�1�1�1�������������������
"�!�!�!�!�!�0�0�0�0�0�0�
��	�8�	$�	$��d/�d/�d/�d/�d/��d/�d/�d/�d/�d/rdefence360agent/subsys/features/__pycache__/kernel_care.cpython-311.pyc0000644000000000000000000001445700000000000023113 0ustar  �

���������ddlZddlZddlZddlmZddlmZmZddlm	Z	m
Z
mZddlm
Z
ddlmZeje��ZGd�de	��ZdS)	�N)�Core)�
OsReleaseInfo�run_cmd_and_log_in_own_cgroup)�AbstractFeature�FeatureError�
FeatureStatus)�utils)�
exceptionsc����eZdZdZdZdejzZdZdZ	dezZ
dezZdezZdZ
d	Zee
egZd
ddd
d�Zdefd�Z�fd�Zejd���Zejd���Zd�Zd�Zd�Z�xZS)�
KernelCarez4/var/imunify360/plesk-previous-kernelcare-stats.jsonz<https://repo.cloudlinux.com/kernelcare/kernelcare_install.shz/var/log/%sz/usr/bin/kcarectlz%s/install-kernelcare.log.*z%s/remove-kernelcare.log.*zcurl -s %s | bashzyum remove -y kernelcarezapt-get -y remove kernelcarez)Host is updated to the latest patch levelzThere are no applied patchesz!There are new not applied patcheszKernel is unsupported)r����returnc��NK�tj�|j��S�N)�os�path�exists�BIN_PATH��selfs �`/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/features/kernel_care.py�_check_installed_implz KernelCare._check_installed_impl-s�����w�~�~�d�m�,�,�,�c
��j�K�t������d{V��}|ddtjk}|s|S|�d���d{V��\}}}	d|dd<|j||dd<n'#t$rtd|�d	|�d
|�d����wxYw|S)z}
        :raises FeatureError: if kernelcare returns unexpected error
        :return: str: feature's current status
        N�items�statusz--statusF�
edf_supported�messagezCUnknown error occured while getting status from kcarectl. stdout: [z], stderr: [z], return code: [�])�superrr�	INSTALLED�get_output_kcarectl�STATUS_MESSAGE�KeyErrorr)rr�is_feature_installed�ret�out�err�	__class__s      �rrzKernelCare.status0s�����
�w�w�~�~�'�'�'�'�'�'�'�'���7�O�H�%��)@�@�	�$�	��M�"�6�6�z�B�B�B�B�B�B�B�B�
��S�#�	�/4�F�7�O�O�,�)-�)<�S�)A�F�7�O�I�&�&���	�	�	��J��J�J�-0�J�J�CF�J�J�J���
�	�����
s�*!B�$B0c��NK�t|j|jddi����d{V��S)N�DEBIAN_FRONTEND�noninteractive)�env)r�INSTALL_CMD�INSTALL_LOG_FILE_MASKrs r�installzKernelCare.installIsR����
3����&�"�$4�5�
�
�
�
�
�
�
�
�
�	
rc��K�tj��tjzr|j}n|j}t||j���d{V��Sr)r�id_like�DEBIAN�REMOVE_CMD_DEBIAN�REMOVE_CMD_REDHATr�REMOVE_LOG_FILE_MASK)r�commands  r�removezKernelCare.removeTsi����� �"�"�]�%9�9�	-��,�G�G��,�G�2��T�.�
�
�
�
�
�
�
�
�	
rc���K�	|�dd���d{V��}n]#t$rtjd���tj$r.}|jdkr	d|jvs�tjd���d}~wwxYw	tj	|�
���d��d��}n"#t$rtjd	���wxYw|S)
Nz
--plugin-infoz--jsonzkcarectl not foundrs--jsonzbYour kcarectl version doesn't support --json option. Please, update to kernelcare-2.15-2 or newer.z	--START--���zTCan't decode kcarectl output as json. Try updating to the latest kernelcare version.)
�run_kcarectl�FileNotFoundErrorr
�RpcErrorr	�
CheckRunError�
returncode�stderr�json�loads�decode�	partition�
ValueError)r�output�e�resultss    r�get_plugin_infozKernelCare.get_plugin_info^s!����	��,�,�_�h�G�G�G�G�G�G�G�G�F�F�� �	<�	<�	<��%�&:�;�;�;��"�	�	�	��L�A�%�%�)�q�x�*?�*?��!�)�E��������	����	��j������!:�!:�;�!G�!G��!K�L�L�G�G���	�	�	��%�B���
�	����
�s!�!�,A;�
)A6�6A;�??B?�?Cc��LK�tj|jf|z���d{V��Sr)r	�	check_runr)r�optionss  rr=zKernelCare.run_kcarectlus2�����_�d�m�%5��%?�@�@�@�@�@�@�@�@�@rc��K�tj|jg|����d{V��\}}}||���|���fSr)r	�runrrE)rrNr(r)r*s     rr$zKernelCare.get_output_kcarectlxsT����#�i���(A��(A�B�B�B�B�B�B�B�B�
��S�#��C�J�J�L�L�#�*�*�,�,�.�.r)�__name__�
__module__�__qualname__�
KC_PROPERTIES�
KC_SCRIPT_URLr�PRODUCT�LOG_DIR�NAMErr1r8r0r7r6�	_CMD_LISTr%�boolrrr�raise_if_shouldnt_install_nowr2�raise_if_shouldnt_remove_nowr:rKr=r$�
__classcell__)r+s@rrrs:�������J�M�F���d�l�*�G��D�"�H�9�G�C��7�'�A��%�
�5�K�2��6���/�1B�C�I�7�)�.�"�	��N�-�T�-�-�-�-������2�2�
�
�3�2�
��1�
�
�2�1�
����.A�A�A�/�/�/�/�/�/�/rr)�loggingrrC� defence360agent.contracts.configr�defence360agent.utilsrr�0defence360agent.subsys.features.abstract_featurerrr�defence360agentr	�defence360agent.rpc_toolsr
�	getLoggerrQ�loggerr�rr�<module>rgs������	�	�	�	�����1�1�1�1�1�1�������������������
"�!�!�!�!�!�0�0�0�0�0�0�
��	�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  �

s�����s���dS)N�r��[/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/__init__.py�<module>rs���rdefence360agent/subsys/panels/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000031000000000000022023 0ustar  �

s�����s���dS)N�r��[/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/__init__.py�<module>rs���rdefence360agent/subsys/panels/__pycache__/base.cpython-311.opt-1.pyc0000644000000000000000000003607200000000000022153 0ustar  �

�6�ݟ�vI��D�ddlZddlmZmZddlmZddlmZddlm	Z	ddl
mZddlm
Z
mZmZmZddlmZmZgd	�Zd
ZGd�de	��ZGd
�de��ZGd�de��ZeGd�d����ZGd�de��Zd�ZGd�de��Zd�ZdS)�N)�ABC�abstractmethod)�defaultdict)�	dataclass)�IntEnum)�Path)�Dict�List�Optional�Set)�APIError�	IPEchoAPI)�20�21�22�25�53�80�110�443�587�993�995z
generic panelc��eZdZdZdZdZdS)�	UserLevel���N)�__name__�
__module__�__qualname__�ADMIN�RESSELER�REGULAR_USER���W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/base.pyrrs������
�E��H��L�L�Lr&rc��eZdZdS)�PanelExceptionN�rr r!r%r&r'r)r)"��������Dr&r)c��eZdZdS)�InvalidTokenExceptionNr*r%r&r'r-r-&r+r&r-c�8�eZdZUeed<eed<eed<eed<dS)�
DomainData�docroot�domain�type�usernameN)rr r!�str�__annotations__r%r&r'r/r/*s4�������
�L�L�L��K�K�K�

�I�I�I��M�M�M�M�Mr&r/c�f�eZdZdZdZdgezdgezd�gd�gd�d�d�ZeZgZ	e
ed	�����Ze
d
���Z
e
d���Ze
d���Zed*d���Zed*d���Zed���Zedeefd���Zedeeeeffd���Zedeeeeffd���Zedefd���Zdeeeeefffd�Zdefd�Zdefd�Ze
d���Zde fd�Z!ede"efd���Z#e
dede$fd���Z%e
dede&efd���Z'e
defd ���Z(e
d
d!�d"���Z)edeeeffd#���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.�MINIMAL�465�113)�in�out)rrrr)rrrr:�123)�tcp�udpc��dS)z\
        Checks if hosting panel installed on the known path
        :return: bool:
        Nr%��clss r'�is_installedzAbstractPanel.is_installedDs	��	
�r&c�L�	tj��S#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��	��&�(�(�(���	�	�	��2�2�	���s��
#�#c��
K�dS�Nr%rAs r'�versionzAbstractPanel.versionXs�����tr&c��K�|jSrI)�NAMErAs r'�namezAbstractPanel.name\s
�����x�r&Nc��
K�dS)zM
        Registers and enables Imunify360 UI plugin in hosting panel
        Nr%��selfrMs  r'�enable_imunify_pluginz#AbstractPanel.enable_imunify_plugin`�
����
	
�r&c��
K�dS)zC
        UnRegisters Imunify360 UI plugin in hosting panel
        Nr%rOs  r'�disable_imunify_pluginz$AbstractPanel.disable_imunify_plugingrRr&c��
K�dS)zP
        Returns domains hosted via control panel
        :return: list
        Nr%�rPs r'�get_user_domainszAbstractPanel.get_user_domainsn�
����	
�r&�returnc��
K�dS)zO
        Returns system users from hosting panel
        :return: list
        Nr%rVs r'�	get_userszAbstractPanel.get_usersvrXr&c��
K�dS)zA
        Returns dict with domain to list of users pairs
        Nr%rVs r'�get_domain_to_ownerz!AbstractPanel.get_domain_to_owner~rRr&c��
K�dS)zA
        Returns dict with user to list of domains pairs
        Nr%rVs r'�get_domains_per_userz"AbstractPanel.get_domains_per_user�rRr&c��
K�dS)z#
        Returns panel url
        Nr%)rPr3s  r'�panel_user_linkzAbstractPanel.panel_user_link�rRr&c��NK�d�|����d{V��D��S)z7
        Returns dict with user to email pairs
        c��i|]}|ddd���	S)rE)�email�localer%)�.0�users  r'�
<dictcomp>z2AbstractPanel.get_user_details.<locals>.<dictcomp>�s2��
�
�
��
�B�"�-�-�
�
�
r&N)r[rVs r'�get_user_detailszAbstractPanel.get_user_details�sC����

�
�"�n�n�.�.�.�.�.�.�.�.�
�
�
�	
r&c��nK�tt|����d{V������SrI)�len�listr[rVs r'�users_countzAbstractPanel.users_count�s8�����4�d�n�n�.�.�.�.�.�.�.�.�/�/�0�0�0r&�datac�n�d}|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)�_uid�pwd�getpwuid�pw_namerg)rP�protocolrnrM�pws     r'�authenticatezAbstractPanel.authenticate�s=�����=�A�����h�m�,�,�B��:�D��}�d�"�"r&c��t�rI��NotImplementedErrorrAs r'�get_modsec_config_pathz$AbstractPanel.get_modsec_config_path�s��!�!r&c��dS)z(
        Return Conflict status
        Fr%rVs r'�get_SMTP_conflict_statusz&AbstractPanel.get_SMTP_conflict_status�s	���ur&c��dSrIr%rVs r'�basedirszAbstractPanel.basedirs�s���r&�home_dirc�R�t|�����j}|SrI)r�resolve�parent)rBr�base_dirs   r'�
base_home_dirzAbstractPanel.base_home_dir�s!����>�>�)�)�+�+�2���r&c�,�	|�|��}t|�����}|�|��}n#tt
f$rYdSwxYwddlm}t|dd��x}r|}t|dz|z��S)Nr)�MalwareTune�RAPID_SCAN_BASEDIR_OVERRIDEz.rapid-scan-db)
r�rr��relative_to�
ValueError�RuntimeError� defence360agent.contracts.configr��getattrr4)rBrr��
resolved_home�tailr��rapid_scan_basedir_overrides       r'�get_rapid_scan_db_dirz#AbstractPanel.get_rapid_scan_db_dir�s���	��(�(��2�2�H� ��N�N�2�2�4�4�M� �,�,�X�6�6�D�D���L�)�	�	�	��4�4�	����	A�@�@�@�@�@�*1��6��+
�+
�
�&�	3�3�H��8�.�.��5�6�6�6s�AA�A#�"A#c��K�t�)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_key�s����"�!r&)rgc��
K�dS)zD
        Notify a customer using the panel internal tooling
        Nr%)rB�message_type�paramsrgs    r'�notifyzAbstractPanel.notify��
����
�tr&c��
K�dS)z5
        :return dict with docroot to domain
        Nr%rVs r'�
list_docrootszAbstractPanel.list_docroots�rRr&�myimunify_enabledc��
K�dS)zK
        Switch UI panel configuration between Im360 and MyImunify
        Nr%)rBr�s  r'�switch_ui_configzAbstractPanel.switch_ui_config�r�r&c���K�|����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)r�rrl�items�append)rP�	doc_roots�domain_paths�doc_root�domain_names     r'�get_domain_pathszAbstractPanel.get_domain_paths�sx�����,�,�.�.�.�.�.�.�.�.�	�"�4�(�(��%.�_�_�%6�%6�	7�	7�!�H�k���%�,�,�X�6�6�6�6��r&c��K�|����d{V��}|����d{V��}|����d{V��}|����d{V��}g}|D]�}||}|d}|d}	|�dd��}
|�dttj����}|�dd��}|�|g��}
g}|
D]0}|�|g��}|�||d����1|�|||	|
|||d	�����|S)
Nrdrer�rE�level�	suspendedF)r1�paths)r3rd�languager�r�r��domains)	r[r�r_ri�get�intrr$r�)rP�panel_usersr��user_domains�user_details�users�	user_name�detailsrdrer�r�r�r��user_domain_pathsr�r�s                 r'�patchman_userszAbstractPanel.patchman_userss����� �N�N�,�,�,�,�,�,�,�,��!�2�2�4�4�4�4�4�4�4�4��!�6�6�8�8�8�8�8�8�8�8��!�2�2�4�4�4�4�4�4�4�4����$�	�	�I�"�9�-�G��G�$�E��X�&�F��[�[��2�.�.�F��K�K���Y�-C�)D�)D�E�E�E����K��7�7�I�"�&�&�y�"�5�5�G� "��&�
�
��$�(�(��b�9�9��!�(�(�"-�!&�������
�L�L� )�"� &�$�"�!*�0���

�

�

�

��r&r3c��"K�t���rIrx)rBr3s  r'�get_user_domains_detailsz&AbstractPanel.get_user_domains_details/s����!�#�#�#r&rI)1rr r!�__doc__rL�TCP_PORTS_COMMON�
OPEN_PORTSr)�	exception�smtp_allow_users�classmethodrrCrGrJrMrQrTrWr
r4r[r	r]r_rarir�rm�dictrvrz�boolr|rr~rr�rr�r�r�r�r�r�r�rlr/r�r%r&r'r7r72sB���������D��'�,�,��7�-�-�
�
�
,�+�+�3�3�3�
�
�	�	�J��I�����
�
��^��[�
�����[������[������[���
�
�
��^�
��
�
�
��^�
��
�
��^�
��
��c��
�
�
��^�
��
�4��T�#�Y��+?�
�
�
��^�
��
�D��d�3�i��,@�
�
�
��^�
��
��
�
�
��^�
�
��S�$�s�C�x�.�-@�(A�
�
�
�
�1�3�1�1�1�1�#�4�#�#�#�#�(�"�"��[�"��$������
�#�c�(�
�
�
��^�
���S��T�����[���7�S�7�X�c�]�7�7�7��[�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:
    c������fd�}|S)z&
        :param fn: coroutine
        c���K�|jdi���s"|�d|jjz����|g|�Ri|���d{V��S)Nz%s is not valid!r%)rCr��	__class__r)rP�args�kwargs�
dec_kwargs�fns   ��r'�wrapperz;ensure_valid_panel.<locals>.real_decorator.<locals>.wrapperFsy�����$�4�$�2�2�z�2�2�
��n�n�&���)@�@������D�2�4�2�2�2�6�2�2�2�2�2�2�2�2�2r&r%)r�r�r�s` �r'�real_decoratorz*ensure_valid_panel.<locals>.real_decoratorAs)����
	3�	3�	3�	3�	3�	3��r&r%)r�r�s` r'�ensure_valid_panelr�4s$���������r&c��eZdZdZdS)�ModsecVendorsErrorz9
    Raises when its impossible to get modsec vendor
    N)rr r!r�r%r&r'r�r�Rs��������	�Dr&r�c����fd�}|S)z�Decorator 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{V��S)Nz'Method is not allowed for dnsonly panel)�_is_dns_onlyr�)rPr�r�r�s   �r'r�z forbid_dns_only.<locals>.wrapper`sa����������	L��.�.�!J�K�K�K��R��.�t�.�.�.�v�.�.�.�.�.�.�.�.�.r&r%)r�r�s` r'�forbid_dns_onlyr�Zs#���/�/�/�/�/�
�Nr&)rq�abcrr�collectionsr�dataclassesr�enumr�pathlibr�typingr	r
rr�defence360agent.utils.ipechor
rr��GENERIC_PANEL_NAMEr�	Exceptionr)r-r/r7r�r�r�r%r&r'�<module>r�s���
�
�
�
�#�#�#�#�#�#�#�#�#�#�#�#�#�#�!�!�!�!�!�!�������������,�,�,�,�,�,�,�,�,�,�,�,�<�<�<�<�<�<�<�<�����%�����������	�	�	�	�	�Y�	�	�	�	�	�	�	�	�I�	�	�	������������$�$�$�$�$�C�$�$�$�D���<	�	�	�	�	��	�	�	�����r&defence360agent/subsys/panels/__pycache__/base.cpython-311.pyc0000644000000000000000000003607200000000000021214 0ustar  �

�6�ݟ�vI��D�ddlZddlmZmZddlmZddlmZddlm	Z	ddl
mZddlm
Z
mZmZmZddlmZmZgd	�Zd
ZGd�de	��ZGd
�de��ZGd�de��ZeGd�d����ZGd�de��Zd�ZGd�de��Zd�ZdS)�N)�ABC�abstractmethod)�defaultdict)�	dataclass)�IntEnum)�Path)�Dict�List�Optional�Set)�APIError�	IPEchoAPI)�20�21�22�25�53�80�110�443�587�993�995z
generic panelc��eZdZdZdZdZdS)�	UserLevel���N)�__name__�
__module__�__qualname__�ADMIN�RESSELER�REGULAR_USER���W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/base.pyrrs������
�E��H��L�L�Lr&rc��eZdZdS)�PanelExceptionN�rr r!r%r&r'r)r)"��������Dr&r)c��eZdZdS)�InvalidTokenExceptionNr*r%r&r'r-r-&r+r&r-c�8�eZdZUeed<eed<eed<eed<dS)�
DomainData�docroot�domain�type�usernameN)rr r!�str�__annotations__r%r&r'r/r/*s4�������
�L�L�L��K�K�K�

�I�I�I��M�M�M�M�Mr&r/c�f�eZdZdZdZdgezdgezd�gd�gd�d�d�ZeZgZ	e
ed	�����Ze
d
���Z
e
d���Ze
d���Zed*d���Zed*d���Zed���Zedeefd���Zedeeeeffd���Zedeeeeffd���Zedefd���Zdeeeeefffd�Zdefd�Zdefd�Ze
d���Zde fd�Z!ede"efd���Z#e
dede$fd���Z%e
dede&efd���Z'e
defd ���Z(e
d
d!�d"���Z)edeeeffd#���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.�MINIMAL�465�113)�in�out)rrrr)rrrr:�123)�tcp�udpc��dS)z\
        Checks if hosting panel installed on the known path
        :return: bool:
        Nr%��clss r'�is_installedzAbstractPanel.is_installedDs	��	
�r&c�L�	tj��S#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��	��&�(�(�(���	�	�	��2�2�	���s��
#�#c��
K�dS�Nr%rAs r'�versionzAbstractPanel.versionXs�����tr&c��K�|jSrI)�NAMErAs r'�namezAbstractPanel.name\s
�����x�r&Nc��
K�dS)zM
        Registers and enables Imunify360 UI plugin in hosting panel
        Nr%��selfrMs  r'�enable_imunify_pluginz#AbstractPanel.enable_imunify_plugin`�
����
	
�r&c��
K�dS)zC
        UnRegisters Imunify360 UI plugin in hosting panel
        Nr%rOs  r'�disable_imunify_pluginz$AbstractPanel.disable_imunify_plugingrRr&c��
K�dS)zP
        Returns domains hosted via control panel
        :return: list
        Nr%�rPs r'�get_user_domainszAbstractPanel.get_user_domainsn�
����	
�r&�returnc��
K�dS)zO
        Returns system users from hosting panel
        :return: list
        Nr%rVs r'�	get_userszAbstractPanel.get_usersvrXr&c��
K�dS)zA
        Returns dict with domain to list of users pairs
        Nr%rVs r'�get_domain_to_ownerz!AbstractPanel.get_domain_to_owner~rRr&c��
K�dS)zA
        Returns dict with user to list of domains pairs
        Nr%rVs r'�get_domains_per_userz"AbstractPanel.get_domains_per_user�rRr&c��
K�dS)z#
        Returns panel url
        Nr%)rPr3s  r'�panel_user_linkzAbstractPanel.panel_user_link�rRr&c��NK�d�|����d{V��D��S)z7
        Returns dict with user to email pairs
        c��i|]}|ddd���	S)rE)�email�localer%)�.0�users  r'�
<dictcomp>z2AbstractPanel.get_user_details.<locals>.<dictcomp>�s2��
�
�
��
�B�"�-�-�
�
�
r&N)r[rVs r'�get_user_detailszAbstractPanel.get_user_details�sC����

�
�"�n�n�.�.�.�.�.�.�.�.�
�
�
�	
r&c��nK�tt|����d{V������SrI)�len�listr[rVs r'�users_countzAbstractPanel.users_count�s8�����4�d�n�n�.�.�.�.�.�.�.�.�/�/�0�0�0r&�datac�n�d}|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)�_uid�pwd�getpwuid�pw_namerg)rP�protocolrnrM�pws     r'�authenticatezAbstractPanel.authenticate�s=�����=�A�����h�m�,�,�B��:�D��}�d�"�"r&c��t�rI��NotImplementedErrorrAs r'�get_modsec_config_pathz$AbstractPanel.get_modsec_config_path�s��!�!r&c��dS)z(
        Return Conflict status
        Fr%rVs r'�get_SMTP_conflict_statusz&AbstractPanel.get_SMTP_conflict_status�s	���ur&c��dSrIr%rVs r'�basedirszAbstractPanel.basedirs�s���r&�home_dirc�R�t|�����j}|SrI)r�resolve�parent)rBr�base_dirs   r'�
base_home_dirzAbstractPanel.base_home_dir�s!����>�>�)�)�+�+�2���r&c�,�	|�|��}t|�����}|�|��}n#tt
f$rYdSwxYwddlm}t|dd��x}r|}t|dz|z��S)Nr)�MalwareTune�RAPID_SCAN_BASEDIR_OVERRIDEz.rapid-scan-db)
r�rr��relative_to�
ValueError�RuntimeError� defence360agent.contracts.configr��getattrr4)rBrr��
resolved_home�tailr��rapid_scan_basedir_overrides       r'�get_rapid_scan_db_dirz#AbstractPanel.get_rapid_scan_db_dir�s���	��(�(��2�2�H� ��N�N�2�2�4�4�M� �,�,�X�6�6�D�D���L�)�	�	�	��4�4�	����	A�@�@�@�@�@�*1��6��+
�+
�
�&�	3�3�H��8�.�.��5�6�6�6s�AA�A#�"A#c��K�t�)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_key�s����"�!r&)rgc��
K�dS)zD
        Notify a customer using the panel internal tooling
        Nr%)rB�message_type�paramsrgs    r'�notifyzAbstractPanel.notify��
����
�tr&c��
K�dS)z5
        :return dict with docroot to domain
        Nr%rVs r'�
list_docrootszAbstractPanel.list_docroots�rRr&�myimunify_enabledc��
K�dS)zK
        Switch UI panel configuration between Im360 and MyImunify
        Nr%)rBr�s  r'�switch_ui_configzAbstractPanel.switch_ui_config�r�r&c���K�|����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)r�rrl�items�append)rP�	doc_roots�domain_paths�doc_root�domain_names     r'�get_domain_pathszAbstractPanel.get_domain_paths�sx�����,�,�.�.�.�.�.�.�.�.�	�"�4�(�(��%.�_�_�%6�%6�	7�	7�!�H�k���%�,�,�X�6�6�6�6��r&c��K�|����d{V��}|����d{V��}|����d{V��}|����d{V��}g}|D]�}||}|d}|d}	|�dd��}
|�dttj����}|�dd��}|�|g��}
g}|
D]0}|�|g��}|�||d����1|�|||	|
|||d	�����|S)
Nrdrer�rE�level�	suspendedF)r1�paths)r3rd�languager�r�r��domains)	r[r�r_ri�get�intrr$r�)rP�panel_usersr��user_domains�user_details�users�	user_name�detailsrdrer�r�r�r��user_domain_pathsr�r�s                 r'�patchman_userszAbstractPanel.patchman_userss����� �N�N�,�,�,�,�,�,�,�,��!�2�2�4�4�4�4�4�4�4�4��!�6�6�8�8�8�8�8�8�8�8��!�2�2�4�4�4�4�4�4�4�4����$�	�	�I�"�9�-�G��G�$�E��X�&�F��[�[��2�.�.�F��K�K���Y�-C�)D�)D�E�E�E����K��7�7�I�"�&�&�y�"�5�5�G� "��&�
�
��$�(�(��b�9�9��!�(�(�"-�!&�������
�L�L� )�"� &�$�"�!*�0���

�

�

�

��r&r3c��"K�t���rIrx)rBr3s  r'�get_user_domains_detailsz&AbstractPanel.get_user_domains_details/s����!�#�#�#r&rI)1rr r!�__doc__rL�TCP_PORTS_COMMON�
OPEN_PORTSr)�	exception�smtp_allow_users�classmethodrrCrGrJrMrQrTrWr
r4r[r	r]r_rarir�rm�dictrvrz�boolr|rr~rr�rr�r�r�r�r�r�r�rlr/r�r%r&r'r7r72sB���������D��'�,�,��7�-�-�
�
�
,�+�+�3�3�3�
�
�	�	�J��I�����
�
��^��[�
�����[������[������[���
�
�
��^�
��
�
�
��^�
��
�
��^�
��
��c��
�
�
��^�
��
�4��T�#�Y��+?�
�
�
��^�
��
�D��d�3�i��,@�
�
�
��^�
��
��
�
�
��^�
�
��S�$�s�C�x�.�-@�(A�
�
�
�
�1�3�1�1�1�1�#�4�#�#�#�#�(�"�"��[�"��$������
�#�c�(�
�
�
��^�
���S��T�����[���7�S�7�X�c�]�7�7�7��[�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:
    c������fd�}|S)z&
        :param fn: coroutine
        c���K�|jdi���s"|�d|jjz����|g|�Ri|���d{V��S)Nz%s is not valid!r%)rCr��	__class__r)rP�args�kwargs�
dec_kwargs�fns   ��r'�wrapperz;ensure_valid_panel.<locals>.real_decorator.<locals>.wrapperFsy�����$�4�$�2�2�z�2�2�
��n�n�&���)@�@������D�2�4�2�2�2�6�2�2�2�2�2�2�2�2�2r&r%)r�r�r�s` �r'�real_decoratorz*ensure_valid_panel.<locals>.real_decoratorAs)����
	3�	3�	3�	3�	3�	3��r&r%)r�r�s` r'�ensure_valid_panelr�4s$���������r&c��eZdZdZdS)�ModsecVendorsErrorz9
    Raises when its impossible to get modsec vendor
    N)rr r!r�r%r&r'r�r�Rs��������	�Dr&r�c����fd�}|S)z�Decorator 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{V��S)Nz'Method is not allowed for dnsonly panel)�_is_dns_onlyr�)rPr�r�r�s   �r'r�z forbid_dns_only.<locals>.wrapper`sa����������	L��.�.�!J�K�K�K��R��.�t�.�.�.�v�.�.�.�.�.�.�.�.�.r&r%)r�r�s` r'�forbid_dns_onlyr�Zs#���/�/�/�/�/�
�Nr&)rq�abcrr�collectionsr�dataclassesr�enumr�pathlibr�typingr	r
rr�defence360agent.utils.ipechor
rr��GENERIC_PANEL_NAMEr�	Exceptionr)r-r/r7r�r�r�r%r&r'�<module>r�s���
�
�
�
�#�#�#�#�#�#�#�#�#�#�#�#�#�#�!�!�!�!�!�!�������������,�,�,�,�,�,�,�,�,�,�,�,�<�<�<�<�<�<�<�<�����%�����������	�	�	�	�	�Y�	�	�	�	�	�	�	�	�I�	�	�	������������$�$�$�$�$�C�$�$�$�D���<	�	�	�	�	��	�	�	�����r&defence360agent/subsys/panels/__pycache__/hosting_panel.cpython-311.opt-1.pyc0000644000000000000000000000305100000000000024062 0ustar  �

��ݡ���l�ddlmZddlmZddlmZdefd�Ze��ada	deddfd�Z
ddefd
�ZdS)�)�get_hosting_panel)�
AbstractPanel)�importer�returnc�2�tjd��rdSdS)z<Use im360 panel classes when the im360 package is installed.�im360�defence360agent)r�exists���`/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/hosting_panel.py�_default_panel_rootrs����w�����w��rN�root_modulec��|adadS)N)�_panel_root�panel)rs r
�set_panel_rootrs���K��E�E�ErFc�J�t�|rtt��atS)z�
    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``.
    )rrr)�check_for_changess r
�HostingPanelrs ��
�}�)�}�!�+�.�.���Lr)F)�3defence360agent.application.determine_hosting_panelr�"defence360agent.subsys.panels.baser�defence360agent.utilsr�strrrrrrrrr
�<module>rs���������=�<�<�<�<�<�*�*�*�*�*�*��S�����"�!�#�#��������������]������rdefence360agent/subsys/panels/__pycache__/hosting_panel.cpython-311.pyc0000644000000000000000000000305100000000000023123 0ustar  �

��ݡ���l�ddlmZddlmZddlmZdefd�Ze��ada	deddfd�Z
ddefd
�ZdS)�)�get_hosting_panel)�
AbstractPanel)�importer�returnc�2�tjd��rdSdS)z<Use im360 panel classes when the im360 package is installed.�im360�defence360agent)r�exists���`/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/hosting_panel.py�_default_panel_rootrs����w�����w��rN�root_modulec��|adadS)N)�_panel_root�panel)rs r
�set_panel_rootrs���K��E�E�ErFc�J�t�|rtt��atS)z�
    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``.
    )rrr)�check_for_changess r
�HostingPanelrs ��
�}�)�}�!�+�.�.���Lr)F)�3defence360agent.application.determine_hosting_panelr�"defence360agent.subsys.panels.baser�defence360agent.utilsr�strrrrrrrrr
�<module>rs���������=�<�<�<�<�<�*�*�*�*�*�*��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  �

��c�z�����ddlmZdgZdS)�)�cPanelrN)�panelr�__all__���b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/cpanel/__init__.py�<module>r	s"���������*���rdefence360agent/subsys/panels/cpanel/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000043200000000000023272 0ustar  �

��c�z�����ddlmZdgZdS)�)�cPanelrN)�panelr�__all__���b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/cpanel/__init__.py�<module>r	s"���������*���rdefence360agent/subsys/panels/cpanel/__pycache__/packages.cpython-311.opt-1.pyc0000644000000000000000000001424100000000000024253 0ustar  �

�KS9�Sj���L�ddlZddlZddlmZddlmZddlmZmZej	e
��ZGd�de��ZGd�de
��Zeejd	�
��d���d
edefd���Zddeefd�Zdededdfd�Zdededdfd�Zdeddfd�Zdeddfd�ZdS)�N)�List)�timed_cache)�WHMAPIException�whmapi1c��eZdZdS)�PackageNotExistErrorN)�__name__�
__module__�__qualname__���b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/cpanel/packages.pyrrs�������Dr
rc�B�eZdZdeefd�Zdedefd�Zdefd�ZdS)�PkgInfo�returnc�R�|�dd�����S)N�_PACKAGE_EXTENSIONS�)�get�split��selfs r�
extensionszPkgInfo.extensionss#���x�x�-�r�2�2�8�8�:�:�:r
�namec�.�||���vS)N)r)rrs  r�
has_extensionzPkgInfo.has_extensions���t���(�(�(�(r
c��|dS)Nrrrs rrzPkgInfo.names���F�|�r
N)	r	r
rr�strr�boolrrrr
rrrsp������;�D��I�;�;�;�;�)�#�)�$�)�)�)�)��c������r
r�Z)�seconds�d)�maxsizerrc���K�	td|����d{V��}n3#t$r&}dt|��vrt|����d}~wwxYwt	|d��}||d<|S)N�
getpkginfo)�pkgzNo such file or directoryr&r)rrrrr)r�data�e�infos    r�get_package_infor*s�������\�t�4�4�4�4�4�4�4�4�4��������&�#�a�&�&�0�0�&�q�)�)�)������	����
�4��;���D��D��L��Ks��
A�!A�A�allc��XK�td|����d{V��}d�|dD��S)N�listpkgs)�wantc�,�g|]}t|����Sr)r)�.0�items  r�
<listcomp>z!list_packages.<locals>.<listcomp>*s��2�2�2�d�G�D�M�M�2�2�2r
r&)r)r.r's  r�
list_packagesr3(sB������$�/�/�/�/�/�/�/�/�/�D�2�2�d�5�k�2�2�2�2r
�extension_name�package_infoc��K�|���}|�|��r6td||����d{V��t�d||��dSt�d||��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 %s�rrr�loggerr))r4r5rs   r�remove_extensionr;-s����������D��!�!�.�1�1����d�~�
�
�
�	
�	
�	
�	
�	
�	
�	
�	���2�N�D�	
�	
�	
�	��
�K�K�:�������r
c��K�|���}|�|��s4t	d||d�|���d{V��t�d||��dSt�d||��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)r4r5�kwargsrs    r�
add_extensionr??s����������D��%�%�n�5�5�
���
�� .�
�
��	
�
�	
�	
�	
�	
�	
�	
�	
�	���1�>�4�	
�	
�	
�	��
�K�K�9�>�4�����r
c���K�t���d{V��D]I}	t||fi|���d{V���#t$r%t�d||d��Y�FwxYwdS)z+Add given extension to all cPanel packages.Nz(Unable to add extension %s to package %sr)r3r?rr:�	exception)r4r>r&s   r�add_extension_for_allrBVs�����"�_�_�$�$�$�$�$�$����	����>�>�v�>�>�>�>�>�>�>�>�>�>���	�	�	����:���F��
�
�
�
�
�	�����s�/�,A�Ac��K�t���d{V��D]K}	t||���d{V���#t$r%t�d||d��Y�HwxYwt�d��dS)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  r�remove_extension_from_allrDcs�����"�_�_�$�$�$�$�$�$����	�"�>�3�7�7�7�7�7�7�7�7�7�7���	�	�	����?���F��
�
�
�
�
�	�����K�K�L�����s�1�,A �A )r+)�logging�datetime�typingr�defence360agent.utilsr�(defence360agent.subsys.panels.cpanel.whmrr�	getLoggerr	r:r�dictr�	timedeltarr*r3r;r?rBrDrr
r�<module>rMs�����������������-�-�-�-�-�-�M�M�M�M�M�M�M�M�	��	�8�	$�	$��	�	�	�	�	�?�	�	�	������d����
��
�X�
��
+�
+�
+�S�9�9�9�
��
��
�
�
�:�9�
�3�3�t�G�}�3�3�3�3�
�3��g��$�����$���'.��	�����.
��
�$�
�
�
�
�
�C�
�D�
�
�
�
�
�
r
defence360agent/subsys/panels/cpanel/__pycache__/packages.cpython-311.pyc0000644000000000000000000001424100000000000023314 0ustar  �

�KS9�Sj���L�ddlZddlZddlmZddlmZddlmZmZej	e
��ZGd�de��ZGd�de
��Zeejd	�
��d���d
edefd���Zddeefd�Zdededdfd�Zdededdfd�Zdeddfd�Zdeddfd�ZdS)�N)�List)�timed_cache)�WHMAPIException�whmapi1c��eZdZdS)�PackageNotExistErrorN)�__name__�
__module__�__qualname__���b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/cpanel/packages.pyrrs�������Dr
rc�B�eZdZdeefd�Zdedefd�Zdefd�ZdS)�PkgInfo�returnc�R�|�dd�����S)N�_PACKAGE_EXTENSIONS�)�get�split��selfs r�
extensionszPkgInfo.extensionss#���x�x�-�r�2�2�8�8�:�:�:r
�namec�.�||���vS)N)r)rrs  r�
has_extensionzPkgInfo.has_extensions���t���(�(�(�(r
c��|dS)Nrrrs rrzPkgInfo.names���F�|�r
N)	r	r
rr�strr�boolrrrr
rrrsp������;�D��I�;�;�;�;�)�#�)�$�)�)�)�)��c������r
r�Z)�seconds�d)�maxsizerrc���K�	td|����d{V��}n3#t$r&}dt|��vrt|����d}~wwxYwt	|d��}||d<|S)N�
getpkginfo)�pkgzNo such file or directoryr&r)rrrrr)r�data�e�infos    r�get_package_infor*s�������\�t�4�4�4�4�4�4�4�4�4��������&�#�a�&�&�0�0�&�q�)�)�)������	����
�4��;���D��D��L��Ks��
A�!A�A�allc��XK�td|����d{V��}d�|dD��S)N�listpkgs)�wantc�,�g|]}t|����Sr)r)�.0�items  r�
<listcomp>z!list_packages.<locals>.<listcomp>*s��2�2�2�d�G�D�M�M�2�2�2r
r&)r)r.r's  r�
list_packagesr3(sB������$�/�/�/�/�/�/�/�/�/�D�2�2�d�5�k�2�2�2�2r
�extension_name�package_infoc��K�|���}|�|��r6td||����d{V��t�d||��dSt�d||��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 %s�rrr�loggerr))r4r5rs   r�remove_extensionr;-s����������D��!�!�.�1�1����d�~�
�
�
�	
�	
�	
�	
�	
�	
�	
�	���2�N�D�	
�	
�	
�	��
�K�K�:�������r
c��K�|���}|�|��s4t	d||d�|���d{V��t�d||��dSt�d||��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)r4r5�kwargsrs    r�
add_extensionr??s����������D��%�%�n�5�5�
���
�� .�
�
��	
�
�	
�	
�	
�	
�	
�	
�	
�	���1�>�4�	
�	
�	
�	��
�K�K�9�>�4�����r
c���K�t���d{V��D]I}	t||fi|���d{V���#t$r%t�d||d��Y�FwxYwdS)z+Add given extension to all cPanel packages.Nz(Unable to add extension %s to package %sr)r3r?rr:�	exception)r4r>r&s   r�add_extension_for_allrBVs�����"�_�_�$�$�$�$�$�$����	����>�>�v�>�>�>�>�>�>�>�>�>�>���	�	�	����:���F��
�
�
�
�
�	�����s�/�,A�Ac��K�t���d{V��D]K}	t||���d{V���#t$r%t�d||d��Y�HwxYwt�d��dS)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  r�remove_extension_from_allrDcs�����"�_�_�$�$�$�$�$�$����	�"�>�3�7�7�7�7�7�7�7�7�7�7���	�	�	����?���F��
�
�
�
�
�	�����K�K�L�����s�1�,A �A )r+)�logging�datetime�typingr�defence360agent.utilsr�(defence360agent.subsys.panels.cpanel.whmrr�	getLoggerr	r:r�dictr�	timedeltarr*r3r;r?rBrDrr
r�<module>rMs�����������������-�-�-�-�-�-�M�M�M�M�M�M�M�M�	��	�8�	$�	$��	�	�	�	�	�?�	�	�	������d����
��
�X�
��
+�
+�
+�S�9�9�9�
��
��
�
�
�:�9�
�3�3�t�G�}�3�3�3�3�
�3��g��$�����$���'.��	�����.
��
�$�
�
�
�
�
�C�
�D�
�
�
�
�
�
r
defence360agent/subsys/panels/cpanel/__pycache__/panel.cpython-311.opt-1.pyc0000644000000000000000000010342600000000000023600 0ustar  �

˿̡5-!��`�ddlZddlZddlZddlZddlZddlZddlZddlZddlm	Z	m
Z
ddlmZddl
mZddlmZmZmZddlmZddlmZddlmZdd	lmZdd
lmZmZmZmZm Z ddl!m"Z"ddl#m$Z$d
dl%m&Z&d
dl&m'Z'm(Z(ddl%m)Z)ddl*m+Z+m,Z,ed��Z-ed��Z.eej/j0��dzZ1dZ2dZ3dZ4dZ5ej6re4ne5Z7dZ8dZ9dZ:ej;e<��Z=dZ>e&j?dgzZ@dZAd ZBiid!�ZCGd"�d#e&jD��ZEGd$�d%e$��ZFGd&�d'e&jG��ZHdS)(�N)�OrderedDict�defaultdict)�suppress)�Path)�Dict�List�Set)�urlparse)�Version��is_cpanel_installed)�config)�
CheckRunError�antivirus_mode�async_lru_cache�	check_run�run)�	IPEchoAPI)�KWConfig�)�base)�
DomainData�forbid_dns_only�)�packages)�WHMAPIException�whmapi1z/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)�	userplans�userdatadomainsc��eZdZdS)�cPanelExceptionN)�__name__�
__module__�__qualname__���_/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/cpanel/panel.pyr"r"?s�������Dr'r"c��eZdZdZdZeZdS)�
AccountConfigz^{}\s+(.*)?$z{} {}N)r#r$r%�SEARCH_PATTERN�
WRITE_PATTERN�WWWACT_CONF�DEFAULT_FILENAMEr&r'r(r*r*Cs ������$�N��M�"���r'r*c��eZdZdZgd�ezgd�ezd�gd�gd�d�d�ZeZdgZdZ	d	Z
ed
���Ze
d���Ze
d���Ze
d
���Zej��d4d���Zej��d4d���Zed���Ze
edfdeefd���Zdedeefd�Zdeefd�Zdeeeeffd�Z d�Z!e"d���defd���Z#de$ddfd�Z%ded eddfd!�Z&ded eddfd"�Z'deeeeefffd#�Z(e
d$���Z)e
d%���Z*e
edfd&���Z+ed5d'���Z,e
d(���Z-e
d)���Z.e
d*eddfd+���Z/e
d,efd-���Z0ed.���Z1de2efd/�Z3e
dd0�d1���Z4deeeffd2�Z5deeeeffd3�Z6dS)6�cPanel)�143�465z	2077-2080z	2082-2083�2095�2096)
�37�43�113�873�2073�2089�2195�2703�6277�24441)�in�out)�20�21�53�443)rArBrCr7�123r8r=r>)�tcp�udp�cpanelz/var/cpanel/users.cache/z/var/cpanel/resellersc�@�tj�d��S)Nz/var/cpanel/dnsonly)�os�path�isfiler&r'r(�_is_dns_onlyzcPanel._is_dns_onlygs���w�~�~�3�4�4�4r'c��d}tj�|��stj��St|��5}|������cddd��S#1swxYwYdS)Nz/var/cpanel/mainip)rJrK�existsr�get_ip�open�read�strip)�cls�ip_conf�fs   r(�
get_server_ipzcPanel.get_server_ipks���&���w�~�~�g�&�&�	&��#�%�%�%�
�'�]�]�	$�a��6�6�8�8�>�>�#�#�	$�	$�	$�	$�	$�	$�	$�	$�	$�	$�	$�	$����	$�	$�	$�	$�	$�	$s�&A7�7A;�>A;c��t��S�Nr)rTs r(�is_installedzcPanel.is_installedts��"�$�$�$r'c��K�tddg���d{V��\}}}|������}|r|dndS)Nz/usr/local/cpanel/cpanelz-Vr�unknown)r�decode�split)rT�_�data�versions    r(razcPanel.versionxs]����� :�D�A�B�B�B�B�B�B�B�B�
��4���+�+�-�-�%�%�'�'��$�3�w�q�z�z�)�3r'Nc��K�|pt}|ttfvrtd|�����t�|���}|dz}tj�|��rtj
||��t|����d{V����td��krtdddd|g���d{V��tj�d	��td
|g���d{V��dS)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_NAME�AV_PLUGIN_NAME�IM360_PLUGIN_NAMEr"�CONFIG_FILE_TEMPLATE�formatrJrKrO�shutil�moverrar�sys�stdout�write)�selfrd�plugin_name�config_filename�new_confs     r(�enable_imunify_pluginzcPanel.enable_imunify_plugin~s^�����)�k���~�/@�A�A�A�!�/��;�!���
�/�5�5�;�5�G�G��"�Y�.��
�7�>�>�(�#�#�	3��K��/�2�2�2�������'�'�'�'�'�'�(�(�7�6�?�?�:�:�����1�#����
�
�
�
�
�
�
�	�
���:�;�;�;��:��
�
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
r'c���K�|pt}|ttfvrtd|�����t�|���}d}tj�|��sxt�
d|�d���t
jtd���t|d��5}|�d	��ddd��n#1swxYwYd}tj�d
��t#d|g���d{V��|rL	t
j|��dS#t&$r(}t�d|����Yd}~dSd}~wwxYwdS)
Nz0Refusing to disable plugin: invalid plugin_name rcFzWarning: cpanel z6.conf missing, creating temporary config for uninstallT)�exist_ok�wz!# Temporary config for uninstall
z cPanel: unregister_appconfig...
z*/usr/local/cpanel/bin/unregister_appconfigz#Failed to remove temporary config: )rerfrgr"rhrirJrKrO�logger�info�makedirs�CONFIG_PATHrQrnrl�stderrr�remove�	Exception�error)rorp�pluginrq�config_createdrV�es       r(�disable_imunify_pluginzcPanel.disable_imunify_plugin�s�����+����.�*;�<�<�<�!�/��6����
�
/�5�5�6�5�B�B�����w�~�~�o�.�.�		"��K�K�:��:�:�:�
�
�
�

�K��d�3�3�3�3��o�s�+�+�
>�q����<�=�=�=�
>�
>�
>�
>�
>�
>�
>�
>�
>�
>�
>����
>�
>�
>�
>�!�N��
���<�=�=�=��<��
�
�
�	
�	
�	
�	
�	
�	
�	
��	H�
H��	�/�*�*�*�*�*���
H�
H�
H����F�1�F�F�G�G�G�G�G�G�G�G�G�����
H����	H�	Hs*�3C�C�C�D1�1
E#�;E�E#c��T�K��fd������d{V��D��S)zD
        :return: list: domains hosted on server via cpanel
        c�J��g|]}��|��D]\}}|��� Sr&)�_userdomains)�.0�user�domain�	user_pathros    �r(�
<listcomp>z+cPanel.get_user_domains.<locals>.<listcomp>�sR���
�
�
��%)�%6�%6�t�%<�%<�
�
�"��	�
�
�
�
�
r'N)�	get_users�ros`r(�get_user_domainszcPanel.get_user_domains�sN�����

�
�
�
�"�n�n�.�.�.�.�.�.�.�.�
�
�
�	
r'T�returnc��N��K�g���fd�}|�|||����S)Nc���|d}|�krdS|d}|d}��t|||������dS)Nrr�)�docrootr��type�username)�appendr)rK�d�domain_data�user_�doc_typer��domainsr�s      ��r(�parserz/cPanel.get_user_domains_details.<locals>.parser�si�����N�E��� � ���"�1�~�H�!�!�n�G��N�N��#�A�H�x����
�
�
�
�
r'��quiet)�_parse_userdatadomains)rTr��_pathr�r�r�s `   @r(�get_user_domains_detailszcPanel.get_user_domains_details�sO��������
	�
	�
	�
	�
	�
	�	�"�"�5�&��"�>�>�>��r'�userplans_pathc��K�tj�|��sgStd�dd��}|tj�|��krtddSt
|dd���5}g}|D]�}|�d��s~|�d	��d
kret|�
����dkr@|�|�d	��d�
������	ddd��n#1swxYwYtj�|��tdd<|tdd<|S)Nr�mtimer�userszutf-8�surrogateescape)�encoding�errors�#�:r�)
rJrKrL�_CACHE�get�getmtimerQ�
startswith�count�lenrSr�r^)ror��
_cached_mtimerVr��lines      r(�
_do_get_userszcPanel._do_get_users�s������w�~�~�n�-�-�	��I��{�+�/�/���;�;�
��B�G�,�,�^�<�<�<�<��+�&�w�/�/�
��W�5F�
�
�
�
	=�
��E��
=�
=������,�,�=��
�
�3���1�,�,��D�J�J�L�L�)�)�A�-�-��L�L����C����!3�!9�!9�!;�!;�<�<�<��

=�	
	=�
	=�
	=�
	=�
	=�
	=�
	=�
	=�
	=�
	=�
	=����
	=�
	=�
	=�
	=�(*�w�'7�'7��'G�'G��{��G�$�',��{��G�$��s�
BD5�5D9�<D9c��FK�|�t���d{V��SrY)r��CPANEL_USERPLANS_PATHr�s r(r�zcPanel.get_users�s/�����'�'�(=�>�>�>�>�>�>�>�>�>r'c���K�tt��}|����d{V��D]8}|�|��D] \}}||�|���!�9|S)zp
        Returns dict with domain to list of users pairs
        :return: dict domain to list of users:
        N�r�listr�r�r�)ro�domain_to_usersr�r�r_s     r(�get_domain_to_ownerzcPanel.get_domain_to_owner�s�����
&�d�+�+���.�.�*�*�*�*�*�*�*�*�	5�	5�D�!�.�.�t�4�4�
5�
5�	�����'�.�.�t�4�4�4�4�
5��r'c���K�tt��}|����d{V��D]8}|�|��D] \}}||�|���!�9|S)zp
        Returns dict with users to list of domains pairs
        :return: dict user to list of domains
        Nr�)ro�user_to_domainsr�r�r_s     r(�get_domains_per_userzcPanel.get_domains_per_user	s�����
&�d�+�+���.�.�*�*�*�*�*�*�*�*�	5�	5�D�!�.�.�t�4�4�
5�
5�	�����%�,�,�V�4�4�4�4�
5��r'�)�maxsizec��K�td|d����d{V��d}t|��dkrdSt|��}|j�d|j�d	�S)
z8
        Returns panel url
        :return: str
        �create_user_session�cpaneld)r��serviceN�urlr�z://z:/cpsess0000000000/frontend/jupiter/imunify/imunify.live.pl)rr�r
�scheme�netloc)ror��link�parseds    r(�panel_user_linkzcPanel.panel_user_links������%�H�i����
�
�
�
�
�
��	��
�t�9�9��>�>��2��$�����-�m�m�F�M�m�m�m�mr'�myimunify_enabledc��K�tjrdStd�����sdS|rdnd}|rdnd}t	jd��D]z}t|�����rWtj�|��}|�	||���d{V��|�
||���d{V���{dS)zK
        Switch UI panel configuration between Im360 and MyImunify
        Nz./var/imunify360/i360-userside-plugin.installed�myimunify_conf�confz!/usr/local/cpanel/base/frontend/*)r�enabledrrO�glob�is_dirrJrK�basename�disable_config�
enable_config)ror��config_to_enable�config_to_disable�
theme_path�
theme_names      r(�switch_ui_configzcPanel.switch_ui_config%s�����!�	��4��D�E�E�L�L�N�N�	��4�/@�L�+�+�f��&7�M�F�F�=M���)�$G�H�H�	G�	G�J��J���&�&�(�(�
G��W�-�-�j�9�9�
��)�)�*;�Z�H�H�H�H�H�H�H�H�H��(�(�)9�:�F�F�F�F�F�F�F�F�F��		G�	Gr'r�themec���K�	ttt�|��d|g���d{V��dS#t$r'}t�d||��Yd}~dSd}~wwxYw)N�--themez!Error in enabling config '%s': %s)r�PLUGIN_INSTALL_SCRIPTrzrrw�warning�rorr�r�s    r(r�zcPanel.enable_config8s�����
	K��)�"�,�F�,�,���	���
�
�
�
�
�
�
�
�
���	K�	K�	K��N�N�>���J�J�J�J�J�J�J�J�J�����	K�����'-�
A�A�Ac���K�	ttt�|��d|g���d{V��dS#t$r'}t�d||��Yd}~dSd}~wwxYw)Nr�z"Error in disabling config '%s': %s)r�PLUGIN_UNINSTALL_SCRIPTrzrrwr�r�s    r(r�zcPanel.disable_configEs�����
	L��+�"�,�F�,�,���	���
�
�
�
�
�
�
�
�
���	L�	L�	L��N�N�?���K�K�K�K�K�K�K�K�K�����	L���r�c��VK�i}	d�t|j��������D��}n#t$rd}YnwxYw|����d{V��D�]�}tjj}|dkr�	td��5}tj|��}ddd��n#1swxYwY|�dd��}n#ttjf$rd}YnwxYwd}d}	d}
tjj}n�	tt j�|j|����5}tj|��}ddd��n#1swxYwY|�dd��}|�d	d��}|�d
d��}	|�dd��dk}
n$#ttjf$rd}d}d}	d}
YnwxYw|r||vrtjj}|||	|
t+|��d
�||<���|S)zB
        Returns dict with user to email and locale pairs
        c�F�h|]}|�dd��d��S)r�rr)r^)r�r�s  r(�	<setcomp>z*cPanel.get_user_details.<locals>.<setcomp>[s;�������
�
�3��"�"�1�%���r'N�rootz/etc/wwwacct.conf.cache�CONTACTEMAILr��enF�LOCALE�OWNER�	SUSPENDED�1)�email�locale�parent�	suspended�level)r�RESELLERS_INFO�	read_text�
splitlinesr}r�r�	UserLevel�REGULAR_USERrQ�json�loadr��FileNotFoundError�JSONDecodeError�ADMINrJrK�join�
USER_INFO_DIR�RESSELER�int)ro�user_details�	resellersr�r�rV�	user_infor�r�r�r�s           r(�get_user_detailszcPanel.get_user_detailsRs�����
��	��� ��!4�5�5�?�?�A�A�L�L�N�N����I�I���	�	�	��I�I�I�	�����.�.�*�*�*�*�*�*�*�*�'	�'	�D��N�/�E��v�~�~���7�8�8�1�A�$(�I�a�L�L�	�1�1�1�1�1�1�1�1�1�1�1����1�1�1�1�%�M�M�.�"�=�=�E�E��)�4�+?�@�����E�E�E���������!�	���,���&��b�g�l�l�4�+=�t�D�D�E�E�1��$(�I�a�L�L�	�1�1�1�1�1�1�1�1�1�1�1����1�1�1�1�%�M�M�.�"�=�=�E�&�]�]�8�R�8�8�F�&�]�]�7�B�7�7�F� )�
�
�k�2� >� >�#� E�I�I��)�4�+?�@�&�&�&��E��F��F� %�I�I�I�	&�����4���!2�!2�!�N�3�E�� � �&��U���"�"�L�����s�AA	�	A�A�C"�C�4C"�C	�C"�C	�C"�"C=�<C=�2G�E,� G�,E0	�0G�3E0	�4AG�G5�4G5c��g}d|vr@tjtj����j}|�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)�pwd�getpwuidrJ�getuid�pw_name�replacer^rKrOr�r��max)rTr��_mtimes�call_as_user�	path_list�path_s      r(�_get_max_mtimezcPanel._get_max_mtime�s���
���u����<��	���4�4�<�L��M�M�(�L�9�9�E��K�K��$�$�	��	8�	8�E��w�~�~�e�$�$�
8����r�w�/�/��6�6�7�7�7��&�-�s�7�|�|�|�A�-r'c�,�td�|i���dd��}||�|��kritd|<dStd�|i���dg��S)z$check and invalidate cache if neededr r�rNr�)r�r�r)rT�cpuserr�r�s    r(�_get_from_cachezcPanel._get_from_cache�s���

�$�%�)�)�&�"�5�5�9�9�'�1�E�E�	��3�-�-�e�4�4�4�4�02�F�$�%�f�-��4��'�(�,�,�V�R�8�8�<�<�Y��K�K�Kr'c�t����|��|��}|�|St���t������fd�}|�|||��������|�|������d�td�<����S)Nc���|d}|�krF|d}d|dkr��||i��dS��||i��dSdS)Nrr��mainr)�update)rKr�r�r��
document_rootrr��domains_tmps     ���r(r�z#cPanel._userdomains.<locals>.parser�sp�����N�E����� +�A��
��[��^�+�+��N�N�A�}�#5�6�6�6�6�6��&�&��=�'9�:�:�:�:�:�
�r'r�)r�r�r )rrr�rr�itemsr�)rTrr�r��cached_datar�r�rs `    @@r(r�zcPanel._userdomains�s�������)�)�&�%�8�8���"���!�m�m���-�-��	;�	;�	;�	;�	;�	;�	;�	�"�"�5�&��"�>�>�>����{�#�#�#��'�'��.�.��}�}���-
�-
�� �!�&�)��}�}���r'c��d|vr@tjtj����j}|�d|��}|�d��}|D�]i}	t|d��}n5#t$r(}|st�
d||��Yd}~�Bd}~wwxYw	t|��D]�\}}		|	���}	n,#t$rt�
d||��Y�CwxYw|	���s�\|	�d��dkr|st�
d||����|	�d��\}
}|����d	��}|||
|����	|�����R#|���wxYwdS)
Nrr�rbzCan'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
rr^rQr}rwr��	enumerater]�UnicodeDecodeErrorrSr��close)
r�r�r�rrr�file_r��ir�r��domain_raw_datar�s
             r(r�zcPanel._parse_userdatadomains�s���u����<��	���4�4�<�L��M�M�(�L�9�9�E��K�K��$�$�	��$	�$	�E�
��U�D�)�)�����
�
�
��H��N�N�#<�e�Q�G�G�G����������
����
� )��/�/�7�7�G�A�t�!�#�{�{�}�}����-�!�!�!����K��!����
!��
!���� �:�:�<�<�!� ��z�z�$�'�'�1�,�,�$��"�N�N�!3� !� %�	���!�.2�j�j��.>�.>�+�F�O�"1�"7�"7�"9�"9�"?�"?��"E�"E�K��F�5�&�+�6�6�6�6�/7�2���
�
�
�
�����
�
�
�
����I$	�$	sI� A1�1
B#�;B�B#�'F.�<C�F.�&C:�7F.�9C:�:BF.�.Gc�4�td�|D����S)Nc3�nK�|]0}t�|�����V��1dSrY)�CPANEL_PACKAGE_EXTENSIONS_PATH�joinpath�is_file)r��files  r(�	<genexpr>z0cPanel.is_extension_installed.<locals>.<genexpr>�sP����
�
��
+�3�3�D�9�9�A�A�C�C�
�
�
�
�
�
r')�all)rT�pkgss  r(�is_extension_installedzcPanel.is_extension_installed�s0���
�
��
�
�
�
�
�	
r'c��v�K�	td���d{V��}td�|dD����}dD]d�t�fd�|dD����}td�|dD����}td	�|d
D����sdS�en#ttf$rYdSwxYwdS)
N�
list_hooksc3�2K�|]}|ddk�|V��dS)�category�WhostmgrNr&)r��cats  r(r+z+cPanel.is_hook_installed.<locals>.<genexpr>�s=��������z�?�j�0�0��0�0�0�0��r'�
categories)zAccounts::change_packagezAccounts::CreatezAccounts::Modifyc3�4�K�|]}|d�k�|V��dS)�eventNr&)r��ev�
event_names  �r(r+z+cPanel.is_hook_installed.<locals>.<genexpr>s>���������'�{�j�0�0��0�0�0�0��r'�eventsc3�2K�|]}|ddk�|V��dS)�stage�postNr&)r��sts  r(r+z+cPanel.is_hook_installed.<locals>.<genexpr>
s9�������B�w�K�6�4I�4I�B�4I�4I�4I�4I��r'�stagesc3�.K�|]}|ddkV��dS)�hookzImunifyHook::hook_processingNr&)r��actions  r(r+z+cPanel.is_hook_installed.<locals>.<genexpr>s@��������6�N�&D�D������r'�actionsFT)r�next�any�
StopIterationr)rT�hooksr2r7r<r9s     @r(�is_hook_installedzcPanel.is_hook_installed�sQ�����	�!�,�/�/�/�/�/�/�/�/�E���� ��.������H��
!�
!�
�
�����&�x�0�������
���!&�x�����������"'�	�"2������!�!�5�5�	!�
!��$��/�	�	�	��5�5�	�����ts�BB!�B!�!B6�5B6�extention_namec��K�t�ddd���|D]$}tjt|zt���%tj|fi|���d{V��tjtj
jdd���tjtdzt��tgd����d{V��dS)Ni�T)�mode�parentsru)rKru�ImunifyHook.pm)�"/usr/local/cpanel/bin/manage_hooks�add�module�ImunifyHook)r'�mkdirrj�copy2�"PREINSTALL_PACKAGE_EXTENSIONS_PATHr�add_extension_for_allrJryr�Core�INBOX_HOOKS_DIR�CPANEL_HOOKS_PATHr)rTrI�extention_files�kwargs�filenames     r(�install_extensionzcPanel.install_extensions����	'�,�,���t�	-�	
�	
�	
�(�	�	�H��L�2�X�=�.�
�
�
�
��,�^�F�F�v�F�F�F�F�F�F�F�F�F�	��F�K�/�e�d�K�K�K�K���.�1A�A��	
�	
�	
��
�
�
�
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
r'�extension_namec��K�tgd����d{V��tt��5tdz���ddd��n#1swxYwYtj|���d{V��|D]J}tt��5t|z���ddd��n#1swxYwY�KdS)N)rN�delrPrQrM)rrr�rX�unlinkr�remove_extension_from_allr')rTr]rYr[s    r(�uninstall_extensionzcPanel.uninstall_extension=s������
�
�
�
�
�	
�	
�	
�	
�	
�	
�	
��'�
(�
(�	<�	<�
�!1�
1�9�9�;�;�;�	<�	<�	<�	<�	<�	<�	<�	<�	<�	<�	<����	<�	<�	<�	<��0��@�@�@�@�@�@�@�@�@�'�	E�	E�H��+�,�,�
E�
E�/�(�:�B�B�D�D�D�
E�
E�
E�
E�
E�
E�
E�
E�
E�
E�
E����
E�
E�
E�
E��	E�	Es#�A�A�A�B=�=C	�C	c��g}tdd��5}|D]V}|������}t|��dkr|�|d���W	ddd��n#1swxYwY|S)Nz/proc/mounts�rr)rQrSr^r�r�)�mountsrVr��valuess    r(rez
cPanel.mountsRs�����
�.�#�
&�
&�	-�!��
-�
-�������+�+�-�-���v�;�;��?�?��M�M�&��)�,�,�,��
-�	-�	-�	-�	-�	-�	-�	-�	-�	-�	-�	-����	-�	-�	-�	-�
�
s�AA:�:A>�A>c�D�td�����}td�����}|�tn|}t|h}|�|S|���D]0}||vr*|�d��s|�|���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*r��BASE_DIRrer�rO)ro�homedir�	homematch�basedirs�mounts     r(rmzcPanel.basedirs\s��� �	�*�*�.�.�0�0��!�+�.�.�2�2�4�4�	�%�o�(�(�7���g�&�����O��[�[�]�]�	$�	$�E��E�!�!�%�*:�*:�?�*K�*K�!����U�#�#�#���r')r�c��|K�tjjsdStj|���sdS|||d�}t�|j�d�|��d}tj|��}t|g|�
������d{V��}tj|�d�	����S)
zG
        Notify a customer using cPanel iContact Notifications
        F)r�)�message_type�paramsr�z.notify(%s)zB/usr/local/cpanel/whostmgr/docroot/cgi/imunify/handlers/notify.cgi)�inputNr�)r�)
r�
AdminContacts�ENABLE_ICONTACT_NOTIFICATIONS�should_send_user_notificationsrwrxr#r��dumpsr�encode�loadsr])rTrprqr�r`�cmd�stdinr@s        r(�notifyz
cPanel.notifyrs�����
�#�A�	��5��4�d�C�C�C�	��5� ,���M�M�����s�|�0�0�0�$�7�7�7�
+�	��
�4� � ���s�e�5�<�<�>�>�:�:�:�:�:�:�:�:�:���z�#�*�*�,=�*�>�>�?�?�?r'c��l�K�t����fd�}|�t|d����S)Nc�*��|d�|d<dS)Nr�r�r&)rKr�r��results   �r(r�z$cPanel.list_docroots.<locals>.parser�s���%0��^�F�;�q�>�"�"�"r'Tr�)�dictr��CPANEL_USERDATADOMAINS_PATH�ror�r~s  @r(�
list_docrootszcPanel.list_docroots�sV���������	4�	4�	4�	4�	4�	
�#�#�'��t�	$�	
�	
�	
��
r'c��T�K�i��fd�}|�t|d����S)Nc� ��|dg�|<dS)Nr�r&)r_r�r�r~s   �r(r�z'cPanel.get_domain_paths.<locals>.parser�s���$�Q��(�F�1�I�I�Ir'Tr�)r�r�r�s  @r(�get_domain_pathszcPanel.get_domain_paths�sR�������	)�	)�	)�	)�	)�	
�#�#�'��t�	$�	
�	
�	
��
r'rY)T)7r#r$r%�NAME�TCP_PORTS_CPANEL�
OPEN_PORTSr"�	exception�smtp_allow_usersr�r��staticmethodrM�classmethodrWrZrar�ensure_valid_panelrsr�rr�r�r�rr��strrr�r�rr�r�rr��boolr�r�r�rrrr�r�r.rHr\rbrer	rmr{r�r�r&r'r(r0r0Is��������D�K�J�J��������
�
�$,�+�+�K�K�K�
�
�%��J�. �I� �z��.�M�,�N��5�5��\�5��$�$��[�$��%�%��[�%��4�4��[�4�
�T����
�
�
���
�>�T����"H�"H�"H���"H�H�
�
��_�
��8����	
�j�	�����[��(�#��$�s�)�����,?�	
�c��?�?�?�?�
	�4��T�#�Y��+?�	�	�	�	�	�	�	��_�S�!�!�!�n��n�n�n�"�!�n� G��G��G�G�G�G�&K�#�K�c�K�d�K�K�K�K�L�3�L�s�L�t�L�L�L�L�9��S�$�s�C�x�.�-@�(A�9�9�9�9�v�.�.��[�.��
L�
L��[�
L��6�d�����[��8�)�)�)��\�)�V�
�
��[�
�����[��@� 
�� 
�

� 
� 
� 
��[� 
�D�E�s�E�E�E��[�E�(����\���#�c�(�����,�8<�@�@�@�@��[�@�,
�T�#�s�(�^�
�
�
�
�
��S�$�s�)�^�(<�
�
�
�
�
�
r'r0)Ir�r��loggingrJ�os.pathrrjrl�collectionsrr�
contextlibr�pathlibr�typingrrr	�urllib.parser
�packaging.versionr�3defence360agent.application.determine_hosting_panelr
�defence360agent.contractsr�defence360agent.utilsrrrrr�defence360agent.utils.ipechor�defence360agent.utils.kwconfigrr�rrrr�whmrrr'rX�	Packaging�DATADIRrTr�r�rfrgr�rer�r�rz�	getLoggerr#rwrh�TCP_PORTS_COMMONr�rjr-r��PanelExceptionr"r*�
AbstractPanelr0r&r'r(�<module>r�s��������������	�	�	�	�����
�
�
�
�
�
�
�
�
�
�
�
�0�0�0�0�0�0�0�0�������������"�"�"�"�"�"�"�"�"�"�!�!�!�!�!�!�%�%�%�%�%�%�������-�,�,�,�,�,���������������3�2�2�2�2�2�3�3�3�3�3�3�������.�.�.�.�.�.�.�.�������)�)�)�)�)�)�)�)�!%��&G�!H�!H���D�,�-�-���D��	�	!�"�"�%A�A�#�)��<��%�� �� .� 6�M�n�n�<M��B��F��1��	��	�8�	$�	$��E���(�K�=�8����!���b�	1�	1��	�	�	�	�	�d�)�	�	�	�#�#�#�#�#�H�#�#�#�V	�V	�V	�V	�V	�T�
�V	�V	�V	�V	�V	r'defence360agent/subsys/panels/cpanel/__pycache__/panel.cpython-311.pyc0000644000000000000000000010342600000000000022641 0ustar  �

˿̡5-!��`�ddlZddlZddlZddlZddlZddlZddlZddlZddlm	Z	m
Z
ddlmZddl
mZddlmZmZmZddlmZddlmZddlmZdd	lmZdd
lmZmZmZmZm Z ddl!m"Z"ddl#m$Z$d
dl%m&Z&d
dl&m'Z'm(Z(ddl%m)Z)ddl*m+Z+m,Z,ed��Z-ed��Z.eej/j0��dzZ1dZ2dZ3dZ4dZ5ej6re4ne5Z7dZ8dZ9dZ:ej;e<��Z=dZ>e&j?dgzZ@dZAd ZBiid!�ZCGd"�d#e&jD��ZEGd$�d%e$��ZFGd&�d'e&jG��ZHdS)(�N)�OrderedDict�defaultdict)�suppress)�Path)�Dict�List�Set)�urlparse)�Version��is_cpanel_installed)�config)�
CheckRunError�antivirus_mode�async_lru_cache�	check_run�run)�	IPEchoAPI)�KWConfig�)�base)�
DomainData�forbid_dns_only�)�packages)�WHMAPIException�whmapi1z/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)�	userplans�userdatadomainsc��eZdZdS)�cPanelExceptionN)�__name__�
__module__�__qualname__���_/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/cpanel/panel.pyr"r"?s�������Dr'r"c��eZdZdZdZeZdS)�
AccountConfigz^{}\s+(.*)?$z{} {}N)r#r$r%�SEARCH_PATTERN�
WRITE_PATTERN�WWWACT_CONF�DEFAULT_FILENAMEr&r'r(r*r*Cs ������$�N��M�"���r'r*c��eZdZdZgd�ezgd�ezd�gd�gd�d�d�ZeZdgZdZ	d	Z
ed
���Ze
d���Ze
d���Ze
d
���Zej��d4d���Zej��d4d���Zed���Ze
edfdeefd���Zdedeefd�Zdeefd�Zdeeeeffd�Z d�Z!e"d���defd���Z#de$ddfd�Z%ded eddfd!�Z&ded eddfd"�Z'deeeeefffd#�Z(e
d$���Z)e
d%���Z*e
edfd&���Z+ed5d'���Z,e
d(���Z-e
d)���Z.e
d*eddfd+���Z/e
d,efd-���Z0ed.���Z1de2efd/�Z3e
dd0�d1���Z4deeeffd2�Z5deeeeffd3�Z6dS)6�cPanel)�143�465z	2077-2080z	2082-2083�2095�2096)
�37�43�113�873�2073�2089�2195�2703�6277�24441)�in�out)�20�21�53�443)rArBrCr7�123r8r=r>)�tcp�udp�cpanelz/var/cpanel/users.cache/z/var/cpanel/resellersc�@�tj�d��S)Nz/var/cpanel/dnsonly)�os�path�isfiler&r'r(�_is_dns_onlyzcPanel._is_dns_onlygs���w�~�~�3�4�4�4r'c��d}tj�|��stj��St|��5}|������cddd��S#1swxYwYdS)Nz/var/cpanel/mainip)rJrK�existsr�get_ip�open�read�strip)�cls�ip_conf�fs   r(�
get_server_ipzcPanel.get_server_ipks���&���w�~�~�g�&�&�	&��#�%�%�%�
�'�]�]�	$�a��6�6�8�8�>�>�#�#�	$�	$�	$�	$�	$�	$�	$�	$�	$�	$�	$�	$����	$�	$�	$�	$�	$�	$s�&A7�7A;�>A;c��t��S�Nr)rTs r(�is_installedzcPanel.is_installedts��"�$�$�$r'c��K�tddg���d{V��\}}}|������}|r|dndS)Nz/usr/local/cpanel/cpanelz-Vr�unknown)r�decode�split)rT�_�data�versions    r(razcPanel.versionxs]����� :�D�A�B�B�B�B�B�B�B�B�
��4���+�+�-�-�%�%�'�'��$�3�w�q�z�z�)�3r'Nc��K�|pt}|ttfvrtd|�����t�|���}|dz}tj�|��rtj
||��t|����d{V����td��krtdddd|g���d{V��tj�d	��td
|g���d{V��dS)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_NAME�AV_PLUGIN_NAME�IM360_PLUGIN_NAMEr"�CONFIG_FILE_TEMPLATE�formatrJrKrO�shutil�moverrar�sys�stdout�write)�selfrd�plugin_name�config_filename�new_confs     r(�enable_imunify_pluginzcPanel.enable_imunify_plugin~s^�����)�k���~�/@�A�A�A�!�/��;�!���
�/�5�5�;�5�G�G��"�Y�.��
�7�>�>�(�#�#�	3��K��/�2�2�2�������'�'�'�'�'�'�(�(�7�6�?�?�:�:�����1�#����
�
�
�
�
�
�
�	�
���:�;�;�;��:��
�
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
r'c���K�|pt}|ttfvrtd|�����t�|���}d}tj�|��sxt�
d|�d���t
jtd���t|d��5}|�d	��ddd��n#1swxYwYd}tj�d
��t#d|g���d{V��|rL	t
j|��dS#t&$r(}t�d|����Yd}~dSd}~wwxYwdS)
Nz0Refusing to disable plugin: invalid plugin_name rcFzWarning: cpanel z6.conf missing, creating temporary config for uninstallT)�exist_ok�wz!# Temporary config for uninstall
z cPanel: unregister_appconfig...
z*/usr/local/cpanel/bin/unregister_appconfigz#Failed to remove temporary config: )rerfrgr"rhrirJrKrO�logger�info�makedirs�CONFIG_PATHrQrnrl�stderrr�remove�	Exception�error)rorp�pluginrq�config_createdrV�es       r(�disable_imunify_pluginzcPanel.disable_imunify_plugin�s�����+����.�*;�<�<�<�!�/��6����
�
/�5�5�6�5�B�B�����w�~�~�o�.�.�		"��K�K�:��:�:�:�
�
�
�

�K��d�3�3�3�3��o�s�+�+�
>�q����<�=�=�=�
>�
>�
>�
>�
>�
>�
>�
>�
>�
>�
>����
>�
>�
>�
>�!�N��
���<�=�=�=��<��
�
�
�	
�	
�	
�	
�	
�	
�	
��	H�
H��	�/�*�*�*�*�*���
H�
H�
H����F�1�F�F�G�G�G�G�G�G�G�G�G�����
H����	H�	Hs*�3C�C�C�D1�1
E#�;E�E#c��T�K��fd������d{V��D��S)zD
        :return: list: domains hosted on server via cpanel
        c�J��g|]}��|��D]\}}|��� Sr&)�_userdomains)�.0�user�domain�	user_pathros    �r(�
<listcomp>z+cPanel.get_user_domains.<locals>.<listcomp>�sR���
�
�
��%)�%6�%6�t�%<�%<�
�
�"��	�
�
�
�
�
r'N)�	get_users�ros`r(�get_user_domainszcPanel.get_user_domains�sN�����

�
�
�
�"�n�n�.�.�.�.�.�.�.�.�
�
�
�	
r'T�returnc��N��K�g���fd�}|�|||����S)Nc���|d}|�krdS|d}|d}��t|||������dS)Nrr�)�docrootr��type�username)�appendr)rK�d�domain_data�user_�doc_typer��domainsr�s      ��r(�parserz/cPanel.get_user_domains_details.<locals>.parser�si�����N�E��� � ���"�1�~�H�!�!�n�G��N�N��#�A�H�x����
�
�
�
�
r'��quiet)�_parse_userdatadomains)rTr��_pathr�r�r�s `   @r(�get_user_domains_detailszcPanel.get_user_domains_details�sO��������
	�
	�
	�
	�
	�
	�	�"�"�5�&��"�>�>�>��r'�userplans_pathc��K�tj�|��sgStd�dd��}|tj�|��krtddSt
|dd���5}g}|D]�}|�d��s~|�d	��d
kret|�
����dkr@|�|�d	��d�
������	ddd��n#1swxYwYtj�|��tdd<|tdd<|S)Nr�mtimer�userszutf-8�surrogateescape)�encoding�errors�#�:r�)
rJrKrL�_CACHE�get�getmtimerQ�
startswith�count�lenrSr�r^)ror��
_cached_mtimerVr��lines      r(�
_do_get_userszcPanel._do_get_users�s������w�~�~�n�-�-�	��I��{�+�/�/���;�;�
��B�G�,�,�^�<�<�<�<��+�&�w�/�/�
��W�5F�
�
�
�
	=�
��E��
=�
=������,�,�=��
�
�3���1�,�,��D�J�J�L�L�)�)�A�-�-��L�L����C����!3�!9�!9�!;�!;�<�<�<��

=�	
	=�
	=�
	=�
	=�
	=�
	=�
	=�
	=�
	=�
	=�
	=����
	=�
	=�
	=�
	=�(*�w�'7�'7��'G�'G��{��G�$�',��{��G�$��s�
BD5�5D9�<D9c��FK�|�t���d{V��SrY)r��CPANEL_USERPLANS_PATHr�s r(r�zcPanel.get_users�s/�����'�'�(=�>�>�>�>�>�>�>�>�>r'c���K�tt��}|����d{V��D]8}|�|��D] \}}||�|���!�9|S)zp
        Returns dict with domain to list of users pairs
        :return: dict domain to list of users:
        N�r�listr�r�r�)ro�domain_to_usersr�r�r_s     r(�get_domain_to_ownerzcPanel.get_domain_to_owner�s�����
&�d�+�+���.�.�*�*�*�*�*�*�*�*�	5�	5�D�!�.�.�t�4�4�
5�
5�	�����'�.�.�t�4�4�4�4�
5��r'c���K�tt��}|����d{V��D]8}|�|��D] \}}||�|���!�9|S)zp
        Returns dict with users to list of domains pairs
        :return: dict user to list of domains
        Nr�)ro�user_to_domainsr�r�r_s     r(�get_domains_per_userzcPanel.get_domains_per_user	s�����
&�d�+�+���.�.�*�*�*�*�*�*�*�*�	5�	5�D�!�.�.�t�4�4�
5�
5�	�����%�,�,�V�4�4�4�4�
5��r'�)�maxsizec��K�td|d����d{V��d}t|��dkrdSt|��}|j�d|j�d	�S)
z8
        Returns panel url
        :return: str
        �create_user_session�cpaneld)r��serviceN�urlr�z://z:/cpsess0000000000/frontend/jupiter/imunify/imunify.live.pl)rr�r
�scheme�netloc)ror��link�parseds    r(�panel_user_linkzcPanel.panel_user_links������%�H�i����
�
�
�
�
�
��	��
�t�9�9��>�>��2��$�����-�m�m�F�M�m�m�m�mr'�myimunify_enabledc��K�tjrdStd�����sdS|rdnd}|rdnd}t	jd��D]z}t|�����rWtj�|��}|�	||���d{V��|�
||���d{V���{dS)zK
        Switch UI panel configuration between Im360 and MyImunify
        Nz./var/imunify360/i360-userside-plugin.installed�myimunify_conf�confz!/usr/local/cpanel/base/frontend/*)r�enabledrrO�glob�is_dirrJrK�basename�disable_config�
enable_config)ror��config_to_enable�config_to_disable�
theme_path�
theme_names      r(�switch_ui_configzcPanel.switch_ui_config%s�����!�	��4��D�E�E�L�L�N�N�	��4�/@�L�+�+�f��&7�M�F�F�=M���)�$G�H�H�	G�	G�J��J���&�&�(�(�
G��W�-�-�j�9�9�
��)�)�*;�Z�H�H�H�H�H�H�H�H�H��(�(�)9�:�F�F�F�F�F�F�F�F�F��		G�	Gr'r�themec���K�	ttt�|��d|g���d{V��dS#t$r'}t�d||��Yd}~dSd}~wwxYw)N�--themez!Error in enabling config '%s': %s)r�PLUGIN_INSTALL_SCRIPTrzrrw�warning�rorr�r�s    r(r�zcPanel.enable_config8s�����
	K��)�"�,�F�,�,���	���
�
�
�
�
�
�
�
�
���	K�	K�	K��N�N�>���J�J�J�J�J�J�J�J�J�����	K�����'-�
A�A�Ac���K�	ttt�|��d|g���d{V��dS#t$r'}t�d||��Yd}~dSd}~wwxYw)Nr�z"Error in disabling config '%s': %s)r�PLUGIN_UNINSTALL_SCRIPTrzrrwr�r�s    r(r�zcPanel.disable_configEs�����
	L��+�"�,�F�,�,���	���
�
�
�
�
�
�
�
�
���	L�	L�	L��N�N�?���K�K�K�K�K�K�K�K�K�����	L���r�c��VK�i}	d�t|j��������D��}n#t$rd}YnwxYw|����d{V��D�]�}tjj}|dkr�	td��5}tj|��}ddd��n#1swxYwY|�dd��}n#ttjf$rd}YnwxYwd}d}	d}
tjj}n�	tt j�|j|����5}tj|��}ddd��n#1swxYwY|�dd��}|�d	d��}|�d
d��}	|�dd��dk}
n$#ttjf$rd}d}d}	d}
YnwxYw|r||vrtjj}|||	|
t+|��d
�||<���|S)zB
        Returns dict with user to email and locale pairs
        c�F�h|]}|�dd��d��S)r�rr)r^)r�r�s  r(�	<setcomp>z*cPanel.get_user_details.<locals>.<setcomp>[s;�������
�
�3��"�"�1�%���r'N�rootz/etc/wwwacct.conf.cache�CONTACTEMAILr��enF�LOCALE�OWNER�	SUSPENDED�1)�email�locale�parent�	suspended�level)r�RESELLERS_INFO�	read_text�
splitlinesr}r�r�	UserLevel�REGULAR_USERrQ�json�loadr��FileNotFoundError�JSONDecodeError�ADMINrJrK�join�
USER_INFO_DIR�RESSELER�int)ro�user_details�	resellersr�r�rV�	user_infor�r�r�r�s           r(�get_user_detailszcPanel.get_user_detailsRs�����
��	��� ��!4�5�5�?�?�A�A�L�L�N�N����I�I���	�	�	��I�I�I�	�����.�.�*�*�*�*�*�*�*�*�'	�'	�D��N�/�E��v�~�~���7�8�8�1�A�$(�I�a�L�L�	�1�1�1�1�1�1�1�1�1�1�1����1�1�1�1�%�M�M�.�"�=�=�E�E��)�4�+?�@�����E�E�E���������!�	���,���&��b�g�l�l�4�+=�t�D�D�E�E�1��$(�I�a�L�L�	�1�1�1�1�1�1�1�1�1�1�1����1�1�1�1�%�M�M�.�"�=�=�E�&�]�]�8�R�8�8�F�&�]�]�7�B�7�7�F� )�
�
�k�2� >� >�#� E�I�I��)�4�+?�@�&�&�&��E��F��F� %�I�I�I�	&�����4���!2�!2�!�N�3�E�� � �&��U���"�"�L�����s�AA	�	A�A�C"�C�4C"�C	�C"�C	�C"�"C=�<C=�2G�E,� G�,E0	�0G�3E0	�4AG�G5�4G5c��g}d|vr@tjtj����j}|�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)�pwd�getpwuidrJ�getuid�pw_name�replacer^rKrOr�r��max)rTr��_mtimes�call_as_user�	path_list�path_s      r(�_get_max_mtimezcPanel._get_max_mtime�s���
���u����<��	���4�4�<�L��M�M�(�L�9�9�E��K�K��$�$�	��	8�	8�E��w�~�~�e�$�$�
8����r�w�/�/��6�6�7�7�7��&�-�s�7�|�|�|�A�-r'c�,�td�|i���dd��}||�|��kritd|<dStd�|i���dg��S)z$check and invalidate cache if neededr r�rNr�)r�r�r)rT�cpuserr�r�s    r(�_get_from_cachezcPanel._get_from_cache�s���

�$�%�)�)�&�"�5�5�9�9�'�1�E�E�	��3�-�-�e�4�4�4�4�02�F�$�%�f�-��4��'�(�,�,�V�R�8�8�<�<�Y��K�K�Kr'c�t����|��|��}|�|St���t������fd�}|�|||��������|�|������d�td�<����S)Nc���|d}|�krF|d}d|dkr��||i��dS��||i��dSdS)Nrr��mainr)�update)rKr�r�r��
document_rootrr��domains_tmps     ���r(r�z#cPanel._userdomains.<locals>.parser�sp�����N�E����� +�A��
��[��^�+�+��N�N�A�}�#5�6�6�6�6�6��&�&��=�'9�:�:�:�:�:�
�r'r�)r�r�r )rrr�rr�itemsr�)rTrr�r��cached_datar�r�rs `    @@r(r�zcPanel._userdomains�s�������)�)�&�%�8�8���"���!�m�m���-�-��	;�	;�	;�	;�	;�	;�	;�	�"�"�5�&��"�>�>�>����{�#�#�#��'�'��.�.��}�}���-
�-
�� �!�&�)��}�}���r'c��d|vr@tjtj����j}|�d|��}|�d��}|D�]i}	t|d��}n5#t$r(}|st�
d||��Yd}~�Bd}~wwxYw	t|��D]�\}}		|	���}	n,#t$rt�
d||��Y�CwxYw|	���s�\|	�d��dkr|st�
d||����|	�d��\}
}|����d	��}|||
|����	|�����R#|���wxYwdS)
Nrr�rbzCan'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
rr^rQr}rwr��	enumerater]�UnicodeDecodeErrorrSr��close)
r�r�r�rrr�file_r��ir�r��domain_raw_datar�s
             r(r�zcPanel._parse_userdatadomains�s���u����<��	���4�4�<�L��M�M�(�L�9�9�E��K�K��$�$�	��$	�$	�E�
��U�D�)�)�����
�
�
��H��N�N�#<�e�Q�G�G�G����������
����
� )��/�/�7�7�G�A�t�!�#�{�{�}�}����-�!�!�!����K��!����
!��
!���� �:�:�<�<�!� ��z�z�$�'�'�1�,�,�$��"�N�N�!3� !� %�	���!�.2�j�j��.>�.>�+�F�O�"1�"7�"7�"9�"9�"?�"?��"E�"E�K��F�5�&�+�6�6�6�6�/7�2���
�
�
�
�����
�
�
�
����I$	�$	sI� A1�1
B#�;B�B#�'F.�<C�F.�&C:�7F.�9C:�:BF.�.Gc�4�td�|D����S)Nc3�nK�|]0}t�|�����V��1dSrY)�CPANEL_PACKAGE_EXTENSIONS_PATH�joinpath�is_file)r��files  r(�	<genexpr>z0cPanel.is_extension_installed.<locals>.<genexpr>�sP����
�
��
+�3�3�D�9�9�A�A�C�C�
�
�
�
�
�
r')�all)rT�pkgss  r(�is_extension_installedzcPanel.is_extension_installed�s0���
�
��
�
�
�
�
�	
r'c��v�K�	td���d{V��}td�|dD����}dD]d�t�fd�|dD����}td�|dD����}td	�|d
D����sdS�en#ttf$rYdSwxYwdS)
N�
list_hooksc3�2K�|]}|ddk�|V��dS)�category�WhostmgrNr&)r��cats  r(r+z+cPanel.is_hook_installed.<locals>.<genexpr>�s=��������z�?�j�0�0��0�0�0�0��r'�
categories)zAccounts::change_packagezAccounts::CreatezAccounts::Modifyc3�4�K�|]}|d�k�|V��dS)�eventNr&)r��ev�
event_names  �r(r+z+cPanel.is_hook_installed.<locals>.<genexpr>s>���������'�{�j�0�0��0�0�0�0��r'�eventsc3�2K�|]}|ddk�|V��dS)�stage�postNr&)r��sts  r(r+z+cPanel.is_hook_installed.<locals>.<genexpr>
s9�������B�w�K�6�4I�4I�B�4I�4I�4I�4I��r'�stagesc3�.K�|]}|ddkV��dS)�hookzImunifyHook::hook_processingNr&)r��actions  r(r+z+cPanel.is_hook_installed.<locals>.<genexpr>s@��������6�N�&D�D������r'�actionsFT)r�next�any�
StopIterationr)rT�hooksr2r7r<r9s     @r(�is_hook_installedzcPanel.is_hook_installed�sQ�����	�!�,�/�/�/�/�/�/�/�/�E���� ��.������H��
!�
!�
�
�����&�x�0�������
���!&�x�����������"'�	�"2������!�!�5�5�	!�
!��$��/�	�	�	��5�5�	�����ts�BB!�B!�!B6�5B6�extention_namec��K�t�ddd���|D]$}tjt|zt���%tj|fi|���d{V��tjtj
jdd���tjtdzt��tgd����d{V��dS)Ni�T)�mode�parentsru)rKru�ImunifyHook.pm)�"/usr/local/cpanel/bin/manage_hooks�add�module�ImunifyHook)r'�mkdirrj�copy2�"PREINSTALL_PACKAGE_EXTENSIONS_PATHr�add_extension_for_allrJryr�Core�INBOX_HOOKS_DIR�CPANEL_HOOKS_PATHr)rTrI�extention_files�kwargs�filenames     r(�install_extensionzcPanel.install_extensions����	'�,�,���t�	-�	
�	
�	
�(�	�	�H��L�2�X�=�.�
�
�
�
��,�^�F�F�v�F�F�F�F�F�F�F�F�F�	��F�K�/�e�d�K�K�K�K���.�1A�A��	
�	
�	
��
�
�
�
�
�	
�	
�	
�	
�	
�	
�	
�	
�	
r'�extension_namec��K�tgd����d{V��tt��5tdz���ddd��n#1swxYwYtj|���d{V��|D]J}tt��5t|z���ddd��n#1swxYwY�KdS)N)rN�delrPrQrM)rrr�rX�unlinkr�remove_extension_from_allr')rTr]rYr[s    r(�uninstall_extensionzcPanel.uninstall_extension=s������
�
�
�
�
�	
�	
�	
�	
�	
�	
�	
��'�
(�
(�	<�	<�
�!1�
1�9�9�;�;�;�	<�	<�	<�	<�	<�	<�	<�	<�	<�	<�	<����	<�	<�	<�	<��0��@�@�@�@�@�@�@�@�@�'�	E�	E�H��+�,�,�
E�
E�/�(�:�B�B�D�D�D�
E�
E�
E�
E�
E�
E�
E�
E�
E�
E�
E����
E�
E�
E�
E��	E�	Es#�A�A�A�B=�=C	�C	c��g}tdd��5}|D]V}|������}t|��dkr|�|d���W	ddd��n#1swxYwY|S)Nz/proc/mounts�rr)rQrSr^r�r�)�mountsrVr��valuess    r(rez
cPanel.mountsRs�����
�.�#�
&�
&�	-�!��
-�
-�������+�+�-�-���v�;�;��?�?��M�M�&��)�,�,�,��
-�	-�	-�	-�	-�	-�	-�	-�	-�	-�	-�	-����	-�	-�	-�	-�
�
s�AA:�:A>�A>c�D�td�����}td�����}|�tn|}t|h}|�|S|���D]0}||vr*|�d��s|�|���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*r��BASE_DIRrer�rO)ro�homedir�	homematch�basedirs�mounts     r(rmzcPanel.basedirs\s��� �	�*�*�.�.�0�0��!�+�.�.�2�2�4�4�	�%�o�(�(�7���g�&�����O��[�[�]�]�	$�	$�E��E�!�!�%�*:�*:�?�*K�*K�!����U�#�#�#���r')r�c��|K�tjjsdStj|���sdS|||d�}t�|j�d�|��d}tj|��}t|g|�
������d{V��}tj|�d�	����S)
zG
        Notify a customer using cPanel iContact Notifications
        F)r�)�message_type�paramsr�z.notify(%s)zB/usr/local/cpanel/whostmgr/docroot/cgi/imunify/handlers/notify.cgi)�inputNr�)r�)
r�
AdminContacts�ENABLE_ICONTACT_NOTIFICATIONS�should_send_user_notificationsrwrxr#r��dumpsr�encode�loadsr])rTrprqr�r`�cmd�stdinr@s        r(�notifyz
cPanel.notifyrs�����
�#�A�	��5��4�d�C�C�C�	��5� ,���M�M�����s�|�0�0�0�$�7�7�7�
+�	��
�4� � ���s�e�5�<�<�>�>�:�:�:�:�:�:�:�:�:���z�#�*�*�,=�*�>�>�?�?�?r'c��l�K�t����fd�}|�t|d����S)Nc�*��|d�|d<dS)Nr�r�r&)rKr�r��results   �r(r�z$cPanel.list_docroots.<locals>.parser�s���%0��^�F�;�q�>�"�"�"r'Tr�)�dictr��CPANEL_USERDATADOMAINS_PATH�ror�r~s  @r(�
list_docrootszcPanel.list_docroots�sV���������	4�	4�	4�	4�	4�	
�#�#�'��t�	$�	
�	
�	
��
r'c��T�K�i��fd�}|�t|d����S)Nc� ��|dg�|<dS)Nr�r&)r_r�r�r~s   �r(r�z'cPanel.get_domain_paths.<locals>.parser�s���$�Q��(�F�1�I�I�Ir'Tr�)r�r�r�s  @r(�get_domain_pathszcPanel.get_domain_paths�sR�������	)�	)�	)�	)�	)�	
�#�#�'��t�	$�	
�	
�	
��
r'rY)T)7r#r$r%�NAME�TCP_PORTS_CPANEL�
OPEN_PORTSr"�	exception�smtp_allow_usersr�r��staticmethodrM�classmethodrWrZrar�ensure_valid_panelrsr�rr�r�r�rr��strrr�r�rr�r�rr��boolr�r�r�rrrr�r�r.rHr\rbrer	rmr{r�r�r&r'r(r0r0Is��������D�K�J�J��������
�
�$,�+�+�K�K�K�
�
�%��J�. �I� �z��.�M�,�N��5�5��\�5��$�$��[�$��%�%��[�%��4�4��[�4�
�T����
�
�
���
�>�T����"H�"H�"H���"H�H�
�
��_�
��8����	
�j�	�����[��(�#��$�s�)�����,?�	
�c��?�?�?�?�
	�4��T�#�Y��+?�	�	�	�	�	�	�	��_�S�!�!�!�n��n�n�n�"�!�n� G��G��G�G�G�G�&K�#�K�c�K�d�K�K�K�K�L�3�L�s�L�t�L�L�L�L�9��S�$�s�C�x�.�-@�(A�9�9�9�9�v�.�.��[�.��
L�
L��[�
L��6�d�����[��8�)�)�)��\�)�V�
�
��[�
�����[��@� 
�� 
�

� 
� 
� 
��[� 
�D�E�s�E�E�E��[�E�(����\���#�c�(�����,�8<�@�@�@�@��[�@�,
�T�#�s�(�^�
�
�
�
�
��S�$�s�)�^�(<�
�
�
�
�
�
r'r0)Ir�r��loggingrJ�os.pathrrjrl�collectionsrr�
contextlibr�pathlibr�typingrrr	�urllib.parser
�packaging.versionr�3defence360agent.application.determine_hosting_panelr
�defence360agent.contractsr�defence360agent.utilsrrrrr�defence360agent.utils.ipechor�defence360agent.utils.kwconfigrr�rrrr�whmrrr'rX�	Packaging�DATADIRrTr�r�rfrgr�rer�r�rz�	getLoggerr#rwrh�TCP_PORTS_COMMONr�rjr-r��PanelExceptionr"r*�
AbstractPanelr0r&r'r(�<module>r�s��������������	�	�	�	�����
�
�
�
�
�
�
�
�
�
�
�
�0�0�0�0�0�0�0�0�������������"�"�"�"�"�"�"�"�"�"�!�!�!�!�!�!�%�%�%�%�%�%�������-�,�,�,�,�,���������������3�2�2�2�2�2�3�3�3�3�3�3�������.�.�.�.�.�.�.�.�������)�)�)�)�)�)�)�)�!%��&G�!H�!H���D�,�-�-���D��	�	!�"�"�%A�A�#�)��<��%�� �� .� 6�M�n�n�<M��B��F��1��	��	�8�	$�	$��E���(�K�=�8����!���b�	1�	1��	�	�	�	�	�d�)�	�	�	�#�#�#�#�#�H�#�#�#�V	�V	�V	�V	�V	�T�
�V	�V	�V	�V	�V	r'defence360agent/subsys/panels/cpanel/__pycache__/whm.cpython-311.opt-1.pyc0000644000000000000000000001446400000000000023277 0ustar  �

8i�g�ʪ�����ddlZddlZddlZddlZddlmZddlmZmZddl	m
Z
eje��Z
dZgd�ZGd�de
��ZGd	�d
e��Zdd�Zd
�Zd�Zd�Zd�ZdS)�N)�quote)�	check_run�
CheckRunError)�PanelExceptionz/usr/sbin/whmapi1)zno certificatezno key with the idzcannot read license filezinvalid license filezlicense file expiredc��eZdZdZdS)�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������?�?��Drrc��eZdZdZdS)�WHMAPILicenseErrorz$Raises when cannot Read License FileNr	rrrrrs������.�.��DrrFc��K�|rdgng}|�td|g��d�|���D��}	t||z���d{V�����}nS#t
$rF}|jtjkr)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)N�sudo�
--output=jsonc�Z�g|](\}}d�|t|������)S)z{}={})�formatr)�.0�k�vs   r�
<listcomp>zwhmapi1.<locals>.<listcomp>'s0��
E�
E�
E�d�a��g�n�n�Q��a���)�)�
E�
E�
ErzBroken output from whmapi1: z
, reason: �metadata�result�datazwhmapi {} command failed: {}�command�reason)�	statusmsgzCannot Read License FilezCannot Read CPanel License Filez/Broken output from whmapi1 (KeyError: {}): {!r})�extend�WHMAPI1_CMD�itemsr�decoder�
returncode�signal�SIGTERM�logger�warningr�json�loads�JSONDecodeErrorr�KeyErrorr)�functionr�kwargs�cmd�params�
raw_output�e�outputs        r�whmapi1r6$s�����
"�6�(�(��C��J�J��_�h�7�8�8�8�
E�
E�f�l�l�n�n�
E�
E�
E�F��%�c�F�l�3�3�3�3�3�3�3�3�;�;�=�=�
�
�������<�F�N�?�*�*��N�N�1����!�!�$�$�$��G������������J�'�'����������F�:�F�F�1�F�F�
�
��	���������
��*��h�'�	��&�>�!�!�.�5�5��:�&�y�1�6�*�3E�h�3O�����
��
�	�	�	�4������F�F��N�N�<�=�=�=�$�$�!�A�H�H��v�����
�����	���sP�*A1�1
C�;AB<�<C�C�D�)C?�?D�E�;E�
G�#AF=�=Gc��tg|�d�}	t�d|��tj|dtj���}n*#tj$r}td|z��|�d}~wwxYw|r�tj	|j
�����}g}t|��D]l\}}	|}	|D]
}
|	|
}	�|�
|	���+#t$r5}|dkrtd��|�|�
d��Yd}~�ed}~wwxYwndSt|��dkr|dS|S)	Nrzsubprocess.run(%r)T)�check�stdoutzFailed to run whmapi1: %srzCould not parse whmapi1 output�)r#r)�debug�
subprocess�run�PIPE�CalledProcessErrorrr+r,r9r%�	enumerate�appendr.�len)�args�	path_listr1�resr4�decoded_outputr�i�element_path�item�keys           r�
run_whmapirKLs����
/��
/��
/�C�F����)�3�/�/�/��n�S��Z�_�E�E�E�����(�F�F�F��9�A�=�>�>�A�E�����F��������C�J�$5�$5�$7�$7�8�8����(��3�3�	(�	(�O�A�|�
(�%��'�%�%�C���9�D�D��
�
�d�#�#�#�#���	
(�	
(�	
(���6�6�*�8�����
�M�M�$�'�'�'�'�'�'�'�'�����	
(����
	(�"	��
�6�{�{�a����a�y���
s/�<A�A2�A-�-A2�9$C�
D�(+D�Dc�&�t|ddg��S)Nrr�rK)rCs r�run_whmapi_resultrNss���d�Z��2�3�3�3rc�:�t|ddgddg��\}}||fS)Nrrr rM)rCrr s   r�run_whmapi_result_and_reasonrPws2����z�8�$�z�8�&<���N�F�F��6�>�rc����fd�}|S)Nc����K�d}	�|i|���d{V��}nZ#t$r1}t�t|����Yd}~n$d}~wt�d��YnxYw|S)NzSomething went wrong)rr)�error�str�	exception)rCr0�rv�sww�funcs    �r�wrapperz catch_exception.<locals>.wrapper�s������
��	5��t�T�,�V�,�,�,�,�,�,�,�,�B�B���	#�	#�	#�
�L�L��S���"�"�"�"�"�"�"�"�����	5����3�4�4�4�4�4�����	s��
A-�'A�A-r)rXrYs` r�catch_exceptionrZs#���������Nr)F)r+�loggingr<r'�urllib.parser�defence360agent.utilsrr�"defence360agent.subsys.panels.baser�	getLoggerr
r)r#�WHMAPI_CERT_ERROR_LISTrrr6rKrNrPrZrrr�<module>rasC��������������
�
�
�
�������:�:�:�:�:�:�:�:�=�=�=�=�=�=�	��	�8�	$�	$��"������	�	�	�	�	�n�	�	�	�	�	�	�	�	��	�	�	�%�%�%�%�P$�$�$�N4�4�4��������rdefence360agent/subsys/panels/cpanel/__pycache__/whm.cpython-311.pyc0000644000000000000000000001446400000000000022340 0ustar  �

8i�g�ʪ�����ddlZddlZddlZddlZddlmZddlmZmZddl	m
Z
eje��Z
dZgd�ZGd�de
��ZGd	�d
e��Zdd�Zd
�Zd�Zd�Zd�ZdS)�N)�quote)�	check_run�
CheckRunError)�PanelExceptionz/usr/sbin/whmapi1)zno certificatezno key with the idzcannot read license filezinvalid license filezlicense file expiredc��eZdZdZdS)�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������?�?��Drrc��eZdZdZdS)�WHMAPILicenseErrorz$Raises when cannot Read License FileNr	rrrrrs������.�.��DrrFc��K�|rdgng}|�td|g��d�|���D��}	t||z���d{V�����}nS#t
$rF}|jtjkr)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)N�sudo�
--output=jsonc�Z�g|](\}}d�|t|������)S)z{}={})�formatr)�.0�k�vs   r�
<listcomp>zwhmapi1.<locals>.<listcomp>'s0��
E�
E�
E�d�a��g�n�n�Q��a���)�)�
E�
E�
ErzBroken output from whmapi1: z
, reason: �metadata�result�datazwhmapi {} command failed: {}�command�reason)�	statusmsgzCannot Read License FilezCannot Read CPanel License Filez/Broken output from whmapi1 (KeyError: {}): {!r})�extend�WHMAPI1_CMD�itemsr�decoder�
returncode�signal�SIGTERM�logger�warningr�json�loads�JSONDecodeErrorr�KeyErrorr)�functionr�kwargs�cmd�params�
raw_output�e�outputs        r�whmapi1r6$s�����
"�6�(�(��C��J�J��_�h�7�8�8�8�
E�
E�f�l�l�n�n�
E�
E�
E�F��%�c�F�l�3�3�3�3�3�3�3�3�;�;�=�=�
�
�������<�F�N�?�*�*��N�N�1����!�!�$�$�$��G������������J�'�'����������F�:�F�F�1�F�F�
�
��	���������
��*��h�'�	��&�>�!�!�.�5�5��:�&�y�1�6�*�3E�h�3O�����
��
�	�	�	�4������F�F��N�N�<�=�=�=�$�$�!�A�H�H��v�����
�����	���sP�*A1�1
C�;AB<�<C�C�D�)C?�?D�E�;E�
G�#AF=�=Gc��tg|�d�}	t�d|��tj|dtj���}n*#tj$r}td|z��|�d}~wwxYw|r�tj	|j
�����}g}t|��D]l\}}	|}	|D]
}
|	|
}	�|�
|	���+#t$r5}|dkrtd��|�|�
d��Yd}~�ed}~wwxYwndSt|��dkr|dS|S)	Nrzsubprocess.run(%r)T)�check�stdoutzFailed to run whmapi1: %srzCould not parse whmapi1 output�)r#r)�debug�
subprocess�run�PIPE�CalledProcessErrorrr+r,r9r%�	enumerate�appendr.�len)�args�	path_listr1�resr4�decoded_outputr�i�element_path�item�keys           r�
run_whmapirKLs����
/��
/��
/�C�F����)�3�/�/�/��n�S��Z�_�E�E�E�����(�F�F�F��9�A�=�>�>�A�E�����F��������C�J�$5�$5�$7�$7�8�8����(��3�3�	(�	(�O�A�|�
(�%��'�%�%�C���9�D�D��
�
�d�#�#�#�#���	
(�	
(�	
(���6�6�*�8�����
�M�M�$�'�'�'�'�'�'�'�'�����	
(����
	(�"	��
�6�{�{�a����a�y���
s/�<A�A2�A-�-A2�9$C�
D�(+D�Dc�&�t|ddg��S)Nrr�rK)rCs r�run_whmapi_resultrNss���d�Z��2�3�3�3rc�:�t|ddgddg��\}}||fS)Nrrr rM)rCrr s   r�run_whmapi_result_and_reasonrPws2����z�8�$�z�8�&<���N�F�F��6�>�rc����fd�}|S)Nc����K�d}	�|i|���d{V��}nZ#t$r1}t�t|����Yd}~n$d}~wt�d��YnxYw|S)NzSomething went wrong)rr)�error�str�	exception)rCr0�rv�sww�funcs    �r�wrapperz catch_exception.<locals>.wrapper�s������
��	5��t�T�,�V�,�,�,�,�,�,�,�,�B�B���	#�	#�	#�
�L�L��S���"�"�"�"�"�"�"�"�����	5����3�4�4�4�4�4�����	s��
A-�'A�A-r)rXrYs` r�catch_exceptionrZs#���������Nr)F)r+�loggingr<r'�urllib.parser�defence360agent.utilsrr�"defence360agent.subsys.panels.baser�	getLoggerr
r)r#�WHMAPI_CERT_ERROR_LISTrrr6rKrNrPrZrrr�<module>rasC��������������
�
�
�
�������:�:�:�:�:�:�:�:�=�=�=�=�=�=�	��	�8�	$�	$��"������	�	�	�	�	�n�	�	�	�	�	�	�	�	��	�	�	�%�%�%�%�P$�$�$�N4�4�4��������rdefence360agent/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  �

��A������ddlmZdgZdS)�)�DirectAdminrN)�panelr�__all__���g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/directadmin/__init__.py�<module>r	s"���������/���rdefence360agent/subsys/panels/directadmin/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000044400000000000024316 0ustar  �

��A������ddlmZdgZdS)�)�DirectAdminrN)�panelr�__all__���g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/directadmin/__init__.py�<module>r	s"���������/���rdefence360agent/subsys/panels/directadmin/__pycache__/config.cpython-311.opt-1.pyc0000644000000000000000000000145700000000000024770 0ustar  �

�����:�ddlZddlmZdZGd�de��ZdS)�N)�KWConfigz/usr/local/directadmin/confc�L�eZdZdZdZej�ed��Z	dS)�
ConfigOptionsz^\s*{}\s*=\s*(.*?)\s*$z{}={}zdirectadmin.confN)
�__name__�
__module__�__qualname__�SEARCH_PATTERN�
WRITE_PATTERN�os�path�join�BASEDIR�DEFAULT_FILENAME���e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/directadmin/config.pyrrs1������.�N��M��w�|�|�G�-?�@�@���rr)r�defence360agent.utils.kwconfigrrrrrr�<module>rsc��	�	�	�	�3�3�3�3�3�3�
'��A�A�A�A�A�H�A�A�A�A�Ardefence360agent/subsys/panels/directadmin/__pycache__/config.cpython-311.pyc0000644000000000000000000000145700000000000024031 0ustar  �

�����:�ddlZddlmZdZGd�de��ZdS)�N)�KWConfigz/usr/local/directadmin/confc�L�eZdZdZdZej�ed��Z	dS)�
ConfigOptionsz^\s*{}\s*=\s*(.*?)\s*$z{}={}zdirectadmin.confN)
�__name__�
__module__�__qualname__�SEARCH_PATTERN�
WRITE_PATTERN�os�path�join�BASEDIR�DEFAULT_FILENAME���e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/directadmin/config.pyrrs1������.�N��M��w�|�|�G�-?�@�@���rr)r�defence360agent.utils.kwconfigrrrrrr�<module>rsc��	�	�	�	�3�3�3�3�3�3�
'��A�A�A�A�A�H�A�A�A�A�Ardefence360agent/subsys/panels/directadmin/__pycache__/panel.cpython-311.opt-1.pyc0000644000000000000000000006625200000000000024626 0ustar  �

�x'>�:���ddlZddlZddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlZddlZddl
mZddlmZddlmZmZmZmZddlmZddlmZmZddlmZddlmZmZm Z m!Z!m"Z"m#Z#d	d
l$m%Z%d	dl%m&Z&ej'e(��Z)dZ*d
Z+dZ,dZ-d�.e-e+��Z/dZ0dZ1e%j2ddgzZ3dZ4e	j5d��Z6Gd�de%j&��Z7de8fd�Z9e1fdee8e8ffd�Z:defd�Z;Gd�de%j<��Z=dS)�N)�defaultdict)�Path)�Any�Dict�List�Set)�Version)�DA_FILE�is_directadmin_installed)�Core)�HTTP_REQUEST_RETRY_TIMEOUT�async_lru_cache�
backoff_sleep�retry_on�run�timeit�)�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/domainowners�2222�35000-35999z"/usr/local/directadmin/data/users/z^[a-z_][a-z0-9_-]{0,31}\Zc��eZdZdS)�DirectAdminExceptionN)�__name__�
__module__�__qualname__���d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/directadmin/panel.pyrr3s�������Drr�returnc��t|t��rt�|��st	d|�����|S)zGReturn ``user`` if safe; otherwise raise :class:`DirectAdminException`.z.Refusing to manage sudouser: invalid username )�
isinstance�str�_SUDOUSER_NAME_RE�matchr)�users r�_validate_sudouserr'7sN���d�C� � �
�(9�(?�(?��(E�(E�
�"�"�BF�$�H�
�
�	
��Krc	��i}t|d��5}|D]�}	|���}n4#t$r'}t�d|||��Yd}~�Dd}~wwxYw|�d��}|dkr<||dzd����||d|����<��	ddd��n#1swxYwY|S)zBReturn a mapping from domain name to user name owning this domain.�rbzBroken line in %s: %r (%s)N�:����)�open�decode�UnicodeDecodeError�logger�warning�find�strip)�path�domains�f�bline�line�e�poss       r�get_user_domainsr;@s?���G�	
�d�D�	�	�	F�Q��	F�	F�E�
��|�|�~�~����%�
�
�
����;�T�5�!�L�L�L����������
�����)�)�C�.�.�C��b�y�y�.2�3��7�9�9�o�.C�.C�.E�.E���T�c�T�
�(�(�*�*�+��	F�	F�	F�	F�	F�	F�	F�	F�	F�	F�	F�	F����	F�	F�	F�	F��Ns7�C�.�C�
A�A�C�A�AC�C�Cc	��JK�ddg}t|���d{V��\}}}	d}tj||tj���}t	|�d�������S#ttf$rtd|�d|�d|�����wxYw)	N�"/usr/local/directadmin/directadmin�vs&^(Version: )?DirectAdmin (v.)?([\d.]+))�flags�z-Failed to parse directadmin version. retcode=z	, stdout=�	, stderr=)
r�re�search�	MULTILINEr	�groupr.�
ValueError�AttributeErrorr)�cmd�retcode�stdout�stderr�version_pattern�results      r�get_directadmin_versionrNPs�����/��
5�C�$'��H�H�n�n�n�n�n�n��G�V�V�
�D����?�F�"�,�G�G�G���v�|�|�A���-�-�/�/�0�0�0����'�
�
�
��
1��
1�
1�"�
1�
1�'-�
1�
1�
�
�	
�
���s�AA8�8*B"c�8�eZdZdZeZdgezdgezd�gd�gd�d�d�ZeZ	e
d���Ze
d���Ze
j��d	���Ze
j��d
���Zed���Zed���Zd
�Zd�Zd�Zd�Ze
j��d+d���Ze
j��d+d���Zdeefd�Zd�Zd�Zd�Zde efd�Z!de"fd�Z#de"fd�Z$de"fd�Z%ede"fd���Z&de"eeffd�Z'de"ee"eefffd�Z(de"eeffd �Z)e*e+e,e-�!��d"e.j/j0de1fd#���Z2defd$�Z3e4d%d&�'��de"eee
j5ffd(���Z6d)ede7e
j5fd*�Z8dS),�DirectAdmin�465�113)�in�out)�20�21�53�443r�80)rUrVrWrR�123r)�tcp�udpc��t��S�N)r��clss r�is_installedzDirectAdmin.is_installedms��'�)�)�)rc��HK�tt���d{V����Sr^)r#rNr_s r�versionzDirectAdmin.versionqs/�����0�2�2�2�2�2�2�2�2�3�3�3rc��,K�t|��||���vs#tj�d��dkrGtdd|tg���d{V��\}}}|dkr!t�d|||��dSdSdS)N�usertype�admin�gpasswdz-arz&gpasswd -a failed for %r: rc=%s err=%r)	r'�_get_admins�os�environ�getr�
SUDO_GROUPr0r1��selfr&rI�_out�errs     r�add_sudouserzDirectAdmin.add_sudouservs������4� � � ��4�#�#�%�%�%�%�����
�)C�)C�w�)N�)N�'*�I�t�T�:�+N�'O�'O�!O�!O�!O�!O�!O�!O��G�T�3��!�|�|����<����	�����*O�)N��|rc���K�t|��||���vrGtdd|tg���d{V��\}}}|dkr!t�d|||��dSdSdS)Nrgz-drz&gpasswd -d failed for %r: rc=%s err=%r)r'rhrrlr0r1rms     r�delete_sudouserzDirectAdmin.delete_sudouser�s������4� � � ��4�#�#�%�%�%�%�'*�I�t�T�:�+N�'O�'O�!O�!O�!O�!O�!O�!O��G�T�3��!�|�|����<����	�����	&�%��|rc��t|d��5}|dz
}||���vr|�|��ddd��dS#1swxYwYdS)N�r+�
)r-�	readlines�write)r4�contentr6s   r�	_add_linezDirectAdmin._add_line�s���
�$��
�
�	!���t�O�G��a�k�k�m�m�+�+����� � � �	!�	!�	!�	!�	!�	!�	!�	!�	!�	!�	!�	!����	!�	!�	!�	!�	!�	!s�1A�A�Ac���t|d��5}d��fd�|D����}|�d��|�d��|�|��ddd��dS#1swxYwYdS)Nru�c3�H�K�|]}�|���v�|V��dSr^)r3)�.0r8rys  �r�	<genexpr>z+DirectAdmin._remove_line.<locals>.<genexpr>�s5�����M�M�D���
�
���1L�1L�4�1L�1L�1L�1L�M�Mrr)r-�join�seek�truncaterx)r4ryr6�datas `  r�_remove_linezDirectAdmin._remove_line�s����
�$��
�
�	���7�7�M�M�M�M�A�M�M�M�M�M�D�
�F�F�1�I�I�I�
�J�J�q�M�M�M�
�G�G�D�M�M�M�		�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	�	s�A!B�B�Bc��tdd��5}|������}ddd��n#1swxYwY|S)Nz,/usr/local/directadmin/data/admin/admin.list�r�r-�read�split)rnr6�
admin_lists   rrhzDirectAdmin._get_admins�s���
�@�#�
F�
F�	*�!��������)�)�J�	*�	*�	*�	*�	*�	*�	*�	*�	*�	*�	*����	*�	*�	*�	*����'A�A�Ac��tdd��5}|������}ddd��n#1swxYwY|S)Nz//usr/local/directadmin/data/admin/reseller.listr�r�)rnr6�
reseller_lists   r�_get_resellerszDirectAdmin._get_resellers�s���
�C�S�
I�
I�	-�Q��F�F�H�H�N�N�,�,�M�	-�	-�	-�	-�	-�	-�	-�	-�	-�	-�	-����	-�	-�	-�	-��r�c���tj�t|��}tj�|��s�t|d�����|�|d��tj	d��j
}tj	d��j
}tj|||��tj|d��|�||��dS)N�wz	#!/bin/sh�diradmini�)
rir4r��	HOOKS_DIR�existsr-�closerz�pwd�getpwnam�pw_uid�chown�chmod)rn�hookryr4�uid�gids      r�_create_hookzDirectAdmin._create_hook�s����w�|�|�I�t�,�,���w�~�~�d�#�#�	"���s�O�O�!�!�#�#�#��N�N�4��-�-�-��,�z�*�*�1�C��,�z�*�*�1�C��H�T�3��$�$�$��H�T�5�!�!�!����t�W�%�%�%�%�%rc��tj�t|��}tj�|��r|�||��dSdSr^)rir4r�r�r�r�)rnr�ryr4s    r�_delete_hookzDirectAdmin._delete_hook�sS���w�|�|�I�t�,�,��
�7�>�>�$���	-����d�G�,�,�,�,�,�	-�	-rNc��K�tjd�t����|�dt
��|�dt��|���D]Q}	|�|���d{V���#t$r&}t�d||��Yd}~�Jd}~wwxYw|�dd��|�dd��|�dd��dS)	Nz/usr/sbin/groupadd -f {}�/etc/sudoers�&Skipping invalid sudouser entry %r: %s�user_create_post.sh�9/usr/bin/imunify360-agent add-sudouser --user "$username"�user_destroy_pre.sh�</usr/bin/imunify360-agent delete-sudouser --user "$username"�user_restore_post.sh)
ri�system�formatrlrz�	SUDO_LINE�
SUDO_TTY_LINErhrqrr0r1r�)rn�namer&�excs    r�enable_imunify_pluginz!DirectAdmin.enable_imunify_plugin�sU����
�	�,�3�3�J�?�?�@�@�@����~�y�1�1�1����~�}�5�5�5��$�$�&�&�	�	�D�
��'�'��-�-�-�-�-�-�-�-�-�-��'�
�
�
����<�d�C�������������
����
	
���!�G�	
�	
�	
�	
���!�J�	
�	
�	
�	
���"�G�	
�	
�	
�	
�	
s�<B�
C�"C�Cc��K�|�dt��|�dt��|���D]Q}	|�|���d{V���#t
$r&}t�d||��Yd}~�Jd}~wwxYwtj	d�
t����|�dd��|�dd��|�dd��dS)	Nr�r�z/usr/sbin/groupdel {}r�r�r�r�r�)
r�r�r�rhrsrr0r1rir�r�rlr�)rn�plugin_namer&r�s    r�disable_imunify_pluginz"DirectAdmin.disable_imunify_plugin�sY�������.�)�4�4�4����.�-�8�8�8��$�$�&�&�	�	�D�
��*�*�4�0�0�0�0�0�0�0�0�0�0��'�
�
�
����<�d�C�������������
����	�	�)�0�0��<�<�=�=�=����!�G�	
�	
�	
�	
���!�J�	
�	
�	
�	
���"�G�	
�	
�	
�	
�	
s�A,�,
B�6B�Br c��zK�ttt���������S)z:
        :return: list: list of directadmin users
        )�list�setr;�values�rns r�	get_userszDirectAdmin.get_users�s1�����C�(�*�*�1�1�3�3�4�4�5�5�5rc��`K�tt�������S)zI
        :return: list: domains hosted on server via directadmin
        )r�r;�keysr�s rr;zDirectAdmin.get_user_domains�s)�����$�&�&�+�+�-�-�.�.�.rc��ZK�d�t�����D��S)z8
        :return: domain to list of users pairs
        c��i|]	\}}||g��
Srr)r~�domainr&s   r�
<dictcomp>z3DirectAdmin.get_domain_to_owner.<locals>.<dictcomp>s ��N�N�N�<�6�4����N�N�Nr)r;�itemsr�s r�get_domain_to_ownerzDirectAdmin.get_domain_to_owner�s0����O�N�3C�3E�3E�3K�3K�3M�3M�N�N�N�Nrc��K�tt��}t�����D] \}}||�|���!|S)z8
        :return: user to list of domains pairs
        )rr�r;r��append)rn�user_to_domainsr�r&s    r�get_domains_per_userz DirectAdmin.get_domains_per_users\����&�d�+�+��,�.�.�4�4�6�6�	1�	1�L�F�D��D�!�(�(��0�0�0�0��rc��thSr^)�BASE_DIRr�s r�basedirszDirectAdmin.basedirs
s
���z�rc��K�t���d{V��td��kr|����d{V��S|����d{V��S)Nz1.62.8)rNr	�docroots_info_new�docroots_info_legacyr�s r�
docroots_infozDirectAdmin.docroots_infosy����(�*�*�*�*�*�*�*�*�g�h�.?�.?�?�?��/�/�1�1�1�1�1�1�1�1�1��.�.�0�0�0�0�0�0�0�0�0rc��nK�ddg}tdt��5t|���d{V��\}}}ddd��n#1swxYwY|dkrtd|�d|�d|�����tj�|��������}|j	�
d��\}}tj|�
�������}d	�|�|�
�����dg��}t�d|��t%j��}	tj�|d
d|��id���}
|	�d|j|
���d{V��S)Nr=z--root-auth-urlz!Call DA binary to obtain auth URLrz2Failed to obtain auth URL. Unexpected return code �	. stdout=rA�@�/)�netlocz0CMD_API_DOMAIN?json=yes&action=document_root_allzDocument roots URL: %s�
AuthorizationzBasic �GET)�headers�method)rr0rr�urllib�parse�urlparser.r3r�r��base64�standard_b64encode�encoder��_replace�geturl�info�asyncio�get_event_loop�request�Request�run_in_executor�_do_request)rnrHrIrJrK�
parsed_url�
basic_authr��document_roots_url�loopr�s           rr�zDirectAdmin.docroots_info_news����3�5F�G��
�7��
@�
@�	5�	5�,/��H�H�n�n�n�n�n�n�#�G�V�V�	5�	5�	5�	5�	5�	5�	5�	5�	5�	5�	5����	5�	5�	5�	5��a�<�<� �9�W�9�9�!�9�9�.4�9�9���
�
�\�*�*�6�=�=�?�?�+@�+@�+B�+B�C�C�
�'�.�4�4�S�9�9��
�F��.�z�/@�/@�/B�/B�C�C�J�J�L�L�
� �X�X��#�#�6�#�2�2�9�9�;�;�B�
�
�
��	���,�.@�A�A�A��%�'�'���.�(�(��$�&;�z�&;�&;�<��)�
�
��
�)�)�$��0@�'�J�J�J�J�J�J�J�J�Js�A�A�	Ac��K�ddg}tdt��5t|���d{V��\}}}ddd��n#1swxYwY|dkr|dkrtd|�d|�d|�����	t	j|�����}n*#tj$r}td	|�d
����d}~wwxYw|S)Nr=z--DocumentRootz%Call DA binary to obtain all docrootsrr,z8Failed to obtain document roots. Unexpected return code r�rAz7Failed to obtain document roots. Failed to decode json �.)rr0rr�json�loadsr.�JSONDecodeError)rnrH�retrTrp�outputr9s       rr�z DirectAdmin.docroots_info_legacy4sN����0��
���;�V�
D�
D�	+�	+�"%�c�(�(�N�N�N�N�N�N�M�C��c�	+�	+�	+�	+�	+�	+�	+�	+�	+�	+�	+����	+�	+�	+�	+��!�8�8��q��� �:��:�:�"%�:�:�25�:�:���
�	��Z��
�
���-�-�F�F���#�	�	�	� �N�!�N�N�N���
�����	����
�
s)�A�A�	A�2&B�C�(B;�;Cc�~�t��}|d���D]�\}}|d���D]s\}}|�d��r|||d<|�di�����D]%\}}|�d��r|||d<�&�t��|S)N�usersr5�public_html�
subdomains)�dictr�rk)r�r��username�userdata�
domainname�
domaindata�_�sub_datas        r�parse_document_root_outputz&DirectAdmin.parse_document_root_outputIs����f�f��"(��/�"7�"7�"9�"9�	B�	B��H�h�*2�9�*=�*C�*C�*E�*E�
B�
B�&�
�J��>�>�-�0�0�@�5?�C�
�=�1�2�#-�>�>�,��#C�#C�#I�#I�#K�#K�B�B�K�A�x��|�|�M�2�2�B�7A��H�]�3�4��B�
B��
rc��dK�|����d{V��}|�|��Sr^)r�r�)rnr�s  r�
list_docrootszDirectAdmin.list_docrootsUs=�����'�'�)�)�)�)�)�)�)�)���.�.�t�4�4�4rc��K�i}|����d{V��}t|�����}t|�����}|D]�}	|�|��}t
jj}||vrt
jj}||vrt
jj	}|�
dd��|�
dd��|�
dd��|�
d��dkt|��d�||<��#t$r.}ddd�||<t�d	||��Yd}~��d}~wwxYw|S)
N�languager|�email�creator�	suspended�yes)�localer��parentr�level)r�rz!Failed to get_user_details: %s %s)r�r�rhr��get_user_details_for_usernamer�	UserLevel�REGULAR_USER�RESSELER�ADMINrk�int�	Exceptionr0r1)	rn�res�	usernames�admins�	resellersr��
parsed_configrr9s	         r�get_user_detailszDirectAdmin.get_user_detailsYs��������.�.�*�*�*�*�*�*�*�*�	��T�%�%�'�'�(�(����+�+�-�-�.�.�	�!�	�	�H�
� $� B� B�8� L� L�
���3���y�(�(� �N�3�E��v�%�%� �N�0�E�+�/�/�
�B�?�?�*�.�.�w��;�;�+�/�/�	�2�>�>�!.�!2�!2�;�!?�!?�5�!H� ��Z�Z�!�!��H�
�
���
�
�
�� �!�!��H�
����7��1�������������
�����
s�&B;D"�"
E�,$E�Ec���tt|�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)r�USERS_CONF_DIR�	read_text�configparser�ConfigParser�read_string)rnr��
user_conf_strrs    rrz)DirectAdmin.get_user_details_for_usernamewsk����x�3�3�3�
�
�
�)�+�+�	�%�1�3�3�
�!�M�1�
��!�!�-�0�0�0�%�e�,�
��r)�on_error�timeoutr�c��	tj�|tj���5}|jdkr't
d�|j�����tj	|�
�������cddd��S#1swxYwYdS#ttjjtjt"jtjjf$r
}t|�d}~wwxYw)N)r��zstatus code is {})r�r��urlopenr�DEFAULT_SOCKET_TIMEOUT�statusrr�r�r�r�r.r/�http�client�
HTTPExceptionr��socketr�error�URLError)rnr��responser9s    rr�zDirectAdmin._do_request�s.��	(���'�'���!<�(���
<���?�c�)�)�(�+�2�2�8�?�C�C�����z�(�-�-�/�/�"8�"8�":�":�;�;�
<�
<�
<�
<�
<�
<�
<�
<�
<�
<�
<�
<����
<�
<�
<�
<�
<�
<��
��K�%�� ��N��L�!�
�	(�	(�	(�!�a�'�����	(���s<�+B1�A*B$�B1�$B(�(B1�+B(�,B1�1AC?�2C:�:C?c��
K�dS)z8
        Returns panel url
        :return: str
        r|r)rnr�s  r�panel_user_linkzDirectAdmin.panel_user_link�s
����
�rrr,�<)�maxsize�ttlc��K�|����d{V��}tt��}|�di�����D]�\}}|�di�����D]�\}}|�d��}|r1||�t
j||d|�����|�d��pi���D]R\}}	|	�d��}
|
r6||�t
j|
|�d|��d|������S�ʌ�|S)	Nr�r5r��main)�docrootr��typer�r�r��sub)r�rr�rkr�r�r�
DomainData)rnr�rMr&r�r��domain_datar�r1r��sub_public_htmls           r�_get_domains_details_per_userz)DirectAdmin._get_domains_details_per_user�s������'�'�)�)�)�)�)�)�)�)��3>�t�3D�3D��"�h�h�w��3�3�9�9�;�;�	�	�N�D�(�'/�|�|�I�r�'B�'B�'H�'H�'J�'J�
�
�#���)�o�o�m�<�<�����4�L�'�'���$/�#)�!'�%)�	������ �O�O�L�1�1�7�R��%�'�'���M�C��'/�l�l�=�&A�&A�O�&���t��+�+� �O�(7�*-�'8�'8��'8�'8�%*�)-�	��������
�0�
rr�c��K�|����d{V��}t|�|g����Sr^)r5r�rk)rnr��detailss   r�get_user_domains_detailsz$DirectAdmin.get_user_domains_details�sG�����:�:�<�<�<�<�<�<�<�<���G�K�K��"�-�-�.�.�.rr^)9rrr�NAMEr
�	DA_BINARY�TCP_PORTS_DA�
OPEN_PORTSr�	exception�classmethodrarcr�ensure_valid_panelrqrs�staticmethodrzr�rhr�r�r�r�r�rr#r�r;r�r�rr�rr�r�r�r�r�rrrrrr
r�r�r�rr�r)rr2r5r�r8rrrrPrP^s��������D��I��'�L�(��7�\�)�
�
�
A�@�@�B�B�B�
�
�	�	�J�%�I��*�*��[�*��4�4��[�4��T����������T����������!�!��\�!�����\�����
���
	&�	&�	&�-�-�-�
�T����
�
�
���
�4�T����
�
�
���
�46��c��6�6�6�6�/�/�/�O�O�O�����#�c�(�����1�T�1�1�1�1�
K��K�K�K�K�>�D�����*�	�d�	�	�	��\�	�5�T�#�s�(�^�5�5�5�5���S�$�s�C�x�.�-@�(A�����<
��c�3�h��
�
�
�
��X���*����
(�6�>�#9�(�c�(�(�(���
(�&�������_�Q�B�'�'�'��	
�c�4���(�(�	)����(�'��@/��/�	
�d�o�	�/�/�/�/�/�/rrP)>r�r�r�http.clientr!r��loggingrir�rBr$r��urllib.parse�collectionsr�pathlibr�typingrrrr�packaging.versionr	�3defence360agent.application.determine_hosting_panelr
r� defence360agent.contracts.configr�defence360agent.utilsr
rrrrrr|rr�	getLoggerrr0r��CMDr�rlr�r�r��_VIRTUAL_DOMAINOWNERS�TCP_PORTS_COMMONr;r�compiler$rr#r'r;rN�
AbstractPanelrPrrr�<module>rQs�������
�
�
�
�����������������	�	�	�	�
�
�
�
�	�	�	�	�
�
�
�
�
�
�
�
�����#�#�#�#�#�#�������'�'�'�'�'�'�'�'�'�'�'�'�%�%�%�%�%�%���������2�1�1�1�1�1�����������������������!�!�!�!�!�!�	��	�8�	$�	$����+��3�	�
#�
�$�+�+�J��<�<�	�K�
�3���$��
�'>�>��5���B�J�;�<�<��	�	�	�	�	�4�.�	�	�	�������0�
�
�D��c��N�
�
�
�
� 
�w�
�
�
�
�l/�l/�l/�l/�l/�$�$�l/�l/�l/�l/�l/rdefence360agent/subsys/panels/directadmin/__pycache__/panel.cpython-311.pyc0000644000000000000000000006625200000000000023667 0ustar  �

�x'>�:���ddlZddlZddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlZddlZddl
mZddlmZddlmZmZmZmZddlmZddlmZmZddlmZddlmZmZm Z m!Z!m"Z"m#Z#d	d
l$m%Z%d	dl%m&Z&ej'e(��Z)dZ*d
Z+dZ,dZ-d�.e-e+��Z/dZ0dZ1e%j2ddgzZ3dZ4e	j5d��Z6Gd�de%j&��Z7de8fd�Z9e1fdee8e8ffd�Z:defd�Z;Gd�de%j<��Z=dS)�N)�defaultdict)�Path)�Any�Dict�List�Set)�Version)�DA_FILE�is_directadmin_installed)�Core)�HTTP_REQUEST_RETRY_TIMEOUT�async_lru_cache�
backoff_sleep�retry_on�run�timeit�)�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/domainowners�2222�35000-35999z"/usr/local/directadmin/data/users/z^[a-z_][a-z0-9_-]{0,31}\Zc��eZdZdS)�DirectAdminExceptionN)�__name__�
__module__�__qualname__���d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/directadmin/panel.pyrr3s�������Drr�returnc��t|t��rt�|��st	d|�����|S)zGReturn ``user`` if safe; otherwise raise :class:`DirectAdminException`.z.Refusing to manage sudouser: invalid username )�
isinstance�str�_SUDOUSER_NAME_RE�matchr)�users r�_validate_sudouserr'7sN���d�C� � �
�(9�(?�(?��(E�(E�
�"�"�BF�$�H�
�
�	
��Krc	��i}t|d��5}|D]�}	|���}n4#t$r'}t�d|||��Yd}~�Dd}~wwxYw|�d��}|dkr<||dzd����||d|����<��	ddd��n#1swxYwY|S)zBReturn a mapping from domain name to user name owning this domain.�rbzBroken line in %s: %r (%s)N�:����)�open�decode�UnicodeDecodeError�logger�warning�find�strip)�path�domains�f�bline�line�e�poss       r�get_user_domainsr;@s?���G�	
�d�D�	�	�	F�Q��	F�	F�E�
��|�|�~�~����%�
�
�
����;�T�5�!�L�L�L����������
�����)�)�C�.�.�C��b�y�y�.2�3��7�9�9�o�.C�.C�.E�.E���T�c�T�
�(�(�*�*�+��	F�	F�	F�	F�	F�	F�	F�	F�	F�	F�	F�	F����	F�	F�	F�	F��Ns7�C�.�C�
A�A�C�A�AC�C�Cc	��JK�ddg}t|���d{V��\}}}	d}tj||tj���}t	|�d�������S#ttf$rtd|�d|�d|�����wxYw)	N�"/usr/local/directadmin/directadmin�vs&^(Version: )?DirectAdmin (v.)?([\d.]+))�flags�z-Failed to parse directadmin version. retcode=z	, stdout=�	, stderr=)
r�re�search�	MULTILINEr	�groupr.�
ValueError�AttributeErrorr)�cmd�retcode�stdout�stderr�version_pattern�results      r�get_directadmin_versionrNPs�����/��
5�C�$'��H�H�n�n�n�n�n�n��G�V�V�
�D����?�F�"�,�G�G�G���v�|�|�A���-�-�/�/�0�0�0����'�
�
�
��
1��
1�
1�"�
1�
1�'-�
1�
1�
�
�	
�
���s�AA8�8*B"c�8�eZdZdZeZdgezdgezd�gd�gd�d�d�ZeZ	e
d���Ze
d���Ze
j��d	���Ze
j��d
���Zed���Zed���Zd
�Zd�Zd�Zd�Ze
j��d+d���Ze
j��d+d���Zdeefd�Zd�Zd�Zd�Zde efd�Z!de"fd�Z#de"fd�Z$de"fd�Z%ede"fd���Z&de"eeffd�Z'de"ee"eefffd�Z(de"eeffd �Z)e*e+e,e-�!��d"e.j/j0de1fd#���Z2defd$�Z3e4d%d&�'��de"eee
j5ffd(���Z6d)ede7e
j5fd*�Z8dS),�DirectAdmin�465�113)�in�out)�20�21�53�443r�80)rUrVrWrR�123r)�tcp�udpc��t��S�N)r��clss r�is_installedzDirectAdmin.is_installedms��'�)�)�)rc��HK�tt���d{V����Sr^)r#rNr_s r�versionzDirectAdmin.versionqs/�����0�2�2�2�2�2�2�2�2�3�3�3rc��,K�t|��||���vs#tj�d��dkrGtdd|tg���d{V��\}}}|dkr!t�d|||��dSdSdS)N�usertype�admin�gpasswdz-arz&gpasswd -a failed for %r: rc=%s err=%r)	r'�_get_admins�os�environ�getr�
SUDO_GROUPr0r1��selfr&rI�_out�errs     r�add_sudouserzDirectAdmin.add_sudouservs������4� � � ��4�#�#�%�%�%�%�����
�)C�)C�w�)N�)N�'*�I�t�T�:�+N�'O�'O�!O�!O�!O�!O�!O�!O��G�T�3��!�|�|����<����	�����*O�)N��|rc���K�t|��||���vrGtdd|tg���d{V��\}}}|dkr!t�d|||��dSdSdS)Nrgz-drz&gpasswd -d failed for %r: rc=%s err=%r)r'rhrrlr0r1rms     r�delete_sudouserzDirectAdmin.delete_sudouser�s������4� � � ��4�#�#�%�%�%�%�'*�I�t�T�:�+N�'O�'O�!O�!O�!O�!O�!O�!O��G�T�3��!�|�|����<����	�����	&�%��|rc��t|d��5}|dz
}||���vr|�|��ddd��dS#1swxYwYdS)N�r+�
)r-�	readlines�write)r4�contentr6s   r�	_add_linezDirectAdmin._add_line�s���
�$��
�
�	!���t�O�G��a�k�k�m�m�+�+����� � � �	!�	!�	!�	!�	!�	!�	!�	!�	!�	!�	!�	!����	!�	!�	!�	!�	!�	!s�1A�A�Ac���t|d��5}d��fd�|D����}|�d��|�d��|�|��ddd��dS#1swxYwYdS)Nru�c3�H�K�|]}�|���v�|V��dSr^)r3)�.0r8rys  �r�	<genexpr>z+DirectAdmin._remove_line.<locals>.<genexpr>�s5�����M�M�D���
�
���1L�1L�4�1L�1L�1L�1L�M�Mrr)r-�join�seek�truncaterx)r4ryr6�datas `  r�_remove_linezDirectAdmin._remove_line�s����
�$��
�
�	���7�7�M�M�M�M�A�M�M�M�M�M�D�
�F�F�1�I�I�I�
�J�J�q�M�M�M�
�G�G�D�M�M�M�		�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	�	s�A!B�B�Bc��tdd��5}|������}ddd��n#1swxYwY|S)Nz,/usr/local/directadmin/data/admin/admin.list�r�r-�read�split)rnr6�
admin_lists   rrhzDirectAdmin._get_admins�s���
�@�#�
F�
F�	*�!��������)�)�J�	*�	*�	*�	*�	*�	*�	*�	*�	*�	*�	*����	*�	*�	*�	*����'A�A�Ac��tdd��5}|������}ddd��n#1swxYwY|S)Nz//usr/local/directadmin/data/admin/reseller.listr�r�)rnr6�
reseller_lists   r�_get_resellerszDirectAdmin._get_resellers�s���
�C�S�
I�
I�	-�Q��F�F�H�H�N�N�,�,�M�	-�	-�	-�	-�	-�	-�	-�	-�	-�	-�	-����	-�	-�	-�	-��r�c���tj�t|��}tj�|��s�t|d�����|�|d��tj	d��j
}tj	d��j
}tj|||��tj|d��|�||��dS)N�wz	#!/bin/sh�diradmini�)
rir4r��	HOOKS_DIR�existsr-�closerz�pwd�getpwnam�pw_uid�chown�chmod)rn�hookryr4�uid�gids      r�_create_hookzDirectAdmin._create_hook�s����w�|�|�I�t�,�,���w�~�~�d�#�#�	"���s�O�O�!�!�#�#�#��N�N�4��-�-�-��,�z�*�*�1�C��,�z�*�*�1�C��H�T�3��$�$�$��H�T�5�!�!�!����t�W�%�%�%�%�%rc��tj�t|��}tj�|��r|�||��dSdSr^)rir4r�r�r�r�)rnr�ryr4s    r�_delete_hookzDirectAdmin._delete_hook�sS���w�|�|�I�t�,�,��
�7�>�>�$���	-����d�G�,�,�,�,�,�	-�	-rNc��K�tjd�t����|�dt
��|�dt��|���D]Q}	|�|���d{V���#t$r&}t�d||��Yd}~�Jd}~wwxYw|�dd��|�dd��|�dd��dS)	Nz/usr/sbin/groupadd -f {}�/etc/sudoers�&Skipping invalid sudouser entry %r: %s�user_create_post.sh�9/usr/bin/imunify360-agent add-sudouser --user "$username"�user_destroy_pre.sh�</usr/bin/imunify360-agent delete-sudouser --user "$username"�user_restore_post.sh)
ri�system�formatrlrz�	SUDO_LINE�
SUDO_TTY_LINErhrqrr0r1r�)rn�namer&�excs    r�enable_imunify_pluginz!DirectAdmin.enable_imunify_plugin�sU����
�	�,�3�3�J�?�?�@�@�@����~�y�1�1�1����~�}�5�5�5��$�$�&�&�	�	�D�
��'�'��-�-�-�-�-�-�-�-�-�-��'�
�
�
����<�d�C�������������
����
	
���!�G�	
�	
�	
�	
���!�J�	
�	
�	
�	
���"�G�	
�	
�	
�	
�	
s�<B�
C�"C�Cc��K�|�dt��|�dt��|���D]Q}	|�|���d{V���#t
$r&}t�d||��Yd}~�Jd}~wwxYwtj	d�
t����|�dd��|�dd��|�dd��dS)	Nr�r�z/usr/sbin/groupdel {}r�r�r�r�r�)
r�r�r�rhrsrr0r1rir�r�rlr�)rn�plugin_namer&r�s    r�disable_imunify_pluginz"DirectAdmin.disable_imunify_plugin�sY�������.�)�4�4�4����.�-�8�8�8��$�$�&�&�	�	�D�
��*�*�4�0�0�0�0�0�0�0�0�0�0��'�
�
�
����<�d�C�������������
����	�	�)�0�0��<�<�=�=�=����!�G�	
�	
�	
�	
���!�J�	
�	
�	
�	
���"�G�	
�	
�	
�	
�	
s�A,�,
B�6B�Br c��zK�ttt���������S)z:
        :return: list: list of directadmin users
        )�list�setr;�values�rns r�	get_userszDirectAdmin.get_users�s1�����C�(�*�*�1�1�3�3�4�4�5�5�5rc��`K�tt�������S)zI
        :return: list: domains hosted on server via directadmin
        )r�r;�keysr�s rr;zDirectAdmin.get_user_domains�s)�����$�&�&�+�+�-�-�.�.�.rc��ZK�d�t�����D��S)z8
        :return: domain to list of users pairs
        c��i|]	\}}||g��
Srr)r~�domainr&s   r�
<dictcomp>z3DirectAdmin.get_domain_to_owner.<locals>.<dictcomp>s ��N�N�N�<�6�4����N�N�Nr)r;�itemsr�s r�get_domain_to_ownerzDirectAdmin.get_domain_to_owner�s0����O�N�3C�3E�3E�3K�3K�3M�3M�N�N�N�Nrc��K�tt��}t�����D] \}}||�|���!|S)z8
        :return: user to list of domains pairs
        )rr�r;r��append)rn�user_to_domainsr�r&s    r�get_domains_per_userz DirectAdmin.get_domains_per_users\����&�d�+�+��,�.�.�4�4�6�6�	1�	1�L�F�D��D�!�(�(��0�0�0�0��rc��thSr^)�BASE_DIRr�s r�basedirszDirectAdmin.basedirs
s
���z�rc��K�t���d{V��td��kr|����d{V��S|����d{V��S)Nz1.62.8)rNr	�docroots_info_new�docroots_info_legacyr�s r�
docroots_infozDirectAdmin.docroots_infosy����(�*�*�*�*�*�*�*�*�g�h�.?�.?�?�?��/�/�1�1�1�1�1�1�1�1�1��.�.�0�0�0�0�0�0�0�0�0rc��nK�ddg}tdt��5t|���d{V��\}}}ddd��n#1swxYwY|dkrtd|�d|�d|�����tj�|��������}|j	�
d��\}}tj|�
�������}d	�|�|�
�����dg��}t�d|��t%j��}	tj�|d
d|��id���}
|	�d|j|
���d{V��S)Nr=z--root-auth-urlz!Call DA binary to obtain auth URLrz2Failed to obtain auth URL. Unexpected return code �	. stdout=rA�@�/)�netlocz0CMD_API_DOMAIN?json=yes&action=document_root_allzDocument roots URL: %s�
AuthorizationzBasic �GET)�headers�method)rr0rr�urllib�parse�urlparser.r3r�r��base64�standard_b64encode�encoder��_replace�geturl�info�asyncio�get_event_loop�request�Request�run_in_executor�_do_request)rnrHrIrJrK�
parsed_url�
basic_authr��document_roots_url�loopr�s           rr�zDirectAdmin.docroots_info_news����3�5F�G��
�7��
@�
@�	5�	5�,/��H�H�n�n�n�n�n�n�#�G�V�V�	5�	5�	5�	5�	5�	5�	5�	5�	5�	5�	5����	5�	5�	5�	5��a�<�<� �9�W�9�9�!�9�9�.4�9�9���
�
�\�*�*�6�=�=�?�?�+@�+@�+B�+B�C�C�
�'�.�4�4�S�9�9��
�F��.�z�/@�/@�/B�/B�C�C�J�J�L�L�
� �X�X��#�#�6�#�2�2�9�9�;�;�B�
�
�
��	���,�.@�A�A�A��%�'�'���.�(�(��$�&;�z�&;�&;�<��)�
�
��
�)�)�$��0@�'�J�J�J�J�J�J�J�J�Js�A�A�	Ac��K�ddg}tdt��5t|���d{V��\}}}ddd��n#1swxYwY|dkr|dkrtd|�d|�d|�����	t	j|�����}n*#tj$r}td	|�d
����d}~wwxYw|S)Nr=z--DocumentRootz%Call DA binary to obtain all docrootsrr,z8Failed to obtain document roots. Unexpected return code r�rAz7Failed to obtain document roots. Failed to decode json �.)rr0rr�json�loadsr.�JSONDecodeError)rnrH�retrTrp�outputr9s       rr�z DirectAdmin.docroots_info_legacy4sN����0��
���;�V�
D�
D�	+�	+�"%�c�(�(�N�N�N�N�N�N�M�C��c�	+�	+�	+�	+�	+�	+�	+�	+�	+�	+�	+����	+�	+�	+�	+��!�8�8��q��� �:��:�:�"%�:�:�25�:�:���
�	��Z��
�
���-�-�F�F���#�	�	�	� �N�!�N�N�N���
�����	����
�
s)�A�A�	A�2&B�C�(B;�;Cc�~�t��}|d���D]�\}}|d���D]s\}}|�d��r|||d<|�di�����D]%\}}|�d��r|||d<�&�t��|S)N�usersr5�public_html�
subdomains)�dictr�rk)r�r��username�userdata�
domainname�
domaindata�_�sub_datas        r�parse_document_root_outputz&DirectAdmin.parse_document_root_outputIs����f�f��"(��/�"7�"7�"9�"9�	B�	B��H�h�*2�9�*=�*C�*C�*E�*E�
B�
B�&�
�J��>�>�-�0�0�@�5?�C�
�=�1�2�#-�>�>�,��#C�#C�#I�#I�#K�#K�B�B�K�A�x��|�|�M�2�2�B�7A��H�]�3�4��B�
B��
rc��dK�|����d{V��}|�|��Sr^)r�r�)rnr�s  r�
list_docrootszDirectAdmin.list_docrootsUs=�����'�'�)�)�)�)�)�)�)�)���.�.�t�4�4�4rc��K�i}|����d{V��}t|�����}t|�����}|D]�}	|�|��}t
jj}||vrt
jj}||vrt
jj	}|�
dd��|�
dd��|�
dd��|�
d��dkt|��d�||<��#t$r.}ddd�||<t�d	||��Yd}~��d}~wwxYw|S)
N�languager|�email�creator�	suspended�yes)�localer��parentr�level)r�rz!Failed to get_user_details: %s %s)r�r�rhr��get_user_details_for_usernamer�	UserLevel�REGULAR_USER�RESSELER�ADMINrk�int�	Exceptionr0r1)	rn�res�	usernames�admins�	resellersr��
parsed_configrr9s	         r�get_user_detailszDirectAdmin.get_user_detailsYs��������.�.�*�*�*�*�*�*�*�*�	��T�%�%�'�'�(�(����+�+�-�-�.�.�	�!�	�	�H�
� $� B� B�8� L� L�
���3���y�(�(� �N�3�E��v�%�%� �N�0�E�+�/�/�
�B�?�?�*�.�.�w��;�;�+�/�/�	�2�>�>�!.�!2�!2�;�!?�!?�5�!H� ��Z�Z�!�!��H�
�
���
�
�
�� �!�!��H�
����7��1�������������
�����
s�&B;D"�"
E�,$E�Ec���tt|�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)r�USERS_CONF_DIR�	read_text�configparser�ConfigParser�read_string)rnr��
user_conf_strrs    rrz)DirectAdmin.get_user_details_for_usernamewsk����x�3�3�3�
�
�
�)�+�+�	�%�1�3�3�
�!�M�1�
��!�!�-�0�0�0�%�e�,�
��r)�on_error�timeoutr�c��	tj�|tj���5}|jdkr't
d�|j�����tj	|�
�������cddd��S#1swxYwYdS#ttjjtjt"jtjjf$r
}t|�d}~wwxYw)N)r��zstatus code is {})r�r��urlopenr�DEFAULT_SOCKET_TIMEOUT�statusrr�r�r�r�r.r/�http�client�
HTTPExceptionr��socketr�error�URLError)rnr��responser9s    rr�zDirectAdmin._do_request�s.��	(���'�'���!<�(���
<���?�c�)�)�(�+�2�2�8�?�C�C�����z�(�-�-�/�/�"8�"8�":�":�;�;�
<�
<�
<�
<�
<�
<�
<�
<�
<�
<�
<�
<����
<�
<�
<�
<�
<�
<��
��K�%�� ��N��L�!�
�	(�	(�	(�!�a�'�����	(���s<�+B1�A*B$�B1�$B(�(B1�+B(�,B1�1AC?�2C:�:C?c��
K�dS)z8
        Returns panel url
        :return: str
        r|r)rnr�s  r�panel_user_linkzDirectAdmin.panel_user_link�s
����
�rrr,�<)�maxsize�ttlc��K�|����d{V��}tt��}|�di�����D]�\}}|�di�����D]�\}}|�d��}|r1||�t
j||d|�����|�d��pi���D]R\}}	|	�d��}
|
r6||�t
j|
|�d|��d|������S�ʌ�|S)	Nr�r5r��main)�docrootr��typer�r�r��sub)r�rr�rkr�r�r�
DomainData)rnr�rMr&r�r��domain_datar�r1r��sub_public_htmls           r�_get_domains_details_per_userz)DirectAdmin._get_domains_details_per_user�s������'�'�)�)�)�)�)�)�)�)��3>�t�3D�3D��"�h�h�w��3�3�9�9�;�;�	�	�N�D�(�'/�|�|�I�r�'B�'B�'H�'H�'J�'J�
�
�#���)�o�o�m�<�<�����4�L�'�'���$/�#)�!'�%)�	������ �O�O�L�1�1�7�R��%�'�'���M�C��'/�l�l�=�&A�&A�O�&���t��+�+� �O�(7�*-�'8�'8��'8�'8�%*�)-�	��������
�0�
rr�c��K�|����d{V��}t|�|g����Sr^)r5r�rk)rnr��detailss   r�get_user_domains_detailsz$DirectAdmin.get_user_domains_details�sG�����:�:�<�<�<�<�<�<�<�<���G�K�K��"�-�-�.�.�.rr^)9rrr�NAMEr
�	DA_BINARY�TCP_PORTS_DA�
OPEN_PORTSr�	exception�classmethodrarcr�ensure_valid_panelrqrs�staticmethodrzr�rhr�r�r�r�r�rr#r�r;r�r�rr�rr�r�r�r�r�rrrrrr
r�r�r�rr�r)rr2r5r�r8rrrrPrP^s��������D��I��'�L�(��7�\�)�
�
�
A�@�@�B�B�B�
�
�	�	�J�%�I��*�*��[�*��4�4��[�4��T����������T����������!�!��\�!�����\�����
���
	&�	&�	&�-�-�-�
�T����
�
�
���
�4�T����
�
�
���
�46��c��6�6�6�6�/�/�/�O�O�O�����#�c�(�����1�T�1�1�1�1�
K��K�K�K�K�>�D�����*�	�d�	�	�	��\�	�5�T�#�s�(�^�5�5�5�5���S�$�s�C�x�.�-@�(A�����<
��c�3�h��
�
�
�
��X���*����
(�6�>�#9�(�c�(�(�(���
(�&�������_�Q�B�'�'�'��	
�c�4���(�(�	)����(�'��@/��/�	
�d�o�	�/�/�/�/�/�/rrP)>r�r�r�http.clientr!r��loggingrir�rBr$r��urllib.parse�collectionsr�pathlibr�typingrrrr�packaging.versionr	�3defence360agent.application.determine_hosting_panelr
r� defence360agent.contracts.configr�defence360agent.utilsr
rrrrrr|rr�	getLoggerrr0r��CMDr�rlr�r�r��_VIRTUAL_DOMAINOWNERS�TCP_PORTS_COMMONr;r�compiler$rr#r'r;rN�
AbstractPanelrPrrr�<module>rQs�������
�
�
�
�����������������	�	�	�	�
�
�
�
�	�	�	�	�
�
�
�
�
�
�
�
�����#�#�#�#�#�#�������'�'�'�'�'�'�'�'�'�'�'�'�%�%�%�%�%�%���������2�1�1�1�1�1�����������������������!�!�!�!�!�!�	��	�8�	$�	$����+��3�	�
#�
�$�+�+�J��<�<�	�K�
�3���$��
�'>�>��5���B�J�;�<�<��	�	�	�	�	�4�.�	�	�	�������0�
�
�D��c��N�
�
�
�
� 
�w�
�
�
�
�l/�l/�l/�l/�l/�$�$�l/�l/�l/�l/�l/rdefence360agent/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.py0000644000000000000000000003727100000000000017326 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 .. 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


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",
        )
        return await loop.run_in_executor(None, self._do_request, request)

    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 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:
        try:
            with urllib.request.urlopen(
                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

    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  �

e���#�#����ddlmZdgZdS)�)�GenericPanelrN)�panelr�__all__���c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/generic/__init__.py�<module>r	s#���������
���rdefence360agent/subsys/panels/generic/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000044200000000000023445 0ustar  �

e���#�#����ddlmZdgZdS)�)�GenericPanelrN)�panelr�__all__���c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/generic/__init__.py�<module>r	s#���������
���rdefence360agent/subsys/panels/generic/__pycache__/panel.cpython-311.opt-1.pyc0000644000000000000000000005205200000000000023750 0ustar  �

������4���ddlZddlZddlZddlZddlZddlZddlZddlmZddl	m
Z
mZmZddl
Z
ddlZddlmZmZddlmZddlmZddlmZddlmZdd	lmZmZmZmZd
dlm Z ej!e"��Z#ej$�%e&��dzZ'd
Z(dZ)ej*edd�����Z+ej,d
���de-de
j.fd���Z/d�Z0d�Z1ee+d���de-fd���Z2de-de-dee-fd�Z3de-de-fd�Z4d�Z5d�Z6d �Z7dee-fd!�Z8Gd"�d#e j9��Z:Gd$�d%e j;��Z<dS)&�N)�defaultdict)�Dict�List�Set)�ClIntegrationConfig�IntegrationConfig)�	JWTIssuer��is_generic_panel_installed)�int_from_envvar)�UserType)�
CheckRunError�	check_run�get_non_system_users�timed_cache�)�basez$/users_script_schemas/schema-{}.yamlz$/etc/sysconfig/imunify360/auth.admin�metadata�-IMUNIFY360_EXPIRATION_FOR_INTEGRATION_SCRIPTS�Z)�seconds)�maxsize�script�returnc��tt�|����5}tj|��}|t
urddi|t
<t
j|��cddd��S#1swxYwYdS)z%Returns a validator for given script.�requiredTN)�open�_SCHEMA_PATH_TMPL�format�yaml�	safe_load�METADATA�cerberus�	Validator)r�schema_file�schemas   �`/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/generic/panel.py�_get_validatorr(/s���
��&�&�v�.�.�	/�	/�*�;����,�,����!�!� *�D�1�F�8���!�&�)�)�	*�*�*�*�*�*�*�*�*�*�*�*����*�*�*�*�*�*s�=A2�2A6�9A6c�2�d�t��D��S)Nc�8�g|]}t|j�����S)��name)�dict�pw_name)�.0�pws  r'�
<listcomp>z*get_users_default_impl.<locals>.<listcomp>:s%��C�C�C�b�D�b�j�!�!�!�C�C�C�)r�r2r'�get_users_default_implr49s��C�C�,@�,B�,B�C�C�C�Cr2c�f�|���}d|vr||dvr|d|SdS)N�integration_scripts)�to_dict)�clr�ds   r'�_get_conf_pathr:=s@��
�
�
���A���!�!�f��2G�0H�&H�&H��&�'��/�/��4r2�
)�
expirationrc���K�tt��|��}|stt��|��}|std|z���t	||���d{V��S)Nzl%s not found neither in /etc/sysconfig/imunify360/integration.conf nor in /opt/cpvendor/etc/integration.ini.)r:rr�IntegrationScriptError�_get_integration_data)r�paths  r'�get_integration_datarADs������+�-�-�v�6�6�D��=��1�3�3�V�<�<���
�$�
8�:@�
A�
�
�	
�'�v�t�4�4�4�4�4�4�4�4�4r2r@c�@�|r|���std|z���	tj|��}n'#t$r}td|�d|�����d}~wwxYw|std|z���|d}t
j�|��std|�d|�����t
j�|��std|�d	|�����tj	|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>�shlex�split�
ValueError�osr@�isabs�isfile�access�X_OK)rr@�argv�e�
executables     r'�_build_integration_argvrOSsv���
�t�z�z�|�|�
�$�2�V�;�
�
�	
�
��{�4� � �����
�
�
�$�$�<B�F�F�A�A�F�
�
�	
�����
�����
�$�2�V�;�
�
�	
��a��J�
�7�=�=��$�$�
�$�$��v�v�z�z�
#�
�
�	
��7�>�>�*�%�%�
�$�$��v�v�z�z�
#�
�
�	
��9�Z���)�)�
�$�$��v�v�z�z�
#�
�
�	
��Ks�?�
A#�	A�A#c��K�t||��}	t|���d{V��}n6#t$r)}td�||������d}~wwxYw	tj|�����}n1#tt
j	f$r}td|z��|�d}~wwxYwt|t��std|z���tt��}|�|��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: �result�ok�messagez: %szValidation error in �data)rOrrr>r�json�loads�decode�UnicodeDecodeError�JSONDecodeError�
isinstancer-r(r"�validate�errors)	rr@rL�stdoutrMrT�metadata_validator�metadata_error�	validators	         r'r?r?xs����"�6�4�0�0�D�
� ����&�&�&�&�&�&�����
�
�
�$�
���v���3�3�
�
�	
�����
������z�&�-�-�/�/�*�*������ 4�5����$�0�4�7�
�
��	����������d�D�!�!�E�$�%<�t�%C�D�D�D�'��1�1���&�&�t�,�,�
�$�$��v�v�)�0�0�
2�
�
�	
�
�H�~�h��4�'�'��h���1����X��&�&��f�t�H�~�i�'@�@�@�N�$�^�4�4�4��v�&�&�I����d�#�#�
�$�$�39�6�6�9�;K�;K�L�
�
�	
���<�s,�*�
A�$A�A�!&B�B6�B1�1B6c��K�	t���d{V��}td���d{V��}|���D]>\}}|dr1|�|dg��}|�|���?d�|���D��S#t
$r+t�d��t��cYSwxYw)N�domains�ownerc��g|]
\}}||d���S)�r,rbr3)r/�k�vs   r'r1z$_get_client_data.<locals>.<listcomp>�s$��D�D�D�d�a���q�)�)�D�D�Dr2z:Applying default implementation of users and domains lists)	�get_users_integration_datarA�items�
setdefault�appendr>�logger�warningr4)�usersrbrfrg�user_domainss     r'�_get_client_datarp�s����
(�0�2�2�2�2�2�2�2�2��,�Y�7�7�7�7�7�7�7�7���M�M�O�O�	'�	'�D�A�q���z�
'�$�/�/��'�
�B�?�?���#�#�A�&�&�&��D�D�e�k�k�m�m�D�D�D�D��!�(�(�(����H�	
�	
�	
�&�'�'�'�'�'�	(���s�BB�2C�Cc��K�td���d{V��}i}|D]3}|dst�d|�����(g||d<�4|S)Nrn�usernamez#Found user with an empty username: )rArlrm)rn�
users_dict�users   r'rhrh�s|����&�w�/�/�/�/�/�/�/�/�E��J��.�.���J��	.��N�N�G��G�G�H�H�H�H�+-�J�t�J�'�(�(��r2c��K�	td���d{V��S#t$rt�d��icYSwxYw)NrbzCould not parse domains lists)rAr>rlrmr3r2r'�get_domain_datarv�sb�����)�)�4�4�4�4�4�4�4�4�4��!�������6�7�7�7��	�	�	����s��&A�Ac
��K�d}dh}tjt|tt	��|����t|tt��|����d����d{V��}d�|D��}|st�d|��||z}	tt��5}|�
|��������ddd��n#1swxYwYn0#t$r#t�dt��YnwxYwt|��S)N�admins�rootT)�return_exceptionsc�R�h|]$}t|t���|D]
}|d���%Sr+)rZ�list)r/rx�admins   r'�	<setcomp>z!get_admin_list.<locals>.<setcomp>�sW�������f�d�#�#���	��
�	�f�
����r2zDError occurred during extracting admins from integration configs: %sz&Failed to retrieve admins list from %s)�asyncio�gatherr?r:rrrlrmr�ADMIN_LIST_FILE_PATH�update�read�
splitlines�OSErrorr|)�script_name�
admins_set�admins_from_integration_scripts�
custom_admins�admin_list_files     r'�get_admin_listr��s������K���J�,3�N����!�#�#��
�
�	
�	
�	���#�%�%��
�
�	
�	
��-�-�-�'�'�'�'�'�'�#�"��5����M��
����
+�+�	
�	
�	
��-��J�
�
�&�
'�
'�	C�?����o�2�2�4�4�?�?�A�A�B�B�B�	C�	C�	C�	C�	C�	C�	C�	C�	C�	C�	C����	C�	C�	C�	C����
�
�
����4�6J�	
�	
�	
�	
�	
�
�����
���s6�$D
�8:C>�2D
�>D�D
�D�D
�
*D7�6D7c���eZdZ�fd�Z�xZS)r>c�h��t��j|�t�|��dS�N)�super�__init__rlrm)�self�args�kwargs�	__class__s   �r'r�zIntegrationScriptError.__init__�s/��������$������t�����r2)�__name__�
__module__�__qualname__r��
__classcell__�r�s@r'r>r>�s8���������������r2r>c����eZdZdZejZeZe	d���Z
dd�Zdd�Ze	d���Z
e	d���Zd�Zd	eefd
�Zd	eeeeffd�Zd	eeeeefff�fd�Zd	eeeeffd
�Zdefd�Zd	eefd�Zd	eeeffd�Zd	eeeeffd�Zd	efd�Ze	ded	eejfd���Z �xZ!S)�GenericPanelzb
    Panel, UI to which is provided by
    imunify{-antivirus,360-firewall}-generic.{rpm,deb}
    c��t��Sr�r
)�clss r'�is_installedzGenericPanel.is_installeds��)�+�+�+r2Nc��
K�dSr�r3)r�r,s  r'�enable_imunify_pluginz"GenericPanel.enable_imunify_plugin������r2c��
K�dSr�r3)r��plugin_names  r'�disable_imunify_pluginz#GenericPanel.disable_imunify_pluginr�r2c��nK�	td���d{V��}djdi|��S#t$rYdSwxYw)N�
panel_infoz{name} {version}�0r3)rArr>�r��infos  r'�versionzGenericPanel.versionsf����	�-�l�;�;�;�;�;�;�;�;�D�,�%�,�4�4�t�4�4�4��%�	�	�	��3�3�	���s�!&�
4�4c��zK�	td���d{V��}djdi|��S#t$r
|jcYSwxYw)Nr�z{name}r3)rArr>�NAMEr�s  r'r,zGenericPanel.namesh����	�-�l�;�;�;�;�;�;�;�;�D�"�8�?�*�*�T�*�*�*��%�	�	�	��8�O�O�O�	���s�!&�:�:c��K�t���d{V��}g}|D]7}|�|�dt�������8|S)Nrb)rp�extend�get�tuple)r�rnrQrts    r'�get_user_domainszGenericPanel.get_user_domainssc����&�(�(�(�(�(�(�(�(�����	8�	8�D��M�M�$�(�(�9�e�g�g�6�6�7�7�7�7��
r2rc��FK�t���d{V��}d�|D��S)Nc��g|]
}|d��Sr+r3�r/rts  r'r1z*GenericPanel.get_users.<locals>.<listcomp>'s��/�/�/���V��/�/�/r2�rp�r�rns  r'�	get_userszGenericPanel.get_users%s7����&�(�(�(�(�(�(�(�(��/�/��/�/�/�/r2c���K�t���d{V��}tt��}|D]<}|�dg��D]#}||�|d���$�=|S)Nrbr,)rprr|r�rk)r�rnrQrt�domains     r'�get_domain_to_ownerz GenericPanel.get_domain_to_owner)s�����&�(�(�(�(�(�(�(�(���T�"�"���	4�	4�D��(�(�9�b�1�1�
4�
4���v��%�%�d�6�l�3�3�3�3�
4��
r2c��<����K�	td���d{V��}n6#t$r)t������d{V��cYSwxYw|����d{V���t���d{V���dtf�fd����fd�|D��S)Nrn�	user_infoc����|�d��dkrtjjS|�d���vrtjjStjjS)Nrrry)r�r�	UserLevel�ADMIN�RESSELER�REGULAR_USER)r�rxs �r'�user_info_to_levelz9GenericPanel.get_user_details.<locals>.user_info_to_level9sR����}�}�Z�(�(�F�2�2��~�+�+��}�}�Z�(�(�F�2�2��~�.�.��>�.�.r2c
����i|]q}|�d���v�|�d��|�dd��|�dd��t�|����d���rS)rr�email��locale_code)r��locale�level)r��int)r/r�r��	usernamess  ��r'�
<dictcomp>z1GenericPanel.get_user_details.<locals>.<dictcomp>Bs����
�
�
���x�x�
�#�#�y�0�0�

�H�H�Z� � ����'�2�.�.��(�(�=�"�5�5��/�/��5�5�6�6�#�#�1�0�0r2)rAr>r��get_user_detailsr�r�r)r��	user_datarxr�r�r�s  @@@�r'r�zGenericPanel.get_user_details1s��������	4�2�7�;�;�;�;�;�;�;�;�I�I��%�	4�	4�	4����1�1�3�3�3�3�3�3�3�3�3�3�3�	4�����.�.�*�*�*�*�*�*�*�*�	�%�'�'�'�'�'�'�'�'��	/�$�	/�	/�	/�	/�	/�	/�
�
�
�
�
�"�

�
�
�	
s��0A�Ac��FK�t���d{V��}d�|D��S)Nc�H�i|]}|d|�dg���� Sre)r�r�s  r'r�z5GenericPanel.get_domains_per_user.<locals>.<dictcomp>Os,��H�H�H�$��V��d�h�h�y�"�5�5�H�H�Hr2r�r�s  r'�get_domains_per_userz!GenericPanel.get_domains_per_userLs7����&�(�(�(�(�(�(�(�(��H�H�%�H�H�H�Hr2rTc��|jdkrf|dddgkrX|d�dd��}tj|��}|d|dtjkr|dndfS|jdfS)	Nr�command�login�pam�params�jwt�	user_type�	user_name)�_uid�popr	�parse_tokenr
�NON_ROOTrt)r��protocolrT�token�parsed_tokens     r'�authenticatezGenericPanel.authenticateQs����=�A���$�y�/�g�u�5E�"E�"E���N�&�&�u�d�3�3�E�$�0��7�7�L���,���,��0A�A�A��[�)�)���
��=�$�&�&r2c���t�����}d|vr7d|dvr-t|dd�����St��S)N�malware�basedir)rr7�setrE)r��confs  r'�basedirszGenericPanel.basedirs]s^�� �"�"�*�*�,�,�������d�9�o�!=�!=��t�I��y�1�7�7�9�9�:�:�:��u�u�r2c��jK�t���d{V��}d�|���D��S)Nc�&�i|]\}}|d|��S��
document_rootr3�r/r�rgs   r'r�z.GenericPanel.list_docroots.<locals>.<dictcomp>es#��L�L�L�y�v�q��/�"�F�L�L�Lr2�rvri�r�rbs  r'�
list_docrootszGenericPanel.list_docrootscs?����'�)�)�)�)�)�)�)�)��L�L�G�M�M�O�O�L�L�L�Lr2c��jK�t���d{V��}d�|���D��S)Nc�(�i|]\}}||dg��Sr�r3r�s   r'r�z1GenericPanel.get_domain_paths.<locals>.<dictcomp>is&��N�N�N������?�+�,�N�N�Nr2r�r�s  r'�get_domain_pathszGenericPanel.get_domain_pathsgs?����'�)�)�)�)�)�)�)�)��N�N�g�m�m�o�o�N�N�N�Nr2c��
K�dS)z8
        Returns panel url
        :return: str
        r�r3)r�rrs  r'�panel_user_linkzGenericPanel.panel_user_linkks
����
�rr2rrc��
K�gSr�r3)r�rrs  r'�get_user_domains_detailsz%GenericPanel.get_user_domains_detailsrs
�����	r2r�)"r�r�r��__doc__r�GENERIC_PANEL_NAMEr�r>�	exception�classmethodr�r�r�r�r,r�r�strr�rr�r�r�r-r�rr�r�r�r�r|�
DomainDatar�r�r�s@r'r�r��sJ���������
�"�D�&�I��,�,��[�,�
�
�
�
�
�
�
�
�����[������[�����0��c��0�0�0�0��4��T�#�Y��+?�����
��S�$�s�C�x�.�-@�(A�
�
�
�
�
�
�6I�D��d�3�i��,@�I�I�I�I�

'�4�
'�
'�
'�
'��#�c�(�����M�T�#�s�(�^�M�M�M�M�O��S�$�s�)�^�(<�O�O�O�O�����������	
�d�o�	�����[�����r2r�)=r�datetime�	functoolsrU�loggingrGrD�collectionsr�typingrrrr#r �$defence360agent.api.integration_confrr�defence360agent.api.jwt_issuerr	�3defence360agent.application.determine_hosting_panelr� defence360agent.contracts.configr� defence360agent.rpc_tools.lookupr
�defence360agent.utilsrrrrr�r�	getLoggerr�rlr@�dirname�__file__rr�r"�	timedelta�'EXPIRATION_TIME_FOR_INTEGRATION_SCRIPTS�	lru_cacher�r$r(r4r:rArOr?rprhrvr��PanelExceptionr>�
AbstractPanelr�r3r2r'�<module>rsj����������������������	�	�	�	�����#�#�#�#�#�#�"�"�"�"�"�"�"�"�"�"�����������������5�4�4�4�4�4�������=�<�<�<�<�<�5�5�5�5�5�5�������������������	��	�8�	$�	$���G�O�O�H��� F�F��>����*<�(�*<��O�7��
�
�+�+�+�'����Q����*�3�*�8�#5�*�*�*� ��*�D�D�D����
��?��L�L�L�5�s�5�5�5�M�L�5�"�C�"�s�"�t�C�y�"�"�"�"�J'��'�3�'�'�'�'�T(�(�(�"
�
�
����*�d�3�i�*�*�*�*�Z�����T�0����{�{�{�{�{�4�%�{�{�{�{�{r2defence360agent/subsys/panels/generic/__pycache__/panel.cpython-311.pyc0000644000000000000000000005205200000000000023011 0ustar  �

������4���ddlZddlZddlZddlZddlZddlZddlZddlmZddl	m
Z
mZmZddl
Z
ddlZddlmZmZddlmZddlmZddlmZddlmZdd	lmZmZmZmZd
dlm Z ej!e"��Z#ej$�%e&��dzZ'd
Z(dZ)ej*edd�����Z+ej,d
���de-de
j.fd���Z/d�Z0d�Z1ee+d���de-fd���Z2de-de-dee-fd�Z3de-de-fd�Z4d�Z5d�Z6d �Z7dee-fd!�Z8Gd"�d#e j9��Z:Gd$�d%e j;��Z<dS)&�N)�defaultdict)�Dict�List�Set)�ClIntegrationConfig�IntegrationConfig)�	JWTIssuer��is_generic_panel_installed)�int_from_envvar)�UserType)�
CheckRunError�	check_run�get_non_system_users�timed_cache�)�basez$/users_script_schemas/schema-{}.yamlz$/etc/sysconfig/imunify360/auth.admin�metadata�-IMUNIFY360_EXPIRATION_FOR_INTEGRATION_SCRIPTS�Z)�seconds)�maxsize�script�returnc��tt�|����5}tj|��}|t
urddi|t
<t
j|��cddd��S#1swxYwYdS)z%Returns a validator for given script.�requiredTN)�open�_SCHEMA_PATH_TMPL�format�yaml�	safe_load�METADATA�cerberus�	Validator)r�schema_file�schemas   �`/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/generic/panel.py�_get_validatorr(/s���
��&�&�v�.�.�	/�	/�*�;����,�,����!�!� *�D�1�F�8���!�&�)�)�	*�*�*�*�*�*�*�*�*�*�*�*����*�*�*�*�*�*s�=A2�2A6�9A6c�2�d�t��D��S)Nc�8�g|]}t|j�����S)��name)�dict�pw_name)�.0�pws  r'�
<listcomp>z*get_users_default_impl.<locals>.<listcomp>:s%��C�C�C�b�D�b�j�!�!�!�C�C�C�)r�r2r'�get_users_default_implr49s��C�C�,@�,B�,B�C�C�C�Cr2c�f�|���}d|vr||dvr|d|SdS)N�integration_scripts)�to_dict)�clr�ds   r'�_get_conf_pathr:=s@��
�
�
���A���!�!�f��2G�0H�&H�&H��&�'��/�/��4r2�
)�
expirationrc���K�tt��|��}|stt��|��}|std|z���t	||���d{V��S)Nzl%s not found neither in /etc/sysconfig/imunify360/integration.conf nor in /opt/cpvendor/etc/integration.ini.)r:rr�IntegrationScriptError�_get_integration_data)r�paths  r'�get_integration_datarADs������+�-�-�v�6�6�D��=��1�3�3�V�<�<���
�$�
8�:@�
A�
�
�	
�'�v�t�4�4�4�4�4�4�4�4�4r2r@c�@�|r|���std|z���	tj|��}n'#t$r}td|�d|�����d}~wwxYw|std|z���|d}t
j�|��std|�d|�����t
j�|��std|�d	|�����tj	|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>�shlex�split�
ValueError�osr@�isabs�isfile�access�X_OK)rr@�argv�e�
executables     r'�_build_integration_argvrOSsv���
�t�z�z�|�|�
�$�2�V�;�
�
�	
�
��{�4� � �����
�
�
�$�$�<B�F�F�A�A�F�
�
�	
�����
�����
�$�2�V�;�
�
�	
��a��J�
�7�=�=��$�$�
�$�$��v�v�z�z�
#�
�
�	
��7�>�>�*�%�%�
�$�$��v�v�z�z�
#�
�
�	
��9�Z���)�)�
�$�$��v�v�z�z�
#�
�
�	
��Ks�?�
A#�	A�A#c��K�t||��}	t|���d{V��}n6#t$r)}td�||������d}~wwxYw	tj|�����}n1#tt
j	f$r}td|z��|�d}~wwxYwt|t��std|z���tt��}|�|��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: �result�ok�messagez: %szValidation error in �data)rOrrr>r�json�loads�decode�UnicodeDecodeError�JSONDecodeError�
isinstancer-r(r"�validate�errors)	rr@rL�stdoutrMrT�metadata_validator�metadata_error�	validators	         r'r?r?xs����"�6�4�0�0�D�
� ����&�&�&�&�&�&�����
�
�
�$�
���v���3�3�
�
�	
�����
������z�&�-�-�/�/�*�*������ 4�5����$�0�4�7�
�
��	����������d�D�!�!�E�$�%<�t�%C�D�D�D�'��1�1���&�&�t�,�,�
�$�$��v�v�)�0�0�
2�
�
�	
�
�H�~�h��4�'�'��h���1����X��&�&��f�t�H�~�i�'@�@�@�N�$�^�4�4�4��v�&�&�I����d�#�#�
�$�$�39�6�6�9�;K�;K�L�
�
�	
���<�s,�*�
A�$A�A�!&B�B6�B1�1B6c��K�	t���d{V��}td���d{V��}|���D]>\}}|dr1|�|dg��}|�|���?d�|���D��S#t
$r+t�d��t��cYSwxYw)N�domains�ownerc��g|]
\}}||d���S)�r,rbr3)r/�k�vs   r'r1z$_get_client_data.<locals>.<listcomp>�s$��D�D�D�d�a���q�)�)�D�D�Dr2z:Applying default implementation of users and domains lists)	�get_users_integration_datarA�items�
setdefault�appendr>�logger�warningr4)�usersrbrfrg�user_domainss     r'�_get_client_datarp�s����
(�0�2�2�2�2�2�2�2�2��,�Y�7�7�7�7�7�7�7�7���M�M�O�O�	'�	'�D�A�q���z�
'�$�/�/��'�
�B�?�?���#�#�A�&�&�&��D�D�e�k�k�m�m�D�D�D�D��!�(�(�(����H�	
�	
�	
�&�'�'�'�'�'�	(���s�BB�2C�Cc��K�td���d{V��}i}|D]3}|dst�d|�����(g||d<�4|S)Nrn�usernamez#Found user with an empty username: )rArlrm)rn�
users_dict�users   r'rhrh�s|����&�w�/�/�/�/�/�/�/�/�E��J��.�.���J��	.��N�N�G��G�G�H�H�H�H�+-�J�t�J�'�(�(��r2c��K�	td���d{V��S#t$rt�d��icYSwxYw)NrbzCould not parse domains lists)rAr>rlrmr3r2r'�get_domain_datarv�sb�����)�)�4�4�4�4�4�4�4�4�4��!�������6�7�7�7��	�	�	����s��&A�Ac
��K�d}dh}tjt|tt	��|����t|tt��|����d����d{V��}d�|D��}|st�d|��||z}	tt��5}|�
|��������ddd��n#1swxYwYn0#t$r#t�dt��YnwxYwt|��S)N�admins�rootT)�return_exceptionsc�R�h|]$}t|t���|D]
}|d���%Sr+)rZ�list)r/rx�admins   r'�	<setcomp>z!get_admin_list.<locals>.<setcomp>�sW�������f�d�#�#���	��
�	�f�
����r2zDError occurred during extracting admins from integration configs: %sz&Failed to retrieve admins list from %s)�asyncio�gatherr?r:rrrlrmr�ADMIN_LIST_FILE_PATH�update�read�
splitlines�OSErrorr|)�script_name�
admins_set�admins_from_integration_scripts�
custom_admins�admin_list_files     r'�get_admin_listr��s������K���J�,3�N����!�#�#��
�
�	
�	
�	���#�%�%��
�
�	
�	
��-�-�-�'�'�'�'�'�'�#�"��5����M��
����
+�+�	
�	
�	
��-��J�
�
�&�
'�
'�	C�?����o�2�2�4�4�?�?�A�A�B�B�B�	C�	C�	C�	C�	C�	C�	C�	C�	C�	C�	C����	C�	C�	C�	C����
�
�
����4�6J�	
�	
�	
�	
�	
�
�����
���s6�$D
�8:C>�2D
�>D�D
�D�D
�
*D7�6D7c���eZdZ�fd�Z�xZS)r>c�h��t��j|�t�|��dS�N)�super�__init__rlrm)�self�args�kwargs�	__class__s   �r'r�zIntegrationScriptError.__init__�s/��������$������t�����r2)�__name__�
__module__�__qualname__r��
__classcell__�r�s@r'r>r>�s8���������������r2r>c����eZdZdZejZeZe	d���Z
dd�Zdd�Ze	d���Z
e	d���Zd�Zd	eefd
�Zd	eeeeffd�Zd	eeeeefff�fd�Zd	eeeeffd
�Zdefd�Zd	eefd�Zd	eeeffd�Zd	eeeeffd�Zd	efd�Ze	ded	eejfd���Z �xZ!S)�GenericPanelzb
    Panel, UI to which is provided by
    imunify{-antivirus,360-firewall}-generic.{rpm,deb}
    c��t��Sr�r
)�clss r'�is_installedzGenericPanel.is_installeds��)�+�+�+r2Nc��
K�dSr�r3)r�r,s  r'�enable_imunify_pluginz"GenericPanel.enable_imunify_plugin������r2c��
K�dSr�r3)r��plugin_names  r'�disable_imunify_pluginz#GenericPanel.disable_imunify_pluginr�r2c��nK�	td���d{V��}djdi|��S#t$rYdSwxYw)N�
panel_infoz{name} {version}�0r3)rArr>�r��infos  r'�versionzGenericPanel.versionsf����	�-�l�;�;�;�;�;�;�;�;�D�,�%�,�4�4�t�4�4�4��%�	�	�	��3�3�	���s�!&�
4�4c��zK�	td���d{V��}djdi|��S#t$r
|jcYSwxYw)Nr�z{name}r3)rArr>�NAMEr�s  r'r,zGenericPanel.namesh����	�-�l�;�;�;�;�;�;�;�;�D�"�8�?�*�*�T�*�*�*��%�	�	�	��8�O�O�O�	���s�!&�:�:c��K�t���d{V��}g}|D]7}|�|�dt�������8|S)Nrb)rp�extend�get�tuple)r�rnrQrts    r'�get_user_domainszGenericPanel.get_user_domainssc����&�(�(�(�(�(�(�(�(�����	8�	8�D��M�M�$�(�(�9�e�g�g�6�6�7�7�7�7��
r2rc��FK�t���d{V��}d�|D��S)Nc��g|]
}|d��Sr+r3�r/rts  r'r1z*GenericPanel.get_users.<locals>.<listcomp>'s��/�/�/���V��/�/�/r2�rp�r�rns  r'�	get_userszGenericPanel.get_users%s7����&�(�(�(�(�(�(�(�(��/�/��/�/�/�/r2c���K�t���d{V��}tt��}|D]<}|�dg��D]#}||�|d���$�=|S)Nrbr,)rprr|r�rk)r�rnrQrt�domains     r'�get_domain_to_ownerz GenericPanel.get_domain_to_owner)s�����&�(�(�(�(�(�(�(�(���T�"�"���	4�	4�D��(�(�9�b�1�1�
4�
4���v��%�%�d�6�l�3�3�3�3�
4��
r2c��<����K�	td���d{V��}n6#t$r)t������d{V��cYSwxYw|����d{V���t���d{V���dtf�fd����fd�|D��S)Nrn�	user_infoc����|�d��dkrtjjS|�d���vrtjjStjjS)Nrrry)r�r�	UserLevel�ADMIN�RESSELER�REGULAR_USER)r�rxs �r'�user_info_to_levelz9GenericPanel.get_user_details.<locals>.user_info_to_level9sR����}�}�Z�(�(�F�2�2��~�+�+��}�}�Z�(�(�F�2�2��~�.�.��>�.�.r2c
����i|]q}|�d���v�|�d��|�dd��|�dd��t�|����d���rS)rr�email��locale_code)r��locale�level)r��int)r/r�r��	usernamess  ��r'�
<dictcomp>z1GenericPanel.get_user_details.<locals>.<dictcomp>Bs����
�
�
���x�x�
�#�#�y�0�0�

�H�H�Z� � ����'�2�.�.��(�(�=�"�5�5��/�/��5�5�6�6�#�#�1�0�0r2)rAr>r��get_user_detailsr�r�r)r��	user_datarxr�r�r�s  @@@�r'r�zGenericPanel.get_user_details1s��������	4�2�7�;�;�;�;�;�;�;�;�I�I��%�	4�	4�	4����1�1�3�3�3�3�3�3�3�3�3�3�3�	4�����.�.�*�*�*�*�*�*�*�*�	�%�'�'�'�'�'�'�'�'��	/�$�	/�	/�	/�	/�	/�	/�
�
�
�
�
�"�

�
�
�	
s��0A�Ac��FK�t���d{V��}d�|D��S)Nc�H�i|]}|d|�dg���� Sre)r�r�s  r'r�z5GenericPanel.get_domains_per_user.<locals>.<dictcomp>Os,��H�H�H�$��V��d�h�h�y�"�5�5�H�H�Hr2r�r�s  r'�get_domains_per_userz!GenericPanel.get_domains_per_userLs7����&�(�(�(�(�(�(�(�(��H�H�%�H�H�H�Hr2rTc��|jdkrf|dddgkrX|d�dd��}tj|��}|d|dtjkr|dndfS|jdfS)	Nr�command�login�pam�params�jwt�	user_type�	user_name)�_uid�popr	�parse_tokenr
�NON_ROOTrt)r��protocolrT�token�parsed_tokens     r'�authenticatezGenericPanel.authenticateQs����=�A���$�y�/�g�u�5E�"E�"E���N�&�&�u�d�3�3�E�$�0��7�7�L���,���,��0A�A�A��[�)�)���
��=�$�&�&r2c���t�����}d|vr7d|dvr-t|dd�����St��S)N�malware�basedir)rr7�setrE)r��confs  r'�basedirszGenericPanel.basedirs]s^�� �"�"�*�*�,�,�������d�9�o�!=�!=��t�I��y�1�7�7�9�9�:�:�:��u�u�r2c��jK�t���d{V��}d�|���D��S)Nc�&�i|]\}}|d|��S��
document_rootr3�r/r�rgs   r'r�z.GenericPanel.list_docroots.<locals>.<dictcomp>es#��L�L�L�y�v�q��/�"�F�L�L�Lr2�rvri�r�rbs  r'�
list_docrootszGenericPanel.list_docrootscs?����'�)�)�)�)�)�)�)�)��L�L�G�M�M�O�O�L�L�L�Lr2c��jK�t���d{V��}d�|���D��S)Nc�(�i|]\}}||dg��Sr�r3r�s   r'r�z1GenericPanel.get_domain_paths.<locals>.<dictcomp>is&��N�N�N������?�+�,�N�N�Nr2r�r�s  r'�get_domain_pathszGenericPanel.get_domain_pathsgs?����'�)�)�)�)�)�)�)�)��N�N�g�m�m�o�o�N�N�N�Nr2c��
K�dS)z8
        Returns panel url
        :return: str
        r�r3)r�rrs  r'�panel_user_linkzGenericPanel.panel_user_linkks
����
�rr2rrc��
K�gSr�r3)r�rrs  r'�get_user_domains_detailsz%GenericPanel.get_user_domains_detailsrs
�����	r2r�)"r�r�r��__doc__r�GENERIC_PANEL_NAMEr�r>�	exception�classmethodr�r�r�r�r,r�r�strr�rr�r�r�r-r�rr�r�r�r�r|�
DomainDatar�r�r�s@r'r�r��sJ���������
�"�D�&�I��,�,��[�,�
�
�
�
�
�
�
�
�����[������[�����0��c��0�0�0�0��4��T�#�Y��+?�����
��S�$�s�C�x�.�-@�(A�
�
�
�
�
�
�6I�D��d�3�i��,@�I�I�I�I�

'�4�
'�
'�
'�
'��#�c�(�����M�T�#�s�(�^�M�M�M�M�O��S�$�s�)�^�(<�O�O�O�O�����������	
�d�o�	�����[�����r2r�)=r�datetime�	functoolsrU�loggingrGrD�collectionsr�typingrrrr#r �$defence360agent.api.integration_confrr�defence360agent.api.jwt_issuerr	�3defence360agent.application.determine_hosting_panelr� defence360agent.contracts.configr� defence360agent.rpc_tools.lookupr
�defence360agent.utilsrrrrr�r�	getLoggerr�rlr@�dirname�__file__rr�r"�	timedelta�'EXPIRATION_TIME_FOR_INTEGRATION_SCRIPTS�	lru_cacher�r$r(r4r:rArOr?rprhrvr��PanelExceptionr>�
AbstractPanelr�r3r2r'�<module>rsj����������������������	�	�	�	�����#�#�#�#�#�#�"�"�"�"�"�"�"�"�"�"�����������������5�4�4�4�4�4�������=�<�<�<�<�<�5�5�5�5�5�5�������������������	��	�8�	$�	$���G�O�O�H��� F�F��>����*<�(�*<��O�7��
�
�+�+�+�'����Q����*�3�*�8�#5�*�*�*� ��*�D�D�D����
��?��L�L�L�5�s�5�5�5�M�L�5�"�C�"�s�"�t�C�y�"�"�"�"�J'��'�3�'�'�'�'�T(�(�(�"
�
�
����*�d�3�i�*�*�*�*�Z�����T�0����{�{�{�{�{�4�%�{�{�{�{�{r2defence360agent/subsys/panels/generic/panel.py0000644000000000000000000002631200000000000016451 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["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["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.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()}

    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()}

    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/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  �

��L`��r���ddlmZdgZdS)�)�NoCPrN)�panelr�__all__���a/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/no_cp/__init__.py�<module>r	s"���������(���rdefence360agent/subsys/panels/no_cp/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000042700000000000023132 0ustar  �

��L`��r���ddlmZdgZdS)�)�NoCPrN)�panelr�__all__���a/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/no_cp/__init__.py�<module>r	s"���������(���rdefence360agent/subsys/panels/no_cp/__pycache__/panel.cpython-311.opt-1.pyc0000644000000000000000000001757100000000000023441 0ustar  �

2�^
D�����R�ddlZddlZddlZddlZddlmZddlmZmZm	Z	m
Z
ddlZddlZddl
mZddlmZmZddlmZeje��ZdZejd�	��d
edejfd���Zde	efd
�ZGd�dej��Z Gd�dej!��ZdS)�N)�defaultdict)�Dict�List�Optional�Set)�NoCP)�get_non_system_users�run�)�basez;/opt/imunify360/venv/share/imunify360/no_cp/schema-v{}.yaml)�maxsize�version�returnc���tt�|����5}tj|��}tj|��cddd��S#1swxYwYdS)z*Returns a validator for given API version.N)�open�_SCHEMA_PATH_TMPL�format�yaml�	safe_load�cerberus�	Validator)r�schema_file�schemas   �^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/no_cp/panel.py�_get_validatorrs���
��&�&�w�/�/�	0�	0�*�K����,�,���!�&�)�)�*�*�*�*�*�*�*�*�*�*�*�*����*�*�*�*�*�*s�(A�A!�$A!c��DK�	ttjttj��g���d{V��}n=#t
$rYdStj$r�t$r}td��|�d}~wwxYw|\}}}|dkr"td�
|�����	tj|�
����}n"#t$r}td��|�d}~wwxYw|�d��}|�td���t|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` field�zinvalid API version: {}zvalidation error: {})r
�Config�
CLIENT_SCRIPT�str�LATEST_VERSION�FileNotFoundError�asyncio�CancelledError�	Exception�ScriptErrorr�json�loads�decode�get�
isinstance�intrr�validate�errors)	�result�exc�code�stdout�_�datar�	validator�oks	         r�_get_client_datar7s�����;��F�0�#�f�6K�2L�2L�M�N�N�N�N�N�N�N�N���������t�t��!����
��;�;�;��0�1�1�s�:�����;�����O�D�&�!��q�y�y��0�7�7��=�=�>�>�>�C��z�&�-�-�/�/�*�*�����C�C�C��8�9�9�s�B�����C�����h�h�y�!�!�G����?�@�@�@��7�C� � �E��q�L�L��t�*�*�*��3�:�:�7�C�C�D�D�D��w�'�'�I�	�	�	�D�	!�	!�B�
�D��0�)�2B�C�C�C��Ks3�8=�
A7�
A7�"A2�2A7�)&C�
C/�C*�*C/c��eZdZdS)r&N)�__name__�
__module__�__qualname__��rr&r&?s�������Dr=r&c�6�eZdZdZeZed���Zdd�Zdd�Z	ed���Z
d�Zdee
fd	�Zdee
ee
ffd
�Zdee
ee
ffd�Zdee
fd�Zdee
e
ffd
�Zde
fd�Zede
deejfd���ZdS)rzno panelc��dS)NFr<��clss r�is_installedzNoCP.is_installedGs���ur=Nc��
K�dS�Nr<)�self�names  r�enable_imunify_pluginzNoCP.enable_imunify_pluginK������r=c��
K�dSrDr<)rE�plugin_names  r�disable_imunify_pluginzNoCP.disable_imunify_pluginNrHr=c��
K�dS)N�0r<r@s rrzNoCP.versionQs�����sr=c��ZK�t���d{V��}|�gSd�|dD��S)Nc��g|]
}|d��S)rFr<��.0�domains  r�
<listcomp>z)NoCP.get_user_domains.<locals>.<listcomp>Ys��=�=�=�6��v��=�=�=r=�domains�r7�rEr4s  r�get_user_domainszNoCP.get_user_domainsUsE����%�'�'�'�'�'�'�'�'���<��I�=�=�T�)�_�=�=�=�=r=rc��6K�d�t��D��S)Nc��g|]	}|j��
Sr<)�pw_name)rQ�pws  rrSz"NoCP.get_users.<locals>.<listcomp>\s��<�<�<�r��
�<�<�<r=)r	�rEs r�	get_userszNoCP.get_users[s!����<�<�%9�%;�%;�<�<�<�<r=c��ZK�t���d{V��}|�iSd�|dD��S)Nc�.�i|]}|d|dg��S)rF�ownerr<rPs  r�
<dictcomp>z,NoCP.get_domain_to_owner.<locals>.<dictcomp>bs2��
�
�
�28�F�6�N�V�G�_�-�
�
�
r=rTrUrVs  r�get_domain_to_ownerzNoCP.get_domain_to_owner^sR����%�'�'�'�'�'�'�'�'���<��I�
�
�<@��O�
�
�
�	
r=c���K�t���d{V��}|�iStt��}|dD])}||d�|d���*t	|��S)NrTr`rF)r7r�list�append�dict)rEr4�user_to_domainsrRs    r�get_domains_per_userzNoCP.get_domains_per_userfs�����%�'�'�'�'�'�'�'�'���<��I�%�d�+�+���9�o�	D�	D�F��F�7�O�,�3�3�F�6�N�C�C�C�C��O�$�$�$r=c��t��SrD)�setr\s r�basedirsz
NoCP.basedirsqs���u�u�r=c��"K�t��SrD)rfr\s r�
list_docrootszNoCP.list_docrootsts�����v�v�
r=c��
K�dS)z8
        Returns panel url
        :return: str
        �r<)rE�usernames  r�panel_user_linkzNoCP.panel_user_linkws
����
�rr=rpc��
K�gSrDr<)rArps  r�get_user_domains_detailszNoCP.get_user_domains_details~s
�����	r=rD)r9r:r;�NAMEr&�	exception�classmethodrBrGrKrrWrr r]rrbrhrrkrmrqrdr�
DomainDatarsr<r=rrrCs��������D��I�����[��
�
�
�
�
�
�
�
�����[��>�>�>�=��c��=�=�=�=�
�4��T�#�Y��+?�
�
�
�
�	%�D��d�3�i��,@�	%�	%�	%�	%��#�c�(������T�#�s�(�^���������������	
�d�o�	�����[���r=r)"r#�	functoolsr'�logging�collectionsr�typingrrrrrr� defence360agent.contracts.configrr�defence360agent.utilsr	r
ror�	getLoggerr9�loggerr�	lru_cacher,rrrfr7�PanelExceptionr&�
AbstractPanelr<r=r�<module>r�s�������������������#�#�#�#�#�#�,�,�,�,�,�,�,�,�,�,�,�,���������;�;�;�;�;�;�;�;�;�;�;�;�;�;�������	��	�8�	$�	$��A��
���Q����*�C�*�H�$6�*�*�*� ��*���������B	�	�	�	�	�$�%�	�	�	�?�?�?�?�?�4��?�?�?�?�?r=defence360agent/subsys/panels/no_cp/__pycache__/panel.cpython-311.pyc0000644000000000000000000001757100000000000022502 0ustar  �

2�^
D�����R�ddlZddlZddlZddlZddlmZddlmZmZm	Z	m
Z
ddlZddlZddl
mZddlmZmZddlmZeje��ZdZejd�	��d
edejfd���Zde	efd
�ZGd�dej��Z Gd�dej!��ZdS)�N)�defaultdict)�Dict�List�Optional�Set)�NoCP)�get_non_system_users�run�)�basez;/opt/imunify360/venv/share/imunify360/no_cp/schema-v{}.yaml)�maxsize�version�returnc���tt�|����5}tj|��}tj|��cddd��S#1swxYwYdS)z*Returns a validator for given API version.N)�open�_SCHEMA_PATH_TMPL�format�yaml�	safe_load�cerberus�	Validator)r�schema_file�schemas   �^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/no_cp/panel.py�_get_validatorrs���
��&�&�w�/�/�	0�	0�*�K����,�,���!�&�)�)�*�*�*�*�*�*�*�*�*�*�*�*����*�*�*�*�*�*s�(A�A!�$A!c��DK�	ttjttj��g���d{V��}n=#t
$rYdStj$r�t$r}td��|�d}~wwxYw|\}}}|dkr"td�
|�����	tj|�
����}n"#t$r}td��|�d}~wwxYw|�d��}|�td���t|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` field�zinvalid API version: {}zvalidation error: {})r
�Config�
CLIENT_SCRIPT�str�LATEST_VERSION�FileNotFoundError�asyncio�CancelledError�	Exception�ScriptErrorr�json�loads�decode�get�
isinstance�intrr�validate�errors)	�result�exc�code�stdout�_�datar�	validator�oks	         r�_get_client_datar7s�����;��F�0�#�f�6K�2L�2L�M�N�N�N�N�N�N�N�N���������t�t��!����
��;�;�;��0�1�1�s�:�����;�����O�D�&�!��q�y�y��0�7�7��=�=�>�>�>�C��z�&�-�-�/�/�*�*�����C�C�C��8�9�9�s�B�����C�����h�h�y�!�!�G����?�@�@�@��7�C� � �E��q�L�L��t�*�*�*��3�:�:�7�C�C�D�D�D��w�'�'�I�	�	�	�D�	!�	!�B�
�D��0�)�2B�C�C�C��Ks3�8=�
A7�
A7�"A2�2A7�)&C�
C/�C*�*C/c��eZdZdS)r&N)�__name__�
__module__�__qualname__��rr&r&?s�������Dr=r&c�6�eZdZdZeZed���Zdd�Zdd�Z	ed���Z
d�Zdee
fd	�Zdee
ee
ffd
�Zdee
ee
ffd�Zdee
fd�Zdee
e
ffd
�Zde
fd�Zede
deejfd���ZdS)rzno panelc��dS)NFr<��clss r�is_installedzNoCP.is_installedGs���ur=Nc��
K�dS�Nr<)�self�names  r�enable_imunify_pluginzNoCP.enable_imunify_pluginK������r=c��
K�dSrDr<)rE�plugin_names  r�disable_imunify_pluginzNoCP.disable_imunify_pluginNrHr=c��
K�dS)N�0r<r@s rrzNoCP.versionQs�����sr=c��ZK�t���d{V��}|�gSd�|dD��S)Nc��g|]
}|d��S)rFr<��.0�domains  r�
<listcomp>z)NoCP.get_user_domains.<locals>.<listcomp>Ys��=�=�=�6��v��=�=�=r=�domains�r7�rEr4s  r�get_user_domainszNoCP.get_user_domainsUsE����%�'�'�'�'�'�'�'�'���<��I�=�=�T�)�_�=�=�=�=r=rc��6K�d�t��D��S)Nc��g|]	}|j��
Sr<)�pw_name)rQ�pws  rrSz"NoCP.get_users.<locals>.<listcomp>\s��<�<�<�r��
�<�<�<r=)r	�rEs r�	get_userszNoCP.get_users[s!����<�<�%9�%;�%;�<�<�<�<r=c��ZK�t���d{V��}|�iSd�|dD��S)Nc�.�i|]}|d|dg��S)rF�ownerr<rPs  r�
<dictcomp>z,NoCP.get_domain_to_owner.<locals>.<dictcomp>bs2��
�
�
�28�F�6�N�V�G�_�-�
�
�
r=rTrUrVs  r�get_domain_to_ownerzNoCP.get_domain_to_owner^sR����%�'�'�'�'�'�'�'�'���<��I�
�
�<@��O�
�
�
�	
r=c���K�t���d{V��}|�iStt��}|dD])}||d�|d���*t	|��S)NrTr`rF)r7r�list�append�dict)rEr4�user_to_domainsrRs    r�get_domains_per_userzNoCP.get_domains_per_userfs�����%�'�'�'�'�'�'�'�'���<��I�%�d�+�+���9�o�	D�	D�F��F�7�O�,�3�3�F�6�N�C�C�C�C��O�$�$�$r=c��t��SrD)�setr\s r�basedirsz
NoCP.basedirsqs���u�u�r=c��"K�t��SrD)rfr\s r�
list_docrootszNoCP.list_docrootsts�����v�v�
r=c��
K�dS)z8
        Returns panel url
        :return: str
        �r<)rE�usernames  r�panel_user_linkzNoCP.panel_user_linkws
����
�rr=rpc��
K�gSrDr<)rArps  r�get_user_domains_detailszNoCP.get_user_domains_details~s
�����	r=rD)r9r:r;�NAMEr&�	exception�classmethodrBrGrKrrWrr r]rrbrhrrkrmrqrdr�
DomainDatarsr<r=rrrCs��������D��I�����[��
�
�
�
�
�
�
�
�����[��>�>�>�=��c��=�=�=�=�
�4��T�#�Y��+?�
�
�
�
�	%�D��d�3�i��,@�	%�	%�	%�	%��#�c�(������T�#�s�(�^���������������	
�d�o�	�����[���r=r)"r#�	functoolsr'�logging�collectionsr�typingrrrrrr� defence360agent.contracts.configrr�defence360agent.utilsr	r
ror�	getLoggerr9�loggerr�	lru_cacher,rrrfr7�PanelExceptionr&�
AbstractPanelr<r=r�<module>r�s�������������������#�#�#�#�#�#�,�,�,�,�,�,�,�,�,�,�,�,���������;�;�;�;�;�;�;�;�;�;�;�;�;�;�������	��	�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  �

lC�(f�'����ddlmZdgZdS)�)�PleskrN)�panelr�__all__���a/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/plesk/__init__.py�<module>r	s"���������)���rdefence360agent/subsys/panels/plesk/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000043000000000000023144 0ustar  �

lC�(f�'����ddlmZdgZdS)�)�PleskrN)�panelr�__all__���a/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/plesk/__init__.py�<module>r	s"���������)���rdefence360agent/subsys/panels/plesk/__pycache__/api.cpython-311.opt-1.pyc0000644000000000000000000002707300000000000023131 0ustar  �

Q�&��&����dZddlZddlmZddlmZmZmZddlm	Z	m
Z
ddlmZm
Z
mZmZeje��Zd�Zeede�	��d
edefd���Zdeeeeffd
�Zdeeeeffd�Zdeeeeefffd�Zdeefd�Zdeefd�Zdeeefd�Zdefd�Zdefd�Zdeefd�Z d�Z!e
dd���dee	fd���Z"dS)z.Gather information from Plesk via DB querries.�N)�defaultdict)�Dict�List�Sequence)�
DomainData�PanelException)�
CheckRunError�async_lru_cache�	check_run�retry_onc��K�t|i|���)N)r)�args�kwargss  �\/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/plesk/api.py�raise_panel_exceptionrs����
�$�
)�&�
)�
)�)��)�	max_tries�on_error�query�returnc��^K�tdddd|g���d{V�����S)N�plesk�dbz-Nz-e)r�decode�rs r�
_run_queryrs=�����W�d�D�$��>�?�?�?�?�?�?�?�?�G�G�I�I�Irc��K�td���d{V�����}tt��}t	|ddd�|ddd���D]&\}}|dkr||�|���'|S)z'Return mapping: user -> user's domains.z�select login, name from domains    left join hosting on dom_id = domains.id    right join sys_users on hosting.sys_user_id = sys_users.idNr���NULL)r�splitr�list�zip�append)�result�result_mapping�user�domains    r�get_user_to_domainr*s������
L�
�
�	
�	
�	
�	
�	
�	
�
�e�g�g�
�!��&�&�N��F�1�4�a�4�L�&���A��,�7�7�0�0���f��V����4� �'�'��/�/�/���rc��K�td���d{V�����}d�t|ddd�|ddd���D��S)zReturn mapping: domain -> user.z�select name, login from domains    left join hosting on dom_id = domains.id    left join sys_users on hosting.sys_user_id = sys_users.idNc��i|]	\}}||g��
S�r-)�.0r)r(s   r�
<dictcomp>z&get_domain_to_user.<locals>.<dictcomp>7s ��O�O�O�|�v�t�F�T�F�O�O�Orrrr )rr"r$)r&s r�get_domain_to_userr0,s~�����
K�
�
�	
�	
�	
�	
�	
�	
��e�g�g��P�O�s�6�!�$�Q�$�<����1���/N�/N�O�O�O�Orc���K�i}td���d{V��}|�d��D]:}|s�|�d��\}}}||�dd��d�||<�;|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�
�;�-�_)�email�locale)rr"�replace)�user_details�results�recordr(r6r7s      r�get_user_detailsr<:s������L��C���������G��-�-��%�%�
�
���	��$�l�l�3�/�/���e�V���n�n�S�#�.�.�
�
��T���
�rc��TK�td���d{V�����S)zReturn: list of domainszselect name from domainsN�rr"r-rr�get_domainsr?Qs5�����7�8�8�8�8�8�8�8�8�?�?�A�A�Arc��TK�td���d{V�����S)z#Return: users that created by Pleskz�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=0Nr>r-rr�	get_usersrAWsI�����
C�
�
�	
�	
�	
�	
�	
�	
�
�e�g�g�
rc��rK�td���d{V��}d�|�d��D��}|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�
<listcomp>z*get_users_for_patchman.<locals>.<listcomp>zs%��
J�
J�
J��6�
J�f�l�l�n�n�
J�
J�
Jrr2r>)�raw_data�tupless  r�get_users_for_patchmanrJcsa���� �	�
�
�
�
�
�
�
�
�H�K�
J�8�>�>�$�+?�+?�
J�
J�
J�F��Mrc��JK�ttd���d{V����S)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�
�
�	
�	
�	
�	
�	
�	
���rc��nK�td���d{V��}d�|�d��D��S)Nz-SELECT email FROM clients WHERE type='admin';c��g|]}|�|��Sr-r-)r.r6s  rrGz$get_admin_emails.<locals>.<listcomp>�s��;�;�;�e�U�;�E�;�;�;rr2r>)�emailss r�get_admin_emailsrQ�sF�����M�N�N�
N�
N�
N�
N�
N�
N�F�;�;�v�|�|�D�1�1�;�;�;�;rc��XK�d}t|���d{V�����S)Nz.SELECT DISTINCT hosting.www_root FROM hosting;r>rs r�
list_docrootsrS�s7����<�E��U�#�#�#�#�#�#�#�#�*�*�,�,�,rc��vK�d}t|���d{V��}d�|�d��D��}|S)Nz�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.idc�:�g|]}|�|�����Sr-rDrEs  rrGz/list_docroots_domains_users.<locals>.<listcomp>�s%��
F�
F�
F��v�
F�f�l�l�n�n�
F�
F�
Frr2r>)�sql�data�retvals   r�list_docroots_domains_usersrY�sT����	D���C��� � � � � � �D�
F�
F�4�:�:�d�+;�+;�
F�
F�
F�F��Mrr �<)�maxsize�ttlc��K�d}t|���d{V��}d�|�d��D��}d�|D��S)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.<locals>.<listcomp>�s%��H�H�H�6��H������H�H�Hrr2c	�d�g|]-}t|d|d|d|d�����.S)�r��)�docrootr)�type�username)r)r.�rows  rrGz,get_user_domains_details.<locals>.<listcomp>�sI������	�3�q�6�#�a�&�s�1�v��A��O�O�O���rr>)rVrWrHs   r�get_user_domains_detailsrg�sm����=�C�|�C��� � � � � � �D�H�H�T�Z�Z��-=�-=�H�H�H�H�������r)#�__doc__�logging�collectionsr�typingrrr�"defence360agent.subsys.panels.baserr�defence360agent.utilsr	r
rr�	getLogger�__name__�loggerr�strrr*r0r<r?rAr#rJrLrMrQrSrYrgr-rr�<module>rrs���4�4�����#�#�#�#�#�#�'�'�'�'�'�'�'�'�'�'�I�I�I�I�I�I�I�I�������������
��	�8�	$�	$��*�*�*�
��-�1�/D�E�E�E�J�C�J�C�J�J�J�F�E�J��$�s�D��I�~�"6�����"P�$�s�D��I�~�"6�P�P�P�P���S�$�s�C�x�.�%8� 9�����.B�4��9�B�B�B�B�	��c��	�	�	�	��d�4��9�o�����6�#�����<��<�<�<�<�
-�X�c�]�-�-�-�-�
	�	�	�����#�#�#�D��Z�(8�D�D�D�$�#�D�D�Drdefence360agent/subsys/panels/plesk/__pycache__/api.cpython-311.pyc0000644000000000000000000002707300000000000022172 0ustar  �

Q�&��&����dZddlZddlmZddlmZmZmZddlm	Z	m
Z
ddlmZm
Z
mZmZeje��Zd�Zeede�	��d
edefd���Zdeeeeffd
�Zdeeeeffd�Zdeeeeefffd�Zdeefd�Zdeefd�Zdeeefd�Zdefd�Zdefd�Zdeefd�Z d�Z!e
dd���dee	fd���Z"dS)z.Gather information from Plesk via DB querries.�N)�defaultdict)�Dict�List�Sequence)�
DomainData�PanelException)�
CheckRunError�async_lru_cache�	check_run�retry_onc��K�t|i|���)N)r)�args�kwargss  �\/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/plesk/api.py�raise_panel_exceptionrs����
�$�
)�&�
)�
)�)��)�	max_tries�on_error�query�returnc��^K�tdddd|g���d{V�����S)N�plesk�dbz-Nz-e)r�decode�rs r�
_run_queryrs=�����W�d�D�$��>�?�?�?�?�?�?�?�?�G�G�I�I�Irc��K�td���d{V�����}tt��}t	|ddd�|ddd���D]&\}}|dkr||�|���'|S)z'Return mapping: user -> user's domains.z�select login, name from domains    left join hosting on dom_id = domains.id    right join sys_users on hosting.sys_user_id = sys_users.idNr���NULL)r�splitr�list�zip�append)�result�result_mapping�user�domains    r�get_user_to_domainr*s������
L�
�
�	
�	
�	
�	
�	
�	
�
�e�g�g�
�!��&�&�N��F�1�4�a�4�L�&���A��,�7�7�0�0���f��V����4� �'�'��/�/�/���rc��K�td���d{V�����}d�t|ddd�|ddd���D��S)zReturn mapping: domain -> user.z�select name, login from domains    left join hosting on dom_id = domains.id    left join sys_users on hosting.sys_user_id = sys_users.idNc��i|]	\}}||g��
S�r-)�.0r)r(s   r�
<dictcomp>z&get_domain_to_user.<locals>.<dictcomp>7s ��O�O�O�|�v�t�F�T�F�O�O�Orrrr )rr"r$)r&s r�get_domain_to_userr0,s~�����
K�
�
�	
�	
�	
�	
�	
�	
��e�g�g��P�O�s�6�!�$�Q�$�<����1���/N�/N�O�O�O�Orc���K�i}td���d{V��}|�d��D]:}|s�|�d��\}}}||�dd��d�||<�;|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�
�;�-�_)�email�locale)rr"�replace)�user_details�results�recordr(r6r7s      r�get_user_detailsr<:s������L��C���������G��-�-��%�%�
�
���	��$�l�l�3�/�/���e�V���n�n�S�#�.�.�
�
��T���
�rc��TK�td���d{V�����S)zReturn: list of domainszselect name from domainsN�rr"r-rr�get_domainsr?Qs5�����7�8�8�8�8�8�8�8�8�?�?�A�A�Arc��TK�td���d{V�����S)z#Return: users that created by Pleskz�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=0Nr>r-rr�	get_usersrAWsI�����
C�
�
�	
�	
�	
�	
�	
�	
�
�e�g�g�
rc��rK�td���d{V��}d�|�d��D��}|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�
<listcomp>z*get_users_for_patchman.<locals>.<listcomp>zs%��
J�
J�
J��6�
J�f�l�l�n�n�
J�
J�
Jrr2r>)�raw_data�tupless  r�get_users_for_patchmanrJcsa���� �	�
�
�
�
�
�
�
�
�H�K�
J�8�>�>�$�+?�+?�
J�
J�
J�F��Mrc��JK�ttd���d{V����S)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�
�
�	
�	
�	
�	
�	
�	
���rc��nK�td���d{V��}d�|�d��D��S)Nz-SELECT email FROM clients WHERE type='admin';c��g|]}|�|��Sr-r-)r.r6s  rrGz$get_admin_emails.<locals>.<listcomp>�s��;�;�;�e�U�;�E�;�;�;rr2r>)�emailss r�get_admin_emailsrQ�sF�����M�N�N�
N�
N�
N�
N�
N�
N�F�;�;�v�|�|�D�1�1�;�;�;�;rc��XK�d}t|���d{V�����S)Nz.SELECT DISTINCT hosting.www_root FROM hosting;r>rs r�
list_docrootsrS�s7����<�E��U�#�#�#�#�#�#�#�#�*�*�,�,�,rc��vK�d}t|���d{V��}d�|�d��D��}|S)Nz�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.idc�:�g|]}|�|�����Sr-rDrEs  rrGz/list_docroots_domains_users.<locals>.<listcomp>�s%��
F�
F�
F��v�
F�f�l�l�n�n�
F�
F�
Frr2r>)�sql�data�retvals   r�list_docroots_domains_usersrY�sT����	D���C��� � � � � � �D�
F�
F�4�:�:�d�+;�+;�
F�
F�
F�F��Mrr �<)�maxsize�ttlc��K�d}t|���d{V��}d�|�d��D��}d�|D��S)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.<locals>.<listcomp>�s%��H�H�H�6��H������H�H�Hrr2c	�d�g|]-}t|d|d|d|d�����.S)�r��)�docrootr)�type�username)r)r.�rows  rrGz,get_user_domains_details.<locals>.<listcomp>�sI������	�3�q�6�#�a�&�s�1�v��A��O�O�O���rr>)rVrWrHs   r�get_user_domains_detailsrg�sm����=�C�|�C��� � � � � � �D�H�H�T�Z�Z��-=�-=�H�H�H�H�������r)#�__doc__�logging�collectionsr�typingrrr�"defence360agent.subsys.panels.baserr�defence360agent.utilsr	r
rr�	getLogger�__name__�loggerr�strrr*r0r<r?rAr#rJrLrMrQrSrYrgr-rr�<module>rrs���4�4�����#�#�#�#�#�#�'�'�'�'�'�'�'�'�'�'�I�I�I�I�I�I�I�I�������������
��	�8�	$�	$��*�*�*�
��-�1�/D�E�E�E�J�C�J�C�J�J�J�F�E�J��$�s�D��I�~�"6�����"P�$�s�D��I�~�"6�P�P�P�P���S�$�s�C�x�.�%8� 9�����.B�4��9�B�B�B�B�	��c��	�	�	�	��d�4��9�o�����6�#�����<��<�<�<�<�
-�X�c�]�-�-�-�-�
	�	�	�����#�#�#�D��Z�(8�D�D�D�$�#�D�D�Drdefence360agent/subsys/panels/plesk/__pycache__/panel.cpython-311.opt-1.pyc0000644000000000000000000003625600000000000023462 0ustar  �

��������ddlZddlZddlZddlZddlmZddlmZddl	m
Z
ddlmZm
Z
mZmZmZddlmZddlmZddlmZdd	lmZmZdd
lmZddlmZd
dlmZd
dlm Z dZ!dZ"ej#gd�zZ$dZ%dZ&ej'e(��Z)de*fd�Z+dee*e*ffd�Z,Gd�dej-��Z.Gd�dej/��Z0dS)�N)�Error)�defaultdict)�Path)�Dict�List�Set�Tuple�Union)�ElementTree��is_plesk_installed)�config)�
OsReleaseInfo�	check_run)�get_hostname�)�base�)�api)�PleskConfigz
/etc/sw/keys/zext-imunify360)�953�990�8443�8447zK/usr/local/psa/admin/plib/modules/imunify360/scripts/send-notifications.phpz@/opt/imunify360/venv/share/imunify360/scripts/send-notifications�returnc�B�|�|��}|�|jSdS)z%Avoid AttributeError if tag not foundN�)�find�text)�node�tag�_nodes   �^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/plesk/panel.py�_safe_get_textr$"s&��
�I�I�c�N�N�E����z��
�2�c���d}d}|�d��D]J}t|d��dkrt|d��}t|d��dkrt|d��}�K||fS)z.Return product name and filename from key datar�value/struct/member�name�filenamezvalue/string�key_product_name)�findallr$)�keyr)r*�datas    r#�
_get_key_datar.+s����H������1�2�2�D�D���$��'�'�:�5�5�%�d�N�;�;�H��$��'�'�+=�=�=�-�d�N�C�C����X�%�%r%c��eZdZdS)�PleskExceptionN)�__name__�
__module__�__qualname__�r%r#r0r08s�������Dr%r0c�&�eZdZdZgd�ezddgezd�gd�gd�d�d�ZeZed���Z	e
d	���Zej
��d d���Zej
��d d���Zd
eefd�Zd�Zd�Zd�Zd
eeeffd�Zd�Zd
efd�Zed���Zd
eefd�Zed
efd���Zed
e ed
ffd���Z!ed
efd���Z"d�Z#d
efd�Z$ed
d�d���Z%eded
e&ej'fd���Z(d
S)!�Plesk)�143�465�8880z49152-65535�113�5224)�in�out)�20�21�53�443)r>r?r@r:�123)�tcp�udpc��t��S�Nr��clss r#�is_installedzPlesk.is_installedJs��!�#�#�#r%c��K�tdd��5}|������dcddd��S#1swxYwYdS)Nz/usr/local/psa/version�rr)�open�read�split)�fs r#�versionz
Plesk.versionNs�����
�*�C�
0�
0�	'�A��6�6�8�8�>�>�#�#�A�&�	'�	'�	'�	'�	'�	'�	'�	'�	'�	'�	'�	'����	'�	'�	'�	'�	'�	's�,A�A�ANc��
K�dSrFr4)�selfr(s  r#�enable_imunify_pluginzPlesk.enable_imunify_pluginS������r%c��
K�dSrFr4)rR�plugin_names  r#�disable_imunify_pluginzPlesk.disable_imunify_pluginWrTr%rc��K�	tj���d{V��S#tj$r'}t�d|��gcYd}~Sd}~wwxYw)z$Returns a list of Plesk system usersNzFailed to get users: %s)r�	get_usersr�PanelException�logger�error)rR�es  r#rYzPlesk.get_users[so����	�����(�(�(�(�(�(�(���"�	�	�	��L�L�2�A�6�6�6��I�I�I�I�I�I�����	���s��A�A�A�Ac	��K�tj���d{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#�<lambda>z&Plesk.patchman_users.<locals>.<lambda>gs��4�>�3N�r%)�admin�reseller�client�NULLr�email�language�username�parent�level�	suspended�domains)�domain�paths)r�get_users_for_patchmanr�dict�updaterr`�ADMIN�RESSELERra�int�bool�get�append�list�values)rR�tuples�res�client_type_to_levelrirgrj�locale�client_typern�homedirrls            r#�patchman_userszPlesk.patchman_userscs������1�3�3�3�3�3�3�3�3���$����*�+N�+N�O�O���#�#���-� �N�3��.�5�
�
�	
�	
�	
� �	�	�	
���������+0�F�?�?�R�R��C��M�'�"�.4��.>�.>���F�C��M�*�%�(0�C��M�*�%�,2�f�,<�,<�b�b�&�C��M�(�#�%(�)=�k�)J�%K�%K�C��M�'�"�)-�c�)�n�n�)=�)=�C��M�+�&��8�}� � ��+�+�3�+-��H�
�i�(������H�
�i�(�/�/�"(�")���������C�J�J�L�L�!�!�!r%c��8K�tj���d{V��S)zC
        :return: list: domains hosted on server via plesk
        N)r�get_domains�rRs r#�get_user_domainszPlesk.get_user_domains�s(�����_�&�&�&�&�&�&�&�&�&r%c��8K�tj���d{V��S)z8
        :return: domain to list of users pairs
        N)r�get_domain_to_userr�s r#�get_domain_to_ownerzPlesk.get_domain_to_owner��)�����+�-�-�-�-�-�-�-�-�-r%c��8K�tj���d{V��S)z7
        Returns dict with user to email pairs
        N)r�get_user_to_emailr�s r#r�zPlesk.get_user_to_email�s)�����*�,�,�,�,�,�,�,�,�,r%c��8K�tj���d{V��S)z8
        :return: user to list of domains pairs
        N)r�get_user_to_domainr�s r#�get_domains_per_userzPlesk.get_domains_per_user�r�r%c��8K�tj���d{V��SrF)r�"count_customers_with_subscriptionsr�s r#�users_countzPlesk.users_count�s'�����;�=�=�=�=�=�=�=�=�=r%c�J�tj��tjzrdSdS)Nz*/etc/apache2/mods-available/security2.confz /etc/httpd/conf.d/security2.conf)r�id_like�DEBIANrGs r#�get_modsec_config_pathzPlesk.get_modsec_config_path�s'��� �"�"�]�%9�9�	6�?�?�5�5r%c�j�td�����}|r|hn
t��S)N�HTTPD_VHOSTS_D)rrw�set)rR�basedirs  r#�basedirszPlesk.basedirs�s1���.�/�/�3�3�5�5��#�.��y�y����.r%c��ddlm}td��5}d|���z}ddd��n#1swxYwY|ddg���}|j|��t|d�d	d
����}|S)Nr)�ConfigParserz/etc/psa/psa.confz[dummy section]
� �	)�
delimitersz
dummy sectionr�z/var/www/vhosts)�configparserr�rLrM�read_stringrrw)rH�_r��crr�base_dirs       r#�
base_home_dirzPlesk.base_home_dir�s���	.�-�-�-�-�-�
�%�
&�
&�	2�!�&������1�D�	2�	2�	2�	2�	2�	2�	2�	2�	2�	2�	2����	2�	2�	2�	2���#�t��5�5�5�����4� � � ���?�#�'�'�(8�:K�L�L�
�
���s
�:�>�>c�^�tjtj�t
d����}|����d��D]�}t|d��dkr�|�d��D]�}t|��\}}|tkr�ttjtj�t
d|����d��}tj|�
�������ccS����dS)	z�Parse 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�parse�os�path�join�PLESK_KEY_REGISTRY�getrootr+r$r.�PLESK_IMUNIFY360_PRODUCT_NAME�base64�	b64decode�encode�decode)rH�registry�memberr,r*r)�	key_values       r#�
_retrieve_keyzPlesk._retrieve_key�s,���$��G�L�L�+�^�<�<�
�
���&�&�(�(�0�0��A�A�	M�	M�F��f�f�-�-��9�9�!�>�>�*?�@�@�M�M�C�1>�s�1C�1C�.�$�h�'�+H�H�H�$2�'�-� "����$6���!"�!"���
'�
%�%�	� &�/�	�0@�0@�0B�0B�C�C�J�J�L�L�L�L�L�L�L�I���tr%c��K�	|���}n6#tjttf$r}td|z���d}~wwxYw|rt�d|��|Std���)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)r�r�
ParseError�base64Error�FileNotFoundErrorr0r[�info)rH�resultr]s   r#�retrieve_keyzPlesk.retrieve_key�s�����
	M��&�&�(�(�F�F���&��5F�G�	M�	M�	M� �!G�!�!K�L�L�L�����	M�����	��K�K�*�F�3�3�3��M��0�1�1�1s��A�A�Ac��PK�tj���d{V��}d�|D��S)z1
        :return: dict docroot to domain
        Nc��i|]	\}}}||��
Sr4r4)�.0�docrootrnr�s    r#�
<dictcomp>z'Plesk.list_docroots.<locals>.<dictcomp>�s-��
�
�
� 2����G�V�
�
�
r%)r�list_docroots_domains_users)rR�docroot_domains_userss  r#�
list_docrootszPlesk.list_docroots�sJ����'*�&E�&G�&G� G� G� G� G� G� G��
�
�6K�
�
�
�	
r%c��
K�dS)z8
        Returns panel url
        :return: str
        rr4)rRris  r#�panel_user_linkzPlesk.panel_user_link�s
����
�rr%)�userc���K�tt�����sdStjjsdStj|���sdS|||d�}t�|j	�d�|��tj|��}ttg|�������d{V��||���dt!��|du|d�S)	zB
        Notify a customer using Plesk Notifications Hook
        F�ri)�message_type�paramsr�z.notify(%s))�inputNr)r��mainip�base_url�host_server�sent_to_rootr�)r�PLESK_NOTIFICATION_SCRIPT_PATH�existsr�
AdminContacts�ENABLE_ICONTACT_NOTIFICATIONS�should_send_user_notificationsr[r�r1�json�dumpsr�PLESK_NOTIFICATION_HOOK_PATHr��
get_server_ipr)rHr�r�r�r-�stdins      r#�notifyzPlesk.notify�s�����
�2�3�3�:�:�<�<�	��5��#�A�	��5��4�d�C�C�C�	��5� ,���M�M�����s�|�0�0�0�$�7�7�7��
�4� � ���5�6�e�l�l�n�n�M�M�M�M�M�M�M�M�M�M�)��'�'�)�)��'�>�>� �D�L��

�
�	
r%ric��V�K�tj���d{V��}�fd�|D��S)Nc�*��g|]}|j�k�
|��Sr4r�)r�rnris  �r#�
<listcomp>z2Plesk.get_user_domains_details.<locals>.<listcomp>s,���
�
�
����8�0K�0K�F�0K�0K�0Kr%)r�get_user_domains_details)rHri�all_domainss ` r#r�zPlesk.get_user_domains_detailssT����� �8�:�:�:�:�:�:�:�:��
�
�
�
�!,�
�
�
�	
r%rF))r1r2r3�NAME�TCP_PORTS_PLESK�
OPEN_PORTSr0�	exception�classmethodrI�staticmethodrPr�ensure_valid_panelrSrWr�strrYr�r�r�rr�r�rur�r�rr�rr�r
r�r�r�r�r�ry�
DomainDatar�r4r%r#r6r6<s��������D�8�7�7�/�I��6�?�_�4�
�
�
,�+�+�3�3�3�
�
�	�	�J��I��$�$��[�$��'�'��\�'��T����
�
�
���
��T����
�
�
���
���c������&"�&"�&"�P'�'�'�.�.�.�-��c�3�h��-�-�-�-�.�.�.�>�3�>�>�>�>��6�6��[�6�/�#�c�(�/�/�/�/��������[����e�C��I�.�����[��6�2�3�2�2�2��[�2�
�
�
��������8<�
�
�
�
��[�
�6�
��
�	
�d�o�	�
�
�
��[�
�
�
r%r6)1r�r��loggingr��binasciirr��collectionsr�pathlibr�typingrrrr	r
�	xml.etreer�3defence360agent.application.determine_hosting_panelr
�defence360agent.contractsr�defence360agent.utilsrr�defence360agent.utils.commonrrrr�utilsrr�r��TCP_PORTS_COMMONr�r�r��	getLoggerr1r[r�r$r.rZr0�
AbstractPanelr6r4r%r#�<module>r�s��
�
�
�
���������	�	�	�	�)�)�)�)�)�)�#�#�#�#�#�#�������0�0�0�0�0�0�0�0�0�0�0�0�0�0�!�!�!�!�!�!�������-�,�,�,�,�,�:�:�:�:�:�:�:�:�5�5�5�5�5�5�������������������$�� 0���'�*H�*H�*H�H��!n��F��
��	�8�	$�	$��������
&�%��S��/�
&�
&�
&�
&�	�	�	�	�	�T�(�	�	�	�`
�`
�`
�`
�`
�D��`
�`
�`
�`
�`
r%defence360agent/subsys/panels/plesk/__pycache__/panel.cpython-311.pyc0000644000000000000000000003625600000000000022523 0ustar  �

��������ddlZddlZddlZddlZddlmZddlmZddl	m
Z
ddlmZm
Z
mZmZmZddlmZddlmZddlmZdd	lmZmZdd
lmZddlmZd
dlmZd
dlm Z dZ!dZ"ej#gd�zZ$dZ%dZ&ej'e(��Z)de*fd�Z+dee*e*ffd�Z,Gd�dej-��Z.Gd�dej/��Z0dS)�N)�Error)�defaultdict)�Path)�Dict�List�Set�Tuple�Union)�ElementTree��is_plesk_installed)�config)�
OsReleaseInfo�	check_run)�get_hostname�)�base�)�api)�PleskConfigz
/etc/sw/keys/zext-imunify360)�953�990�8443�8447zK/usr/local/psa/admin/plib/modules/imunify360/scripts/send-notifications.phpz@/opt/imunify360/venv/share/imunify360/scripts/send-notifications�returnc�B�|�|��}|�|jSdS)z%Avoid AttributeError if tag not foundN�)�find�text)�node�tag�_nodes   �^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/plesk/panel.py�_safe_get_textr$"s&��
�I�I�c�N�N�E����z��
�2�c���d}d}|�d��D]J}t|d��dkrt|d��}t|d��dkrt|d��}�K||fS)z.Return product name and filename from key datar�value/struct/member�name�filenamezvalue/string�key_product_name)�findallr$)�keyr)r*�datas    r#�
_get_key_datar.+s����H������1�2�2�D�D���$��'�'�:�5�5�%�d�N�;�;�H��$��'�'�+=�=�=�-�d�N�C�C����X�%�%r%c��eZdZdS)�PleskExceptionN)�__name__�
__module__�__qualname__�r%r#r0r08s�������Dr%r0c�&�eZdZdZgd�ezddgezd�gd�gd�d�d�ZeZed���Z	e
d	���Zej
��d d���Zej
��d d���Zd
eefd�Zd�Zd�Zd�Zd
eeeffd�Zd�Zd
efd�Zed���Zd
eefd�Zed
efd���Zed
e ed
ffd���Z!ed
efd���Z"d�Z#d
efd�Z$ed
d�d���Z%eded
e&ej'fd���Z(d
S)!�Plesk)�143�465�8880z49152-65535�113�5224)�in�out)�20�21�53�443)r>r?r@r:�123)�tcp�udpc��t��S�Nr��clss r#�is_installedzPlesk.is_installedJs��!�#�#�#r%c��K�tdd��5}|������dcddd��S#1swxYwYdS)Nz/usr/local/psa/version�rr)�open�read�split)�fs r#�versionz
Plesk.versionNs�����
�*�C�
0�
0�	'�A��6�6�8�8�>�>�#�#�A�&�	'�	'�	'�	'�	'�	'�	'�	'�	'�	'�	'�	'����	'�	'�	'�	'�	'�	's�,A�A�ANc��
K�dSrFr4)�selfr(s  r#�enable_imunify_pluginzPlesk.enable_imunify_pluginS������r%c��
K�dSrFr4)rR�plugin_names  r#�disable_imunify_pluginzPlesk.disable_imunify_pluginWrTr%rc��K�	tj���d{V��S#tj$r'}t�d|��gcYd}~Sd}~wwxYw)z$Returns a list of Plesk system usersNzFailed to get users: %s)r�	get_usersr�PanelException�logger�error)rR�es  r#rYzPlesk.get_users[so����	�����(�(�(�(�(�(�(���"�	�	�	��L�L�2�A�6�6�6��I�I�I�I�I�I�����	���s��A�A�A�Ac	��K�tj���d{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#�<lambda>z&Plesk.patchman_users.<locals>.<lambda>gs��4�>�3N�r%)�admin�reseller�client�NULLr�email�language�username�parent�level�	suspended�domains)�domain�paths)r�get_users_for_patchmanr�dict�updaterr`�ADMIN�RESSELERra�int�bool�get�append�list�values)rR�tuples�res�client_type_to_levelrirgrj�locale�client_typern�homedirrls            r#�patchman_userszPlesk.patchman_userscs������1�3�3�3�3�3�3�3�3���$����*�+N�+N�O�O���#�#���-� �N�3��.�5�
�
�	
�	
�	
� �	�	�	
���������+0�F�?�?�R�R��C��M�'�"�.4��.>�.>���F�C��M�*�%�(0�C��M�*�%�,2�f�,<�,<�b�b�&�C��M�(�#�%(�)=�k�)J�%K�%K�C��M�'�"�)-�c�)�n�n�)=�)=�C��M�+�&��8�}� � ��+�+�3�+-��H�
�i�(������H�
�i�(�/�/�"(�")���������C�J�J�L�L�!�!�!r%c��8K�tj���d{V��S)zC
        :return: list: domains hosted on server via plesk
        N)r�get_domains�rRs r#�get_user_domainszPlesk.get_user_domains�s(�����_�&�&�&�&�&�&�&�&�&r%c��8K�tj���d{V��S)z8
        :return: domain to list of users pairs
        N)r�get_domain_to_userr�s r#�get_domain_to_ownerzPlesk.get_domain_to_owner��)�����+�-�-�-�-�-�-�-�-�-r%c��8K�tj���d{V��S)z7
        Returns dict with user to email pairs
        N)r�get_user_to_emailr�s r#r�zPlesk.get_user_to_email�s)�����*�,�,�,�,�,�,�,�,�,r%c��8K�tj���d{V��S)z8
        :return: user to list of domains pairs
        N)r�get_user_to_domainr�s r#�get_domains_per_userzPlesk.get_domains_per_user�r�r%c��8K�tj���d{V��SrF)r�"count_customers_with_subscriptionsr�s r#�users_countzPlesk.users_count�s'�����;�=�=�=�=�=�=�=�=�=r%c�J�tj��tjzrdSdS)Nz*/etc/apache2/mods-available/security2.confz /etc/httpd/conf.d/security2.conf)r�id_like�DEBIANrGs r#�get_modsec_config_pathzPlesk.get_modsec_config_path�s'��� �"�"�]�%9�9�	6�?�?�5�5r%c�j�td�����}|r|hn
t��S)N�HTTPD_VHOSTS_D)rrw�set)rR�basedirs  r#�basedirszPlesk.basedirs�s1���.�/�/�3�3�5�5��#�.��y�y����.r%c��ddlm}td��5}d|���z}ddd��n#1swxYwY|ddg���}|j|��t|d�d	d
����}|S)Nr)�ConfigParserz/etc/psa/psa.confz[dummy section]
� �	)�
delimitersz
dummy sectionr�z/var/www/vhosts)�configparserr�rLrM�read_stringrrw)rH�_r��crr�base_dirs       r#�
base_home_dirzPlesk.base_home_dir�s���	.�-�-�-�-�-�
�%�
&�
&�	2�!�&������1�D�	2�	2�	2�	2�	2�	2�	2�	2�	2�	2�	2����	2�	2�	2�	2���#�t��5�5�5�����4� � � ���?�#�'�'�(8�:K�L�L�
�
���s
�:�>�>c�^�tjtj�t
d����}|����d��D]�}t|d��dkr�|�d��D]�}t|��\}}|tkr�ttjtj�t
d|����d��}tj|�
�������ccS����dS)	z�Parse 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�parse�os�path�join�PLESK_KEY_REGISTRY�getrootr+r$r.�PLESK_IMUNIFY360_PRODUCT_NAME�base64�	b64decode�encode�decode)rH�registry�memberr,r*r)�	key_values       r#�
_retrieve_keyzPlesk._retrieve_key�s,���$��G�L�L�+�^�<�<�
�
���&�&�(�(�0�0��A�A�	M�	M�F��f�f�-�-��9�9�!�>�>�*?�@�@�M�M�C�1>�s�1C�1C�.�$�h�'�+H�H�H�$2�'�-� "����$6���!"�!"���
'�
%�%�	� &�/�	�0@�0@�0B�0B�C�C�J�J�L�L�L�L�L�L�L�I���tr%c��K�	|���}n6#tjttf$r}td|z���d}~wwxYw|rt�d|��|Std���)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)r�r�
ParseError�base64Error�FileNotFoundErrorr0r[�info)rH�resultr]s   r#�retrieve_keyzPlesk.retrieve_key�s�����
	M��&�&�(�(�F�F���&��5F�G�	M�	M�	M� �!G�!�!K�L�L�L�����	M�����	��K�K�*�F�3�3�3��M��0�1�1�1s��A�A�Ac��PK�tj���d{V��}d�|D��S)z1
        :return: dict docroot to domain
        Nc��i|]	\}}}||��
Sr4r4)�.0�docrootrnr�s    r#�
<dictcomp>z'Plesk.list_docroots.<locals>.<dictcomp>�s-��
�
�
� 2����G�V�
�
�
r%)r�list_docroots_domains_users)rR�docroot_domains_userss  r#�
list_docrootszPlesk.list_docroots�sJ����'*�&E�&G�&G� G� G� G� G� G� G��
�
�6K�
�
�
�	
r%c��
K�dS)z8
        Returns panel url
        :return: str
        rr4)rRris  r#�panel_user_linkzPlesk.panel_user_link�s
����
�rr%)�userc���K�tt�����sdStjjsdStj|���sdS|||d�}t�|j	�d�|��tj|��}ttg|�������d{V��||���dt!��|du|d�S)	zB
        Notify a customer using Plesk Notifications Hook
        F�ri)�message_type�paramsr�z.notify(%s))�inputNr)r��mainip�base_url�host_server�sent_to_rootr�)r�PLESK_NOTIFICATION_SCRIPT_PATH�existsr�
AdminContacts�ENABLE_ICONTACT_NOTIFICATIONS�should_send_user_notificationsr[r�r1�json�dumpsr�PLESK_NOTIFICATION_HOOK_PATHr��
get_server_ipr)rHr�r�r�r-�stdins      r#�notifyzPlesk.notify�s�����
�2�3�3�:�:�<�<�	��5��#�A�	��5��4�d�C�C�C�	��5� ,���M�M�����s�|�0�0�0�$�7�7�7��
�4� � ���5�6�e�l�l�n�n�M�M�M�M�M�M�M�M�M�M�)��'�'�)�)��'�>�>� �D�L��

�
�	
r%ric��V�K�tj���d{V��}�fd�|D��S)Nc�*��g|]}|j�k�
|��Sr4r�)r�rnris  �r#�
<listcomp>z2Plesk.get_user_domains_details.<locals>.<listcomp>s,���
�
�
����8�0K�0K�F�0K�0K�0Kr%)r�get_user_domains_details)rHri�all_domainss ` r#r�zPlesk.get_user_domains_detailssT����� �8�:�:�:�:�:�:�:�:��
�
�
�
�!,�
�
�
�	
r%rF))r1r2r3�NAME�TCP_PORTS_PLESK�
OPEN_PORTSr0�	exception�classmethodrI�staticmethodrPr�ensure_valid_panelrSrWr�strrYr�r�r�rr�r�rur�r�rr�rr�r
r�r�r�r�r�ry�
DomainDatar�r4r%r#r6r6<s��������D�8�7�7�/�I��6�?�_�4�
�
�
,�+�+�3�3�3�
�
�	�	�J��I��$�$��[�$��'�'��\�'��T����
�
�
���
��T����
�
�
���
���c������&"�&"�&"�P'�'�'�.�.�.�-��c�3�h��-�-�-�-�.�.�.�>�3�>�>�>�>��6�6��[�6�/�#�c�(�/�/�/�/��������[����e�C��I�.�����[��6�2�3�2�2�2��[�2�
�
�
��������8<�
�
�
�
��[�
�6�
��
�	
�d�o�	�
�
�
��[�
�
�
r%r6)1r�r��loggingr��binasciirr��collectionsr�pathlibr�typingrrrr	r
�	xml.etreer�3defence360agent.application.determine_hosting_panelr
�defence360agent.contractsr�defence360agent.utilsrr�defence360agent.utils.commonrrrr�utilsrr�r��TCP_PORTS_COMMONr�r�r��	getLoggerr1r[r�r$r.rZr0�
AbstractPanelr6r4r%r#�<module>r�s��
�
�
�
���������	�	�	�	�)�)�)�)�)�)�#�#�#�#�#�#�������0�0�0�0�0�0�0�0�0�0�0�0�0�0�!�!�!�!�!�!�������-�,�,�,�,�,�:�:�:�:�:�:�:�:�5�5�5�5�5�5�������������������$�� 0���'�*H�*H�*H�H��!n��F��
��	�8�	$�	$��������
&�%��S��/�
&�
&�
&�
&�	�	�	�	�	�T�(�	�	�	�`
�`
�`
�`
�`
�D��`
�`
�`
�`
�`
r%defence360agent/subsys/panels/plesk/__pycache__/upgrade_urls.cpython-311.opt-1.pyc0000644000000000000000000001055700000000000025053 0ustar  �

��x�V�y���dZddlZddlZddlZddlmZddlmZddlm	Z	ej
e��Zed��Z
ed��ZdZd	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�<?php
echo json_encode([
    'buyUrl' => pm_Context::getBuyUrl(),
    'upgradeLicenseUrl' => pm_Context::getUpgradeLicenseUrl(),
]);
�returnc�H�ttz}	|�t��|�d��tjdddddtgdd�	��}|jd
krYt�	d|j|j
dd
���		|�d���dS#t$rYdSwxYwtj|j��	|�d���S#t$rYSwxYw#tt
jtjf$rN}t�	d|��Yd}~	|�d���dS#t$rYdSwxYwd}~wwxYw#	|�d���w#t$rYwwxYwxYw)zBCreate a temporary PHP script, execute it, and return parsed JSON.i��plesk�bin�	extensionz--exec�
imunify360T�)�capture_output�timeoutrz)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_CONTENT�chmod�
subprocess�run�
returncode�logger�warning�stderr�unlink�OSError�json�loads�stdout�TimeoutExpired�JSONDecodeError)�script_path�result�excs   �e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/plesk/upgrade_urls.py�_run_plesk_scriptr&#s��'�,�6�K�!�����/�/�/�	���%� � � ���������

� ��
�
�
�����!�!��N�N�;��!��
�d�s�d�#�
�
�
�
�	����$��/�/�/�/�/���	�	�	��D�D�	�����z�&�-�(�(�
	����$��/�/�/�/���	�	�	��D�	�����

�Z�.��0D�E�������?��E�E�E��t�t�t�	����$��/�/�/�/�/���	�	�	��D�D�	��������
�����	����$��/�/�/�/���	�	�	��D�	������s��B
D�B5�5
C�C�D�C6�6
D�D�!E5�'E0�E8�E�
E-�,E-�0E5�5E8�8F!�:F�F!�
F�F!�F�F!c�(�ddd�}t��rt���s|St��}|s|S|�d��pd���|�d��pd���d�S)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_url�upgrade_license_url�buyUrl�upgradeLicenseUrl)r�PLESK_MARKETPLACE_FLAG�existsr&�get�strip)�empty�datas  r%�get_plesk_upgrade_urlsr3Js����2�6�6�E�����'=�'D�'D�'F�'F�������D������H�H�X�&�&�,�"�3�3�5�5� $���)<� =� =� C��J�J�L�L����)�__doc__r�loggingr�pathlibr�typingr�3defence360agent.application.determine_hosting_panelr�	getLogger�__name__rr-rrr�dictr&r3�r4r%�<module>r>s�����������������������������������
��	�8�	$�	$����E�F�F����:����&����$�8�D�>�$�$�$�$�N�������r4defence360agent/subsys/panels/plesk/__pycache__/upgrade_urls.cpython-311.pyc0000644000000000000000000001055700000000000024114 0ustar  �

��x�V�y���dZddlZddlZddlZddlmZddlmZddlm	Z	ej
e��Zed��Z
ed��ZdZd	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�<?php
echo json_encode([
    'buyUrl' => pm_Context::getBuyUrl(),
    'upgradeLicenseUrl' => pm_Context::getUpgradeLicenseUrl(),
]);
�returnc�H�ttz}	|�t��|�d��tjdddddtgdd�	��}|jd
krYt�	d|j|j
dd
���		|�d���dS#t$rYdSwxYwtj|j��	|�d���S#t$rYSwxYw#tt
jtjf$rN}t�	d|��Yd}~	|�d���dS#t$rYdSwxYwd}~wwxYw#	|�d���w#t$rYwwxYwxYw)zBCreate a temporary PHP script, execute it, and return parsed JSON.i��plesk�bin�	extensionz--exec�
imunify360T�)�capture_output�timeoutrz)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_CONTENT�chmod�
subprocess�run�
returncode�logger�warning�stderr�unlink�OSError�json�loads�stdout�TimeoutExpired�JSONDecodeError)�script_path�result�excs   �e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/plesk/upgrade_urls.py�_run_plesk_scriptr&#s��'�,�6�K�!�����/�/�/�	���%� � � ���������

� ��
�
�
�����!�!��N�N�;��!��
�d�s�d�#�
�
�
�
�	����$��/�/�/�/�/���	�	�	��D�D�	�����z�&�-�(�(�
	����$��/�/�/�/���	�	�	��D�	�����

�Z�.��0D�E�������?��E�E�E��t�t�t�	����$��/�/�/�/�/���	�	�	��D�D�	��������
�����	����$��/�/�/�/���	�	�	��D�	������s��B
D�B5�5
C�C�D�C6�6
D�D�!E5�'E0�E8�E�
E-�,E-�0E5�5E8�8F!�:F�F!�
F�F!�F�F!c�(�ddd�}t��rt���s|St��}|s|S|�d��pd���|�d��pd���d�S)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_url�upgrade_license_url�buyUrl�upgradeLicenseUrl)r�PLESK_MARKETPLACE_FLAG�existsr&�get�strip)�empty�datas  r%�get_plesk_upgrade_urlsr3Js����2�6�6�E�����'=�'D�'D�'F�'F�������D������H�H�X�&�&�,�"�3�3�5�5� $���)<� =� =� C��J�J�L�L����)�__doc__r�loggingr�pathlibr�typingr�3defence360agent.application.determine_hosting_panelr�	getLogger�__name__rr-rrr�dictr&r3�r4r%�<module>r>s�����������������������������������
��	�8�	$�	$����E�F�F����:����&����$�8�D�>�$�$�$�$�N�������r4defence360agent/subsys/panels/plesk/__pycache__/utils.cpython-311.opt-1.pyc0000644000000000000000000000116600000000000023513 0ustar  �

��g�^���.�ddlmZGd�de��ZdS)�)�KWConfigc��eZdZdZdZdZdS)�PleskConfigz^{}\s+(.*)?$z{} {}z/etc/psa/psa.confN)�__name__�
__module__�__qualname__�SEARCH_PATTERN�
WRITE_PATTERN�DEFAULT_FILENAME���^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/plesk/utils.pyrrs ������$�N��M�*���r
rN)�defence360agent.utils.kwconfigrrrr
r�<module>rsK��3�3�3�3�3�3�+�+�+�+�+�(�+�+�+�+�+r
defence360agent/subsys/panels/plesk/__pycache__/utils.cpython-311.pyc0000644000000000000000000000116600000000000022554 0ustar  �

��g�^���.�ddlmZGd�de��ZdS)�)�KWConfigc��eZdZdZdZdZdS)�PleskConfigz^{}\s+(.*)?$z{} {}z/etc/psa/psa.confN)�__name__�
__module__�__qualname__�SEARCH_PATTERN�
WRITE_PATTERN�DEFAULT_FILENAME���^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/plesk/utils.pyrrs ������$�N��M�*���r
rN)�defence360agent.utils.kwconfigrrrr
r�<module>rsK��3�3�3�3�3�3�+�+�+�+�+�(�+�+�+�+�+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 = """\
<?php
echo json_encode([
    'buyUrl' => 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  �

�`����V�ddlZddlZddlZddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlZddlZddl
Z
ddlZddlZddlZddlmZddlmZmZddlmZmZddlmZmZmZddlmZddl m!Z!ddl"m#Z#m$Z$m%Z%m&Z&dd	lm'Z'dd
lm(Z(ddl)m*Z*ddl+m,Z,dd
l-m.Z.m/Z/m0Z0m1Z1m2Z2m3Z3m4Z4m5Z5ddl6Z6ddl7Z7ddl8Z8ddl9m:Z:ddl;m<Z<m=Z=ddl>m?Z?e5de0���Z@ejAeB��ZCdZDdZEdaFdZGe*d��ZHdZIe*d��ZJe*d��ZKe*d��ZLeMejN�Odd����ZPGd�d e!��ZQejRd�!��d"���ZSed�d#���ZTeCddfd$�ZUGd%�d&��ZVdejWejWd'dfd(e4eMeXeXffd)�ZYddd*�d+�ZZGd,�d-ej[��Z\e\fd(eXfd.�Z]e\fd�d/�Z^d�d(e_fd1�Z`d2�ZaGd3�d4��Zbd5�ZcGd6�d7ed��Zed8�Zfd9�Zg	d�d;�Zhd0ddd0ddd<�d=eie_zejjzd>eMdzd(eifd?�ZkejRd��d@���Zld�d(e_fdA�ZmGdB�dC��ZnejodDfdEe_dFeMd(e_fdG�Zp	d�dEe_dFeMd(e4e_eMffdH�ZqdI�ZrdJ�Zsd�dL�Zt	d�dN�ZudO�ZvejR��dPeMfdQ���Zwejxe6jydRd0�S��ZzdT�Z{dEejjdUeXd(dfdV�Z|dW�Z}dEejjdXeXd(eifdY�Z~dZ�ZGd[�d\��Z�eEfd]�Z�d^�Z�ed_eMfd`���Z�d�dbe_d(e*fdc�Z�dde_dbe_d(eifde�Z�df�Z�d�dhedieMd(efdj�Z�dk�Z�Gdl�dme���Z�ejRd:�!��dn���Z�do�Z�d�d(eifdp�Z�Gdq�dr��Z�e�Z�dsdt�du�Z�dv�Z�dw�Z�ejRd�!��d(eifdx���Z�dyZ�dz�Z�d�d{e_d(e_fd|�Z�d�d}�Z�dd~�d�Z�dd~�d��Z�dd~�d��Z�d�Z�ejRd�!��d(eifd����Z�d�d{e_d(e_fd��Z�dd~�d��Z�Gd��d�ej���Z�d��Z�						d�d��Z�d��Z�eddfd��Z�e�fd��Z�d��Z�d�Z�dsZ�d�Z�d�Z�d(eifd��Z�d'd��d��Z�e�d��d��Z�d��Z�d��Z�dde*d(eXfd��Z�d�eXd(e*fd��Z�d��Z�d��Z�d�d��Z�d�e0d�e/ffd��Z�d��Z�dsZ�dsZ�d��Z�dRd��d{e3e_fd��Z�d�dsd��d��Z�d�d��Z�Gd��d���Z�dXe_d(e�e_e_fdzfd��Z�dXe_d(e�e_e_fdzfd��Z�ejRd�!��d(e�e�e_e0e_ge�e_e_fdzfffd����Z�Gd��d�ed��Z�d��Z�e�hd����Z�d�ee_d(e�e_e_dzffd��Z�d�e_d�e�e_d�e0e_ge�e_e_fdzfd(e�e_e_dzffd��Z�eCj�fd(e_fd��Z�d�eMfd��Z�d�e1e.e.fd�eMfd��Z�ejRd�!��d����Z�d�e*d(eMfd��Z�d�e*fd��Z�d„Z�dS)��N)�Future)�OrderedDict�deque)�	Generator�Iterable)�	ExitStack�contextmanager�suppress)�	timedelta)�Enum)�LOCK_EX�LOCK_NB�LOCK_UN�flock��wraps)�islice)�Path)�NamedTemporaryFile)�Any�	Awaitable�Callable�Dict�	FrozenSet�List�Tuple�TypeVar)�OperationalError�)�is_safe_subdir_name�rmtree)�atomic_rewrite_fd�F)�bound�user_id)z
User-AgentzAccept-LanguagezAccept-Encoding�
Connection�DNT���z.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�<c��eZdZdZdZdZdZdS)�ScopezAV onlyzAV and IM360z
IM360 onlyzIM360 resident onlyN)�__name__�
__module__�__qualname__�AV�AV_IM360�IM360�IM360_RESIDENT���S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/__init__.pyr,r,Hs"������	�B��H��E�*�N�N�Nr5r,)�maxsizec��t���o2t���ot���S)z�Return True if /run/systemd/system folder exists:
    [sd_booted]
    (https://www.freedesktop.org/software/systemd/man/sd_booted.html)
    )�_SYSTEMD_BOOTED_DIR�exists�is_dir�
is_symlinkr4r5r6�is_systemd_bootr=OsC��	�"�"�$�$�	1��&�&�(�(�	1�#�.�.�0�0�0�r5c#�K�|s|sJ�tj��}|p|jd|��dV�tj��}|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�	monotonic�debug)�action�logger_�log�start�stops     r6�timeitrG\sz������c���>��N���E��S��G�M�<��0�0�0�	�E�E�E��>���D��S��G�M�3�V�T�E�\�J�J�J�J�Jr5c��������fd�}|S)Nc�N���tj�������fd���}|S)Nc���K�t�p�j�����5�|i|���d{V��cddd��S#1swxYwYdS�N)rCrD�rGr-��args�kwargsrB�funrDrCs  ����r6�wrapperz+timefun.<locals>.decorator.<locals>.wrapperns��������.�#�,��S�I�I�I�
2�
2� �S�$�1�&�1�1�1�1�1�1�1�1�
2�
2�
2�
2�
2�
2�
2�
2�
2�
2�
2�
2����
2�
2�
2�
2�
2�
2s�8�<�<��	functoolsr�rPrQrBrDrCs` ���r6�	decoratorztimefun.<locals>.decoratormsH����	���	�	�	2�	2�	2�	2�	2�	2�	2�
�	�	2��r5r4�rCrBrDrUs``` r6�timefunrWls0�������������r5c�0�eZdZdZeeddfd���ZdS)�synczF
    the same timefun decorator variation but without async/await
    Nc��������fd�}|S)z�
        :param logging.Logger: logger you want action name and timing
            to be logged with
        :param str: action name to log
        c�N���tj�������fd���}|S)Nc�z��t�p�j�����5�|i|��cddd��S#1swxYwYdSrKrLrMs  ����r6rQz0sync.timefun.<locals>.decorator.<locals>.wrapper�s�����F�2�c�l�G��M�M�M�0�0��3��/��/�/�0�0�0�0�0�0�0�0�0�0�0�0����0�0�0�0�0�0s�0�4�4rRrTs` ���r6rUzsync.timefun.<locals>.decorator�sH����
�_�S�
!�
!�
0�
0�
0�
0�
0�
0�
0�"�
!�
0��Nr5r4rVs``` r6rWzsync.timefun}s0�����	�	�	�	�	�	�	��r5)r-r.r/�__doc__�staticmethod�loggerrWr4r5r6rYrYxsE����������t������\���r5rYF�returnc	��$K�|�|�td���tj}|r't|t��sJ�|g}t
j}n*t|ttf��sJ�t
j	}ttdtd������|��||||dd�|���d{V��}|�
|���d{V��\}	}
|����d{V��}t�d	|||||	|
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_tries�on_errorT��stdin�stdout�stderr�start_new_sessionz run(%s, stdin=%s, shell=%s) = %s)�
ValueError�_subprocess�PIPE�
isinstance�str�asyncio�create_subprocess_shell�list�tuple�create_subprocess_exec�retry_on�BlockingIOError�	await_for�communicate�waitr_rA)�commandrgrhri�shell�inputrO�create_subprocess�proc�out�err�	exit_codes            r6�runr��s�����
�����N�O�O�O�� ���;��'�3�'�'�'�'�'��)��#�;����'�D�%�=�1�1�1�1�1�#�:������1�y��/C�/C�/C����	���

����������������D��%�%�e�,�,�,�,�,�,�,�,�H�C���i�i�k�k�!�!�!�!�!�!�I�
�L�L�*��
�
�	�C�������c�3��r5)�loop�timeoutc��|�rtd��D]b}	tj��}|���sn7n#t$rYnwxYwtjtj�����c|�tjt|tj
��r|ntj|��|�����S)zjRun coroutine from a blocking code (outside the event loop).

    Coroutine will be wrapped in Task.

    Nrb�r�)�rangerp�get_event_loop�	is_closed�RuntimeError�set_event_loop�new_event_loop�run_until_complete�wait_forrnr�Task)�coror�r��_s    r6�run_coror��s����|��q���	=�	=�A�
��-�/�/���~�~�'�'���E��� �
�
�
���
����

�"�7�#9�#;�#;�<�<�<�<��"�"����t�W�^�4�4�L�D�D�'�,�t�:L�:L��	
�	
�	
���s�?�
A�Ac��eZdZd�ZdS)�
CheckRunErrorc��d}|�|j|j|j���pd|j���pd���S)Nz[Command {cmd!r} returned non-zero code {returncode},
		Stdout: {output},
		Stderr: {error}
)�cmd�
returncode�output�error)�formatr�r�r��decoderi)�self�_MESSAGEs  r6�__str__zCheckRunError.__str__�s_��
$�	�
��������;�%�%�'�'�/�4��+�$�$�&�&�.�$�	�
�
�	
r5N)r-r.r/r�r4r5r6r�r��s#������
�
�
�
�
r5r�c��`K�t|fi|���d{V��\}}}|dkr|||||���|S)zJ
    Asynchronous command executor.
    Returns output as bytestring.
    Nr)r�)rz�	raise_excrOr�rr�s      r6�	check_runr��sZ����
"%�W�!7�!7��!7�!7�7�7�7�7�7�7��J��S��Q����i�
�G�S�#�6�6�6��Jr5c��K�t|tjtjtj����d{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)r�rl�DEVNULL)rzr��coder�s    r6�check_exit_coder��sy����
���!��"��"�	���������J�D�!�Q��q�y�y��i��g�&�&�&��yr5Tc��K�	t|fi|���d{V��\}}}n,#t$rt�d|��YdSwxYw|r%|dkrt�d|||��dS	|������}n,#t$rt�d|��YdSwxYw|S)zGSafe run command.
    Returns stdout as string or empty string on errorNzCommand %s failed with OSError�rz'Command %s failed with exit code %s: %sz#Command %s returned non-utf8 output)r��OSErrorr_�warning�stripr��UnicodeDecodeError)rz�check_returncoderO�rcrr��results       r6�safe_runr�s����� ��3�3�F�3�3�3�3�3�3�3�3���C�����������7��A�A�A��r�r�������B�!�G�G����5�w��C�	
�	
�	
��r�������#�#�%�%�����������<�g�F�F�F��r�r������Ms!��%A�A�0&B�%C�?Cc����d���fd�}|S)znon asyncio vesion of lazy initNc� ����
�����S�Nr4)�decorated_f�placeholders��r6rQz#plainold_lazy_init.<locals>.wrapper!s�����%�+�-�-�K��r5r4)r�rQr�s` @r6�plainold_lazy_initr�s.�����K��������Nr5c��eZdZdZd�Zd�ZdS)�
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_resultr�rp�Lock�_lock)r��cb_coro�check_every_n_secondss   r6�__init__zPeriodicCheck.__init__3sD����
�&;��#�%)�^�%5�%5�8M�%M��"�"&���'���5�5��
�
�
r5c��K�|���4�d{V��tj��|jz
}||jkrVt
�d|j|j��tj��|_|j|i|���d{V��|_|jcddd���d{V��S#1�d{V��swxYwYdS)Nz3Timeout %d seconds has expired, doing the check: %s)	r�r?r@r�r�r_rAr�r�)r�rNrO�deltas    r6�__call__zPeriodicCheck.__call__<s\�����:�:�<�<�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+��N�$�$�t�'A�A�E���3�3�3����I��/��M����
.2�^�-=�-=��*�0=��
�t�0N�v�0N�0N�*N�*N�*N�*N�*N�*N��'��*�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+����
	+�
	+�
	+�
	+�
	+�
	+s�BB3�3
B=�B=N)r-r.r/r]r�r�r4r5r6r�r�*s<��������6�6�6�+�+�+�+�+r5r�c����fd�}|S)Nc�$��t|���Sr�)r�)r��nsecs �r6�decoratezcache_result.<locals>.decorateKs����T�4�(�(�(r5r4)r�r�s` r6�cache_resultr�Js#���)�)�)�)�)��Or5c��eZdZdZdS)�RecurringCheckStopz:
    raised by coroutine to stop recurring_check loop
    N�r-r.r/r]r4r5r6r�r�Qs��������	�Dr5r�c���K�	t|��r!tj|di|�����d{V��ntj|���d{V��dS#tj$rYdSwxYw)NFTr4)�callablerp�sleep�CancelledError)�period�
period_kwargss  r6�wait_for_periodr�Ys�������F���	(��-��� 7� 7�� 7� 7�8�8�8�8�8�8�8�8�8�8��-��'�'�'�'�'�'�'�'�'��u���!�����t�t����s�A
A�A#�"A#c��8K�|r|rt|fi|���d{V��ndS�NF)r�)�checkr�r�s   r6�should_stop_after_period_passedr�dsG�����	��	�o�f�6�6�
�6�6�6�6�6�6�6�6�6�
�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:
    c�F���t��������fd���}|S)Nc���K�d}	t��fi����d{V��rdS	�|i|���d{V��d}�n#t$rOd|vrG	t|dt��r|d���n#t
$rYnwxYwYdStj$rYdSt$r�}|dz
}|�kr t�
d��Yd}~dSt|tj��r3t�
d|j
|j|j|j��nt�
d���Yd}~nd}~wwxYwt��fi����d{V��rdS��T)NrT�	lock_filerz-Error count exceeded limit,exiting check loopz+Failed to run %s (%s). stdout=%s, stderr=%szError executing %s)r�r�rnr�unlink�FileNotFoundErrorrpr��	Exceptionr_�	exceptionrl�CalledProcessErrorr�r�r�ri)	rNrO�consecutive_err_cnt�exc�check_period_first�consecutive_err_limitrPr�r�s	    �����r6�wrappedz3recurring_check.<locals>.decorator.<locals>.wrappedysH�����"#��'
�8�&����2?�����������E�,��#�t�.�v�.�.�.�.�.�.�.�.�.�:+,�'�'��9*����"�f�,�,�!�)�&��*=�t�D�D�=� &�{� 3� :� :� <� <� <���0�!�!�!� �D�!�����E�E��-�����E�E� �D�D�D�'�1�,�'�*�-B�B�B��(�(�K����������!�#�{�'E�F�F�	D��(�(�I��G��N��J��J������(�(�)=�s�C�C�C����������!D����&9�*�*�F���6C�����������E�O'
sK�0�D?�5A5�4D?�5
B�?D?�B�D?�D?�	D?�"%D:�
A(D:�:D?r)rPr�r�r�r�r�s` ����r6rUz"recurring_check.<locals>.decoratorxsI����	�s���)	�)	�)	�)	�)	�)	�)	�)	�
��)	�V�r5r4)r�r�r�r�rUs```` r6�recurring_checkr�ls7������-�-�-�-�-�-�-�-�^�r5)�backup�uid�gid�allow_empty_content�permissions�dir_fdr�r�c	���t|t��r|���}|�'|rtd���t	|||||||���Stt��5t|d��5}|�t|��dz��}	ddd��n#1swxYwY|	|kr	ddd��dS	ddd��n#1swxYwY|s |st�d||��dS|r�t|ttj
f��r|}
ntj|��tz}
tt��5t!j||
��ddd��n#1swxYwY|�k	t%jtj|��j��}n>#t$r1tjd��}tj|��d	|z}YnwxYwtj�|��\}}
t1|�����st
d
|�����t5��5}t7d|d|
d
zdd���5��fd�}|�|����|������|�*|�(tj�� ��||��tj!�� ��|��tj"�� ����ddd��n#1swxYwYtj#�j$|��|�%��ddd��n#1swxYwYdS)a�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*.
    Nz/backup is not supported when dir_fd is provided)r�r�r�r�r��rbrFzempty content: %r for file: %sri�zParent dir is missing: �wbz	.i360editr�)�mode�dir�suffix�prefix�	buffering�deletec���tt��5tj�j��ddd��dS#1swxYwYdSr�)r
r��os�remove�name)�tfs�r6�cleanupzatomic_rewrite.<locals>.cleanup�s�����/�0�0�'�'��I�b�g�&�&�&�'�'�'�'�'�'�'�'�'�'�'�'����'�'�'�'�'�'s�=�A�AT)&rnro�encoderkr"r
r��open�read�lenr_r�r��PathLike�fspath�BACKUP_EXTENSION�shutil�copy�stat�S_IMODE�st_mode�umask�path�splitrr:rr�callback�write�flush�chown�fileno�chmod�fsync�renamer��pop_all)�filename�datar�r�r�r�r�r��file�old_content�backup_filename�
current_umask�dirpath�basename�stackr�r�s                @r6�atomic_rewriter�s����6�$������{�{�}�}��
���	P��N�O�O�O� ����� 3�#��
�
�
�	
�
�#�	$�	$���
�(�D�
!�
!�	3�T��)�)�C��I�I��M�2�2�K�	3�	3�	3�	3�	3�	3�	3�	3�	3�	3�	3����	3�	3�	3�	3��$����	�����������������������������t�����5�t�X�F�F�F��u�
�3��f�s�B�K�0�1�1�	E�$�O�O� �i��1�1�4D�D�O�
�'�
(�
(�	3�	3��K��/�2�2�2�	3�	3�	3�	3�	3�	3�	3�	3�	3�	3�	3����	3�	3�	3�	3���	1��,�r�w�x�'8�'8�'@�A�A�K�K�� �	1�	1�	1��H�Q�K�K�M��H�]�#�#�#��=�.�0�K�K�K�		1������
�
�h�/�/��G�X���=�=���!�!�G�� E�'� E� E�F�F�F�	�����
�����c�>���

�
�
�	"��
'�
'�
'�
'�
'�
�N�N�7�#�#�#��H�H�T�N�N�N��H�H�J�J�J���3�?��������c�3�/�/�/��H�R�Y�Y�[�[�+�.�.�.��H�R�Y�Y�[�[�!�!�!�-	"�	"�	"�	"�	"�	"�	"�	"�	"�	"�	"����	"�	"�	"�	"�.	�	�"�'�8�$�$�$�
�
�
����3������������������8�4s��(C�9&B+�C�+B/	�/C�2B/	�3
C�C�C�E<�<F�F�
+F6�68G1�0G1�M>�0B=L9�-M>�9L=	�=M>�L=	�1M>�>N�Nc��	td��������S#t$rYdSwxYw)Nz/etc/system-release)r�	read_text�rstripr�r4r5r6�os_release_and_versionr"sP����)�*�*�4�4�6�6�=�=�?�?�?�������t�t����s�25�
A�Ac���|p
t��}|r-tjd|��}|r|�d��Snt���td|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"�re�search�group�cache_clearrk)�release_and_version�rv�matchs   r6�
os_versionr+st��
�	8� 6� 8� 8�B�	�-��	�3�R�8�8���	"��;�;�q�>�>�!�	"�	�*�*�,�,�,�
�8�2�=�
>�
>�>r5c��eZdZdZed��Zed��Zed��ZdZe	d���Z
e	deee
ffd���Ze	deefd	���Ze	defd
���Ze	defd���Ze	d���Ze	d
���Ze	d���Ze	d���Ze	d���Ze	d���Ze	d���Ze	d���Ze	d���ZdS)�
OsReleaseInfoz/etc/os-release)�debian)�rhel�fedora�centos)�unknownNc��t|j��5}|D]U}	|����d��\}}|�d��||<�F#t
$rY�RwxYw	ddd��n#1swxYwYd|vr,t
|d�����|d<dSt
|�dd��f��|d<dS)N�=�"�ID_LIKE�ID�linux)r��ETC_OS_RELEASEr!rr�rk�	frozenset�get)�cls�dict_�f�line�k�vs      r6�dict_from_filezOsReleaseInfo.dict_from_file.s3��
�#�$�
%�
%�	���
�
����;�;�=�=�.�.�s�3�3�D�A�q� �w�w�s�|�|�E�!�H�H��!�����D�����	
�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�����(��y�)9�)?�)?�)A�)A�B�B�E�)���� )�%�)�)�D�'�*B�*B�)D�E�E�E�)���s5�A;�AA�A;�
A+�(A;�*A+�+A;�;A?�A?r`c�\�|j��t��}tj�|j��r|�|��n�tj��}|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)Nr�redzRed Hat Enterprise Linuxr/r7z
{} {} ({})rrb�PRETTY_NAME)�
cloudlinuxr1r/r6)�ubuntur.r2)r=�dictr�r
r:r9rB�distro�linux_distribution�lowerrr��RHEL_FEDORA_CENTOS�DEBIAN�UNKNOWN)r<r=�d�osids    r6�to_dictzOsReleaseInfo.to_dict=sQ���9��$(�F�F�E��w�~�~�c�0�1�1�
5��"�"�5�)�)�)�)��-�/�/���5��1��5��Q�4�:�:�<�<�-�-�/�/��2�D��u�}�}�)C�q��t�)K�)K�%��"&�E�$�K�+7�+>�+>��!��a��d�A�a�D�,�,�E�-�(��?�?�?�+.�+A��i�(�(��!5�5�5�+.�:��i�(�(�+.�;��i�(�(�"+�E�$�K�'*�{�E�)�$�+4�E�-�(��C�I��y�r5c�6�|���dS)Nr6�rQ�r<s r6�id_likezOsReleaseInfo.id_like\s���{�{�}�}�Y�'�'r5c�6�|���dS)NrErSrTs r6�pretty_namezOsReleaseInfo.pretty_name`s���{�{�}�}�]�+�+r5c�R�|����dd��S)zi
        :return: OS name, like centos, ubuntu, debian, cloudlinux, redhat
        in lower case
        r7r2)rQr;rTs r6�get_oszOsReleaseInfo.get_osds"���{�{�}�}� � ��y�1�1�1r5c�2�|���dkS)Nr/�rYrTs r6�is_rhelzOsReleaseInfo.is_rhells���z�z�|�|�v�%�%r5c�2�|���dkS)Nr1r[rTs r6�	is_centoszOsReleaseInfo.is_centosp����z�z�|�|�x�'�'r5c�2�|���dkS)NrGr[rTs r6�	is_ubuntuzOsReleaseInfo.is_ubuntutr_r5c�.�|���dvS)N)rF�cloudlinuxserverr[rTs r6�
is_cloudlinuxzOsReleaseInfo.is_cloudlinuxxs���z�z�|�|�A�A�Ar5c�J�tj�t��Sr�)r�r
r:�_CL_SOLO_EDITION_FILErTs r6�is_cloudlinux_soloz OsReleaseInfo.is_cloudlinux_solo|s���w�~�~�3�4�4�4r5c�2�|���dkS)Nr.r[rTs r6�	is_debianzOsReleaseInfo.is_debian�r_r5c�2�|���dkS)N�olr[rTs r6�is_oracle_linuxzOsReleaseInfo.is_oracle_linux�s���z�z�|�|�t�#�#r5c�2�|���dkS)N�	almalinuxr[rTs r6�is_almalinuxzOsReleaseInfo.is_almalinux�s���z�z�|�|�{�*�*r5c�2�|���dkS)N�rockyr[rTs r6�
is_rockylinuxzOsReleaseInfo.is_rockylinux�s���z�z�|�|�w�&�&r5)r-r.r/r9r:rMrLrNr=�classmethodrBrrorrQrrUrWrYr\r^rardrgrirlrorrr4r5r6r-r-&s������&�N�
�Y�{�
#�
#�F�"��#?�@�@���i��%�%�G��E��F�F��[�F����S�#�X������[��<�(�	�#��(�(�(��[�(��,�C�,�,�,��[�,��2�s�2�2�2��[�2��&�&��[�&��(�(��[�(��(�(��[�(��B�B��[�B��5�5��[�5��(�(��[�(��$�$��[�$��+�+��[�+��'�'��[�'�'�'r5r-�r�	chunksizec�0�t|||��dS)z�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.
    r)�file_hash_and_size)r�	hash_funcrus   r6�	file_hashry�s���h�	�9�=�=�a�@�@r5c��|��}d}t|d��5}	|�|��}|sn(|�|��|t|��z
}�@	ddd��n#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).rr�TN)r�r��updater�	hexdigest)rrxru�hash_�sizer>�chunks       r6rwrw�s���
�I�K�K�E��D�	
�h��	�	���	��F�F�9�%�%�E��
���L�L������C��J�J��D�	��	�������������������?�?���d�"�"s�AA,�,A0�3A0c��|���\}}||kr&tdjdit�������|S)z0Given login.defs line, return *varname*'s value.z"Expected {varname!r}, got {name!r}r4)rrkr��vars)�varname�	defs_liner��values    r6�_parse_name_valuer��sJ���/�/�#�#�K�D�%��$����D�=�D�N�N�t�v�v�N�N�O�O�O��Lr5c�H�tdkrt��\a}tS)Nr()�_MIN_UID�_get_max_min_uid)r�s r6�get_min_uidr��s���2�~�~�&�(�(���!��Or5�/etc/login.defsc�h�d\}}	t|��5}|D]f}|�d��rttd|����}|�d��rttd|����}�g	ddd��n#1swxYwYn#tt
f$rYnwxYw||fS)z�Get UID_MIN, UID_MAX from the login.defs file specified as *path*.

    On error, return default for the current OS values.

    )i�i`��UID_MIN�UID_MAXN)r��
startswith�intr�r�rk)r
�uid_min�uid_maxrr?s     r6r�r��s(��#��G�W�	
�
�$�Z�Z�	F�4��
F�
F���?�?�9�-�-�F�!�"3�I�t�"D�"D�E�E�G��?�?�9�-�-�F�!�"3�I�t�"D�"D�E�E�G��
F�	F�	F�	F�	F�	F�	F�	F�	F�	F�	F�	F����	F�	F�	F�	F���
�Z� �
�
�
���
�����G��s5�B�A*B
�B�
B�B�B�B�B-�,B-�zimunify360-captchazimunify360-webshieldc�l����t��\�����fd�tj��D��S)z~
    :param excludes: users to exclude in results
    :return: list: list of pwd.struct_passwd objects representing users
    c�P��g|]"}�|jcxkr�k�nn|j�v� |��#Sr4��pw_uid�pw_name)�.0�entry�excludesr�r�s  ���r6�
<listcomp>z(get_non_system_users.<locals>.<listcomp>�sS��������e�l�-�-�-�-�g�-�-�-�-�-�%�-�x�2O�2O�	�2O�2O�2Or5�r��pwd�getpwall)r�r�r�s`@@r6�get_non_system_usersr��sR�����(�)�)��G�W��������\�^�^����r5c�d��t��\�}�fd�tj��D��S)z;
    :return: list: list of str with system user names
    c�4��g|]}�|jk�
|j��Sr4r�)r�r�r�s  �r6r�z)get_system_user_names.<locals>.<listcomp>�s.�������W���5L�5L��
�5L�5L�5Lr5r�)r�r�s @r6�get_system_user_namesr��sE���"�#�#�J�G�Q�����#&�<�>�>����r5r�c�0�t��\}}||kSr�)r�)r�r�r�s   r6�is_system_userr��s��'�)�)��G�W���=�r5�d)r7�typedc��t|d��5}|�dd��}|dkrF|�|dz
��|�d��dkr|�d��|�|��|�d��s|�d��ddd��dS#1swxYwYdS)Nzr+rrbr�
�r��seekr�r
�endswith�rrr>�
last_char_poss    r6�append_with_newliner�s���	
�h��	�	�
�����q�!���
��A���
�F�F�=�1�$�%�%�%��v�v�a�y�y�D� � �����
�
�
�	����
�
�
��}�}�T�"�"�	�
�G�G�D�M�M�M�
�
�
�
�
�
�
�
�
�
�
�
����
�
�
�
�
�
��B"C�C�Crc��t|d��5}|�dd��}|dkrF|�|dz
��|�d��dkr|�d��|�|��|�d��s|�d��ddd��dS#1swxYwYdS)z>Append *data* to *filename* making sure there is 
 at the end.zr+brrbr�
Nr�r�s    r6�append_with_newline_bytesr�s���	
�h��	�	�
�!����q�!���
��A���
�F�F�=�1�$�%�%�%��v�v�a�y�y�E�!�!��������	����
�
�
��}�}�U�#�#�	�
�G�G�E�N�N�N�
�
�
�
�
�
�
�
�
�
�
�
����
�
�
�
�
�
r�c���d}t|d��5}t�fd�|D����sd}ddd��n#1swxYwY|rt|���|S)z�Add *line* to *filename* if it is not present in the file

    Returns:
        True if the file was changed, False otherwise.
    F�rc3�H�K�|]}|����kV��dSr��r��r��_liner?s  �r6�	<genexpr>z&ensure_line_in_file.<locals>.<genexpr>)�0�����8�8�U�5�;�;�=�=�D�(�8�8�8�8�8�8r5TN)r��anyr��rr?�changedr>s `  r6�ensure_line_in_filer�!s�����G�	
�h��	�	����8�8�8�8�a�8�8�8�8�8�	��G��������������������,��H�d�+�+�+��N��>�A�Ar?c���d}t|d��5}t�fd�|D����sd}ddd��n#1swxYwY|rt|���|S)z�Add *line* to *filename* if it is not present in the file.

    Returns:
        True if the file was changed, False otherwise.
    Fr�c3�H�K�|]}|����kV��dSr�r�r�s  �r6r�z,ensure_line_in_file_bytes.<locals>.<genexpr>8r�r5TN)r�r�r�r�s `  r6�ensure_line_in_file_bytesr�0s�����G�	
�h��	�	����8�8�8�8�a�8�8�8�8�8�	��G��������������������2�!�(�D�1�1�1��Nr�c��tj�|��}t|d��5}t	d|d���5}|D]/}|���|kr|�|���0tj|j|��ddd��n#1swxYwYddd��dS#1swxYwYdS)Nr��wF)r�r�r�)	r�r
�dirnamer�rr�r
rr�)rr?�basedir�sfr�r�s      r6�remove_line_from_filer�?s5���g�o�o�h�'�'�G��X�s���%�!�����?�?�?�%�CE��	 �	 �E��{�{�}�}��$�$���������
�	�"�'�8�$�$�$�%�%�%�%�%�%�%�%�%�%�%����%�%�%�%�%�%�%�%�%�%�%�%�%�%�%�%����%�%�%�%�%�%s6�B4�A
B�B4�B 	� B4�#B 	�$B4�4B8�;B8c�,�eZdZdZdZefd�Zd�Zd�ZdS)�FileLockz`
    Simple context manager to enable
    UNIX-specific file locking with flock system call
    r�c�Z�||_d|_t|d��|_||_dS)NFr�)r
�lockedr�rr�)r�r
r�s   r6r�zFileLock.__init__Ss*����	������s�O�O��	�����r5c��K�tj��}		t|jttz��d|_|S#ttf$r}|jtj	kr�|j
tj��|z
kr&t�d|j
��Yd}~dStjd���d{V��Yd}~nd}~wwxYw��)NTz)Failed to lock file %s. Timeout exceeded.r)r?rrr
rr�r��IOError�errno�EAGAINr�r_r�r
rpr�)r�rE�exs   r6�
__aenter__zFileLock.__aenter__Ys������	����	'�
'��d�i��7�!2�3�3�3�"�������W�%�
'�
'�
'��8�u�|�+�+���\�D�I�K�K�%�$7�7�7��N�N�C�T�Y�����E�E�E�E�E��m�A�&�&�&�&�&�&�&�&�&�&�&�&�&�&�����
'����	's�*A�C�AC�/C�Cc��K�|jrt|jt��d|_|j���dSr�)r�rrr�close)r��exc_type�exc_val�exc_tbs    r6�	__aexit__zFileLock.__aexit__qsC�����;�	&��$�)�W�%�%�%�����	�������r5N)r-r.r/r]�_TIMEOUTr�r�r�r4r5r6r�r�KsZ��������
�H�%-�����'�'�'�0����r5r�c����	|g}|��fd�t|�����D����tj��}|�d�|���dd����|���S#ttf$r%}t�d|��Yd}~nd}~wwxYwdS)Nc3�D�K�|]\}}|�v�	t|��V��dSr�)ro)r��fieldr��fieldss   �r6r�z user_identity.<locals>.<genexpr>�s?�����
�
���u�����
��J�J�����
�
r5r��utf8�surrogateescapez9Generation of user identity hash failed, invalid data: %s)
�extend�sorted�items�hashlib�sha1r{�joinr�r|rk�UnicodeEncodeErrorr_r�)�attackers_ip�sourcer��uid_data�hash_alg�es  `   r6�
user_identityr�{s���
�!�>�����
�
�
�
� &�v�|�|�~�~� 6� 6�
�
�
�	
�	
�	
��<�>�>���������)�)�0�0��9J�K�K�L�L�L��!�!�#�#�#���*�+�
�
�
����G��	
�	
�	
�	
�	
�	
�	
�	
�����
����
�4s�B%B)�)C�:C�Cc�0�tj��dkS�Nr)r��getuidr4r5r6�is_root_userr��s��
�9�;�;�!��r5�maskc#�K�tj|��}	dV�tj|��dS#tj|��wxYwr�)r�r	)r��current_masks  r6�run_with_umaskr��sM�����8�D�>�>�L��
����
������������������s	�2�Ar��usernamec�~�t|t��std|z���tj|vrtd���	tj|��}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)
rnrorkr��sepr��getpwnam�KeyErrorr�r
r��pw_dirr)r��relpath�pw�abs_paths    r6�get_abspath_from_user_dirr��s����h��$�$�K��>��I�J�J�J�	�v�����+�,�,�,�E�
�\�(�
#�
#�����E�E�E��2�9�9�(�C�C�D�D�D�E�����w�|�|�B�I�w�/�/�H���>�>�s�A�-Br
c���d}	t|��}t|���|��d}n>#t$r1}t�t
|����Yd}~nd}~wwxYw|S)NFT)r�r�relative_torkr_r�ro)r
r��status�	user_homer�s     r6�does_path_belong_to_userr�s���
�F��-�h�7�7�	��T�
�
���y�)�)�)������������s�1�v�v������������������Ms�38�
A3�'A.�.A3c��tj�|��std|z���	tj�|��rg	tjtj|��j��j	S#t$r)ttj|��j��cYSwxYwtj�|��}��)NzPath %s should be absolute!)
r�r
�abspathrkr:r��getpwuidr�st_uidr�r�ror��r
s r6�get_path_ownerr	�s���
�7�?�?�4� � �?��6��=�>�>�>�%�
�7�>�>�$���	1�
1��|�B�G�D�M�M�$8�9�9�A�A���
1�
1�
1��2�7�4�=�=�/�0�0�0�0�0�
1�����w���t�$�$��
%s�/B�0B6�5B6���iterable�
chunk_sizec#�K�t|��}tt||����}|r%|V�tt||����}|�#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)rr�i�pieces    r6�split_for_chunkr�sp����	
�X���A����:�&�&�'�'�E�
�,������V�A�z�*�*�+�+���,�,�,�,�,r5c���t|t��r+td�|���D����St|t��rtd�|D����S|S)Nc3�>K�|]\}}|t|��fV��dSr���freeze)r��keyr�s   r6r�zfreeze.<locals>.<genexpr>�s1����J�J�*�#�u�#�v�e�}�}�-�J�J�J�J�J�Jr5c3�4K�|]}t|��V��dSr�r)r�r�s  r6r�zfreeze.<locals>.<genexpr>�s(����2�2�u�V�E�]�]�2�2�2�2�2�2r5)rnrHr:r�rrrs)rOs r6rr�sm���!�T���3��J�J����	�	�J�J�J�J�J�J�	�A�t�	�	�3��2�2��2�2�2�2�2�2��Hr5c�&��eZdZdZiZ�fd�Z�xZS)�	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    �r6r�zSingleton.__call__�sr����F�4�L�L�&��.�.�1���~�!�!�#�&�&�	�"@�%�	�3�"7�"7�"@��#��#�#�C�N�3���~�c�"�"r5)r-r.r/r]rr��
__classcell__)rs@r6rr�sI���������
�J�#�#�#�#�#�#�#�#�#r5rc���tj�dd���5}|������cddd��S#1swxYwYdS)z3
    :return str: server's external IP address
    zhttps://api.ipify.orgrbr�N)�urllib�request�urlopenr�r�)r�s r6�get_external_ipr#�s���

��	�	� 7��	�	C�	C�!�q��v�v�x�x��� � �!�!�!�!�!�!�!�!�!�!�!�!����!�!�!�!�!�!s�&A�A�Ac�<�d}|�||���}tj�|��st	d|�d|�����t|d��5}|������}ddd��n#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 r�N)r�r�r
r:rkr�r�r�)�module_namer&�
_MOD_PAR_PATH�
param_file�pr�s      r6�get_kernel_module_parameterr+�s���?�M��%�%�+��%�K�K�J�
�7�>�>�*�%�%�
��j�8A�	�	�;�;�O�
�
�	
�
�j�#�	�	�!�!�������� � ��!�!�!�!�!�!�!�!�!�!�!����!�!�!�!��Ls�'B�B�Bc���d}|���D][\}}t|t��r%||vs|s|||<d}�(t|||��}�?||vs|sJ|�d|�����|||<d}�\|S)z�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 recursivelyFTz already exists in )r�rnrH�dict_deep_update)�dst�src�allow_overwrite�updatedr@rAs      r6r-r-s����G��	�	�������1��a����	���|�|�1�|���A�����*�3�q�6�1�5�5�����������-�-��-�-���/��C��F��G�G��Nr5c�8�eZdZd
d�Zd�Zd�Zd�Zdedefd�Zd	S)�
TimedCacher�c��t|t��sJ�||_||_t	��|_i|_dSr�)rnr�
expirationr7r�cache�_locks)r�r5r7s   r6r�zTimedCache.__init__*s<���*�i�0�0�0�0�0�$������ �]�]��
�����r5c���t��}|jD]J}|j|\}}tj��|z
|j���kr||f||<�K||_dS)zClear cache from expired valuesN)rr6r?r5�
total_seconds)r��	tmp_cacherr��added_ats     r6�_collectzTimedCache._collect1sh���M�M�	��:�	1�	1�C�"�j��o�O�E�8��	���h�&�$�/�*G�*G�*I�*I�I�I�!&���	�#�����
�
�
r5c�:�t��|_i|_dSr�)rr6r7�r�s 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:
        )r�r�rs�hash)r�rNrO�seed�kws     r6�	_make_keyzTimedCache._make_key>sB�����	�������'�'�B��E�"�I�I��D��D�z�z�r5�funcr`c����t�����fd���}t�����fd���}tj���r|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.
        c��h�K���||��}�j�|��}|�tj��x}�j|<		tj|����j������d{V��nP#tj	$r=|�j|urtj��x}�j|<n
�j|}YnwxYw��	��
��	�j|\}}ns#t$rft�j���jkr�j�d����|i|���d{V��}|t!j��f�j|<YnwxYw|���n#|���wxYw|S)NTF��last)rCr7r;rpr�r��acquirer5r9�TimeoutErrorr<r6r�rr7�popitemr?�release)rNrOr�lockr�r�rDr�s      ��r6�
wrapper_asyncz*TimedCache.__call__.<locals>.wrapper_asyncXs�������.�.��v�.�.�C��;�?�?�3�'�'�D��|�*1�,�.�.�8��t�{�3�'�
0�0�!�*��������(E�(E�(G�(G�������������+�	0�	0�	0��t�{�3�/�/�/�29�,�.�.�@��t�{�3�/�/�#�{�3�/����	0����

0� 

��
�
����:� $�
�3��I�F�A�A���:�:�:��4�:���$�,�6�6��
�*�*��*�6�6�6�#'�4��#8��#8�#8�8�8�8�8�8�8�F�&,�d�i�k�k�&9�D�J�s�O�O�O�	:�����������������������MsE�AB�A	C&�%C&�+F�D�F�A-F�>F�F�F�F/c�Z��������||��}	�j|\}}nm#t$r`t	�j���jkr�j�d����|i|��}|tj��f�j|<YnwxYw|S)NFrG)r<rCr6r�rr7rKr?)rNrOrr�r�rDr�s     ��r6�wrapper_syncz)TimedCache.__call__.<locals>.wrapper_sync{s�����M�M�O�O�O��.�.��v�.�.�C�
6� �J�s�O�	������
6�
6�
6��t�z�?�?�d�l�2�2��J�&�&�E�&�2�2�2���t�.�v�.�.��"(�$�)�+�+�"5��
�3����	
6����
�Ms�>�A'B(�'B()rrp�iscoroutinefunctionr')r�rDrNrPrQs``   r6r�zTimedCache.__call__Ks�����
�t��� 	� 	� 	� 	� 	�
�� 	�D
�t���
	�
	�
	�
	�
	�
��
	��*�4�0�0�
�M�M��	�
#�.����r5N)r�)	r-r.r/r�r<r'rCr#r�r4r5r6r3r3)s�������������������C�Q�C�1�C�C�C�C�C�Cr5r3�r�c��>K�|���r<|���s&	|���n#t$rYnwxYwdS|���tj|h|����d{V��\}}|rL|���s|���nd}|rt�	d||��dSdS|���s4t�	d||��|�
d���dSdS)u�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.
    Nr�z&Task %r raised during cancellation: %sz.Task %r did not finish within %ds after cancelc�$�t|d���S)Nz*Abandoned task failed after cancel timeout)�message)�log_future_errors)�ts r6�<lambda>z"safe_cancel_task.<locals>.<lambda>�s��'��G����r5)�done�	cancelledr�r��cancelrpryr�r_r��add_done_callback)�taskr�rYr�r�s     r6�safe_cancel_taskr^�s^�����y�y�{�{���~�~���	�
����
�
�
�
���
�
�
���
�������K�K�M�M�M��L�$���9�9�9�9�9�9�9�9�9�G�D�!��
�&*�n�n�&6�&6�@�d�n�n����D���	P��N�N�C�T�3�O�O�O�O�O�	P�	P�
�Y�Y�[�[�
����<�d�G�	
�	
�	
�	
���
�
�	
�	
�	
�	
�	
�	
�
s�A�
A�
Ac�f�tjtj��tj��dS)z�
    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.
    N)r��kill�getpid�signal�SIGUSR2r4r5r6�fail_agent_servicerd�s$���G�B�I�K�K���(�(�(�(�(r5c
���K�|�dttj������}t	|d��5}|�t
tjj	||d�����t�d||��tjj|fi|���d{V��}t	|dzd��5}|�
d�|jt!j|j������������ddd��n#1swxYwYddd��n#1swxYwY|S)	z�
    Runs command and log it's output to the log file

    :param cmd:
    :param log_file_mask:
    :return: str path of log file
    �*r�Trfz
Popen(%r, %r)Nz.pidz{:d}	{}
)�replaceror�rar�r{rHrp�
subprocessr�r_rArqr
r��pid�psutil�Process�create_time�hex)r��
log_file_mask�popen_kwargs�live_log�live_log_fpr~�pfs       r6�run_cmd_and_logrs�s������$�$�S�#�b�i�k�k�*:�*:�;�;�H�	
�h��	�	��������(�0�"�"�"&�	
�
�
�	
�	
�	
�	���_�c�<�8�8�8��'�?��
�
��
�
�
�
�
�
�
�
���(�V�#�S�
)�
)�	�R��H�H��#�#��H�f�n�T�X�6�6�B�B�D�D�H�H�J�J���
�
�
�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�������������������&�Os8�BE�	A*D?�3E�?E	�E�E	�E�E�Ec��	td��5}|D]C}|�d��r,|���ddkccddd��S�D	ddd��n#1swxYwYn#t$rYnwxYwdS)aLReturn 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).
    z/proc/self/statuszNoNewPrivs:r�1NF)r�r�rr�)r>r?s  r6�_has_no_new_privsrv�s���
�
�%�
&�
&�	2�!��
2�
2���?�?�=�1�1�2��:�:�<�<��?�c�1�1�1�	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����
�
�
���
�����5s@�A1�9A%�
A1�A%�A1�%A)�)A1�,A)�-A1�1
A>�=A>)�systemd-runz--quietz--waitz--pipez	--collectz--property=NoNewPrivileges=noz--property=ProtectSystem=noc�`�|sdStd�|���D����S)Nr4c3�,K�|]\}}d|�d|��V��dS)z	--setenv=r4Nr4)r�r@rAs   r6r�z+_systemd_run_setenv_args.<locals>.<genexpr>s7����=�=���A�$�Q�$�$��$�$�=�=�=�=�=�=r5)rsr���envs r6�_systemd_run_setenv_argsr|s4�����r��=�=������=�=�=�=�=�=r5r�c��t��s|Stt|��zdd|fz}d�d�|D����S)z�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.�/bin/sh�-c� c3�>K�|]}tj|��V��dSr���shlex�quote�r�r*s  r6r�z._wrap_outside_sandbox_shell.<locals>.<genexpr>�*����2�2�q�E�K��N�N�2�2�2�2�2�2r5)rv�_SYSTEMD_RUN_BASEr|r��r�r{�partss   r6�_wrap_outside_sandbox_shellr�sa�������
��
"�3�
'�
'�	(��d�C�
 �	!�
�
�8�8�2�2�E�2�2�2�2�2�2r5c��t��st|��Sttt|��zdzt	|��z��S)z5Argv-form counterpart of _wrap_outside_sandbox_shell.)z--)rvrrr�r|rs)�argvr{s  r6�_wrap_outside_sandbox_argvr�"sY�������D�z�z����
"�3�
'�
'�	(�
�	���+�+�	���r5rzc��LK�tt||���|fi|���d{V��S)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)rsr��r�rnr{ros    r6�run_cmd_and_log_outside_sandboxr�.sZ����!�#�C�S�1�1�1�������������r5c��JK�tt||���fi|���d{V��S)z7run() variant that escapes the agent's systemd sandbox.rzN)r�r��r�r{rOs   r6�run_outside_sandboxr�>s<�����/��#�>�>�>�I�I�&�I�I�I�I�I�I�I�I�Ir5c��JK�tt||���fi|���d{V��S)z=check_run() variant that escapes the agent's systemd sandbox.rzN)r�r�r�s   r6�check_run_outside_sandboxr�Cs<�����5�d��D�D�D�O�O��O�O�O�O�O�O�O�O�Or5��c�N�t��sdS	tjddgtjtjdd���j}n#ttjf$rYdSwxYwtj	d|��}|�dSt|�����tkS)z=Return True iff systemd-run can host a transient unit for us.Frwz	--versionT�)rhri�textr�z\d+)
r=rlr�rmr�rhr��SubprocessErrorr$r%r�r&�_SYSTEMD_RUN_MIN_VERSION)�version_liner*s  r6�_systemd_run_supportedr�Ws��������u�	�"��
�K�(��#��&���
�
�
��
	���
�[�0�1�����u�u������I�f�l�+�+�E��}��u��u�{�{�}�}���!9�9�9s�4A�A!� A!c��t��s|Stt|��zdd|fz}d�d�|D����S)z�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.r~rr�c3�>K�|]}tj|��V��dSr�r�r�s  r6r�z,_wrap_in_own_cgroup_shell.<locals>.<genexpr>wr�r5)r�r�r|r�r�s   r6�_wrap_in_own_cgroup_shellr�lsa��"�#�#���
��
"�3�
'�
'�	(��d�C�
 �	!�
�
�8�8�2�2�E�2�2�2�2�2�2r5c��LK�tt||���|fi|���d{V��S)z�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.
    rzN)rsr�r�s    r6�run_cmd_and_log_in_own_cgroupr�zsZ����!�!�#�3�/�/�/�������������r5c��eZdZd�ZdS)r�c	�`�|jdkr7|jr0|dd�}|jr
|j|d<|j�|��	tj|��dS#t$r�t|j	dd��pt|j	dd��}t|j	dd��pt|j	dd��}t|j	d	d��pt|j	d
d��}|j
}|r|jp|j}td�||j||����YdSwxYw)N�PENDINGz%Task was destroyed but it is pending!)r]rU�source_tracebackr/r-�gi_code�cr_code�gi_frame�cr_framez+!> Finalizer error in {}() {} at {} line {})�_state�_log_destroy_pending�_source_traceback�_loop�call_exception_handlerr�__del__�AttributeError�getattr�_coro�co_filename�f_lineno�co_firstlineno�printr�)r��contextr�r��framer�linenos       r6r�zTask.__del__�s{���;�)�#�#��(A�#��B���G��%�
E�.2�.D��*�+��J�-�-�g�6�6�6�	��N�4� � � � � ���	�	�	��4�:�~�t�<�<����
�J��A�A�D��4�:�y�$�7�7��7��
�I�t�<�<�D��D�J�
�D�9�9��W��
�J��>�>�E��'�H��.���F�4�3F�F��=�D�D��$�+�x����
�
�
�
�
�
�	���s�A�CD-�,D-N)r-r.r/r�r4r5r6r�r��s#����������r5r�c����fd�}|S)z�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.'
    c��<�K�tj����d{V��Sr�)rpr�)rNrcs �r6�pausezawait_for.<locals>.pause�s)������]�7�+�+�+�+�+�+�+�+�+r5r4)rcr�s` r6rwrw�s#���,�,�,�,�,��Lr5c�j��������t��g��std����������fd�}|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���}tj���r|S|S)Nc��~�K��rtj���z}�
stjd��nt	d�
dz��D]�}	�rg|tj��z
}|dkr$tj�|i|��|����d{V��cS�
st
j��	r�	�d���n�|i|���d{V��cS�}#t
jt
j	f$r��$rX}���||���d{V��}|s�
}|�
kr�
s��	r�	�d�|�����||���d{V��Yd}~��d}~wwxYwdS)Nrrr�� Timeout exceeded when calling %s�0Max tries exceeded when calling %s with error %s)
r?r@�	itertools�countr�rpr�rJr�r��rNrO�end_timer�remaining_timer��should_retry_retr�rDrDrdre�should_retry�silentr�s       ��������r6rNz2retry_on.<locals>.decorator.<locals>.wrapper_async�s,������
6��>�+�+�g�5��!�-�	���"�"�"��1�i�!�m�,�,�(
/�(
/��
#/��;�)1�D�N�4D�4D�)D��)�A�-�-�)0�)9� $��d� 5�f� 5� 5�~�*�*�*�$�$�$�$�$�$����$*�"�&-�&:� :�!$�"� #�	�	�$F��!"�!"�!"��&*�T�4�%:�6�%:�%:�:�:�:�:�:�:�:�:�:����,�g�.D�E����� �/�/�/�#�/�1=��c�1�1E�1E�+E�+E�+E�+E�+E�+E�(�/�*� )�A��I�~�~�%��!� ���I�I�!,� $� #�	��� �+�&�h�s�A�.�.�.�.�.�.�.�.�.����������#/����/(
/�(
/s�?C�
4C� D:�"AD5�5D:c�����rtj���z}�
stjd��nt	d�
dz��D]�}	�rH|tj��z
}|dkr
�|i|��cS�
st
��	r�	�d���n
�|i|��cS�X#�$rL}���||��}|s�
}|�
kr�
s��	r�	�d�|�����||��Yd}~��d}~wwxYwdS)Nrrr�r�)r?r@r�r�r�rJr�r�s       ��������r6rPz1retry_on.<locals>.decorator.<locals>.wrapper_sync�s�����
6��>�+�+�g�5��!�-�	���"�"�"��1�i�!�m�,�,�$
)�$
)��
)��5�)1�D�N�4D�4D�)D��)�A�-�-�#'�4��#8��#8�#8�8�8�8�#)�"�&2� 2�!$�"� #�	�	�$F��!"�!"�!"�� $�t�T�4�V�4�4�4�4�4��� �)�)�)�#�/�+7�<��Q�+?�+?�(�/�*� )�A��I�~�~�%��!� ���I�I�!,� $� #�	��� �+� ���a�(�(�(����������#)����'$
)�$
)s�%B�.)B�C,� AC'�'C,�rSrrprQ)
rDrNrPr�rDrdrer�r�r�s
`  �������r6rUzretry_on.<locals>.decorator�s�����	���	�	�+	/�+	/�+	/�+	/�+	/�+	/�+	/�+	/�+	/�+	/�+	/�
�	�+	/�Z
���	�	�'	)�'	)�'	)�'	)�'	)�'	)�'	)�'	)�'	)�'	)�'	)�
�	�'	)�R�&�t�,�,�	 � � ��r5)r�rk)r�rerdr�r�rDr�rUs``````` r6ruru�sz���������$�	�7�#�$�$�:��8�9�9�9�\ �\ �\ �\ �\ �\ �\ �\ �\ �\ �\ �|�r5c���tj����fd���}tj����fd���}tj���r|n|S)zhIf func throws an exception it is catched, converted to a string and
    returned as a result of a call.c��r�K�	�|i|���d{V��S#t$r}t|��cYd}~Sd}~wwxYwr��r��repr�rNrOr�rDs   �r6rNz,stub_unexpected_error.<locals>.wrapper_async5sg�����	���t�.�v�.�.�.�.�.�.�.�.�.���	�	�	���7�7�N�N�N�N�N�N�����	���s�
�
6�1�6�6c�b��	�|i|��S#t$r}t|��cYd}~Sd}~wwxYwr�r�r�s   �r6rPz+stub_unexpected_error.<locals>.wrapper_sync<sQ���	��4��(��(�(�(���	�	�	���7�7�N�N�N�N�N�N�����	���s��
.�)�.�.r�)rDrNrPs`  r6�stub_unexpected_errorr�1s����_�T�����������_�T����������$�7��=�=�O�=�=�<�Or5c�2�����tj���fd�}|S)zkA decorator that logs uncaught exceptions ignoring them otherwise.

    CancelledError is not handled.
    Nc����tj������fd���}tj������fd���}tj���r|S|S)Nc	���K�	�|i|���d{V��S#tj$r��$r'}�dt�dd��|��Yd}~dSd}~wwxYw�NzIgnoring exception from %s: %sr/r�)rpr�r��rNrOr�r�r��log_handlers   ���r6rNz>log_error_and_ignore.<locals>.decorator.<locals>.wrapper_asyncOs������	
�!�T�4�2�6�2�2�2�2�2�2�2�2�2���)�
�
�
���
�
�
���4��D�.�&�9�9���������������
���s�
�A�A	�	Ac	�t��	�|i|��S#�$r'}�dt�dd��|��Yd}~dSd}~wwxYwr�)r�r�s   ���r6rPz=log_error_and_ignore.<locals>.decorator.<locals>.wrapper_sync\s����
��t�T�,�V�,�,�,���
�
�
���4��D�.�&�9�9���������������
���s��7�2�7r�)r�rNrPr�r�s`  ��r6rUz'log_error_and_ignore.<locals>.decoratorNs�����	���	�	�
	�
	�
	�
	�
	�
	�
�	�
	�
���	�	�	�	�	�	�	�	�
�	�	��&�t�,�,�	 � � ��r5)r_r�)r�r�rUs`` r6�log_error_and_ignorer�Fs9����
���l�� � � � � � �<�r5c������fd�}|S)z'Abort the agent service on *exception*.c�L���tj������fd���}|S)Nc���K�	�|i|���d{V��S#�$r/}t�|�����Yd}~dSd}~wwxYwr�)r_r�)rNrOr��abortr�r�s   ���r6rQz2abort_agent_on.<locals>.decorator.<locals>.wrapperss�����
�!�T�4�2�6�2�2�2�2�2�2�2�2�2���
�
�
�� � ��#�#�#����������������	
���s�
�A�$A�ArR)r�rQr�r�s` ��r6rUz!abort_agent_on.<locals>.decoratorrsC����	���	�	�	�	�	�	�	�	�
�	�	��r5r4)r�r�rUs`` r6�abort_agent_onr�os*�����������r5c�R�tjdd|�����S)zPascalCase to snake_casez([a-z])([A-Z])z\1_\2)r$�subrK)�strings r6�
snake_caser��s#��
�6�"�H�f�5�5�;�;�=�=�=r5��g�������?g�?c�H�dt|�����vS)Nr�)rorK)r�s r6�_is_db_locked_errorr��s���s�3�x�x�~�~�'�'�'�'r5)�exec_expr_with_empty_iterc'��K�|s|rdg}nt|t���}ddlm}|j���5|D]}||g|�R�Ed{V���	ddd��dS#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:
    N�rr��instance)r�CHUNK_SIZE_SQL_QUERY�defence360agent.modelr��db�transaction)�exprrr�rN�chunksr�rs       r6�get_results_iterable_expressionr��s����&�L�1�L����� ��6J�K�K�K��.�.�.�.�.�.�	��	 �	 �	"�	"�*�*��	*�	*�E��t�E�)�D�)�)�)�)�)�)�)�)�)�)�)�	*�*�*�*�*�*�*�*�*�*�*�*�*����*�*�*�*�*�*s�A#�#A'�*A'r�c�������tt||������ddlm�d�}t	t
|tdzd��������fd���}|��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.
    r�rr�c��ttd|dz
zzt��}t�d||t
|��t
j|��dS)Nrbrz;SQLite lock contention, retrying in %.3fs (retry %d/%d): %s)�min�DB_LOCK_RETRY_BACKOFF_BASE�DB_LOCK_RETRY_BACKOFF_MAXr_r��DB_LOCK_MAX_RETRIESr?r�)r��attempt�backoffs   r6�_backoffz-execute_iterable_expression.<locals>._backoff�sd���&�!��!��*<�=�%�
�
��	���I�����	
�	
�	
�	
�
�7�����r5rc� �t|��Sr�)r�)r�r�s  r6rXz-execute_iterable_expression.<locals>.<lambda>�s��*=�c�*B�*B�r5)rerdr�c���d}�j���5�D] }|�|g��R����z
}�!	ddd��n#1swxYwY|Sr�)r�r��execute)r�rrNr�r�r�s  ����r6�_execute_allz1execute_iterable_expression.<locals>._execute_all�s������
�[�
$�
$�
&�
&�	8�	8��
8�
8���4�4��-��-�-�-�5�5�7�7�7���
8�	8�	8�	8�	8�	8�	8�	8�	8�	8�	8�	8����	8�	8�	8�	8��s�$A�A�A)rrrr�r�rurr�)r�rrrNr�rr�r�s`  `  @@r6�execute_iterable_expressionr�s�������$�/�(�z�B�B�B�
C�
C�F�.�.�.�.�.�.�������%��)�B�B�	����������
����<�>�>�r5c�X�tj|�dd����dzS)Nr��\nr�)r��fsencoderg�rs r6�encode_filenamer�s%��
�;�t�|�|�D�%�0�0�1�1�E�9�9r5c�b�tj|��dd��dd��S)Nr(rr�)r��fsdecodergrs r6�decode_filenamer	�s+��
�;�t���S�b�S�!�)�)�%��6�6�6r5c�N�tjtj|����Sr�)�base64�	b64encoder�rrs r6�base64_encode_filenamer
�s����B�K��-�-�.�.�.r5�b64namec�h�ttjtj|������Sr�)rr�rr�	b64decode)rs r6�base64_decode_filenamer�s%�����F�,�W�5�5�6�6�7�7�7r5c�V�	tj|��}n#t$rd}YnwxYw|S)zS
    Like pwd.getpwnam(username) but returns None instead of raising KeyError.
    N)r�r�r�)r�r�s  r6r�r��sA�����h�'�'�����������������Ms��&�&c�>�tt||��|��S)zH
    Put the specified `value` inside the [`low`, `high`] interval.
    )�maxr�)r��low�highs   r6�cliprs���s�5�$����%�%�%r5�Background 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_r�r�rpr�r�)�futr�rUr�s    r6rVrVs������n��*��
�
��������!�
�
�
�����*�*�*���H�g�q�)�)�)�)�)�)�)�)�)�����*���s�&�A�	A�
A�Ar�.c�r���fd�}��||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|���|d���dSdSdS)Nz1Unhandled exception during plugin initialization!)rUr�r])rZr�r�)r]r�s �r6�_log_exceptionz6create_task_and_log_exceptions.<locals>._log_exception+sv����~�~���		�D�N�N�$4�$4�$@��'�'�L�!%���!1�!1� ���
�
�
�
�
�		�		�$@�$@r5)�create_taskr\)r�r�rNrOr�new_tasks`     r6�create_task_and_log_exceptionsr "sY���
�
�
�
�
������d� 5�f� 5� 5�6�6�H����~�.�.�.��Or5c����fd�}|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
    c���K��|i|��Sr�r4)rNrO�functions  �r6r�zmake_coro.<locals>.coroFs������x��(��(�(�(r5r4)r#r�s` r6�	make_coror$<s#���)�)�)�)�)��Kr5c���K�|tkr
tj}ntj}|dt	|dd��x}rd|�d�nd||��tjt���d{V��dS)Nz7Failed to copy data%s to modsec ruleset dir %r, try: %srz (�)r�)�COPY_TO_MODSEC_MAXTRIESr_r�r�r�rpr��_MODSEC_COPY_FAILURE_TIMEOUT)r�rrD�fns    r6�log_failed_to_copy_to_modsecr*Ps������#�#�#��l����n���C�A�$�S�*�d�;�;�;�r�D�
�R�
�
�
�
�"��	�	����-�4�
5�
5�5�5�5�5�5�5�5�5�5r5)�err_buf_sizec
�4K�d�}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.
    c��JK�|23d{V��}|�|���6dSr�)�append)�pipe�bufr?s   r6�read_pipe_intoz1readlines_from_cmd_output.<locals>.read_pipe_intohsL�����	�	�	�	�	�	�	�$��J�J�t������$�$s�")�maxlenT)rjrhriNrr5)rrprtrhrmrrirhryr�r�)r�r+ror1�err_bufr~r?r�s        r6�readlines_from_cmd_outputr4^s���������<�(�(�(�G��/�	���!�&��!�&�	��
���������D�	I�	��N�N�4�;��@�@�A�A�A��+�	�	�	�	�	�	�	�$��J�J�J�J�J�&�+� �9�9�;�;�&�&�&�&�&�&�
���?�?��
�C��c�h�h�w�6G�6G�H�H�H��?�� �9�9�;�;�&�&�&�&�&�&�
���?�?��
�C��c�h�h�w�6G�6G�H�H�H�H�H�H�Hs�*C�:B�C�ADrb)rd�delayc��K�td|dz��D]3}||��d{V��}|s!||krtj|���d{V���0|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)r�rpr�)�predicate_corordr5rNr�r�s      r6�finally_happenedr8�s�������I��M�*�*����%�~�t�,�,�,�,�,�,�,���	�'�I�-�-��-��&�&�&�&�&�&�&�&�&���
�
�
��r5�'c�K�t|d���D]-\}}|WV�||zdkrtjd���d{V���.dS)z6Yield to the event loop every *chunk_size* iterations.r)rErN)�	enumeraterpr�)rrr�items    r6�
nice_iteratorr=�so�����X�Q�/�/�/�#�#���4��
�
�
�
�
�
�N�q� � ��-��"�"�"�"�"�"�"�"�"��#�#r5c��eZdZdZd�Zd�ZdS)�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.
    c��d|_dSr�)r�r>s r6r�zLazyLock.__init__�s
����
�
�
r5c�N�|jstj��|_|jSr�)r�rpr�)r�r��owners   r6�__get__zLazyLock.__get__�s!���z�	(� ����D�J��z�r5N)r-r.r/r]r�rCr4r5r6r?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)r�rKr)r?�pkg_name�versions   r6�_parse_rpm_linerH�s[���:�:�<�<�D���?�d�j�j�l�l�2�2�d�$�6F�6F��t��
�
�4��+�+��H�g��W��r5c�
�|���}|rd|���vsd|vrdS|�d��sdS|�dd��\}}|r|���dnd}||fS)zVParse dpkg-query output line, return (package_name, version) or None if not installed.zno packages foundrENz
 ok installedrrr�)r�rKr�r)r?rF�restrGs    r6�_parse_dpkg_linerK�s����:�:�<�<�D���&�$�*�*�,�,�6�6�$�d�:J�:J��t�
�=�=��)�)���t��Z�Z��a�(�(�N�H�d�!%�-�d�j�j�l�l�1�o�o�2�G��W��r5c��t���st���rgd�tfSgd�tfS)N)z
dpkg-queryz--showz--showformatz!${Package}: ${Version} ${Status}
)�rpmz-qz5--queryformat=%{NAME}: %{VERSION}-%{RELEASE}.%{ARCH}
)r-rarirKrHr4r5r6�_get_package_query_cmdrN�sg����� � �	
�M�$;�$;�$=�$=�	
�
�
�
�
�
�	
�	
�	
�	
�
	�
�r5c��eZdZdZdS)�FirewallDisabledExceptionz;Exception in case of using firewall api, when it's disabledNr�r4r5r6rPrP�s������E�E�E�Er5rPc�<��t����fd���}|S)Nc���K�tj�d��rtd����|i|���d{V��S)Nz!/var/imunify360/firewall_disabledz"Not available in the current build)r�r
r:rP)rNrOrDs  �r6rQz(check_disabled_firewall.<locals>.wrapper�s\�����
�7�>�>�=�>�>�	�+�4���
��T�4�*�6�*�*�*�*�*�*�*�*�*r5r)rDrQs` r6�check_disabled_firewallrS�s3���
�4�[�[�+�+�+�+��[�+��Nr5>�
imunify-ui�imunify-core�imunify-antivirus�imunify360-firewall�packagesc��K�t��\}}t|��}t||zdd����d{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.
    r�F)r�r�N)rNrr�safe_run_with_timeout�_parse_package_info_output)rXr��
parse_line�
packages_listr�s     r6�system_packages_infor^�st����-�.�.�O�C����N�N�M�(��m��R�%����������F�&�f�m�Z�H�H�Hr5r�r\c�n����������fd�|���D����fd�|D��S)Nc�X��i|]&}�|��x���dx���dx��#����'S)rrr4)r�r?r\�pkgr��vers  ����r6�
<dictcomp>z._parse_package_info_output.<locals>.<dictcomp>sf������� �j��&�&�&�F���1�I�
�S�	�
�1�I�
�S���S���r5c�<��i|]}|��|����Sr4)r;)r�ra�parseds  �r6rcz._parse_package_info_output.<locals>.<dictcomp> s%���5�5�5�S�C����C���5�5�5r5)�
splitlines)r�rXr\rerar�rbs  `@@@@r6r[r[sf�������
��������%�%�'�'����F�6�5�5�5�H�5�5�5�5r5c��K�	tjt|fi|��|����d{V��S#tj$r|d|��YdSwxYw)Nr�z#Command %s failed: Timeout occurredr�)rpr�r�rJ)rzr�rDrOs    r6rZrZ#s�������%��W�'�'��'�'��
�
�
�
�
�
�
�
�
�	
���������1�7�;�;�;��r�r����s�&+�A
�	A
�nc#��K�|dkrtd���t|��}tt||����x}r%|V�tt||����x}�#dSdS)Nrzn must be at least one)rkrrrr)rrh�it�batchs    r6�batchedrl/s�����
	�1�u�u��1�2�2�2�	
�h���B���r�1�
�
�&�&�
&�%��������r�1�
�
�&�&�
&�%�����r5rOc#�R�K�t�|��D]}�fd�|D��V��dS)Nc�"��i|]}|�|��Sr4r4)r�r@rOs  �r6rcz batched_dict.<locals>.<dictcomp>=s���&�&�&�1�q�!�A�$�&�&�&r5)rl)rOrhrks`  r6�batched_dictro;sK�������A���'�'��&�&�&�&��&�&�&�&�&�&�&�'�'r5c�n�	tjddgd������}|�d��}|s?t	d�����rtjddgd���}d|vrd}|S#t$r&}t�d	|��Yd}~d
Sd}~wwxYw)N�hostnamez-fT)r�)z.cloudwaysapps.comz.cloudwaysstagingapps.comz/usr/local/sbin/apm�info�	Cloudwaysz$Error while checking environment: %sF)	rl�check_outputr�r�rr:r�r_r�)rq�
_is_cloudwaysr�r�s    r6�is_cloudwaysrv@s�����+�
���T�
�
�
�
�%�'�'�	�!�)�)�?�
�
�
��	%��&;�!<�!<�!C�!C�!E�!E�	%� �-�&��/�d����F��f�$�$� $�
�����������;�Q�?�?�?��u�u�u�u�u��������s�BB�
B4�B/�/B4�pid_filec���tj��}|rt|��dkr|S	|�|�d���|S#t$r'}t
�d|��|cYd}~Sd}~wwxYw)Nr�r�z Error while creatin PID file: %s)r�raro�
write_textr�r_r�)rwrir�s   r6�write_pid_filerzUs���
�)�+�+�C���s�8�}�}��*�*��
�����s�J�J�J�'�'�'��
���������7��;�;�;��
�
�
�
�
�
��������s�A�
A7�A2�,A7�2A7c���|rt|��dkrdS	|���r|���dS#t$r&}t�d|��Yd}~dSd}~wwxYw)Nr�z Error while cleanup PID file: %s)ror:r�r�r_r�)rwr�s  r6�cleanup_pid_filer|cs�����s�8�}�}��*�*��t���?�?���	��O�O��������������7��;�;�;��������������s�(A�
A3�
A.�.A3c��|K�t�d||��tjd|z���d{V��dS)a
    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():
            ...
        ```
    z#%s sleep on: %srbN)r_r�rpr�)r�r�s  r6�
backoff_sleepr~psK�����N�N�%�w�	�:�:�:�
�-��W��
%�
%�%�%�%�%�%�%�%�%�%r5)NN)r`N)T)r�Fr�)rt)r�)r�)r�)r
)NNNFNN)Nr)r9)�rprr�rSr�r��loggingr�r�r$r�rrbrrhrlr?�urllib.requestr r�collectionsrr�collections.abcrr�
contextlibrr	r
�datetimer�enumr�fcntlr
rrrrr�pathlibr�tempfiler�typingrrrrrrrr�	async_lrurIrj�peeweer�_shutilr r!�fd_opsr"r#�	getLoggerr-r_�USER_IDENTITY_FIELD�USER_IDENTITY_HEADERSr�rr9rf�AV_PID_PATH�IM360_NON_RESIDENT_PID_PATH�IM360_RESIDENT_PID_PATHr��environr;�HTTP_REQUEST_RETRY_TIMEOUTr,�	lru_cacher=rGrWrYrm�bytesr�r�r�r�r�r�ror�r�r�r�r�r�r�r�r��boolrrr"r+r-�md5ryrwr�r�r�r�r�r��partial�
alru_cache�async_lru_cacher�r�r�r�r�r�r�r�r�r�rr	rr�typerr#r+r-r3�timed_cacher^rdrsrvr�r|r�r�r�r�r�r�r�r�r�r�rwrur�r�r�r�r�r�r�r�r�r�rrr	r
rr�rrVr r$r'r(r*r4r8r=r?rsrHrKrrrNrPrSr:�IMUNIFY_PACKAGE_NAMESrHr^r[r�rZrlrorvrzr|r~r4r5r6�<module>r�s�������
�
�
�
���������������������	�	�	�	�
�
�
�
�	�	�	�	�����
�
�
�
�
�
�
�
����� � � � ���������������*�*�*�*�*�*�*�*�/�/�/�/�/�/�/�/�:�:�:�:�:�:�:�:�:�:�������������2�2�2�2�2�2�2�2�2�2�2�2�������������������'�'�'�'�'�'�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�����
�
�
�
�
�
�
�
�#�#�#�#�#�#�0�0�0�0�0�0�0�0�%�%�%�%�%�%��G�C�x� � � ��	��	�8�	$�	$�����������d�0�1�1��6���d�3�4�4��"�d�#B�C�C���$�8�9�9�� �S��J�N�N�:�B�?�?����
+�+�+�+�+�D�+�+�+����Q����	�	� ��	��K�K�K���K��4�T�	�	�	�	���������4�����
�
�
0�0��3��u���0�0�0�0�f �������0
�
�
�
�
�K�2�
�
�
�(5�
�
�5�
�
�
�
�.;�
'�
'�
'�
'�
'� �������,
�
�
�+�+�+�+�+�+�+�+�@���	�	�	�	�	��	�	�	�������:?�;�;�;�;�F(,������d�d�d�

�3�J���$�d�
�$�J�d�
�d�d�d�d�N���Q��������
?�
?�C�
?�
?�
?�
?�h'�h'�h'�h'�h'�h'�h'�h'�X%�[�4�
A�
A��
A�58�
A��
A�
A�
A�
A� �#�#��#��#��3��8�_�	#�#�#�#�2����������.<���������������������
$�)�#�
���������������5��T������������5��T�����	%�	%�	%�-�-�-�-�-�-�-�-�`0E�����6����������������D�����&�3��#��$�����	%�	%�	%�
,�
,�h�
,�C�
,�)�
,�
,�
,�
,� 
�
�
�#�#�#�#�#��#�#�#�"���R� � � �!�!�!� �!����"�������2e�e�e�e�e�e�e�e�P��-.�!
�!
�!
�!
�!
�H)�)�)����Z���Q�����4���� ���$��>�>�>�3�3�S�3�s�3�3�3�3�	�	�	�	� $�
�
�
�
�
� ,0�J�J�J�J�J�
26�P�P�P�P�P�"�����Q����:��:�:�:� ��:�(3�3�3�3�S�3�3�3�3� $������ �����7�<����B
�
�
�$������s�s�s�s�lP�P�P�*$-�$�&�&�&�&�R%7�����&>�>�>�
����!����(��(�(�(�(�
6;�*�*�*�*�*�@';�1�1�1�1�1�h:�:�:�7�7�7�/��/�%�/�/�/�/�8�E�8�d�8�8�8�8����&�&�&�*�*�*�*�.���i��(�����4
�
�
� �� ��6�6�6�%(�I�I�I�	
�c��I�I�I�I�D=>�Q�
�
�
�
�
� #�#�#�#���������0�#��%��S��/�D�"8������3��5��c��?�T�#9�����"���Q�����	�$�s�)�X�s�e�U�3��8�_�t�%;�;�<�
<�=���� ���.F�F�F�F�F�	�F�F�F�	�	�	�"�	�������I��s�m�I�	�#�s�T�z�/��I�I�I�I�,6��6��3�i�6��#���c�3�h��$� 6�6�7�6�
�#�s�T�z�/��	6�6�6�6� !�,�	�	��	�	�	�	�	��	�	�	�	�'�D��c��N�'�s�'�'�'�'�
���Q������ ���(�T��c�����
�t�
�
�
�
�&�&�&�&�&r5defence360agent/utils/__pycache__/__init__.cpython-311.pyc0000644000000000000000000031323500000000000020366 0ustar  �

�`����V�ddlZddlZddlZddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlZddlZddl
Z
ddlZddlZddlZddlmZddlmZmZddlmZmZddlmZmZmZddlmZddl m!Z!ddl"m#Z#m$Z$m%Z%m&Z&dd	lm'Z'dd
lm(Z(ddl)m*Z*ddl+m,Z,dd
l-m.Z.m/Z/m0Z0m1Z1m2Z2m3Z3m4Z4m5Z5ddl6Z6ddl7Z7ddl8Z8ddl9m:Z:ddl;m<Z<m=Z=ddl>m?Z?e5de0���Z@ejAeB��ZCdZDdZEdaFdZGe*d��ZHdZIe*d��ZJe*d��ZKe*d��ZLeMejN�Odd����ZPGd�d e!��ZQejRd�!��d"���ZSed�d#���ZTeCddfd$�ZUGd%�d&��ZVdejWejWd'dfd(e4eMeXeXffd)�ZYddd*�d+�ZZGd,�d-ej[��Z\e\fd(eXfd.�Z]e\fd�d/�Z^d�d(e_fd1�Z`d2�ZaGd3�d4��Zbd5�ZcGd6�d7ed��Zed8�Zfd9�Zg	d�d;�Zhd0ddd0ddd<�d=eie_zejjzd>eMdzd(eifd?�ZkejRd��d@���Zld�d(e_fdA�ZmGdB�dC��ZnejodDfdEe_dFeMd(e_fdG�Zp	d�dEe_dFeMd(e4e_eMffdH�ZqdI�ZrdJ�Zsd�dL�Zt	d�dN�ZudO�ZvejR��dPeMfdQ���Zwejxe6jydRd0�S��ZzdT�Z{dEejjdUeXd(dfdV�Z|dW�Z}dEejjdXeXd(eifdY�Z~dZ�ZGd[�d\��Z�eEfd]�Z�d^�Z�ed_eMfd`���Z�d�dbe_d(e*fdc�Z�dde_dbe_d(eifde�Z�df�Z�d�dhedieMd(efdj�Z�dk�Z�Gdl�dme���Z�ejRd:�!��dn���Z�do�Z�d�d(eifdp�Z�Gdq�dr��Z�e�Z�dsdt�du�Z�dv�Z�dw�Z�ejRd�!��d(eifdx���Z�dyZ�dz�Z�d�d{e_d(e_fd|�Z�d�d}�Z�dd~�d�Z�dd~�d��Z�dd~�d��Z�d�Z�ejRd�!��d(eifd����Z�d�d{e_d(e_fd��Z�dd~�d��Z�Gd��d�ej���Z�d��Z�						d�d��Z�d��Z�eddfd��Z�e�fd��Z�d��Z�d�Z�dsZ�d�Z�d�Z�d(eifd��Z�d'd��d��Z�e�d��d��Z�d��Z�d��Z�dde*d(eXfd��Z�d�eXd(e*fd��Z�d��Z�d��Z�d�d��Z�d�e0d�e/ffd��Z�d��Z�dsZ�dsZ�d��Z�dRd��d{e3e_fd��Z�d�dsd��d��Z�d�d��Z�Gd��d���Z�dXe_d(e�e_e_fdzfd��Z�dXe_d(e�e_e_fdzfd��Z�ejRd�!��d(e�e�e_e0e_ge�e_e_fdzfffd����Z�Gd��d�ed��Z�d��Z�e�hd����Z�d�ee_d(e�e_e_dzffd��Z�d�e_d�e�e_d�e0e_ge�e_e_fdzfd(e�e_e_dzffd��Z�eCj�fd(e_fd��Z�d�eMfd��Z�d�e1e.e.fd�eMfd��Z�ejRd�!��d����Z�d�e*d(eMfd��Z�d�e*fd��Z�d„Z�dS)��N)�Future)�OrderedDict�deque)�	Generator�Iterable)�	ExitStack�contextmanager�suppress)�	timedelta)�Enum)�LOCK_EX�LOCK_NB�LOCK_UN�flock��wraps)�islice)�Path)�NamedTemporaryFile)�Any�	Awaitable�Callable�Dict�	FrozenSet�List�Tuple�TypeVar)�OperationalError�)�is_safe_subdir_name�rmtree)�atomic_rewrite_fd�F)�bound�user_id)z
User-AgentzAccept-LanguagezAccept-Encoding�
Connection�DNT���z.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�<c��eZdZdZdZdZdZdS)�ScopezAV onlyzAV and IM360z
IM360 onlyzIM360 resident onlyN)�__name__�
__module__�__qualname__�AV�AV_IM360�IM360�IM360_RESIDENT���S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/__init__.pyr,r,Hs"������	�B��H��E�*�N�N�Nr5r,)�maxsizec��t���o2t���ot���S)z�Return True if /run/systemd/system folder exists:
    [sd_booted]
    (https://www.freedesktop.org/software/systemd/man/sd_booted.html)
    )�_SYSTEMD_BOOTED_DIR�exists�is_dir�
is_symlinkr4r5r6�is_systemd_bootr=OsC��	�"�"�$�$�	1��&�&�(�(�	1�#�.�.�0�0�0�r5c#�K�|s|sJ�tj��}|p|jd|��dV�tj��}|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�	monotonic�debug)�action�logger_�log�start�stops     r6�timeitrG\sz������c���>��N���E��S��G�M�<��0�0�0�	�E�E�E��>���D��S��G�M�3�V�T�E�\�J�J�J�J�Jr5c��������fd�}|S)Nc�N���tj�������fd���}|S)Nc���K�t�p�j�����5�|i|���d{V��cddd��S#1swxYwYdS�N)rCrD�rGr-��args�kwargsrB�funrDrCs  ����r6�wrapperz+timefun.<locals>.decorator.<locals>.wrapperns��������.�#�,��S�I�I�I�
2�
2� �S�$�1�&�1�1�1�1�1�1�1�1�
2�
2�
2�
2�
2�
2�
2�
2�
2�
2�
2�
2����
2�
2�
2�
2�
2�
2s�8�<�<��	functoolsr�rPrQrBrDrCs` ���r6�	decoratorztimefun.<locals>.decoratormsH����	���	�	�	2�	2�	2�	2�	2�	2�	2�
�	�	2��r5r4�rCrBrDrUs``` r6�timefunrWls0�������������r5c�0�eZdZdZeeddfd���ZdS)�synczF
    the same timefun decorator variation but without async/await
    Nc��������fd�}|S)z�
        :param logging.Logger: logger you want action name and timing
            to be logged with
        :param str: action name to log
        c�N���tj�������fd���}|S)Nc�z��t�p�j�����5�|i|��cddd��S#1swxYwYdSrKrLrMs  ����r6rQz0sync.timefun.<locals>.decorator.<locals>.wrapper�s�����F�2�c�l�G��M�M�M�0�0��3��/��/�/�0�0�0�0�0�0�0�0�0�0�0�0����0�0�0�0�0�0s�0�4�4rRrTs` ���r6rUzsync.timefun.<locals>.decorator�sH����
�_�S�
!�
!�
0�
0�
0�
0�
0�
0�
0�"�
!�
0��Nr5r4rVs``` r6rWzsync.timefun}s0�����	�	�	�	�	�	�	��r5)r-r.r/�__doc__�staticmethod�loggerrWr4r5r6rYrYxsE����������t������\���r5rYF�returnc	��$K�|�|�td���tj}|r't|t��sJ�|g}t
j}n*t|ttf��sJ�t
j	}ttdtd������|��||||dd�|���d{V��}|�
|���d{V��\}	}
|����d{V��}t�d	|||||	|
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_tries�on_errorT��stdin�stdout�stderr�start_new_sessionz run(%s, stdin=%s, shell=%s) = %s)�
ValueError�_subprocess�PIPE�
isinstance�str�asyncio�create_subprocess_shell�list�tuple�create_subprocess_exec�retry_on�BlockingIOError�	await_for�communicate�waitr_rA)�commandrgrhri�shell�inputrO�create_subprocess�proc�out�err�	exit_codes            r6�runr��s�����
�����N�O�O�O�� ���;��'�3�'�'�'�'�'��)��#�;����'�D�%�=�1�1�1�1�1�#�:������1�y��/C�/C�/C����	���

����������������D��%�%�e�,�,�,�,�,�,�,�,�H�C���i�i�k�k�!�!�!�!�!�!�I�
�L�L�*��
�
�	�C�������c�3��r5)�loop�timeoutc��|�rtd��D]b}	tj��}|���sn7n#t$rYnwxYwtjtj�����c|�tjt|tj
��r|ntj|��|�����S)zjRun coroutine from a blocking code (outside the event loop).

    Coroutine will be wrapped in Task.

    Nrb�r�)�rangerp�get_event_loop�	is_closed�RuntimeError�set_event_loop�new_event_loop�run_until_complete�wait_forrnr�Task)�coror�r��_s    r6�run_coror��s����|��q���	=�	=�A�
��-�/�/���~�~�'�'���E��� �
�
�
���
����

�"�7�#9�#;�#;�<�<�<�<��"�"����t�W�^�4�4�L�D�D�'�,�t�:L�:L��	
�	
�	
���s�?�
A�Ac��eZdZd�ZdS)�
CheckRunErrorc��d}|�|j|j|j���pd|j���pd���S)Nz[Command {cmd!r} returned non-zero code {returncode},
		Stdout: {output},
		Stderr: {error}
)�cmd�
returncode�output�error)�formatr�r�r��decoderi)�self�_MESSAGEs  r6�__str__zCheckRunError.__str__�s_��
$�	�
��������;�%�%�'�'�/�4��+�$�$�&�&�.�$�	�
�
�	
r5N)r-r.r/r�r4r5r6r�r��s#������
�
�
�
�
r5r�c��`K�t|fi|���d{V��\}}}|dkr|||||���|S)zJ
    Asynchronous command executor.
    Returns output as bytestring.
    Nr)r�)rz�	raise_excrOr�rr�s      r6�	check_runr��sZ����
"%�W�!7�!7��!7�!7�7�7�7�7�7�7��J��S��Q����i�
�G�S�#�6�6�6��Jr5c��K�t|tjtjtj����d{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)r�rl�DEVNULL)rzr��coder�s    r6�check_exit_coder��sy����
���!��"��"�	���������J�D�!�Q��q�y�y��i��g�&�&�&��yr5Tc��K�	t|fi|���d{V��\}}}n,#t$rt�d|��YdSwxYw|r%|dkrt�d|||��dS	|������}n,#t$rt�d|��YdSwxYw|S)zGSafe run command.
    Returns stdout as string or empty string on errorNzCommand %s failed with OSError�rz'Command %s failed with exit code %s: %sz#Command %s returned non-utf8 output)r��OSErrorr_�warning�stripr��UnicodeDecodeError)rz�check_returncoderO�rcrr��results       r6�safe_runr�s����� ��3�3�F�3�3�3�3�3�3�3�3���C�����������7��A�A�A��r�r�������B�!�G�G����5�w��C�	
�	
�	
��r�������#�#�%�%�����������<�g�F�F�F��r�r������Ms!��%A�A�0&B�%C�?Cc����d���fd�}|S)znon asyncio vesion of lazy initNc� ����
�����S�Nr4)�decorated_f�placeholders��r6rQz#plainold_lazy_init.<locals>.wrapper!s�����%�+�-�-�K��r5r4)r�rQr�s` @r6�plainold_lazy_initr�s.�����K��������Nr5c��eZdZdZd�Zd�ZdS)�
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_resultr�rp�Lock�_lock)r��cb_coro�check_every_n_secondss   r6�__init__zPeriodicCheck.__init__3sD����
�&;��#�%)�^�%5�%5�8M�%M��"�"&���'���5�5��
�
�
r5c��K�|���4�d{V��tj��|jz
}||jkrVt
�d|j|j��tj��|_|j|i|���d{V��|_|jcddd���d{V��S#1�d{V��swxYwYdS)Nz3Timeout %d seconds has expired, doing the check: %s)	r�r?r@r�r�r_rAr�r�)r�rNrO�deltas    r6�__call__zPeriodicCheck.__call__<s\�����:�:�<�<�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+��N�$�$�t�'A�A�E���3�3�3����I��/��M����
.2�^�-=�-=��*�0=��
�t�0N�v�0N�0N�*N�*N�*N�*N�*N�*N��'��*�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+�
	+����
	+�
	+�
	+�
	+�
	+�
	+s�BB3�3
B=�B=N)r-r.r/r]r�r�r4r5r6r�r�*s<��������6�6�6�+�+�+�+�+r5r�c����fd�}|S)Nc�$��t|���Sr�)r�)r��nsecs �r6�decoratezcache_result.<locals>.decorateKs����T�4�(�(�(r5r4)r�r�s` r6�cache_resultr�Js#���)�)�)�)�)��Or5c��eZdZdZdS)�RecurringCheckStopz:
    raised by coroutine to stop recurring_check loop
    N�r-r.r/r]r4r5r6r�r�Qs��������	�Dr5r�c���K�	t|��r!tj|di|�����d{V��ntj|���d{V��dS#tj$rYdSwxYw)NFTr4)�callablerp�sleep�CancelledError)�period�
period_kwargss  r6�wait_for_periodr�Ys�������F���	(��-��� 7� 7�� 7� 7�8�8�8�8�8�8�8�8�8�8��-��'�'�'�'�'�'�'�'�'��u���!�����t�t����s�A
A�A#�"A#c��8K�|r|rt|fi|���d{V��ndS�NF)r�)�checkr�r�s   r6�should_stop_after_period_passedr�dsG�����	��	�o�f�6�6�
�6�6�6�6�6�6�6�6�6�
�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:
    c�F���t��������fd���}|S)Nc���K�d}	t��fi����d{V��rdS	�|i|���d{V��d}�n#t$rOd|vrG	t|dt��r|d���n#t
$rYnwxYwYdStj$rYdSt$r�}|dz
}|�kr t�
d��Yd}~dSt|tj��r3t�
d|j
|j|j|j��nt�
d���Yd}~nd}~wwxYwt��fi����d{V��rdS��T)NrT�	lock_filerz-Error count exceeded limit,exiting check loopz+Failed to run %s (%s). stdout=%s, stderr=%szError executing %s)r�r�rnr�unlink�FileNotFoundErrorrpr��	Exceptionr_�	exceptionrl�CalledProcessErrorr�r�r�ri)	rNrO�consecutive_err_cnt�exc�check_period_first�consecutive_err_limitrPr�r�s	    �����r6�wrappedz3recurring_check.<locals>.decorator.<locals>.wrappedysH�����"#��'
�8�&����2?�����������E�,��#�t�.�v�.�.�.�.�.�.�.�.�.�:+,�'�'��9*����"�f�,�,�!�)�&��*=�t�D�D�=� &�{� 3� :� :� <� <� <���0�!�!�!� �D�!�����E�E��-�����E�E� �D�D�D�'�1�,�'�*�-B�B�B��(�(�K����������!�#�{�'E�F�F�	D��(�(�I��G��N��J��J������(�(�)=�s�C�C�C����������!D����&9�*�*�F���6C�����������E�O'
sK�0�D?�5A5�4D?�5
B�?D?�B�D?�D?�	D?�"%D:�
A(D:�:D?r)rPr�r�r�r�r�s` ����r6rUz"recurring_check.<locals>.decoratorxsI����	�s���)	�)	�)	�)	�)	�)	�)	�)	�
��)	�V�r5r4)r�r�r�r�rUs```` r6�recurring_checkr�ls7������-�-�-�-�-�-�-�-�^�r5)�backup�uid�gid�allow_empty_content�permissions�dir_fdr�r�c	���t|t��r|���}|�'|rtd���t	|||||||���Stt��5t|d��5}|�t|��dz��}	ddd��n#1swxYwY|	|kr	ddd��dS	ddd��n#1swxYwY|s |st�d||��dS|r�t|ttj
f��r|}
ntj|��tz}
tt��5t!j||
��ddd��n#1swxYwY|�k	t%jtj|��j��}n>#t$r1tjd��}tj|��d	|z}YnwxYwtj�|��\}}
t1|�����st
d
|�����t5��5}t7d|d|
d
zdd���5��fd�}|�|����|������|�*|�(tj�� ��||��tj!�� ��|��tj"�� ����ddd��n#1swxYwYtj#�j$|��|�%��ddd��n#1swxYwYdS)a�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*.
    Nz/backup is not supported when dir_fd is provided)r�r�r�r�r��rbrFzempty content: %r for file: %sri�zParent dir is missing: �wbz	.i360editr�)�mode�dir�suffix�prefix�	buffering�deletec���tt��5tj�j��ddd��dS#1swxYwYdSr�)r
r��os�remove�name)�tfs�r6�cleanupzatomic_rewrite.<locals>.cleanup�s�����/�0�0�'�'��I�b�g�&�&�&�'�'�'�'�'�'�'�'�'�'�'�'����'�'�'�'�'�'s�=�A�AT)&rnro�encoderkr"r
r��open�read�lenr_r�r��PathLike�fspath�BACKUP_EXTENSION�shutil�copy�stat�S_IMODE�st_mode�umask�path�splitrr:rr�callback�write�flush�chown�fileno�chmod�fsync�renamer��pop_all)�filename�datar�r�r�r�r�r��file�old_content�backup_filename�
current_umask�dirpath�basename�stackr�r�s                @r6�atomic_rewriter�s����6�$������{�{�}�}��
���	P��N�O�O�O� ����� 3�#��
�
�
�	
�
�#�	$�	$���
�(�D�
!�
!�	3�T��)�)�C��I�I��M�2�2�K�	3�	3�	3�	3�	3�	3�	3�	3�	3�	3�	3����	3�	3�	3�	3��$����	�����������������������������t�����5�t�X�F�F�F��u�
�3��f�s�B�K�0�1�1�	E�$�O�O� �i��1�1�4D�D�O�
�'�
(�
(�	3�	3��K��/�2�2�2�	3�	3�	3�	3�	3�	3�	3�	3�	3�	3�	3����	3�	3�	3�	3���	1��,�r�w�x�'8�'8�'@�A�A�K�K�� �	1�	1�	1��H�Q�K�K�M��H�]�#�#�#��=�.�0�K�K�K�		1������
�
�h�/�/��G�X���=�=���!�!�G�� E�'� E� E�F�F�F�	�����
�����c�>���

�
�
�	"��
'�
'�
'�
'�
'�
�N�N�7�#�#�#��H�H�T�N�N�N��H�H�J�J�J���3�?��������c�3�/�/�/��H�R�Y�Y�[�[�+�.�.�.��H�R�Y�Y�[�[�!�!�!�-	"�	"�	"�	"�	"�	"�	"�	"�	"�	"�	"����	"�	"�	"�	"�.	�	�"�'�8�$�$�$�
�
�
����3������������������8�4s��(C�9&B+�C�+B/	�/C�2B/	�3
C�C�C�E<�<F�F�
+F6�68G1�0G1�M>�0B=L9�-M>�9L=	�=M>�L=	�1M>�>N�Nc��	td��������S#t$rYdSwxYw)Nz/etc/system-release)r�	read_text�rstripr�r4r5r6�os_release_and_versionr"sP����)�*�*�4�4�6�6�=�=�?�?�?�������t�t����s�25�
A�Ac���|p
t��}|r-tjd|��}|r|�d��Snt���td|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"�re�search�group�cache_clearrk)�release_and_version�rv�matchs   r6�
os_versionr+st��
�	8� 6� 8� 8�B�	�-��	�3�R�8�8���	"��;�;�q�>�>�!�	"�	�*�*�,�,�,�
�8�2�=�
>�
>�>r5c��eZdZdZed��Zed��Zed��ZdZe	d���Z
e	deee
ffd���Ze	deefd	���Ze	defd
���Ze	defd���Ze	d���Ze	d
���Ze	d���Ze	d���Ze	d���Ze	d���Ze	d���Ze	d���Ze	d���ZdS)�
OsReleaseInfoz/etc/os-release)�debian)�rhel�fedora�centos)�unknownNc��t|j��5}|D]U}	|����d��\}}|�d��||<�F#t
$rY�RwxYw	ddd��n#1swxYwYd|vr,t
|d�����|d<dSt
|�dd��f��|d<dS)N�=�"�ID_LIKE�ID�linux)r��ETC_OS_RELEASEr!rr�rk�	frozenset�get)�cls�dict_�f�line�k�vs      r6�dict_from_filezOsReleaseInfo.dict_from_file.s3��
�#�$�
%�
%�	���
�
����;�;�=�=�.�.�s�3�3�D�A�q� �w�w�s�|�|�E�!�H�H��!�����D�����	
�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�����(��y�)9�)?�)?�)A�)A�B�B�E�)���� )�%�)�)�D�'�*B�*B�)D�E�E�E�)���s5�A;�AA�A;�
A+�(A;�*A+�+A;�;A?�A?r`c�\�|j��t��}tj�|j��r|�|��n�tj��}|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)Nr�redzRed Hat Enterprise Linuxr/r7z
{} {} ({})rrb�PRETTY_NAME)�
cloudlinuxr1r/r6)�ubuntur.r2)r=�dictr�r
r:r9rB�distro�linux_distribution�lowerrr��RHEL_FEDORA_CENTOS�DEBIAN�UNKNOWN)r<r=�d�osids    r6�to_dictzOsReleaseInfo.to_dict=sQ���9��$(�F�F�E��w�~�~�c�0�1�1�
5��"�"�5�)�)�)�)��-�/�/���5��1��5��Q�4�:�:�<�<�-�-�/�/��2�D��u�}�}�)C�q��t�)K�)K�%��"&�E�$�K�+7�+>�+>��!��a��d�A�a�D�,�,�E�-�(��?�?�?�+.�+A��i�(�(��!5�5�5�+.�:��i�(�(�+.�;��i�(�(�"+�E�$�K�'*�{�E�)�$�+4�E�-�(��C�I��y�r5c�6�|���dS)Nr6�rQ�r<s r6�id_likezOsReleaseInfo.id_like\s���{�{�}�}�Y�'�'r5c�6�|���dS)NrErSrTs r6�pretty_namezOsReleaseInfo.pretty_name`s���{�{�}�}�]�+�+r5c�R�|����dd��S)zi
        :return: OS name, like centos, ubuntu, debian, cloudlinux, redhat
        in lower case
        r7r2)rQr;rTs r6�get_oszOsReleaseInfo.get_osds"���{�{�}�}� � ��y�1�1�1r5c�2�|���dkS)Nr/�rYrTs r6�is_rhelzOsReleaseInfo.is_rhells���z�z�|�|�v�%�%r5c�2�|���dkS)Nr1r[rTs r6�	is_centoszOsReleaseInfo.is_centosp����z�z�|�|�x�'�'r5c�2�|���dkS)NrGr[rTs r6�	is_ubuntuzOsReleaseInfo.is_ubuntutr_r5c�.�|���dvS)N)rF�cloudlinuxserverr[rTs r6�
is_cloudlinuxzOsReleaseInfo.is_cloudlinuxxs���z�z�|�|�A�A�Ar5c�J�tj�t��Sr�)r�r
r:�_CL_SOLO_EDITION_FILErTs r6�is_cloudlinux_soloz OsReleaseInfo.is_cloudlinux_solo|s���w�~�~�3�4�4�4r5c�2�|���dkS)Nr.r[rTs r6�	is_debianzOsReleaseInfo.is_debian�r_r5c�2�|���dkS)N�olr[rTs r6�is_oracle_linuxzOsReleaseInfo.is_oracle_linux�s���z�z�|�|�t�#�#r5c�2�|���dkS)N�	almalinuxr[rTs r6�is_almalinuxzOsReleaseInfo.is_almalinux�s���z�z�|�|�{�*�*r5c�2�|���dkS)N�rockyr[rTs r6�
is_rockylinuxzOsReleaseInfo.is_rockylinux�s���z�z�|�|�w�&�&r5)r-r.r/r9r:rMrLrNr=�classmethodrBrrorrQrrUrWrYr\r^rardrgrirlrorrr4r5r6r-r-&s������&�N�
�Y�{�
#�
#�F�"��#?�@�@���i��%�%�G��E��F�F��[�F����S�#�X������[��<�(�	�#��(�(�(��[�(��,�C�,�,�,��[�,��2�s�2�2�2��[�2��&�&��[�&��(�(��[�(��(�(��[�(��B�B��[�B��5�5��[�5��(�(��[�(��$�$��[�$��+�+��[�+��'�'��[�'�'�'r5r-�r�	chunksizec�0�t|||��dS)z�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.
    r)�file_hash_and_size)r�	hash_funcrus   r6�	file_hashry�s���h�	�9�=�=�a�@�@r5c��|��}d}t|d��5}	|�|��}|sn(|�|��|t|��z
}�@	ddd��n#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).rr�TN)r�r��updater�	hexdigest)rrxru�hash_�sizer>�chunks       r6rwrw�s���
�I�K�K�E��D�	
�h��	�	���	��F�F�9�%�%�E��
���L�L������C��J�J��D�	��	�������������������?�?���d�"�"s�AA,�,A0�3A0c��|���\}}||kr&tdjdit�������|S)z0Given login.defs line, return *varname*'s value.z"Expected {varname!r}, got {name!r}r4)rrkr��vars)�varname�	defs_liner��values    r6�_parse_name_valuer��sJ���/�/�#�#�K�D�%��$����D�=�D�N�N�t�v�v�N�N�O�O�O��Lr5c�H�tdkrt��\a}tS)Nr()�_MIN_UID�_get_max_min_uid)r�s r6�get_min_uidr��s���2�~�~�&�(�(���!��Or5�/etc/login.defsc�h�d\}}	t|��5}|D]f}|�d��rttd|����}|�d��rttd|����}�g	ddd��n#1swxYwYn#tt
f$rYnwxYw||fS)z�Get UID_MIN, UID_MAX from the login.defs file specified as *path*.

    On error, return default for the current OS values.

    )i�i`��UID_MIN�UID_MAXN)r��
startswith�intr�r�rk)r
�uid_min�uid_maxrr?s     r6r�r��s(��#��G�W�	
�
�$�Z�Z�	F�4��
F�
F���?�?�9�-�-�F�!�"3�I�t�"D�"D�E�E�G��?�?�9�-�-�F�!�"3�I�t�"D�"D�E�E�G��
F�	F�	F�	F�	F�	F�	F�	F�	F�	F�	F�	F����	F�	F�	F�	F���
�Z� �
�
�
���
�����G��s5�B�A*B
�B�
B�B�B�B�B-�,B-�zimunify360-captchazimunify360-webshieldc�l����t��\�����fd�tj��D��S)z~
    :param excludes: users to exclude in results
    :return: list: list of pwd.struct_passwd objects representing users
    c�P��g|]"}�|jcxkr�k�nn|j�v� |��#Sr4��pw_uid�pw_name)�.0�entry�excludesr�r�s  ���r6�
<listcomp>z(get_non_system_users.<locals>.<listcomp>�sS��������e�l�-�-�-�-�g�-�-�-�-�-�%�-�x�2O�2O�	�2O�2O�2Or5�r��pwd�getpwall)r�r�r�s`@@r6�get_non_system_usersr��sR�����(�)�)��G�W��������\�^�^����r5c�d��t��\�}�fd�tj��D��S)z;
    :return: list: list of str with system user names
    c�4��g|]}�|jk�
|j��Sr4r�)r�r�r�s  �r6r�z)get_system_user_names.<locals>.<listcomp>�s.�������W���5L�5L��
�5L�5L�5Lr5r�)r�r�s @r6�get_system_user_namesr��sE���"�#�#�J�G�Q�����#&�<�>�>����r5r�c�0�t��\}}||kSr�)r�)r�r�r�s   r6�is_system_userr��s��'�)�)��G�W���=�r5�d)r7�typedc��t|d��5}|�dd��}|dkrF|�|dz
��|�d��dkr|�d��|�|��|�d��s|�d��ddd��dS#1swxYwYdS)Nzr+rrbr�
�r��seekr�r
�endswith�rrr>�
last_char_poss    r6�append_with_newliner�s���	
�h��	�	�
�����q�!���
��A���
�F�F�=�1�$�%�%�%��v�v�a�y�y�D� � �����
�
�
�	����
�
�
��}�}�T�"�"�	�
�G�G�D�M�M�M�
�
�
�
�
�
�
�
�
�
�
�
����
�
�
�
�
�
��B"C�C�Crc��t|d��5}|�dd��}|dkrF|�|dz
��|�d��dkr|�d��|�|��|�d��s|�d��ddd��dS#1swxYwYdS)z>Append *data* to *filename* making sure there is 
 at the end.zr+brrbr�
Nr�r�s    r6�append_with_newline_bytesr�s���	
�h��	�	�
�!����q�!���
��A���
�F�F�=�1�$�%�%�%��v�v�a�y�y�E�!�!��������	����
�
�
��}�}�U�#�#�	�
�G�G�E�N�N�N�
�
�
�
�
�
�
�
�
�
�
�
����
�
�
�
�
�
r�c���d}t|d��5}t�fd�|D����sd}ddd��n#1swxYwY|rt|���|S)z�Add *line* to *filename* if it is not present in the file

    Returns:
        True if the file was changed, False otherwise.
    F�rc3�H�K�|]}|����kV��dSr��r��r��_liner?s  �r6�	<genexpr>z&ensure_line_in_file.<locals>.<genexpr>)�0�����8�8�U�5�;�;�=�=�D�(�8�8�8�8�8�8r5TN)r��anyr��rr?�changedr>s `  r6�ensure_line_in_filer�!s�����G�	
�h��	�	����8�8�8�8�a�8�8�8�8�8�	��G��������������������,��H�d�+�+�+��N��>�A�Ar?c���d}t|d��5}t�fd�|D����sd}ddd��n#1swxYwY|rt|���|S)z�Add *line* to *filename* if it is not present in the file.

    Returns:
        True if the file was changed, False otherwise.
    Fr�c3�H�K�|]}|����kV��dSr�r�r�s  �r6r�z,ensure_line_in_file_bytes.<locals>.<genexpr>8r�r5TN)r�r�r�r�s `  r6�ensure_line_in_file_bytesr�0s�����G�	
�h��	�	����8�8�8�8�a�8�8�8�8�8�	��G��������������������2�!�(�D�1�1�1��Nr�c��tj�|��}t|d��5}t	d|d���5}|D]/}|���|kr|�|���0tj|j|��ddd��n#1swxYwYddd��dS#1swxYwYdS)Nr��wF)r�r�r�)	r�r
�dirnamer�rr�r
rr�)rr?�basedir�sfr�r�s      r6�remove_line_from_filer�?s5���g�o�o�h�'�'�G��X�s���%�!�����?�?�?�%�CE��	 �	 �E��{�{�}�}��$�$���������
�	�"�'�8�$�$�$�%�%�%�%�%�%�%�%�%�%�%����%�%�%�%�%�%�%�%�%�%�%�%�%�%�%�%����%�%�%�%�%�%s6�B4�A
B�B4�B 	� B4�#B 	�$B4�4B8�;B8c�,�eZdZdZdZefd�Zd�Zd�ZdS)�FileLockz`
    Simple context manager to enable
    UNIX-specific file locking with flock system call
    r�c�Z�||_d|_t|d��|_||_dS)NFr�)r
�lockedr�rr�)r�r
r�s   r6r�zFileLock.__init__Ss*����	������s�O�O��	�����r5c��K�tj��}		t|jttz��d|_|S#ttf$r}|jtj	kr�|j
tj��|z
kr&t�d|j
��Yd}~dStjd���d{V��Yd}~nd}~wwxYw��)NTz)Failed to lock file %s. Timeout exceeded.r)r?rrr
rr�r��IOError�errno�EAGAINr�r_r�r
rpr�)r�rE�exs   r6�
__aenter__zFileLock.__aenter__Ys������	����	'�
'��d�i��7�!2�3�3�3�"�������W�%�
'�
'�
'��8�u�|�+�+���\�D�I�K�K�%�$7�7�7��N�N�C�T�Y�����E�E�E�E�E��m�A�&�&�&�&�&�&�&�&�&�&�&�&�&�&�����
'����	's�*A�C�AC�/C�Cc��K�|jrt|jt��d|_|j���dSr�)r�rrr�close)r��exc_type�exc_val�exc_tbs    r6�	__aexit__zFileLock.__aexit__qsC�����;�	&��$�)�W�%�%�%�����	�������r5N)r-r.r/r]�_TIMEOUTr�r�r�r4r5r6r�r�KsZ��������
�H�%-�����'�'�'�0����r5r�c����	|g}|��fd�t|�����D����tj��}|�d�|���dd����|���S#ttf$r%}t�d|��Yd}~nd}~wwxYwdS)Nc3�D�K�|]\}}|�v�	t|��V��dSr�)ro)r��fieldr��fieldss   �r6r�z user_identity.<locals>.<genexpr>�s?�����
�
���u�����
��J�J�����
�
r5r��utf8�surrogateescapez9Generation of user identity hash failed, invalid data: %s)
�extend�sorted�items�hashlib�sha1r{�joinr�r|rk�UnicodeEncodeErrorr_r�)�attackers_ip�sourcer��uid_data�hash_alg�es  `   r6�
user_identityr�{s���
�!�>�����
�
�
�
� &�v�|�|�~�~� 6� 6�
�
�
�	
�	
�	
��<�>�>���������)�)�0�0��9J�K�K�L�L�L��!�!�#�#�#���*�+�
�
�
����G��	
�	
�	
�	
�	
�	
�	
�	
�����
����
�4s�B%B)�)C�:C�Cc�0�tj��dkS�Nr)r��getuidr4r5r6�is_root_userr��s��
�9�;�;�!��r5�maskc#�K�tj|��}	dV�tj|��dS#tj|��wxYwr�)r�r	)r��current_masks  r6�run_with_umaskr��sM�����8�D�>�>�L��
����
������������������s	�2�Ar��usernamec�~�t|t��std|z���tj|vrtd���	tj|��}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)
rnrorkr��sepr��getpwnam�KeyErrorr�r
r��pw_dirr)r��relpath�pw�abs_paths    r6�get_abspath_from_user_dirr��s����h��$�$�K��>��I�J�J�J�	�v�����+�,�,�,�E�
�\�(�
#�
#�����E�E�E��2�9�9�(�C�C�D�D�D�E�����w�|�|�B�I�w�/�/�H���>�>�s�A�-Br
c���d}	t|��}t|���|��d}n>#t$r1}t�t
|����Yd}~nd}~wwxYw|S)NFT)r�r�relative_torkr_r�ro)r
r��status�	user_homer�s     r6�does_path_belong_to_userr�s���
�F��-�h�7�7�	��T�
�
���y�)�)�)������������s�1�v�v������������������Ms�38�
A3�'A.�.A3c��tj�|��std|z���	tj�|��rg	tjtj|��j��j	S#t$r)ttj|��j��cYSwxYwtj�|��}��)NzPath %s should be absolute!)
r�r
�abspathrkr:r��getpwuidr�st_uidr�r�ror��r
s r6�get_path_ownerr	�s���
�7�?�?�4� � �?��6��=�>�>�>�%�
�7�>�>�$���	1�
1��|�B�G�D�M�M�$8�9�9�A�A���
1�
1�
1��2�7�4�=�=�/�0�0�0�0�0�
1�����w���t�$�$��
%s�/B�0B6�5B6���iterable�
chunk_sizec#�K�t|��}tt||����}|r%|V�tt||����}|�#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)rr�i�pieces    r6�split_for_chunkr�sp����	
�X���A����:�&�&�'�'�E�
�,������V�A�z�*�*�+�+���,�,�,�,�,r5c���t|t��r+td�|���D����St|t��rtd�|D����S|S)Nc3�>K�|]\}}|t|��fV��dSr���freeze)r��keyr�s   r6r�zfreeze.<locals>.<genexpr>�s1����J�J�*�#�u�#�v�e�}�}�-�J�J�J�J�J�Jr5c3�4K�|]}t|��V��dSr�r)r�r�s  r6r�zfreeze.<locals>.<genexpr>�s(����2�2�u�V�E�]�]�2�2�2�2�2�2r5)rnrHr:r�rrrs)rOs r6rr�sm���!�T���3��J�J����	�	�J�J�J�J�J�J�	�A�t�	�	�3��2�2��2�2�2�2�2�2��Hr5c�&��eZdZdZiZ�fd�Z�xZS)�	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    �r6r�zSingleton.__call__�sr����F�4�L�L�&��.�.�1���~�!�!�#�&�&�	�"@�%�	�3�"7�"7�"@��#��#�#�C�N�3���~�c�"�"r5)r-r.r/r]rr��
__classcell__)rs@r6rr�sI���������
�J�#�#�#�#�#�#�#�#�#r5rc���tj�dd���5}|������cddd��S#1swxYwYdS)z3
    :return str: server's external IP address
    zhttps://api.ipify.orgrbr�N)�urllib�request�urlopenr�r�)r�s r6�get_external_ipr#�s���

��	�	� 7��	�	C�	C�!�q��v�v�x�x��� � �!�!�!�!�!�!�!�!�!�!�!�!����!�!�!�!�!�!s�&A�A�Ac�<�d}|�||���}tj�|��st	d|�d|�����t|d��5}|������}ddd��n#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 r�N)r�r�r
r:rkr�r�r�)�module_namer&�
_MOD_PAR_PATH�
param_file�pr�s      r6�get_kernel_module_parameterr+�s���?�M��%�%�+��%�K�K�J�
�7�>�>�*�%�%�
��j�8A�	�	�;�;�O�
�
�	
�
�j�#�	�	�!�!�������� � ��!�!�!�!�!�!�!�!�!�!�!����!�!�!�!��Ls�'B�B�Bc���d}|���D][\}}t|t��r%||vs|s|||<d}�(t|||��}�?||vs|sJ|�d|�����|||<d}�\|S)z�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 recursivelyFTz already exists in )r�rnrH�dict_deep_update)�dst�src�allow_overwrite�updatedr@rAs      r6r-r-s����G��	�	�������1��a����	���|�|�1�|���A�����*�3�q�6�1�5�5�����������-�-��-�-���/��C��F��G�G��Nr5c�8�eZdZd
d�Zd�Zd�Zd�Zdedefd�Zd	S)�
TimedCacher�c��t|t��sJ�||_||_t	��|_i|_dSr�)rnr�
expirationr7r�cache�_locks)r�r5r7s   r6r�zTimedCache.__init__*s<���*�i�0�0�0�0�0�$������ �]�]��
�����r5c���t��}|jD]J}|j|\}}tj��|z
|j���kr||f||<�K||_dS)zClear cache from expired valuesN)rr6r?r5�
total_seconds)r��	tmp_cacherr��added_ats     r6�_collectzTimedCache._collect1sh���M�M�	��:�	1�	1�C�"�j��o�O�E�8��	���h�&�$�/�*G�*G�*I�*I�I�I�!&���	�#�����
�
�
r5c�:�t��|_i|_dSr�)rr6r7�r�s 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:
        )r�r�rs�hash)r�rNrO�seed�kws     r6�	_make_keyzTimedCache._make_key>sB�����	�������'�'�B��E�"�I�I��D��D�z�z�r5�funcr`c����t�����fd���}t�����fd���}tj���r|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.
        c��h�K���||��}�j�|��}|�tj��x}�j|<		tj|����j������d{V��nP#tj	$r=|�j|urtj��x}�j|<n
�j|}YnwxYw��	��
��	�j|\}}ns#t$rft�j���jkr�j�d����|i|���d{V��}|t!j��f�j|<YnwxYw|���n#|���wxYw|S)NTF��last)rCr7r;rpr�r��acquirer5r9�TimeoutErrorr<r6r�rr7�popitemr?�release)rNrOr�lockr�r�rDr�s      ��r6�
wrapper_asyncz*TimedCache.__call__.<locals>.wrapper_asyncXs�������.�.��v�.�.�C��;�?�?�3�'�'�D��|�*1�,�.�.�8��t�{�3�'�
0�0�!�*��������(E�(E�(G�(G�������������+�	0�	0�	0��t�{�3�/�/�/�29�,�.�.�@��t�{�3�/�/�#�{�3�/����	0����

0� 

��
�
����:� $�
�3��I�F�A�A���:�:�:��4�:���$�,�6�6��
�*�*��*�6�6�6�#'�4��#8��#8�#8�8�8�8�8�8�8�F�&,�d�i�k�k�&9�D�J�s�O�O�O�	:�����������������������MsE�AB�A	C&�%C&�+F�D�F�A-F�>F�F�F�F/c�Z��������||��}	�j|\}}nm#t$r`t	�j���jkr�j�d����|i|��}|tj��f�j|<YnwxYw|S)NFrG)r<rCr6r�rr7rKr?)rNrOrr�r�rDr�s     ��r6�wrapper_syncz)TimedCache.__call__.<locals>.wrapper_sync{s�����M�M�O�O�O��.�.��v�.�.�C�
6� �J�s�O�	������
6�
6�
6��t�z�?�?�d�l�2�2��J�&�&�E�&�2�2�2���t�.�v�.�.��"(�$�)�+�+�"5��
�3����	
6����
�Ms�>�A'B(�'B()rrp�iscoroutinefunctionr')r�rDrNrPrQs``   r6r�zTimedCache.__call__Ks�����
�t��� 	� 	� 	� 	� 	�
�� 	�D
�t���
	�
	�
	�
	�
	�
��
	��*�4�0�0�
�M�M��	�
#�.����r5N)r�)	r-r.r/r�r<r'rCr#r�r4r5r6r3r3)s�������������������C�Q�C�1�C�C�C�C�C�Cr5r3�r�c��>K�|���r<|���s&	|���n#t$rYnwxYwdS|���tj|h|����d{V��\}}|rL|���s|���nd}|rt�	d||��dSdS|���s4t�	d||��|�
d���dSdS)u�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.
    Nr�z&Task %r raised during cancellation: %sz.Task %r did not finish within %ds after cancelc�$�t|d���S)Nz*Abandoned task failed after cancel timeout)�message)�log_future_errors)�ts r6�<lambda>z"safe_cancel_task.<locals>.<lambda>�s��'��G����r5)�done�	cancelledr�r��cancelrpryr�r_r��add_done_callback)�taskr�rYr�r�s     r6�safe_cancel_taskr^�s^�����y�y�{�{���~�~���	�
����
�
�
�
���
�
�
���
�������K�K�M�M�M��L�$���9�9�9�9�9�9�9�9�9�G�D�!��
�&*�n�n�&6�&6�@�d�n�n����D���	P��N�N�C�T�3�O�O�O�O�O�	P�	P�
�Y�Y�[�[�
����<�d�G�	
�	
�	
�	
���
�
�	
�	
�	
�	
�	
�	
�
s�A�
A�
Ac�f�tjtj��tj��dS)z�
    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.
    N)r��kill�getpid�signal�SIGUSR2r4r5r6�fail_agent_servicerd�s$���G�B�I�K�K���(�(�(�(�(r5c
���K�|�dttj������}t	|d��5}|�t
tjj	||d�����t�d||��tjj|fi|���d{V��}t	|dzd��5}|�
d�|jt!j|j������������ddd��n#1swxYwYddd��n#1swxYwY|S)	z�
    Runs command and log it's output to the log file

    :param cmd:
    :param log_file_mask:
    :return: str path of log file
    �*r�Trfz
Popen(%r, %r)Nz.pidz{:d}	{}
)�replaceror�rar�r{rHrp�
subprocessr�r_rArqr
r��pid�psutil�Process�create_time�hex)r��
log_file_mask�popen_kwargs�live_log�live_log_fpr~�pfs       r6�run_cmd_and_logrs�s������$�$�S�#�b�i�k�k�*:�*:�;�;�H�	
�h��	�	��������(�0�"�"�"&�	
�
�
�	
�	
�	
�	���_�c�<�8�8�8��'�?��
�
��
�
�
�
�
�
�
�
���(�V�#�S�
)�
)�	�R��H�H��#�#��H�f�n�T�X�6�6�B�B�D�D�H�H�J�J���
�
�
�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�������������������&�Os8�BE�	A*D?�3E�?E	�E�E	�E�E�Ec��	td��5}|D]C}|�d��r,|���ddkccddd��S�D	ddd��n#1swxYwYn#t$rYnwxYwdS)aLReturn 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).
    z/proc/self/statuszNoNewPrivs:r�1NF)r�r�rr�)r>r?s  r6�_has_no_new_privsrv�s���
�
�%�
&�
&�	2�!��
2�
2���?�?�=�1�1�2��:�:�<�<��?�c�1�1�1�	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����
�
�
���
�����5s@�A1�9A%�
A1�A%�A1�%A)�)A1�,A)�-A1�1
A>�=A>)�systemd-runz--quietz--waitz--pipez	--collectz--property=NoNewPrivileges=noz--property=ProtectSystem=noc�`�|sdStd�|���D����S)Nr4c3�,K�|]\}}d|�d|��V��dS)z	--setenv=r4Nr4)r�r@rAs   r6r�z+_systemd_run_setenv_args.<locals>.<genexpr>s7����=�=���A�$�Q�$�$��$�$�=�=�=�=�=�=r5)rsr���envs r6�_systemd_run_setenv_argsr|s4�����r��=�=������=�=�=�=�=�=r5r�c��t��s|Stt|��zdd|fz}d�d�|D����S)z�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.�/bin/sh�-c� c3�>K�|]}tj|��V��dSr���shlex�quote�r�r*s  r6r�z._wrap_outside_sandbox_shell.<locals>.<genexpr>�*����2�2�q�E�K��N�N�2�2�2�2�2�2r5)rv�_SYSTEMD_RUN_BASEr|r��r�r{�partss   r6�_wrap_outside_sandbox_shellr�sa�������
��
"�3�
'�
'�	(��d�C�
 �	!�
�
�8�8�2�2�E�2�2�2�2�2�2r5c��t��st|��Sttt|��zdzt	|��z��S)z5Argv-form counterpart of _wrap_outside_sandbox_shell.)z--)rvrrr�r|rs)�argvr{s  r6�_wrap_outside_sandbox_argvr�"sY�������D�z�z����
"�3�
'�
'�	(�
�	���+�+�	���r5rzc��LK�tt||���|fi|���d{V��S)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)rsr��r�rnr{ros    r6�run_cmd_and_log_outside_sandboxr�.sZ����!�#�C�S�1�1�1�������������r5c��JK�tt||���fi|���d{V��S)z7run() variant that escapes the agent's systemd sandbox.rzN)r�r��r�r{rOs   r6�run_outside_sandboxr�>s<�����/��#�>�>�>�I�I�&�I�I�I�I�I�I�I�I�Ir5c��JK�tt||���fi|���d{V��S)z=check_run() variant that escapes the agent's systemd sandbox.rzN)r�r�r�s   r6�check_run_outside_sandboxr�Cs<�����5�d��D�D�D�O�O��O�O�O�O�O�O�O�O�Or5��c�N�t��sdS	tjddgtjtjdd���j}n#ttjf$rYdSwxYwtj	d|��}|�dSt|�����tkS)z=Return True iff systemd-run can host a transient unit for us.Frwz	--versionT�)rhri�textr�z\d+)
r=rlr�rmr�rhr��SubprocessErrorr$r%r�r&�_SYSTEMD_RUN_MIN_VERSION)�version_liner*s  r6�_systemd_run_supportedr�Ws��������u�	�"��
�K�(��#��&���
�
�
��
	���
�[�0�1�����u�u������I�f�l�+�+�E��}��u��u�{�{�}�}���!9�9�9s�4A�A!� A!c��t��s|Stt|��zdd|fz}d�d�|D����S)z�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.r~rr�c3�>K�|]}tj|��V��dSr�r�r�s  r6r�z,_wrap_in_own_cgroup_shell.<locals>.<genexpr>wr�r5)r�r�r|r�r�s   r6�_wrap_in_own_cgroup_shellr�lsa��"�#�#���
��
"�3�
'�
'�	(��d�C�
 �	!�
�
�8�8�2�2�E�2�2�2�2�2�2r5c��LK�tt||���|fi|���d{V��S)z�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.
    rzN)rsr�r�s    r6�run_cmd_and_log_in_own_cgroupr�zsZ����!�!�#�3�/�/�/�������������r5c��eZdZd�ZdS)r�c	�`�|jdkr7|jr0|dd�}|jr
|j|d<|j�|��	tj|��dS#t$r�t|j	dd��pt|j	dd��}t|j	dd��pt|j	dd��}t|j	d	d��pt|j	d
d��}|j
}|r|jp|j}td�||j||����YdSwxYw)N�PENDINGz%Task was destroyed but it is pending!)r]rU�source_tracebackr/r-�gi_code�cr_code�gi_frame�cr_framez+!> Finalizer error in {}() {} at {} line {})�_state�_log_destroy_pending�_source_traceback�_loop�call_exception_handlerr�__del__�AttributeError�getattr�_coro�co_filename�f_lineno�co_firstlineno�printr�)r��contextr�r��framer�linenos       r6r�zTask.__del__�s{���;�)�#�#��(A�#��B���G��%�
E�.2�.D��*�+��J�-�-�g�6�6�6�	��N�4� � � � � ���	�	�	��4�:�~�t�<�<����
�J��A�A�D��4�:�y�$�7�7��7��
�I�t�<�<�D��D�J�
�D�9�9��W��
�J��>�>�E��'�H��.���F�4�3F�F��=�D�D��$�+�x����
�
�
�
�
�
�	���s�A�CD-�,D-N)r-r.r/r�r4r5r6r�r��s#����������r5r�c����fd�}|S)z�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.'
    c��<�K�tj����d{V��Sr�)rpr�)rNrcs �r6�pausezawait_for.<locals>.pause�s)������]�7�+�+�+�+�+�+�+�+�+r5r4)rcr�s` r6rwrw�s#���,�,�,�,�,��Lr5c�j��������t��g��std����������fd�}|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���}tj���r|S|S)Nc��~�K��rtj���z}�
stjd��nt	d�
dz��D]�}	�rg|tj��z
}|dkr$tj�|i|��|����d{V��cS�
st
j��	r�	�d���n�|i|���d{V��cS�}#t
jt
j	f$r��$rX}���||���d{V��}|s�
}|�
kr�
s��	r�	�d�|�����||���d{V��Yd}~��d}~wwxYwdS)Nrrr�� Timeout exceeded when calling %s�0Max tries exceeded when calling %s with error %s)
r?r@�	itertools�countr�rpr�rJr�r��rNrO�end_timer�remaining_timer��should_retry_retr�rDrDrdre�should_retry�silentr�s       ��������r6rNz2retry_on.<locals>.decorator.<locals>.wrapper_async�s,������
6��>�+�+�g�5��!�-�	���"�"�"��1�i�!�m�,�,�(
/�(
/��
#/��;�)1�D�N�4D�4D�)D��)�A�-�-�)0�)9� $��d� 5�f� 5� 5�~�*�*�*�$�$�$�$�$�$����$*�"�&-�&:� :�!$�"� #�	�	�$F��!"�!"�!"��&*�T�4�%:�6�%:�%:�:�:�:�:�:�:�:�:�:����,�g�.D�E����� �/�/�/�#�/�1=��c�1�1E�1E�+E�+E�+E�+E�+E�+E�(�/�*� )�A��I�~�~�%��!� ���I�I�!,� $� #�	��� �+�&�h�s�A�.�.�.�.�.�.�.�.�.����������#/����/(
/�(
/s�?C�
4C� D:�"AD5�5D:c�����rtj���z}�
stjd��nt	d�
dz��D]�}	�rH|tj��z
}|dkr
�|i|��cS�
st
��	r�	�d���n
�|i|��cS�X#�$rL}���||��}|s�
}|�
kr�
s��	r�	�d�|�����||��Yd}~��d}~wwxYwdS)Nrrr�r�)r?r@r�r�r�rJr�r�s       ��������r6rPz1retry_on.<locals>.decorator.<locals>.wrapper_sync�s�����
6��>�+�+�g�5��!�-�	���"�"�"��1�i�!�m�,�,�$
)�$
)��
)��5�)1�D�N�4D�4D�)D��)�A�-�-�#'�4��#8��#8�#8�8�8�8�#)�"�&2� 2�!$�"� #�	�	�$F��!"�!"�!"�� $�t�T�4�V�4�4�4�4�4��� �)�)�)�#�/�+7�<��Q�+?�+?�(�/�*� )�A��I�~�~�%��!� ���I�I�!,� $� #�	��� �+� ���a�(�(�(����������#)����'$
)�$
)s�%B�.)B�C,� AC'�'C,�rSrrprQ)
rDrNrPr�rDrdrer�r�r�s
`  �������r6rUzretry_on.<locals>.decorator�s�����	���	�	�+	/�+	/�+	/�+	/�+	/�+	/�+	/�+	/�+	/�+	/�+	/�
�	�+	/�Z
���	�	�'	)�'	)�'	)�'	)�'	)�'	)�'	)�'	)�'	)�'	)�'	)�
�	�'	)�R�&�t�,�,�	 � � ��r5)r�rk)r�rerdr�r�rDr�rUs``````` r6ruru�sz���������$�	�7�#�$�$�:��8�9�9�9�\ �\ �\ �\ �\ �\ �\ �\ �\ �\ �\ �|�r5c���tj����fd���}tj����fd���}tj���r|n|S)zhIf func throws an exception it is catched, converted to a string and
    returned as a result of a call.c��r�K�	�|i|���d{V��S#t$r}t|��cYd}~Sd}~wwxYwr��r��repr�rNrOr�rDs   �r6rNz,stub_unexpected_error.<locals>.wrapper_async5sg�����	���t�.�v�.�.�.�.�.�.�.�.�.���	�	�	���7�7�N�N�N�N�N�N�����	���s�
�
6�1�6�6c�b��	�|i|��S#t$r}t|��cYd}~Sd}~wwxYwr�r�r�s   �r6rPz+stub_unexpected_error.<locals>.wrapper_sync<sQ���	��4��(��(�(�(���	�	�	���7�7�N�N�N�N�N�N�����	���s��
.�)�.�.r�)rDrNrPs`  r6�stub_unexpected_errorr�1s����_�T�����������_�T����������$�7��=�=�O�=�=�<�Or5c�2�����tj���fd�}|S)zkA decorator that logs uncaught exceptions ignoring them otherwise.

    CancelledError is not handled.
    Nc����tj������fd���}tj������fd���}tj���r|S|S)Nc	���K�	�|i|���d{V��S#tj$r��$r'}�dt�dd��|��Yd}~dSd}~wwxYw�NzIgnoring exception from %s: %sr/r�)rpr�r��rNrOr�r�r��log_handlers   ���r6rNz>log_error_and_ignore.<locals>.decorator.<locals>.wrapper_asyncOs������	
�!�T�4�2�6�2�2�2�2�2�2�2�2�2���)�
�
�
���
�
�
���4��D�.�&�9�9���������������
���s�
�A�A	�	Ac	�t��	�|i|��S#�$r'}�dt�dd��|��Yd}~dSd}~wwxYwr�)r�r�s   ���r6rPz=log_error_and_ignore.<locals>.decorator.<locals>.wrapper_sync\s����
��t�T�,�V�,�,�,���
�
�
���4��D�.�&�9�9���������������
���s��7�2�7r�)r�rNrPr�r�s`  ��r6rUz'log_error_and_ignore.<locals>.decoratorNs�����	���	�	�
	�
	�
	�
	�
	�
	�
�	�
	�
���	�	�	�	�	�	�	�	�
�	�	��&�t�,�,�	 � � ��r5)r_r�)r�r�rUs`` r6�log_error_and_ignorer�Fs9����
���l�� � � � � � �<�r5c������fd�}|S)z'Abort the agent service on *exception*.c�L���tj������fd���}|S)Nc���K�	�|i|���d{V��S#�$r/}t�|�����Yd}~dSd}~wwxYwr�)r_r�)rNrOr��abortr�r�s   ���r6rQz2abort_agent_on.<locals>.decorator.<locals>.wrapperss�����
�!�T�4�2�6�2�2�2�2�2�2�2�2�2���
�
�
�� � ��#�#�#����������������	
���s�
�A�$A�ArR)r�rQr�r�s` ��r6rUz!abort_agent_on.<locals>.decoratorrsC����	���	�	�	�	�	�	�	�	�
�	�	��r5r4)r�r�rUs`` r6�abort_agent_onr�os*�����������r5c�R�tjdd|�����S)zPascalCase to snake_casez([a-z])([A-Z])z\1_\2)r$�subrK)�strings r6�
snake_caser��s#��
�6�"�H�f�5�5�;�;�=�=�=r5��g�������?g�?c�H�dt|�����vS)Nr�)rorK)r�s r6�_is_db_locked_errorr��s���s�3�x�x�~�~�'�'�'�'r5)�exec_expr_with_empty_iterc'��K�|s|rdg}nt|t���}ddlm}|j���5|D]}||g|�R�Ed{V���	ddd��dS#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:
    N�rr��instance)r�CHUNK_SIZE_SQL_QUERY�defence360agent.modelr��db�transaction)�exprrr�rN�chunksr�rs       r6�get_results_iterable_expressionr��s����&�L�1�L����� ��6J�K�K�K��.�.�.�.�.�.�	��	 �	 �	"�	"�*�*��	*�	*�E��t�E�)�D�)�)�)�)�)�)�)�)�)�)�)�	*�*�*�*�*�*�*�*�*�*�*�*�*����*�*�*�*�*�*s�A#�#A'�*A'r�c�������tt||������ddlm�d�}t	t
|tdzd��������fd���}|��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.
    r�rr�c��ttd|dz
zzt��}t�d||t
|��t
j|��dS)Nrbrz;SQLite lock contention, retrying in %.3fs (retry %d/%d): %s)�min�DB_LOCK_RETRY_BACKOFF_BASE�DB_LOCK_RETRY_BACKOFF_MAXr_r��DB_LOCK_MAX_RETRIESr?r�)r��attempt�backoffs   r6�_backoffz-execute_iterable_expression.<locals>._backoff�sd���&�!��!��*<�=�%�
�
��	���I�����	
�	
�	
�	
�
�7�����r5rc� �t|��Sr�)r�)r�r�s  r6rXz-execute_iterable_expression.<locals>.<lambda>�s��*=�c�*B�*B�r5)rerdr�c���d}�j���5�D] }|�|g��R����z
}�!	ddd��n#1swxYwY|Sr�)r�r��execute)r�rrNr�r�r�s  ����r6�_execute_allz1execute_iterable_expression.<locals>._execute_all�s������
�[�
$�
$�
&�
&�	8�	8��
8�
8���4�4��-��-�-�-�5�5�7�7�7���
8�	8�	8�	8�	8�	8�	8�	8�	8�	8�	8�	8����	8�	8�	8�	8��s�$A�A�A)rrrr�r�rurr�)r�rrrNr�rr�r�s`  `  @@r6�execute_iterable_expressionr�s�������$�/�(�z�B�B�B�
C�
C�F�.�.�.�.�.�.�������%��)�B�B�	����������
����<�>�>�r5c�X�tj|�dd����dzS)Nr��\nr�)r��fsencoderg�rs r6�encode_filenamer�s%��
�;�t�|�|�D�%�0�0�1�1�E�9�9r5c�b�tj|��dd��dd��S)Nr(rr�)r��fsdecodergrs r6�decode_filenamer	�s+��
�;�t���S�b�S�!�)�)�%��6�6�6r5c�N�tjtj|����Sr�)�base64�	b64encoder�rrs r6�base64_encode_filenamer
�s����B�K��-�-�.�.�.r5�b64namec�h�ttjtj|������Sr�)rr�rr�	b64decode)rs r6�base64_decode_filenamer�s%�����F�,�W�5�5�6�6�7�7�7r5c�V�	tj|��}n#t$rd}YnwxYw|S)zS
    Like pwd.getpwnam(username) but returns None instead of raising KeyError.
    N)r�r�r�)r�r�s  r6r�r��sA�����h�'�'�����������������Ms��&�&c�>�tt||��|��S)zH
    Put the specified `value` inside the [`low`, `high`] interval.
    )�maxr�)r��low�highs   r6�cliprs���s�5�$����%�%�%r5�Background 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_r�r�rpr�r�)�futr�rUr�s    r6rVrVs������n��*��
�
��������!�
�
�
�����*�*�*���H�g�q�)�)�)�)�)�)�)�)�)�����*���s�&�A�	A�
A�Ar�.c�r���fd�}��||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|���|d���dSdSdS)Nz1Unhandled exception during plugin initialization!)rUr�r])rZr�r�)r]r�s �r6�_log_exceptionz6create_task_and_log_exceptions.<locals>._log_exception+sv����~�~���		�D�N�N�$4�$4�$@��'�'�L�!%���!1�!1� ���
�
�
�
�
�		�		�$@�$@r5)�create_taskr\)r�r�rNrOr�new_tasks`     r6�create_task_and_log_exceptionsr "sY���
�
�
�
�
������d� 5�f� 5� 5�6�6�H����~�.�.�.��Or5c����fd�}|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
    c���K��|i|��Sr�r4)rNrO�functions  �r6r�zmake_coro.<locals>.coroFs������x��(��(�(�(r5r4)r#r�s` r6�	make_coror$<s#���)�)�)�)�)��Kr5c���K�|tkr
tj}ntj}|dt	|dd��x}rd|�d�nd||��tjt���d{V��dS)Nz7Failed to copy data%s to modsec ruleset dir %r, try: %srz (�)r�)�COPY_TO_MODSEC_MAXTRIESr_r�r�r�rpr��_MODSEC_COPY_FAILURE_TIMEOUT)r�rrD�fns    r6�log_failed_to_copy_to_modsecr*Ps������#�#�#��l����n���C�A�$�S�*�d�;�;�;�r�D�
�R�
�
�
�
�"��	�	����-�4�
5�
5�5�5�5�5�5�5�5�5�5r5)�err_buf_sizec
�4K�d�}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.
    c��JK�|23d{V��}|�|���6dSr�)�append)�pipe�bufr?s   r6�read_pipe_intoz1readlines_from_cmd_output.<locals>.read_pipe_intohsL�����	�	�	�	�	�	�	�$��J�J�t������$�$s�")�maxlenT)rjrhriNrr5)rrprtrhrmrrirhryr�r�)r�r+ror1�err_bufr~r?r�s        r6�readlines_from_cmd_outputr4^s���������<�(�(�(�G��/�	���!�&��!�&�	��
���������D�	I�	��N�N�4�;��@�@�A�A�A��+�	�	�	�	�	�	�	�$��J�J�J�J�J�&�+� �9�9�;�;�&�&�&�&�&�&�
���?�?��
�C��c�h�h�w�6G�6G�H�H�H��?�� �9�9�;�;�&�&�&�&�&�&�
���?�?��
�C��c�h�h�w�6G�6G�H�H�H�H�H�H�Hs�*C�:B�C�ADrb)rd�delayc��K�td|dz��D]3}||��d{V��}|s!||krtj|���d{V���0|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)r�rpr�)�predicate_corordr5rNr�r�s      r6�finally_happenedr8�s�������I��M�*�*����%�~�t�,�,�,�,�,�,�,���	�'�I�-�-��-��&�&�&�&�&�&�&�&�&���
�
�
��r5�'c�K�t|d���D]-\}}|WV�||zdkrtjd���d{V���.dS)z6Yield to the event loop every *chunk_size* iterations.r)rErN)�	enumeraterpr�)rrr�items    r6�
nice_iteratorr=�so�����X�Q�/�/�/�#�#���4��
�
�
�
�
�
�N�q� � ��-��"�"�"�"�"�"�"�"�"��#�#r5c��eZdZdZd�Zd�ZdS)�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.
    c��d|_dSr�)r�r>s r6r�zLazyLock.__init__�s
����
�
�
r5c�N�|jstj��|_|jSr�)r�rpr�)r�r��owners   r6�__get__zLazyLock.__get__�s!���z�	(� ����D�J��z�r5N)r-r.r/r]r�rCr4r5r6r?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)r�rKr)r?�pkg_name�versions   r6�_parse_rpm_linerH�s[���:�:�<�<�D���?�d�j�j�l�l�2�2�d�$�6F�6F��t��
�
�4��+�+��H�g��W��r5c�
�|���}|rd|���vsd|vrdS|�d��sdS|�dd��\}}|r|���dnd}||fS)zVParse dpkg-query output line, return (package_name, version) or None if not installed.zno packages foundrENz
 ok installedrrr�)r�rKr�r)r?rF�restrGs    r6�_parse_dpkg_linerK�s����:�:�<�<�D���&�$�*�*�,�,�6�6�$�d�:J�:J��t�
�=�=��)�)���t��Z�Z��a�(�(�N�H�d�!%�-�d�j�j�l�l�1�o�o�2�G��W��r5c��t���st���rgd�tfSgd�tfS)N)z
dpkg-queryz--showz--showformatz!${Package}: ${Version} ${Status}
)�rpmz-qz5--queryformat=%{NAME}: %{VERSION}-%{RELEASE}.%{ARCH}
)r-rarirKrHr4r5r6�_get_package_query_cmdrN�sg����� � �	
�M�$;�$;�$=�$=�	
�
�
�
�
�
�	
�	
�	
�	
�
	�
�r5c��eZdZdZdS)�FirewallDisabledExceptionz;Exception in case of using firewall api, when it's disabledNr�r4r5r6rPrP�s������E�E�E�Er5rPc�<��t����fd���}|S)Nc���K�tj�d��rtd����|i|���d{V��S)Nz!/var/imunify360/firewall_disabledz"Not available in the current build)r�r
r:rP)rNrOrDs  �r6rQz(check_disabled_firewall.<locals>.wrapper�s\�����
�7�>�>�=�>�>�	�+�4���
��T�4�*�6�*�*�*�*�*�*�*�*�*r5r)rDrQs` r6�check_disabled_firewallrS�s3���
�4�[�[�+�+�+�+��[�+��Nr5>�
imunify-ui�imunify-core�imunify-antivirus�imunify360-firewall�packagesc��K�t��\}}t|��}t||zdd����d{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.
    r�F)r�r�N)rNrr�safe_run_with_timeout�_parse_package_info_output)rXr��
parse_line�
packages_listr�s     r6�system_packages_infor^�st����-�.�.�O�C����N�N�M�(��m��R�%����������F�&�f�m�Z�H�H�Hr5r�r\c�n����������fd�|���D����fd�|D��S)Nc�X��i|]&}�|��x���dx���dx��#����'S)rrr4)r�r?r\�pkgr��vers  ����r6�
<dictcomp>z._parse_package_info_output.<locals>.<dictcomp>sf������� �j��&�&�&�F���1�I�
�S�	�
�1�I�
�S���S���r5c�<��i|]}|��|����Sr4)r;)r�ra�parseds  �r6rcz._parse_package_info_output.<locals>.<dictcomp> s%���5�5�5�S�C����C���5�5�5r5)�
splitlines)r�rXr\rerar�rbs  `@@@@r6r[r[sf�������
��������%�%�'�'����F�6�5�5�5�H�5�5�5�5r5c��K�	tjt|fi|��|����d{V��S#tj$r|d|��YdSwxYw)Nr�z#Command %s failed: Timeout occurredr�)rpr�r�rJ)rzr�rDrOs    r6rZrZ#s�������%��W�'�'��'�'��
�
�
�
�
�
�
�
�
�	
���������1�7�;�;�;��r�r����s�&+�A
�	A
�nc#��K�|dkrtd���t|��}tt||����x}r%|V�tt||����x}�#dSdS)Nrzn must be at least one)rkrrrr)rrh�it�batchs    r6�batchedrl/s�����
	�1�u�u��1�2�2�2�	
�h���B���r�1�
�
�&�&�
&�%��������r�1�
�
�&�&�
&�%�����r5rOc#�R�K�t�|��D]}�fd�|D��V��dS)Nc�"��i|]}|�|��Sr4r4)r�r@rOs  �r6rcz batched_dict.<locals>.<dictcomp>=s���&�&�&�1�q�!�A�$�&�&�&r5)rl)rOrhrks`  r6�batched_dictro;sK�������A���'�'��&�&�&�&��&�&�&�&�&�&�&�'�'r5c�n�	tjddgd������}|�d��}|s?t	d�����rtjddgd���}d|vrd}|S#t$r&}t�d	|��Yd}~d
Sd}~wwxYw)N�hostnamez-fT)r�)z.cloudwaysapps.comz.cloudwaysstagingapps.comz/usr/local/sbin/apm�info�	Cloudwaysz$Error while checking environment: %sF)	rl�check_outputr�r�rr:r�r_r�)rq�
_is_cloudwaysr�r�s    r6�is_cloudwaysrv@s�����+�
���T�
�
�
�
�%�'�'�	�!�)�)�?�
�
�
��	%��&;�!<�!<�!C�!C�!E�!E�	%� �-�&��/�d����F��f�$�$� $�
�����������;�Q�?�?�?��u�u�u�u�u��������s�BB�
B4�B/�/B4�pid_filec���tj��}|rt|��dkr|S	|�|�d���|S#t$r'}t
�d|��|cYd}~Sd}~wwxYw)Nr�r�z Error while creatin PID file: %s)r�raro�
write_textr�r_r�)rwrir�s   r6�write_pid_filerzUs���
�)�+�+�C���s�8�}�}��*�*��
�����s�J�J�J�'�'�'��
���������7��;�;�;��
�
�
�
�
�
��������s�A�
A7�A2�,A7�2A7c���|rt|��dkrdS	|���r|���dS#t$r&}t�d|��Yd}~dSd}~wwxYw)Nr�z Error while cleanup PID file: %s)ror:r�r�r_r�)rwr�s  r6�cleanup_pid_filer|cs�����s�8�}�}��*�*��t���?�?���	��O�O��������������7��;�;�;��������������s�(A�
A3�
A.�.A3c��|K�t�d||��tjd|z���d{V��dS)a
    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():
            ...
        ```
    z#%s sleep on: %srbN)r_r�rpr�)r�r�s  r6�
backoff_sleepr~psK�����N�N�%�w�	�:�:�:�
�-��W��
%�
%�%�%�%�%�%�%�%�%�%r5)NN)r`N)T)r�Fr�)rt)r�)r�)r�)r
)NNNFNN)Nr)r9)�rprr�rSr�r��loggingr�r�r$r�rrbrrhrlr?�urllib.requestr r�collectionsrr�collections.abcrr�
contextlibrr	r
�datetimer�enumr�fcntlr
rrrrr�pathlibr�tempfiler�typingrrrrrrrr�	async_lrurIrj�peeweer�_shutilr r!�fd_opsr"r#�	getLoggerr-r_�USER_IDENTITY_FIELD�USER_IDENTITY_HEADERSr�rr9rf�AV_PID_PATH�IM360_NON_RESIDENT_PID_PATH�IM360_RESIDENT_PID_PATHr��environr;�HTTP_REQUEST_RETRY_TIMEOUTr,�	lru_cacher=rGrWrYrm�bytesr�r�r�r�r�r�ror�r�r�r�r�r�r�r�r��boolrrr"r+r-�md5ryrwr�r�r�r�r�r��partial�
alru_cache�async_lru_cacher�r�r�r�r�r�r�r�r�r�rr	rr�typerr#r+r-r3�timed_cacher^rdrsrvr�r|r�r�r�r�r�r�r�r�r�r�rwrur�r�r�r�r�r�r�r�r�r�rrr	r
rr�rrVr r$r'r(r*r4r8r=r?rsrHrKrrrNrPrSr:�IMUNIFY_PACKAGE_NAMESrHr^r[r�rZrlrorvrzr|r~r4r5r6�<module>r�s�������
�
�
�
���������������������	�	�	�	�
�
�
�
�	�	�	�	�����
�
�
�
�
�
�
�
����� � � � ���������������*�*�*�*�*�*�*�*�/�/�/�/�/�/�/�/�:�:�:�:�:�:�:�:�:�:�������������2�2�2�2�2�2�2�2�2�2�2�2�������������������'�'�'�'�'�'�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�����
�
�
�
�
�
�
�
�#�#�#�#�#�#�0�0�0�0�0�0�0�0�%�%�%�%�%�%��G�C�x� � � ��	��	�8�	$�	$�����������d�0�1�1��6���d�3�4�4��"�d�#B�C�C���$�8�9�9�� �S��J�N�N�:�B�?�?����
+�+�+�+�+�D�+�+�+����Q����	�	� ��	��K�K�K���K��4�T�	�	�	�	���������4�����
�
�
0�0��3��u���0�0�0�0�f �������0
�
�
�
�
�K�2�
�
�
�(5�
�
�5�
�
�
�
�.;�
'�
'�
'�
'�
'� �������,
�
�
�+�+�+�+�+�+�+�+�@���	�	�	�	�	��	�	�	�������:?�;�;�;�;�F(,������d�d�d�

�3�J���$�d�
�$�J�d�
�d�d�d�d�N���Q��������
?�
?�C�
?�
?�
?�
?�h'�h'�h'�h'�h'�h'�h'�h'�X%�[�4�
A�
A��
A�58�
A��
A�
A�
A�
A� �#�#��#��#��3��8�_�	#�#�#�#�2����������.<���������������������
$�)�#�
���������������5��T������������5��T�����	%�	%�	%�-�-�-�-�-�-�-�-�`0E�����6����������������D�����&�3��#��$�����	%�	%�	%�
,�
,�h�
,�C�
,�)�
,�
,�
,�
,� 
�
�
�#�#�#�#�#��#�#�#�"���R� � � �!�!�!� �!����"�������2e�e�e�e�e�e�e�e�P��-.�!
�!
�!
�!
�!
�H)�)�)����Z���Q�����4���� ���$��>�>�>�3�3�S�3�s�3�3�3�3�	�	�	�	� $�
�
�
�
�
� ,0�J�J�J�J�J�
26�P�P�P�P�P�"�����Q����:��:�:�:� ��:�(3�3�3�3�S�3�3�3�3� $������ �����7�<����B
�
�
�$������s�s�s�s�lP�P�P�*$-�$�&�&�&�&�R%7�����&>�>�>�
����!����(��(�(�(�(�
6;�*�*�*�*�*�@';�1�1�1�1�1�h:�:�:�7�7�7�/��/�%�/�/�/�/�8�E�8�d�8�8�8�8����&�&�&�*�*�*�*�.���i��(�����4
�
�
� �� ��6�6�6�%(�I�I�I�	
�c��I�I�I�I�D=>�Q�
�
�
�
�
� #�#�#�#���������0�#��%��S��/�D�"8������3��5��c��?�T�#9�����"���Q�����	�$�s�)�X�s�e�U�3��8�_�t�%;�;�<�
<�=���� ���.F�F�F�F�F�	�F�F�F�	�	�	�"�	�������I��s�m�I�	�#�s�T�z�/��I�I�I�I�,6��6��3�i�6��#���c�3�h��$� 6�6�7�6�
�#�s�T�z�/��	6�6�6�6� !�,�	�	��	�	�	�	�	��	�	�	�	�'�D��c��N�'�s�'�'�'�'�
���Q������ ���(�T��c�����
�t�
�
�
�
�&�&�&�&�&r5defence360agent/utils/__pycache__/_shutil.cpython-311.opt-1.pyc0000644000000000000000000000344300000000000021232 0ustar  �

o�t��+3I��d�dZddlZddlZddlZddlZeje��Zdefd�Z	d	dd�d�Z
dS)
zHigh-level file operations.�N�returnc��t|t��o9t|��o*d|vo&|tj�|��ko|dvS)N�)�.z..)�
isinstance�str�bool�os�path�basename)�names �R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/_shutil.py�is_safe_subdir_namer
s]���4����	$���J�J�	$��$��	$�
�B�G�$�$�T�*�*�*�	$�
��#��F�)�	max_triesc��td|dz��D]s}	tj|||��cS#t$rL}||ks|jtjtjfvr�t�d||��Yd}~�ld}~wwxYwdS)z�More 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)	�range�shutil�rmtree�OSError�errno�EEXIST�	ENOTEMPTY�logger�warning)r�
ignore_errors�onerrorr�i�es      rrrs����1�i�!�m�
$�
$�H�H��
	H��=��}�g�>�>�>�>�>���	H�	H�	H��I�~�~�������1�"�"��
�N�N�=�t�Q�G�G�G�G�G�G�G�G�����	H����H�Hs�/�
B�AB�B)FN)�__doc__r�loggingr
r�	getLogger�__name__rr	rr�rr�<module>r's���!�!���������	�	�	�	�
�
�
�
�	��	�8�	$�	$��������H��H�H�H�H�H�H�Hrdefence360agent/utils/__pycache__/_shutil.cpython-311.pyc0000644000000000000000000000344300000000000020273 0ustar  �

o�t��+3I��d�dZddlZddlZddlZddlZeje��Zdefd�Z	d	dd�d�Z
dS)
zHigh-level file operations.�N�returnc��t|t��o9t|��o*d|vo&|tj�|��ko|dvS)N�)�.z..)�
isinstance�str�bool�os�path�basename)�names �R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/_shutil.py�is_safe_subdir_namer
s]���4����	$���J�J�	$��$��	$�
�B�G�$�$�T�*�*�*�	$�
��#��F�)�	max_triesc��td|dz��D]s}	tj|||��cS#t$rL}||ks|jtjtjfvr�t�d||��Yd}~�ld}~wwxYwdS)z�More 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)	�range�shutil�rmtree�OSError�errno�EEXIST�	ENOTEMPTY�logger�warning)r�
ignore_errors�onerrorr�i�es      rrrs����1�i�!�m�
$�
$�H�H��
	H��=��}�g�>�>�>�>�>���	H�	H�	H��I�~�~�������1�"�"��
�N�N�=�t�Q�G�G�G�G�G�G�G�G�����	H����H�Hs�/�
B�AB�B)FN)�__doc__r�loggingr
r�	getLogger�__name__rr	rr�rr�<module>r's���!�!���������	�	�	�	�
�
�
�
�	��	�8�	$�	$��������H��H�H�H�H�H�H�Hrdefence360agent/utils/__pycache__/antivirus_mode.cpython-311.opt-1.pyc0000644000000000000000000000234100000000000022607 0ustar  �

��B���Q��4�ddlZddlZddlmZd�ZeecZZdS)�N��ANTIVIRUS_MODEc���tj����fd���}tj����fd���}tj���r|n|S)Nc��6�K�trdn
�|i|���d{V��S�Nr��args�kwargs�fs  ��Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/antivirus_mode.py�
async_wrapperzskip.<locals>.async_wrappers7�����%�C�t�t���D�1C�F�1C�1C�+C�+C�+C�+C�+C�+C�C�c�&��trdn�|i|��Srrrs  �r�wrapperzskip.<locals>.wrappers!���%�=�t�t�1�1�d�+=�f�+=�+=�=r)�	functools�wraps�inspect�iscoroutinefunction)rr
rs`  r�skiprs�����_�Q���D�D�D�D���D��_�Q���>�>�>�>���>�$�7��:�:�G�=�=��Gr)rr� defence360agent.contracts.configrr�enabled�disabled�rr�<module>rsS����������;�;�;�;�;�;�	H�	H�	H�#��$6�����rdefence360agent/utils/__pycache__/antivirus_mode.cpython-311.pyc0000644000000000000000000000234100000000000021650 0ustar  �

��B���Q��4�ddlZddlZddlmZd�ZeecZZdS)�N��ANTIVIRUS_MODEc���tj����fd���}tj����fd���}tj���r|n|S)Nc��6�K�trdn
�|i|���d{V��S�Nr��args�kwargs�fs  ��Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/antivirus_mode.py�
async_wrapperzskip.<locals>.async_wrappers7�����%�C�t�t���D�1C�F�1C�1C�+C�+C�+C�+C�+C�+C�C�c�&��trdn�|i|��Srrrs  �r�wrapperzskip.<locals>.wrappers!���%�=�t�t�1�1�d�+=�f�+=�+=�=r)�	functools�wraps�inspect�iscoroutinefunction)rr
rs`  r�skiprs�����_�Q���D�D�D�D���D��_�Q���>�>�>�>���>�$�7��:�:�G�=�=��Gr)rr� defence360agent.contracts.configrr�enabled�disabled�rr�<module>rsS����������;�;�;�;�;�;�	H�	H�	H�#��$6�����rdefence360agent/utils/__pycache__/async_utils.cpython-311.opt-1.pyc0000644000000000000000000000352600000000000022122 0ustar  �

�rb��`��L�ddlmZmZmZddlZGd�d��Zdedefd�ZdS)�)�List�Union�TupleNc�<�eZdZdeeeffd�Zd�Zd�Zd�Z	dS)�AsyncIterate�datac�.�t|��|_dS�N)�iter�queue��selfrs  �V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/async_utils.py�__init__zAsyncIterate.__init__s���$�Z�Z��
�
�
�c��|Sr
�)rs r�	__aiter__zAsyncIterate.__aiter__	s���rc��PK�|����d{V��}|�|St�r
)�
fetch_data�StopAsyncIterationr
s  r�	__anext__zAsyncIterate.__anext__s8�����_�_�&�&�&�&�&�&�&�&�����K�$�$rc��ZK�	t|j��}n#t$rd}YnwxYw|Sr
)�nextr�
StopIteration)r�items  rrzAsyncIterate.fetch_datasE����	���
�#�#�D�D���	�	�	��D�D�D�	�����s��(�(N)
�__name__�
__module__�__qualname__rrrrrrrrrrrrse������ �U�4��;�/� � � � ����%�%�%�����rr�tasks�returnc��LK�tj|��d{V��}t|��Sr
)�asyncio�gatherr)r �resultss  rr$r$s2�����N�E�*�*�*�*�*�*�*�G��� � � r)�typingrrrr#rr$rrr�<module>r's���%�%�%�%�%�%�%�%�%�%�������������,!��!�,�!�!�!�!�!�!rdefence360agent/utils/__pycache__/async_utils.cpython-311.pyc0000644000000000000000000000352600000000000021163 0ustar  �

�rb��`��L�ddlmZmZmZddlZGd�d��Zdedefd�ZdS)�)�List�Union�TupleNc�<�eZdZdeeeffd�Zd�Zd�Zd�Z	dS)�AsyncIterate�datac�.�t|��|_dS�N)�iter�queue��selfrs  �V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/async_utils.py�__init__zAsyncIterate.__init__s���$�Z�Z��
�
�
�c��|Sr
�)rs r�	__aiter__zAsyncIterate.__aiter__	s���rc��PK�|����d{V��}|�|St�r
)�
fetch_data�StopAsyncIterationr
s  r�	__anext__zAsyncIterate.__anext__s8�����_�_�&�&�&�&�&�&�&�&�����K�$�$rc��ZK�	t|j��}n#t$rd}YnwxYw|Sr
)�nextr�
StopIteration)r�items  rrzAsyncIterate.fetch_datasE����	���
�#�#�D�D���	�	�	��D�D�D�	�����s��(�(N)
�__name__�
__module__�__qualname__rrrrrrrrrrrrse������ �U�4��;�/� � � � ����%�%�%�����rr�tasks�returnc��LK�tj|��d{V��}t|��Sr
)�asyncio�gatherr)r �resultss  rr$r$s2�����N�E�*�*�*�*�*�*�*�G��� � � r)�typingrrrr#rr$rrr�<module>r's���%�%�%�%�%�%�%�%�%�%�������������,!��!�,�!�!�!�!�!�!rdefence360agent/utils/__pycache__/benchmark.cpython-311.opt-1.pyc0000644000000000000000000000271500000000000021516 0ustar  �

���ҝ����4�ddlZddlmZGd�d��ZdS)�N)�
TracebackTypec�h�eZdZd	d�Zdeedzdedzdedzddfd�Zede	fd���Z
dS)
�	Benchmark�returnNc�6�tj��|_|S�N)�time�monotonic_ns�
start_time��selfs �T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/benchmark.py�	__enter__zBenchmark.__enter__s���+�-�-������exc_type�exc_val�exc_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 r�elapsed_time_mszBenchmark.elapsed_time_mss���#�d�*�*r)rN)�__name__�
__module__�__qualname__r�type�
BaseExceptionrr�property�floatr�rrrrs�����������?��}�%��,�?���%�?���$�	?�

�?�?�?�?��+��+�+�+��X�+�+�+rr)r	�typesrrr!rr�<module>r#sR������������+�+�+�+�+�+�+�+�+�+rdefence360agent/utils/__pycache__/benchmark.cpython-311.pyc0000644000000000000000000000271500000000000020557 0ustar  �

���ҝ����4�ddlZddlmZGd�d��ZdS)�N)�
TracebackTypec�h�eZdZd	d�Zdeedzdedzdedzddfd�Zede	fd���Z
dS)
�	Benchmark�returnNc�6�tj��|_|S�N)�time�monotonic_ns�
start_time��selfs �T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/benchmark.py�	__enter__zBenchmark.__enter__s���+�-�-������exc_type�exc_val�exc_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 r�elapsed_time_mszBenchmark.elapsed_time_mss���#�d�*�*r)rN)�__name__�
__module__�__qualname__r�type�
BaseExceptionrr�property�floatr�rrrrs�����������?��}�%��,�?���%�?���$�	?�

�?�?�?�?��+��+�+�+��X�+�+�+rr)r	�typesrrr!rr�<module>r#sR������������+�+�+�+�+�+�+�+�+�+rdefence360agent/utils/__pycache__/buffer.cpython-311.opt-1.pyc0000644000000000000000000001020200000000000021023 0ustar  �

O�����+��t�Gd�de��ZGd�de��ZGd�de��ZGd�d��ZdS)	c��eZdZdS)�LineBufferOverflowN��__name__�
__module__�__qualname__���Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/buffer.pyrr��������Dr	rc�4�eZdZdZdZd�Zd�Zd�Zd�Zd�Z	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.
    �c��d|_dS�N���buf��selfs r
�__init__zLineBuffer.__init__�
������r	c���t|j��t|��z|jkr.d|_td�|j�����|xj|z
c_dS)Nrz,LineBuffer exceeded maximum size of {} bytes)�lenr�MAX_SIZEr�format�r�datas  r
�appendzLineBuffer.appendsi���t�x�=�=�3�t�9�9�$�t�}�4�4��D�H�$�>�E�E��M�����
�
	
���D�����r	c��|S�Nrrs r
�__iter__zLineBuffer.__iter__����r	c��|j�d��}|dkr(|jd|�}|j|dzd�|_|St�)N�
�����)r�find�
StopIteration)r�pos�results   r
�__next__zLineBuffer.__next__sN���h�m�m�D�!�!���"�9�9��X�a��e�_�F��x��a��	�	�*�D�H��M��r	c��d|_dSrrrs r
�cleanzLineBuffer.clean'rr	N)
rrr�__doc__rrrr!r,r.rr	r
r
r
sp�������� �H�����������������r	r
c��eZdZdS)�SizeBufferOverflowNrrr	r
r1r1+rr	r1c�,�eZdZdZdd�Zd�Zd�Zd�ZdS)	�
SizeBufferr�c�"�d|_||_dS)Nr	)�_buf�	_size_len)r�size_lens  r
rzSizeBuffer.__init__2s����	�!����r	c���t|j��t|��z|jkr.d|_td�|j�����|xj|z
c_dS)Nr	z,SizeBuffer exceeded maximum size of {} bytes)rr6rr1rrs  r
rzSizeBuffer.append6si���t�y�>�>�C��I�I�%��
�5�5��D�I�$�>�E�E��M�����
�
	
�	�	�T��	�	�	�	r	c��|Sr rrs r
r!zSizeBuffer.__iter__@r"r	c�D�|jst�t�|jd|j�d��}t|j|jd���|kr:|j|j|j|z�}|j|j|zd�|_|St�)N�big)r6r)�int�
from_bytesr7r)r�sizers   r
r,zSizeBuffer.__next__Cs����y�	 ����~�~�d�i�(8�$�.�(8�9�5�A�A���t�y���)�)�*�+�+�t�3�3��9�T�^�d�n�t�.C�C�D�D��	�$�.�4�"7�"9�"9�:�D�I��K��r	N)r4)rrrrrrr!r,rr	r
r3r3/sZ�������H�"�"�"�"�����������r	r3N)�	Exceptionr�objectr
r1r3rr	r
�<module>rBs���	�	�	�	�	��	�	�	�#�#�#�#�#��#�#�#�L	�	�	�	�	��	�	�	����������r	defence360agent/utils/__pycache__/buffer.cpython-311.pyc0000644000000000000000000001020200000000000020064 0ustar  �

O�����+��t�Gd�de��ZGd�de��ZGd�de��ZGd�d��ZdS)	c��eZdZdS)�LineBufferOverflowN��__name__�
__module__�__qualname__���Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/buffer.pyrr��������Dr	rc�4�eZdZdZdZd�Zd�Zd�Zd�Zd�Z	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.
    �c��d|_dS�N���buf��selfs r
�__init__zLineBuffer.__init__�
������r	c���t|j��t|��z|jkr.d|_td�|j�����|xj|z
c_dS)Nrz,LineBuffer exceeded maximum size of {} bytes)�lenr�MAX_SIZEr�format�r�datas  r
�appendzLineBuffer.appendsi���t�x�=�=�3�t�9�9�$�t�}�4�4��D�H�$�>�E�E��M�����
�
	
���D�����r	c��|S�Nrrs r
�__iter__zLineBuffer.__iter__����r	c��|j�d��}|dkr(|jd|�}|j|dzd�|_|St�)N�
�����)r�find�
StopIteration)r�pos�results   r
�__next__zLineBuffer.__next__sN���h�m�m�D�!�!���"�9�9��X�a��e�_�F��x��a��	�	�*�D�H��M��r	c��d|_dSrrrs r
�cleanzLineBuffer.clean'rr	N)
rrr�__doc__rrrr!r,r.rr	r
r
r
sp�������� �H�����������������r	r
c��eZdZdS)�SizeBufferOverflowNrrr	r
r1r1+rr	r1c�,�eZdZdZdd�Zd�Zd�Zd�ZdS)	�
SizeBufferr�c�"�d|_||_dS)Nr	)�_buf�	_size_len)r�size_lens  r
rzSizeBuffer.__init__2s����	�!����r	c���t|j��t|��z|jkr.d|_td�|j�����|xj|z
c_dS)Nr	z,SizeBuffer exceeded maximum size of {} bytes)rr6rr1rrs  r
rzSizeBuffer.append6si���t�y�>�>�C��I�I�%��
�5�5��D�I�$�>�E�E��M�����
�
	
�	�	�T��	�	�	�	r	c��|Sr rrs r
r!zSizeBuffer.__iter__@r"r	c�D�|jst�t�|jd|j�d��}t|j|jd���|kr:|j|j|j|z�}|j|j|zd�|_|St�)N�big)r6r)�int�
from_bytesr7r)r�sizers   r
r,zSizeBuffer.__next__Cs����y�	 ����~�~�d�i�(8�$�.�(8�9�5�A�A���t�y���)�)�*�+�+�t�3�3��9�T�^�d�n�t�.C�C�D�D��	�$�.�4�"7�"9�"9�:�D�I��K��r	N)r4)rrrrrrr!r,rr	r
r3r3/sZ�������H�"�"�"�"�����������r	r3N)�	Exceptionr�objectr
r1r3rr	r
�<module>rBs���	�	�	�	�	��	�	�	�#�#�#�#�#��#�#�#�L	�	�	�	�	��	�	�	����������r	defence360agent/utils/__pycache__/check_db.cpython-311.opt-1.pyc0000644000000000000000000003246600000000000021314 0ustar  �

G𢏸����"�ddlZddlZddlZddlmZddlmZddlmZddlm	Z	m
Z
ddlmZddl
mZddlmZdd	lmZdd
lmZeje��ZGd�de��Zd
Zd�Zdd�Zd�Zd�Zd�Zd�Z d�Z!dd�Z"dede#e$ddfd�Z%dS)�N)�suppress)�datetime)�copy)�connect�
DatabaseError)�SqliteExtDatabase)�app)�
simple_rpc)�Model)�simplificationc��eZdZdS)�OperationErrorN)�__name__�
__module__�__qualname__���S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/check_db.pyrrs�������Drrz.Blank database will be created on agent start c�V�tj}tj��rt	d���t
j�|��st	d|�dt�����t|���r�t|��}|st	d���t|��}t�
d|z��tj|��|st	d|�dt�����t||��}|st	d���t|��r+tj|��t	d	tz���t�
d
|z��tj|��	t�
d��t!j��t%��s+tj|��t	dtz���dS#t&$r3}tj|��t	d
|�dt�����d}~wwxYwdS)Nz�Cannot 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_runningr�os�path�isfile�WORKAROUND_MSG�is_db_corrupted�make_backup�dump_to_sql�logger�info�remove�
load_from_sqlr�migrate�all_tables_are_present�	Exception)�base�backup�dump�restored�es     r�check_and_repairr+su���:�D�����>��
O�
�
�	
��W�^�^�D�
!�
!�9��n�)-���~�~�>�
�
�	
��4� � �4	� ��&�&�F��
�$�5����
�t�$�$�D��K�K�D�t�K�L�L�L��I�d�O�O�O��(
�$�n��v�v�~�~�/����
)��t�4�4����(�:����
#�8�,�,���I�h�'�'�'�(�0�2@�A����
���F��M�����	�$������K�K� L�M�M�M�"�*�,�,�,�2�3�3���	�$����,�!�#1�2�������!�����I�d�O�O�O�(�.��1�1�n�n�.������������O4	�4	s�-G)�)
H&�3.H!�!H&c��tj��}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)r�now�format�	isoformat)�filename�	extension�instant�basenames    r�mark_with_timestampr5`sM���l�n�n�G��~�~�h��(9�(9�#�(>�(>�?�?�H����%�)�+�+�+��rc��t�d|z��d}t|��5}	|�d��}t	|��}d|vrt�d��d}n2#t
$r%}t�d|��Yd}~nd}~wwxYw|cddd��S#1swxYwYdS)NzDatabase %s integrity check...TzPRAGMA INTEGRITY_CHECK;�okz#Database integrity check succeeded.FzDatabaseError detected: %s)rr r�execute�nextr�warning)�db_path�is_corrupted�
connection�cursor�resultr*s      rrros��
�K�K�0�7�:�;�;�;��L�	��	�	�	�Z�	<��'�'�(A�B�B�F��&�\�\�F��v�~�~����A�B�B�B�$�����	<�	<�	<��N�N�7��;�;�;�;�;�;�;�;�����	<�����	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	�	s;�B6�AA6�5B6�6
B%�B �B6� B%�%B6�6B:�=B:c�X�t|d���}t�d|z��	t|d��5}t	|��5}|���D]}|�|���	ddd��n#1swxYwYddd��n#1swxYwYn}#ttf$ri}t�	d|z��tt��5tj|��ddd��n#1swxYwYd}Yd}~nd}~wwxYw|S)N�sql)r2z!Dumping imunify360 database to %s�wz(Error during dump: %s. Operation aborted)
r5rr �openr�iterdump�writer�OSError�errorrrr!)r;�dumpfiler(r=�rowr*s      rrr~s���"�7�e�<�<�<�H�
�K�K�3�h�>�?�?�?��
�(�C�
 �
 �	 �D�'�'�*:�*:�	 �j�!�*�*�,�,�
 �
 ���
�
�3�����
 �	 �	 �	 �	 �	 �	 �	 �	 �	 �	 �	 ����	 �	 �	 �	 �	 �	 �	 �	 �	 �	 �	 �	 �	 �	 �	 ����	 �	 �	 �	 ���
�7�#�������?�!�C�D�D�D�
�g�
�
�	 �	 ��I�h����	 �	 �	 �	 �	 �	 �	 �	 �	 �	 �	 ����	 �	 �	 �	 ������������	����
�Os��B-�B!�-B
�>B!�
B	�B!�B	�B!�B-�!B%�%B-�(B%�)B-�-D'�>1D"�/D�D"�D	�D"�D	�D"�"D'c	��tj�|��rt�d��dSt�d|�d|�d���t
|d��5}t|��5}	|���}|�	|��ns#t$rf}t�|��tt��5tj|��ddd��n#1swxYwYd}Yd}~nd}~wwxYwddd��n#1swxYwYddd��n#1swxYwY|S)Nz^Database already exists. Loading dump to existing database may cause errors. Operation abortedz
Reading dump z into new database z...�r)rr�existsrr:r rCr�read�
executescript�MemoryErrorrGrrFr!)r;rHr(r=rAr*s      rr"r"�s��
�w�~�~�g�������
;�	
�	
�	
��t�
�K�K�K�5=�X�X�w�w�w�G����
�h��	�	�
��g�g�&6�&6�
�*�	��)�)�+�+�C��$�$�S�)�)�)�)���	�	�	��L�L��O�O�O��'�"�"�
#�
#��	�'�"�"�"�
#�
#�
#�
#�
#�
#�
#�
#�
#�
#�
#����
#�
#�
#�
#��G�G�G�G�G�G�����		����
�
�
�
�
�
�
�
�
�
�
����
�
�
�
�
�
�
�
�
�
�
�
�
�
�
����
�
�
�
��Ns��-D?�=D(�?)B)�(D(�)
D�3.D�!D	�6D�D
�D�	D
�
D�D(�D�D(�D?�(D,	�,D?�/D,	�0D?�?E�Ec��t�d|z��t|d��}	t||��t�d|z��nt#t$rg}t�d|��t
t��5tj	|��ddd��n#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   rrr�s��
�K�K�,�w�6�7�7�7�)�'�8�<�<�O���W�o�&�&�&����:�_�L�M�M�M�M���������/��3�3�3�
�g�
�
�	'�	'��I�o�&�&�&�	'�	'�	'�	'�	'�	'�	'�	'�	'�	'�	'����	'�	'�	'�	'������������	����
�s;�-A�
C�'/C	�B7�+C	�7B;	�;C	�>B;	�?C	�	Cc��t�d��tjd�tjD���}t
d�|D����rt�d��dSt�d��dS)NzDVerifying that db schema is up-to-date and all tables are present...c�6�g|]}tj|����Sr�r�
get_models��.0�modules  r�
<listcomp>z*all_tables_are_present.<locals>.<listcomp>�s3��

�

�

��
�%�f�-�-�

�

�

rc3�>K�|]}|���V��dS�N)�table_exists)rW�models  r�	<genexpr>z)all_tables_are_present.<locals>.<genexpr>�s.����
4�
4�E�5�����
4�
4�
4�
4�
4�
4rzAll tables are presentTzSome tables are missing in db.F)rr �	itertools�chainr	�MODULES_WITH_MODELS�allrG)�modelss rr$r$�s���
�K�K�N�����_�

�

��1�

�

�

��F��
4�
4�V�
4�
4�
4�4�4�����,�-�-�-��t����5�6�6�6��ur�returnc���tjj�tj��t�d��g}tj	D]\\}}t�d|��tjj�
d||f��|�|���]ttjj|��t�d��dS)Nz#Recreating schema for linked DBs...z
Attach db: %sz
ATTACH ? AS ?zSchema recreated successfully.)
r�instance�db�initrrrr r	�MIGRATIONS_ATTACHED_DBS�execute_sql�append�recreate_schema_models)�attached_schemasr;�schemas   r�recreate_schemaro�s������#�#�E�J�/�/�/�
�K�K�5�6�6�6����6�(�(�������O�W�-�-�-���"�.�.��g�v�.�	
�	
�	
�	����'�'�'�'��>�2�5�7G�H�H�H�
�K�K�0�1�1�1�1�1rrg�target_schemasc�"���fd�tjd�tjD���D��}t�d|��|�|��|�|��t�d��dS)Nc�0��g|]}|jj�v�|��Sr)�_metarn)rWr]rps  �rrYz*recreate_schema_models.<locals>.<listcomp>�s7���	�	�	���;���/�/�	�0�/�/rc�6�g|]}tj|����SrrTrVs  rrYz*recreate_schema_models.<locals>.<listcomp>�s3�������)�&�1�1���rz%rz%Schema models recreated successfully.)r_r`r	rarr �bind�
create_tables)rgrp�models_to_creates ` rrlrl�s����	�	�	�	��_���!�5����
�	�	�	���K�K��&�'�'�'��G�G��������%�&�&�&�
�K�K�7�8�8�8�8�8rr[)rdN)&�loggingr_r�
contextlibrr�shutilr�sqlite3rr�playhouse.sqlite_extr�defence360agent.applicationr	�defence360agentr
� defence360agent.contracts.configr�defence360agent.modelr�	getLoggerrrr%rrr+r5rrr"rr$ro�list�strrlrrr�<module>r�s�����������	�	�	�	�������������������*�*�*�*�*�*�*�*�2�2�2�2�2�2�+�+�+�+�+�+�&�&�&�&�&�&�2�2�2�2�2�2�0�0�0�0�0�0�
��	�8�	$�	$��	�	�	�	�	�Y�	�	�	�B��@�@�@�F�������������4������$
2�
2�
2�
2� 9��9�+/��9�9�	�9�9�9�9�9�9rdefence360agent/utils/__pycache__/check_db.cpython-311.pyc0000644000000000000000000003246600000000000020355 0ustar  �

G𢏸����"�ddlZddlZddlZddlmZddlmZddlmZddlm	Z	m
Z
ddlmZddl
mZddlmZdd	lmZdd
lmZeje��ZGd�de��Zd
Zd�Zdd�Zd�Zd�Zd�Zd�Z d�Z!dd�Z"dede#e$ddfd�Z%dS)�N)�suppress)�datetime)�copy)�connect�
DatabaseError)�SqliteExtDatabase)�app)�
simple_rpc)�Model)�simplificationc��eZdZdS)�OperationErrorN)�__name__�
__module__�__qualname__���S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/check_db.pyrrs�������Drrz.Blank database will be created on agent start c�V�tj}tj��rt	d���t
j�|��st	d|�dt�����t|���r�t|��}|st	d���t|��}t�
d|z��tj|��|st	d|�dt�����t||��}|st	d���t|��r+tj|��t	d	tz���t�
d
|z��tj|��	t�
d��t!j��t%��s+tj|��t	dtz���dS#t&$r3}tj|��t	d
|�dt�����d}~wwxYwdS)Nz�Cannot 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_runningr�os�path�isfile�WORKAROUND_MSG�is_db_corrupted�make_backup�dump_to_sql�logger�info�remove�
load_from_sqlr�migrate�all_tables_are_present�	Exception)�base�backup�dump�restored�es     r�check_and_repairr+su���:�D�����>��
O�
�
�	
��W�^�^�D�
!�
!�9��n�)-���~�~�>�
�
�	
��4� � �4	� ��&�&�F��
�$�5����
�t�$�$�D��K�K�D�t�K�L�L�L��I�d�O�O�O��(
�$�n��v�v�~�~�/����
)��t�4�4����(�:����
#�8�,�,���I�h�'�'�'�(�0�2@�A����
���F��M�����	�$������K�K� L�M�M�M�"�*�,�,�,�2�3�3���	�$����,�!�#1�2�������!�����I�d�O�O�O�(�.��1�1�n�n�.������������O4	�4	s�-G)�)
H&�3.H!�!H&c��tj��}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)r�now�format�	isoformat)�filename�	extension�instant�basenames    r�mark_with_timestampr5`sM���l�n�n�G��~�~�h��(9�(9�#�(>�(>�?�?�H����%�)�+�+�+��rc��t�d|z��d}t|��5}	|�d��}t	|��}d|vrt�d��d}n2#t
$r%}t�d|��Yd}~nd}~wwxYw|cddd��S#1swxYwYdS)NzDatabase %s integrity check...TzPRAGMA INTEGRITY_CHECK;�okz#Database integrity check succeeded.FzDatabaseError detected: %s)rr r�execute�nextr�warning)�db_path�is_corrupted�
connection�cursor�resultr*s      rrros��
�K�K�0�7�:�;�;�;��L�	��	�	�	�Z�	<��'�'�(A�B�B�F��&�\�\�F��v�~�~����A�B�B�B�$�����	<�	<�	<��N�N�7��;�;�;�;�;�;�;�;�����	<�����	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	�	s;�B6�AA6�5B6�6
B%�B �B6� B%�%B6�6B:�=B:c�X�t|d���}t�d|z��	t|d��5}t	|��5}|���D]}|�|���	ddd��n#1swxYwYddd��n#1swxYwYn}#ttf$ri}t�	d|z��tt��5tj|��ddd��n#1swxYwYd}Yd}~nd}~wwxYw|S)N�sql)r2z!Dumping imunify360 database to %s�wz(Error during dump: %s. Operation aborted)
r5rr �openr�iterdump�writer�OSError�errorrrr!)r;�dumpfiler(r=�rowr*s      rrr~s���"�7�e�<�<�<�H�
�K�K�3�h�>�?�?�?��
�(�C�
 �
 �	 �D�'�'�*:�*:�	 �j�!�*�*�,�,�
 �
 ���
�
�3�����
 �	 �	 �	 �	 �	 �	 �	 �	 �	 �	 �	 ����	 �	 �	 �	 �	 �	 �	 �	 �	 �	 �	 �	 �	 �	 �	 ����	 �	 �	 �	 ���
�7�#�������?�!�C�D�D�D�
�g�
�
�	 �	 ��I�h����	 �	 �	 �	 �	 �	 �	 �	 �	 �	 �	 ����	 �	 �	 �	 ������������	����
�Os��B-�B!�-B
�>B!�
B	�B!�B	�B!�B-�!B%�%B-�(B%�)B-�-D'�>1D"�/D�D"�D	�D"�D	�D"�"D'c	��tj�|��rt�d��dSt�d|�d|�d���t
|d��5}t|��5}	|���}|�	|��ns#t$rf}t�|��tt��5tj|��ddd��n#1swxYwYd}Yd}~nd}~wwxYwddd��n#1swxYwYddd��n#1swxYwY|S)Nz^Database already exists. Loading dump to existing database may cause errors. Operation abortedz
Reading dump z into new database z...�r)rr�existsrr:r rCr�read�
executescript�MemoryErrorrGrrFr!)r;rHr(r=rAr*s      rr"r"�s��
�w�~�~�g�������
;�	
�	
�	
��t�
�K�K�K�5=�X�X�w�w�w�G����
�h��	�	�
��g�g�&6�&6�
�*�	��)�)�+�+�C��$�$�S�)�)�)�)���	�	�	��L�L��O�O�O��'�"�"�
#�
#��	�'�"�"�"�
#�
#�
#�
#�
#�
#�
#�
#�
#�
#�
#����
#�
#�
#�
#��G�G�G�G�G�G�����		����
�
�
�
�
�
�
�
�
�
�
����
�
�
�
�
�
�
�
�
�
�
�
�
�
�
����
�
�
�
��Ns��-D?�=D(�?)B)�(D(�)
D�3.D�!D	�6D�D
�D�	D
�
D�D(�D�D(�D?�(D,	�,D?�/D,	�0D?�?E�Ec��t�d|z��t|d��}	t||��t�d|z��nt#t$rg}t�d|��t
t��5tj	|��ddd��n#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   rrr�s��
�K�K�,�w�6�7�7�7�)�'�8�<�<�O���W�o�&�&�&����:�_�L�M�M�M�M���������/��3�3�3�
�g�
�
�	'�	'��I�o�&�&�&�	'�	'�	'�	'�	'�	'�	'�	'�	'�	'�	'����	'�	'�	'�	'������������	����
�s;�-A�
C�'/C	�B7�+C	�7B;	�;C	�>B;	�?C	�	Cc��t�d��tjd�tjD���}t
d�|D����rt�d��dSt�d��dS)NzDVerifying that db schema is up-to-date and all tables are present...c�6�g|]}tj|����Sr�r�
get_models��.0�modules  r�
<listcomp>z*all_tables_are_present.<locals>.<listcomp>�s3��

�

�

��
�%�f�-�-�

�

�

rc3�>K�|]}|���V��dS�N)�table_exists)rW�models  r�	<genexpr>z)all_tables_are_present.<locals>.<genexpr>�s.����
4�
4�E�5�����
4�
4�
4�
4�
4�
4rzAll tables are presentTzSome tables are missing in db.F)rr �	itertools�chainr	�MODULES_WITH_MODELS�allrG)�modelss rr$r$�s���
�K�K�N�����_�

�

��1�

�

�

��F��
4�
4�V�
4�
4�
4�4�4�����,�-�-�-��t����5�6�6�6��ur�returnc���tjj�tj��t�d��g}tj	D]\\}}t�d|��tjj�
d||f��|�|���]ttjj|��t�d��dS)Nz#Recreating schema for linked DBs...z
Attach db: %sz
ATTACH ? AS ?zSchema recreated successfully.)
r�instance�db�initrrrr r	�MIGRATIONS_ATTACHED_DBS�execute_sql�append�recreate_schema_models)�attached_schemasr;�schemas   r�recreate_schemaro�s������#�#�E�J�/�/�/�
�K�K�5�6�6�6����6�(�(�������O�W�-�-�-���"�.�.��g�v�.�	
�	
�	
�	����'�'�'�'��>�2�5�7G�H�H�H�
�K�K�0�1�1�1�1�1rrg�target_schemasc�"���fd�tjd�tjD���D��}t�d|��|�|��|�|��t�d��dS)Nc�0��g|]}|jj�v�|��Sr)�_metarn)rWr]rps  �rrYz*recreate_schema_models.<locals>.<listcomp>�s7���	�	�	���;���/�/�	�0�/�/rc�6�g|]}tj|����SrrTrVs  rrYz*recreate_schema_models.<locals>.<listcomp>�s3�������)�&�1�1���rz%rz%Schema models recreated successfully.)r_r`r	rarr �bind�
create_tables)rgrp�models_to_creates ` rrlrl�s����	�	�	�	��_���!�5����
�	�	�	���K�K��&�'�'�'��G�G��������%�&�&�&�
�K�K�7�8�8�8�8�8rr[)rdN)&�loggingr_r�
contextlibrr�shutilr�sqlite3rr�playhouse.sqlite_extr�defence360agent.applicationr	�defence360agentr
� defence360agent.contracts.configr�defence360agent.modelr�	getLoggerrrr%rrr+r5rrr"rr$ro�list�strrlrrr�<module>r�s�����������	�	�	�	�������������������*�*�*�*�*�*�*�*�2�2�2�2�2�2�+�+�+�+�+�+�&�&�&�&�&�&�2�2�2�2�2�2�0�0�0�0�0�0�
��	�8�	$�	$��	�	�	�	�	�Y�	�	�	�B��@�@�@�F�������������4������$
2�
2�
2�
2� 9��9�+/��9�9�	�9�9�9�9�9�9rdefence360agent/utils/__pycache__/check_lock.cpython-311.opt-1.pyc0000644000000000000000000000345600000000000021654 0ustar  �

>�w�c���.�ddlZddlZddedefd�Zd�ZdS)�NF�check_lock_period�jitterc�.�|���s�|j�dd���|r\tjt|����}|�ttj��|z|z����|S|�ttj��|z����dSt||��x}dkr8|�ttj��|z����dS|S)NT)�parents�exist_okr)
�exists�parent�mkdir�random�	randrange�int�
write_text�str�time�is_period_passed)r�	lock_filer�delay�	time_lefts     �U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/check_lock.py�
check_lockrs������������t�d��;�;�;��	��$�S�):�%;�%;�<�<�E�� � ��T�Y�[�[�5�%8�;L�%L�!M�!M�N�N�N��L����S�����/@�!@�A�A�B�B�B��q�%�&7��C�C�C�	��I�I����S�����/@�!@�A�A�B�B�B��q���c��	t|�����}n#ttf$rYdSwxYw|t	j��z
S)Nr)�float�	read_text�FileNotFoundError�
ValueErrorr)�periodr�when_to_runs   rrrsZ����I�/�/�1�1�2�2�����z�*�����q�q����������$�$s�!$�9�9)F)rrr
�boolrr�rr�<module>r!sW��
�
�
�
�������#��$�����"%�%�%�%�%rdefence360agent/utils/__pycache__/check_lock.cpython-311.pyc0000644000000000000000000000345600000000000020715 0ustar  �

>�w�c���.�ddlZddlZddedefd�Zd�ZdS)�NF�check_lock_period�jitterc�.�|���s�|j�dd���|r\tjt|����}|�ttj��|z|z����|S|�ttj��|z����dSt||��x}dkr8|�ttj��|z����dS|S)NT)�parents�exist_okr)
�exists�parent�mkdir�random�	randrange�int�
write_text�str�time�is_period_passed)r�	lock_filer�delay�	time_lefts     �U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/check_lock.py�
check_lockrs������������t�d��;�;�;��	��$�S�):�%;�%;�<�<�E�� � ��T�Y�[�[�5�%8�;L�%L�!M�!M�N�N�N��L����S�����/@�!@�A�A�B�B�B��q�%�&7��C�C�C�	��I�I����S�����/@�!@�A�A�B�B�B��q���c��	t|�����}n#ttf$rYdSwxYw|t	j��z
S)Nr)�float�	read_text�FileNotFoundError�
ValueErrorr)�periodr�when_to_runs   rrrsZ����I�/�/�1�1�2�2�����z�*�����q�q����������$�$s�!$�9�9)F)rrr
�boolrr�rr�<module>r!sW��
�
�
�
�������#��$�����"%�%�%�%�%rdefence360agent/utils/__pycache__/cli.cpython-311.opt-1.pyc0000644000000000000000000003757700000000000020351 0ustar  �

�d�_�$
����ddlmZddlZddlZddlZddlZddlZddlZdddd�ZdZ	dZ
dZd	d
gZd\Z
ZZeded
iZe
dee
eeiZd�ZGd�d��Zd�Zd�Zd�Zd�Zd�Zd�Zd�Zd�Zd�Zd�Zd�Zd�Zd�Z d�Z!ide�d e�d!e�d"e�d#e�d$e�d%e�d&e�d'e�d(e�d)e�d*e�d+e�d,e�d-e�d.e�d/e�eee e!d0��Z"d1hZ#d2�Z$d;d4�Z%d5�Z&d<d6�Z'd7e(fd8�Z)	d<ej*d9�d:�Z+dS)=�)�defaultdictNT�)�,�: )�	sort_keys�indent�
separators��z	/bin/lessz	/bin/more)�success�warnings�error�WARNING�ERRORc��tj�dtd�tD��d����}|�t|��dSt
j|g|���tj
���dS)N�PAGERc3�XK�|]%}tj�|���!|V��&dS�N)�os�path�isfile)�.0�ps  �N/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/cli.py�	<genexpr>zpager.<locals>.<genexpr>s5����>�>�Q�B�G�N�N�1�,=�,=�>�q�>�>�>�>�>�>�)�input�stdout)r�environ�get�next�PAGERS�print�
subprocess�run�encode�sysr)�data�pagers  rr)r)st���J�N�N���>�>�&�>�>�>��E�E�
�
�E�
�}�
�d���������w�d�k�k�m�m�C�J�G�G�G�G�G�Grc�d�eZdZd�Z				dd�Zejfd�Zed���Z	ed���Z
dS)	�TablePrinterc�b�i|_tt��|_i|_i|_dSr)�_headersr�list�_mappers�_right_aligned�_widths)�selfs r�__init__zTablePrinter.__init__'s+����
�#�D�)�)��
� �������rNFc��|r
||j|<|r
||j|<||j|<|r|n|���|j|<dSr)r/r1r0�upperr-)r2�field�mappers�	max_width�right_align�headers      r�set_field_propertiesz!TablePrinter.set_field_properties-s\���	+�#*�D�M�%� ��	,�"+�D�L���%0���E�"�)/�B�v�v�U�[�[�]�]��
�e���rc�����fd�|D��}d�|D��}g}|D]�}g}t|��D]�\}	}
|�|
��}�j|
D]
}||��}�t|��}t	|��||	krQ�j�|
��}
|
r#t	|��|
kr|d|
dz
�dz}t	|��||	<|�|����|�|����t��||d����|D]?}t��||�j	�|
d�������@dS)Nc�j��g|]/}�j�||�������0S�)r-r r5)rr6r2s  �r�
<listcomp>z&TablePrinter.print.<locals>.<listcomp>=s3���O�O�O�u�4�=�$�$�U�E�K�K�M�M�:�:�O�O�Orc�,�g|]}t|����Sr>)�len)rr6s  rr?z&TablePrinter.print.<locals>.<listcomp>>s��2�2�2��#�e�*�*�2�2�2rr
z...F)
�	enumerater r/�strrAr1�appendr#�_format_rowr0)r2�fields�items�file�headers�widths�rows�item�row�ir6�v�mapperr8s`             rr#zTablePrinter.print<s����O�O�O�O��O�O�O��2�2�'�2�2�2�����
	�
	�D��C�%�f�-�-�

�

���5��H�H�U�O�O��"�m�E�2�"�"�F���q�	�	�A�A���F�F���q�6�6�F�1�I�%�%� $�� 0� 0�� 7� 7�I� �7�S��V�V�i�%7�%7��o�	�A�
�o�.��6�� #�A���F�1�I��
�
�1�
�
�
�
��K�K������
�d���w���6�6�7�7�7��	�	�C��� � ����!4�!8�!8���!F�!F���
�
�
�
�	�	rc�Z�|r|�|��S|�|��Sr)�rjust�ljust)�value�widthr9s   r�_add_paddingzTablePrinter._add_paddingVs.���	&��;�;�u�%�%�%��{�{�5�!�!�!rc�h�����fd�t|��D��}d�|��S)Nc�Z��g|]'\}}t�|�|�����(Sr>)r+rV)rrNrT�
right_alignedrJs   ��rr?z,TablePrinter._format_row.<locals>.<listcomp>^sC���
�
�
���5�
�%�%�e�V�A�Y�
�F�F�
�
�
rz  )rB�join)�columnsrJrY�colss `` rrEzTablePrinter._format_row\sK����
�
�
�
�
�%�g�.�.�
�
�
���y�y����r)NNFN)�__name__�
__module__�__qualname__r3r;r'rr#�staticmethodrVrEr>rrr+r+&s��������������

C�
C�
C�
C�),�
�����4�"�"��\�"�
����\���rr+c��|�|ndS)Nzn/ar>�rTs r�n_arces���%�5�5�5�0rc�(�|�t|��n|Sr)�intrbs r�to_intrfis���*�3�u�:�:�:��5rc����fd�}|S)Nc�\��t|t��r|����S|Sr)�
isinstance�dictr )rTr6s �r�	extractorz extract_field.<locals>.extractorns,����e�T�"�"�	$��9�9�U�#�#�#��rr>)r6rks` r�
extract_fieldrlms$��������
�rc��t��}|D]}|j|��
|�d�|D��|��dS)Nc��g|]
}|d��S)rr>)rrLs  rr?zprint_table.<locals>.<listcomp>zs��1�1�1�T��a��1�1�1r)r+r;r#)r(�field_props�table�propss    r�print_tablerrvsV���N�N�E��+�+��"��"�E�*�*�*�	�K�K�1�1�[�1�1�1�4�8�8�8�8�8rc��dtgfdtgfdtd��gfdtgfdtgfdtgff}t||��dS)N�	timestamp�abuser�country�code�times�name�severity)rfrcrlrr�r(ros  r�print_incidentsr|}se��	�v�h��	�C�5��	�]�6�*�*�+�,�	�3�%��	�#���	�c�U��
�K���k�"�"�"�"�"rc��ttj����}|D],}|�dd��}|dkr	||z
|d<�'d|d<�-dS)N�
expirationr�ttl)re�timer )r(�nowrLr~s    r�add_ttlr��se��

�d�i�k�k�
�
�C������X�X�l�A�.�.�
���>�>�$�s�*�D��K�K��D��K�K��rc�n�t|��dddtd��gff}t||��dS)N��ip�rrvrw�r�rlrrr{s  r�print_graylistr��sE���D�M�M�M���	�]�6�*�*�+�,��K�
��k�"�"�"�"�"rc�r�t|��dddtd��gfddf}t||��dS)Nr�r�rvrw)�
imported_from)�commentr�r{s  r�print_bwlistr��sK���D�M�M�M���	�]�6�*�*�+�,����K���k�"�"�"�"�"rc���t|ttf��r�t|��r�t	��}t|dt
��rdt
|d�����}|�dtd��g���|�
||��dS|D]}t|���dSdSt|��dS)Nrrvrw)r7)rir.�tuplerAr+rj�sorted�keysr;rlr#)r(�printerr�rLs    r�
guess_printerr��s����$��u�
�&�&�
��t�9�9�
	 �"�n�n�G��$�q�'�4�(�(�
 ��d�1�g�l�l�n�n�-�-���,�,��
�f�(=�(=�'>�-�����
�
�d�D�)�)�)�)�)� � � �D��$�K�K�K�K�
	 �
	 � � �	�d�����rc��t|t��rt|��dSttj|d�����dS)NF)�default_flow_style)rirCr#�yaml�dump�r(s r�yaml_printerr��sF���$����9�
�d������
�d�i���7�7�7�8�8�8�8�8rc��t|t��rt|��dSttj|����dSr)rirCr#�json�dumpsr�s r�json_printerr��sA���$���� �
�d������
�d�j��������rc	�V�t|t��r*td�|d����dSg}|D]B}|�d�|d|d|drdnd�����Ctd	�|����dS)
Nz
Status: {}�statuszEvent: {}, Path: {}{}�eventr�nativez  native��
)rirjr#�formatrDrZ)r(�result�hooks   r�hook_printerr��s����$����!�
�l�!�!�$�x�.�1�1�2�2�2�2�2����	�	�D��M�M�'�.�.���M���L�"&�x�.�8�J�J�b���
�
�
�
�	�d�i�i���� � � � � rc���|std��dSdddd�}|D]}||dxxdz
cc<�tdjdi|����t��t|d��dS)	NzNo users targeted.r)�	succeeded�skipped�failedr��z:{succeeded} succeeded, {skipped} skipped, {failed} failed.))�user)r�)�reasonr>)r#r�rr)rG�countsrLs   r�waf_set_printerr��s�����
�"�#�#�#�����a�
8�
8�F��$�$���t�H�~����!�#�����	�K�D�K�	
�	
��	
�	
����

�G�G�G���<�=�=�=�=�=rc�z�d|�dd��z}|�dd��s|dz
}t|��td|�dd��z��|�d	��pg}|�d
t|����}t|��|kr1td�|t|������n"td�|����t��|std
��dSt	|d��dS)NzGlobal WAF: �
global_waf�unknown�security_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#rAr�rr)r�r:rG�totals    r�waf_status_printerr��s>��
�f�j�j��y�A�A�
A�F��:�:�/��6�6�"��/�!��	�&�M�M�M�	�!�F�J�J�/C�Y�$O�$O�O����
�J�J�w���%�2�E��J�J�}�c�%�j�j�1�1�E�
�5�z�z�E���	�/�6�6�u�c�%�j�j�I�I�J�J�J�J�
�"�)�)�%�0�0�1�1�1�	�G�G�G���
�n�������
�@�����r)�config�show)�eular��r )�	whitelist)r�r�r.)�	blacklist)r�r�r.)�graylist)r�r�r.)�malwarez	on-demandr�)�feature-management�defaults)r�r�)r��enable)r��disable)r�r )r��add)r��delete))r�r.)r�z
add-native)�wordpress-plugin�waf�set�r�r�r�r�c�@�|�d���|dndS)NrG�OKr�)r�s r�_get_default_outputr�s!��$�j�j��1�1�=�6�'�?�?�4�GrFc�Z�|rtni}ttj|fi|����dSr)�PRETTY_JSON_ARGSr#r�r�)r��
is_verbose�pretty_argss   r�_print_json_responser�#s8��&0�8�"�"�b�K�	�$�*�V�
+�
+�{�
+�
+�,�,�,�,�,rc��t�|t��}|tvr
||��dS|t	|����dS)z<Print result in plain text format using appropriate printer.N)�PRINTERSr r��_FULL_RESULT_PRINTERSr�)�methodr��	print_funs   r�_print_plain_responser�(sV�����V�]�3�3�I�
�&�&�&��	�&�������	�%�f�-�-�.�.�.�.�.rc�N�|rt||��dSt||��dSr)r�r�)r�r��is_jsonr�s    r�print_responser�1s6���.��V�Z�0�0�0�0�0��f�f�-�-�-�-�-rr(c��t|t��sdS|�dg��D]}t|tj����dS)Nr
�rH)rirjr r#r'�stderr)r(�warnings  r�print_warningsr�8sX���d�D�!�!�����8�8�J��+�+�(�(��
�g�C�J�'�'�'�'�'�(�(rr�c��|r.|rtni}ttj||ifi|����dSt	|t
tf��r(|D]#}tt|�d|��|����$dSt||���dS)Nrr�)r�r#r�r�rir.r��_CLI_MSG_PREFIX)r��messagesr�r�rHr��msgs       r�print_errorr�As����'�*4�<�&�&�"��
�d�j�&�(�+�;�;�{�;�;�<�<�<�<�<��h��u�
�.�.�	'��
L�
L���/�&�"9�"9�"9�3�3�?�d�K�K�K�K�K�
L�
L�
�(��&�&�&�&�&�&r)F)FF),�collectionsrr�rr$r'r�r�r��EXITCODE_NOT_FOUND�EXITCODE_WARNING�EXITCODE_GENERAL_ERRORr"�SUCCESSrrr��
EXIT_CODESr)r+rcrfrlrrr|r�r�r�r�r�r�r�r�r�r�r�r�r�r�r�rjr�r�r�r>rr�<module>r�sJ��#�#�#�#�#�#�����	�	�	�	�����
�
�
�
���������!%��+�N�N��������
�{�	#��8����%��I�u�g�6���Q��
�	�!��
�H�H�H�<�<�<�<�<�<�<�<�~1�1�1�6�6�6����9�9�9�	#�	#�	#����#�#�#�	#�	#�	#����"9�9�9� � � �
!�
!�
!� 
>�
>�
>� ���4�����e��
�o���L�	�
 ����L�
� ����>�����'���'���#�L��%�l��&�|��"�<�� �\�!�"��#�$#�(�(7�+=�+����4?�?��H�H�H�-�-�-�-�
/�/�/�.�.�.�.�(��(�(�(�(�16�'�?B�z�'�'�'�'�'�'�'rdefence360agent/utils/__pycache__/cli.cpython-311.pyc0000644000000000000000000003757700000000000017412 0ustar  �

�d�_�$
����ddlmZddlZddlZddlZddlZddlZddlZdddd�ZdZ	dZ
dZd	d
gZd\Z
ZZeded
iZe
dee
eeiZd�ZGd�d��Zd�Zd�Zd�Zd�Zd�Zd�Zd�Zd�Zd�Zd�Zd�Zd�Zd�Z d�Z!ide�d e�d!e�d"e�d#e�d$e�d%e�d&e�d'e�d(e�d)e�d*e�d+e�d,e�d-e�d.e�d/e�eee e!d0��Z"d1hZ#d2�Z$d;d4�Z%d5�Z&d<d6�Z'd7e(fd8�Z)	d<ej*d9�d:�Z+dS)=�)�defaultdictNT�)�,�: )�	sort_keys�indent�
separators��z	/bin/lessz	/bin/more)�success�warnings�error�WARNING�ERRORc��tj�dtd�tD��d����}|�t|��dSt
j|g|���tj
���dS)N�PAGERc3�XK�|]%}tj�|���!|V��&dS�N)�os�path�isfile)�.0�ps  �N/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/cli.py�	<genexpr>zpager.<locals>.<genexpr>s5����>�>�Q�B�G�N�N�1�,=�,=�>�q�>�>�>�>�>�>�)�input�stdout)r�environ�get�next�PAGERS�print�
subprocess�run�encode�sysr)�data�pagers  rr)r)st���J�N�N���>�>�&�>�>�>��E�E�
�
�E�
�}�
�d���������w�d�k�k�m�m�C�J�G�G�G�G�G�Grc�d�eZdZd�Z				dd�Zejfd�Zed���Z	ed���Z
dS)	�TablePrinterc�b�i|_tt��|_i|_i|_dSr)�_headersr�list�_mappers�_right_aligned�_widths)�selfs r�__init__zTablePrinter.__init__'s+����
�#�D�)�)��
� �������rNFc��|r
||j|<|r
||j|<||j|<|r|n|���|j|<dSr)r/r1r0�upperr-)r2�field�mappers�	max_width�right_align�headers      r�set_field_propertiesz!TablePrinter.set_field_properties-s\���	+�#*�D�M�%� ��	,�"+�D�L���%0���E�"�)/�B�v�v�U�[�[�]�]��
�e���rc�����fd�|D��}d�|D��}g}|D]�}g}t|��D]�\}	}
|�|
��}�j|
D]
}||��}�t|��}t	|��||	krQ�j�|
��}
|
r#t	|��|
kr|d|
dz
�dz}t	|��||	<|�|����|�|����t��||d����|D]?}t��||�j	�|
d�������@dS)Nc�j��g|]/}�j�||�������0S�)r-r r5)rr6r2s  �r�
<listcomp>z&TablePrinter.print.<locals>.<listcomp>=s3���O�O�O�u�4�=�$�$�U�E�K�K�M�M�:�:�O�O�Orc�,�g|]}t|����Sr>)�len)rr6s  rr?z&TablePrinter.print.<locals>.<listcomp>>s��2�2�2��#�e�*�*�2�2�2rr
z...F)
�	enumerater r/�strrAr1�appendr#�_format_rowr0)r2�fields�items�file�headers�widths�rows�item�row�ir6�v�mapperr8s`             rr#zTablePrinter.print<s����O�O�O�O��O�O�O��2�2�'�2�2�2�����
	�
	�D��C�%�f�-�-�

�

���5��H�H�U�O�O��"�m�E�2�"�"�F���q�	�	�A�A���F�F���q�6�6�F�1�I�%�%� $�� 0� 0�� 7� 7�I� �7�S��V�V�i�%7�%7��o�	�A�
�o�.��6�� #�A���F�1�I��
�
�1�
�
�
�
��K�K������
�d���w���6�6�7�7�7��	�	�C��� � ����!4�!8�!8���!F�!F���
�
�
�
�	�	rc�Z�|r|�|��S|�|��Sr)�rjust�ljust)�value�widthr9s   r�_add_paddingzTablePrinter._add_paddingVs.���	&��;�;�u�%�%�%��{�{�5�!�!�!rc�h�����fd�t|��D��}d�|��S)Nc�Z��g|]'\}}t�|�|�����(Sr>)r+rV)rrNrT�
right_alignedrJs   ��rr?z,TablePrinter._format_row.<locals>.<listcomp>^sC���
�
�
���5�
�%�%�e�V�A�Y�
�F�F�
�
�
rz  )rB�join)�columnsrJrY�colss `` rrEzTablePrinter._format_row\sK����
�
�
�
�
�%�g�.�.�
�
�
���y�y����r)NNFN)�__name__�
__module__�__qualname__r3r;r'rr#�staticmethodrVrEr>rrr+r+&s��������������

C�
C�
C�
C�),�
�����4�"�"��\�"�
����\���rr+c��|�|ndS)Nzn/ar>�rTs r�n_arces���%�5�5�5�0rc�(�|�t|��n|Sr)�intrbs r�to_intrfis���*�3�u�:�:�:��5rc����fd�}|S)Nc�\��t|t��r|����S|Sr)�
isinstance�dictr )rTr6s �r�	extractorz extract_field.<locals>.extractorns,����e�T�"�"�	$��9�9�U�#�#�#��rr>)r6rks` r�
extract_fieldrlms$��������
�rc��t��}|D]}|j|��
|�d�|D��|��dS)Nc��g|]
}|d��S)rr>)rrLs  rr?zprint_table.<locals>.<listcomp>zs��1�1�1�T��a��1�1�1r)r+r;r#)r(�field_props�table�propss    r�print_tablerrvsV���N�N�E��+�+��"��"�E�*�*�*�	�K�K�1�1�[�1�1�1�4�8�8�8�8�8rc��dtgfdtgfdtd��gfdtgfdtgfdtgff}t||��dS)N�	timestamp�abuser�country�code�times�name�severity)rfrcrlrr�r(ros  r�print_incidentsr|}se��	�v�h��	�C�5��	�]�6�*�*�+�,�	�3�%��	�#���	�c�U��
�K���k�"�"�"�"�"rc��ttj����}|D],}|�dd��}|dkr	||z
|d<�'d|d<�-dS)N�
expirationr�ttl)re�timer )r(�nowrLr~s    r�add_ttlr��se��

�d�i�k�k�
�
�C������X�X�l�A�.�.�
���>�>�$�s�*�D��K�K��D��K�K��rc�n�t|��dddtd��gff}t||��dS)N��ip�rrvrw�r�rlrrr{s  r�print_graylistr��sE���D�M�M�M���	�]�6�*�*�+�,��K�
��k�"�"�"�"�"rc�r�t|��dddtd��gfddf}t||��dS)Nr�r�rvrw)�
imported_from)�commentr�r{s  r�print_bwlistr��sK���D�M�M�M���	�]�6�*�*�+�,����K���k�"�"�"�"�"rc���t|ttf��r�t|��r�t	��}t|dt
��rdt
|d�����}|�dtd��g���|�
||��dS|D]}t|���dSdSt|��dS)Nrrvrw)r7)rir.�tuplerAr+rj�sorted�keysr;rlr#)r(�printerr�rLs    r�
guess_printerr��s����$��u�
�&�&�
��t�9�9�
	 �"�n�n�G��$�q�'�4�(�(�
 ��d�1�g�l�l�n�n�-�-���,�,��
�f�(=�(=�'>�-�����
�
�d�D�)�)�)�)�)� � � �D��$�K�K�K�K�
	 �
	 � � �	�d�����rc��t|t��rt|��dSttj|d�����dS)NF)�default_flow_style)rirCr#�yaml�dump�r(s r�yaml_printerr��sF���$����9�
�d������
�d�i���7�7�7�8�8�8�8�8rc��t|t��rt|��dSttj|����dSr)rirCr#�json�dumpsr�s r�json_printerr��sA���$���� �
�d������
�d�j��������rc	�V�t|t��r*td�|d����dSg}|D]B}|�d�|d|d|drdnd�����Ctd	�|����dS)
Nz
Status: {}�statuszEvent: {}, Path: {}{}�eventr�nativez  native��
)rirjr#�formatrDrZ)r(�result�hooks   r�hook_printerr��s����$����!�
�l�!�!�$�x�.�1�1�2�2�2�2�2����	�	�D��M�M�'�.�.���M���L�"&�x�.�8�J�J�b���
�
�
�
�	�d�i�i���� � � � � rc���|std��dSdddd�}|D]}||dxxdz
cc<�tdjdi|����t��t|d��dS)	NzNo users targeted.r)�	succeeded�skipped�failedr��z:{succeeded} succeeded, {skipped} skipped, {failed} failed.))�user)r�)�reasonr>)r#r�rr)rG�countsrLs   r�waf_set_printerr��s�����
�"�#�#�#�����a�
8�
8�F��$�$���t�H�~����!�#�����	�K�D�K�	
�	
��	
�	
����

�G�G�G���<�=�=�=�=�=rc�z�d|�dd��z}|�dd��s|dz
}t|��td|�dd��z��|�d	��pg}|�d
t|����}t|��|kr1td�|t|������n"td�|����t��|std
��dSt	|d��dS)NzGlobal WAF: �
global_waf�unknown�security_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#rAr�rr)r�r:rG�totals    r�waf_status_printerr��s>��
�f�j�j��y�A�A�
A�F��:�:�/��6�6�"��/�!��	�&�M�M�M�	�!�F�J�J�/C�Y�$O�$O�O����
�J�J�w���%�2�E��J�J�}�c�%�j�j�1�1�E�
�5�z�z�E���	�/�6�6�u�c�%�j�j�I�I�J�J�J�J�
�"�)�)�%�0�0�1�1�1�	�G�G�G���
�n�������
�@�����r)�config�show)�eular��r )�	whitelist)r�r�r.)�	blacklist)r�r�r.)�graylist)r�r�r.)�malwarez	on-demandr�)�feature-management�defaults)r�r�)r��enable)r��disable)r�r )r��add)r��delete))r�r.)r�z
add-native)�wordpress-plugin�waf�set�r�r�r�r�c�@�|�d���|dndS)NrG�OKr�)r�s r�_get_default_outputr�s!��$�j�j��1�1�=�6�'�?�?�4�GrFc�Z�|rtni}ttj|fi|����dSr)�PRETTY_JSON_ARGSr#r�r�)r��
is_verbose�pretty_argss   r�_print_json_responser�#s8��&0�8�"�"�b�K�	�$�*�V�
+�
+�{�
+�
+�,�,�,�,�,rc��t�|t��}|tvr
||��dS|t	|����dS)z<Print result in plain text format using appropriate printer.N)�PRINTERSr r��_FULL_RESULT_PRINTERSr�)�methodr��	print_funs   r�_print_plain_responser�(sV�����V�]�3�3�I�
�&�&�&��	�&�������	�%�f�-�-�.�.�.�.�.rc�N�|rt||��dSt||��dSr)r�r�)r�r��is_jsonr�s    r�print_responser�1s6���.��V�Z�0�0�0�0�0��f�f�-�-�-�-�-rr(c��t|t��sdS|�dg��D]}t|tj����dS)Nr
�rH)rirjr r#r'�stderr)r(�warnings  r�print_warningsr�8sX���d�D�!�!�����8�8�J��+�+�(�(��
�g�C�J�'�'�'�'�'�(�(rr�c��|r.|rtni}ttj||ifi|����dSt	|t
tf��r(|D]#}tt|�d|��|����$dSt||���dS)Nrr�)r�r#r�r�rir.r��_CLI_MSG_PREFIX)r��messagesr�r�rHr��msgs       r�print_errorr�As����'�*4�<�&�&�"��
�d�j�&�(�+�;�;�{�;�;�<�<�<�<�<��h��u�
�.�.�	'��
L�
L���/�&�"9�"9�"9�3�3�?�d�K�K�K�K�K�
L�
L�
�(��&�&�&�&�&�&r)F)FF),�collectionsrr�rr$r'r�r�r��EXITCODE_NOT_FOUND�EXITCODE_WARNING�EXITCODE_GENERAL_ERRORr"�SUCCESSrrr��
EXIT_CODESr)r+rcrfrlrrr|r�r�r�r�r�r�r�r�r�r�r�r�r�r�r�rjr�r�r�r>rr�<module>r�sJ��#�#�#�#�#�#�����	�	�	�	�����
�
�
�
���������!%��+�N�N��������
�{�	#��8����%��I�u�g�6���Q��
�	�!��
�H�H�H�<�<�<�<�<�<�<�<�~1�1�1�6�6�6����9�9�9�	#�	#�	#����#�#�#�	#�	#�	#����"9�9�9� � � �
!�
!�
!� 
>�
>�
>� ���4�����e��
�o���L�	�
 ����L�
� ����>�����'���'���#�L��%�l��&�|��"�<�� �\�!�"��#�$#�(�(7�+=�+����4?�?��H�H�H�-�-�-�-�
/�/�/�.�.�.�.�(��(�(�(�(�16�'�?B�z�'�'�'�'�'�'�'rdefence360agent/utils/__pycache__/common.cpython-311.opt-1.pyc0000644000000000000000000005300700000000000021054 0ustar  �

�j8wȀ���N�ddlZddlZddlZddlZddlZddlZddlZddlZddlZej	d����
��Zej	d����
��Zej	d����
��Z
ej	d����
��Zeje��ZGd�de��ZGd	�d
e��ZGd�d��ZeZGd
�d��Ze��Zd�ZGd�d��ZGd�de��Zdd�ZdS)�N�)�minutes)�hours)�days)�weeksc��eZdZdZd�Zd�Zd�Zd�Zd�Zd�Z	Gd�d	e
��ZGd
�de��ZGd�d
e��Z
Gd�de��ZdS)�ServiceBasezBase service class.c�d�||_d|_d|_|�|��|_dS�NF)�_loop�_should_stop�
_main_task�StoppedState�_state)�self�loops  �Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/common.py�__init__zServiceBase.__init__s1����
�!�������'�'��-�-�����c�4�|j���S�N)r�start�rs rrzServiceBase.starts���{� � �"�"�"rc�4�|j���Sr)r�should_stoprs rrzServiceBase.should_stops���{�&�&�(�(�(rc��DK�|j����d{V��Sr)r�waitrs rrzServiceBase.wait"s,�����[�%�%�'�'�'�'�'�'�'�'�'rc�4�|j���Sr)r�
is_runningrs rrzServiceBase.is_running%s���{�%�%�'�'�'rc��K�t�r)�NotImplementedErrorrs r�_runzServiceBase._run(s����!�!rc�,�eZdZd�Zd�Zd�Zd�Zd�ZdS)�ServiceBase.Statec��||_dS)z:type obj: ServiceBaseN)�_obj�r�objs  rrzServiceBase.State.__init__,s
���D�I�I�Irc��dSr�rs rrzServiceBase.State.start0����Drc��dSrr*rs rrzServiceBase.State.should_stop3r+rc��:K�|jj}|r
|�d{V��dSdSr)r&r)r�tasks  rrzServiceBase.State.wait6s7�����9�'�D��
��
�
�
�
�
�
�
�
�
�
�
rc��dSrr*rs rrzServiceBase.State.is_running;s���5rN)�__name__�
__module__�__qualname__rrrrrr*rr�Stater$+s_������	�	�	�	�	�	�	�	�	�	�	�	�
	�	�	�	�	rr3c��eZdZd�Zd�ZdS)�ServiceBase.StoppedStatec�p�t�|j��|j_d|j_dSr)r	rr&rr
)r�futures  r�_on_stopz!ServiceBase.StoppedState._on_stop?s,��*�7�7��	�B�B�D�I��%*�D�I�"�"�"rc���|j}|j�|�����|_|j�|j��t�|��|_	dSr)
r&r�create_taskr"r�add_done_callbackr8r	�RunningStaterr's  rrzServiceBase.StoppedState.startCsY���)�C� �Y�2�2�3�8�8�:�:�>�>�C�N��N�,�,�T�]�;�;�;�$�1�1�#�6�6�C�J�J�JrN)r0r1r2r8rr*rrrr5>s2������	+�	+�	+�	7�	7�	7�	7�	7rrc��eZdZd�Zd�ZdS)�ServiceBase.RunningStatec��|j}d|_|j���t�|��|_dS�NT)r&r
r�cancelr	�
StoppingStaterr's  rrz$ServiceBase.RunningState.should_stopJs>���)�C�#�C���N�!�!�#�#�#�$�2�2�3�7�7�C�J�J�Jrc��dSr@r*rs rrz#ServiceBase.RunningState.is_runningPs���4rN)r0r1r2rrr*rrr<r>Is2������	8�	8�	8�	�	�	�	�	rr<c��eZdZd�ZdS)�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�	7�u�	7�	7�	7������u��������������rr	c��eZdZdS)rGN)r0r1r2r*rrrGrGZs�������DrrGc�H�eZdZdZejfdd�d�Zed���Zd�Z	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)r�period�timerrMs    rrzRateLimit.__init__is#��#�����������
�
�
rc�N�|jdup|j|���kSr)rOrQrs r�should_be_calledzRateLimit.should_be_calledos,��
� �D�(�
5��#�t�{�{�}�}�4�	
rc����tj�����fd���}tj�����fd���}tj���r|n|S)Nc����jr)�����jz�_�|i|��S�j�
�j|i|��SdSr�rVrQrPrOrR��args�kwargs�funcrs  ��r�wrapperz#RateLimit.__call__.<locals>.wrapperwsa����$�
6�'+�{�{�}�}�t�|�'C��$��t�T�,�V�,�,�,���*�$�t�}�d�5�f�5�5�5�+�*rc���K��jr/�����jz�_�|i|���d{V��S�j�
�j|i|��SdSrrYrZs  ��r�
async_wrapperz)RateLimit.__call__.<locals>.async_wrappersw������$�
6�'+�{�{�}�}�t�|�'C��$�!�T�4�2�6�2�2�2�2�2�2�2�2�2���*�$�t�}�d�5�f�5�5�5�+�*r)�	functools�wraps�asyncio�iscoroutinefunction)rr]r^r`s``  r�__call__zRateLimit.__call__vs�����	���	�	�	6�	6�	6�	6�	6�
�	�	6�
���	�	�	6�	6�	6�	6�	6�
�	�	6�!(� ;�D� A� A�N�}�}�w�Nr)
r0r1r2rH�time�	monotonicr�propertyrVrer*rrrLrL^st��������&*�^� �� � � � � ��
�
��X�
�O�O�O�O�OrrLc� �eZdZd�Zdd�d�ZdS)�
CoalesceCallsc�<�td��|_d|_dS)Nz-inf)�float�	call_time�delayed_callrs rrzCoalesceCalls.__init__�s���v����� ����rN)�
done_callbackc��������fd�}|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<c  |
        |----------------+--------------------------------+------------+------|
        | no p. call     | call soon                      | call at c+T| warn |
        | p. call at c+T | cancel the call/warn, call soon| drop call  | warn |
        c�N���tj�������fd���}|S)Nc������	K�|�d���	�	�tj���	|s|r	d|�d|��}nd}�
j�d|�d����	fd������	�
fd�}�	���}|�
j�zkr��
j�Wt�
j��}�
j���d�
_t�
d	|��
j|��t�d
�����	�|�
||���
_dS�
j|cxkr�
j�zkr�nn��
j�z|z
}�
j�t�d��|��dS�
j�J�t�d�|����	�
�
j�z|�
||���
_dSt�
d
��
j|��dS)Nr�*z, **��(�)c���|���sD|����2��d�z|���|d���dSdSdS)z�Log task's error
                       if any with event's loop exception handler.

                    CancelledError is not logged.
                    NzUnhandled exception during )�message�	exceptionr.)�	cancelledry�call_exception_handler)r.�	call_reprrs ��r�
log_exceptionzWCoalesceCalls.coalesce_calls.<locals>.decorator.<locals>.wrapper.<locals>.log_exception�s}��� �>�>�+�+�����0@�0@�0L��3�3�+H�"+�,,�-1�^�^�-=�-=�(,�	���������0L�0Lrc����t�d��������_d�_��||i|����}|����n���dS)z*Call & schedule the delayed coroutine now.zSchedule call %sN)�logger�inforfrmrnr:r;)	�coror[r\r.r|ror}rrs	    �����r�call_delayedzVCoalesceCalls.coalesce_calls.<locals>.decorator.<locals>.wrapper.<locals>.call_delayed�s�����K�K� 2�I�>�>�>�%)�Y�Y�[�[�D�N�(,�D�%��+�+�D�D�$�,A�&�,A�,A�B�B�D��*�*�(�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)�getrc�get_event_loopr0rfrmrn�strrAr�warningr��	call_soon�call_at)r[r\�	args_reprr��now�old_delayed_call_repr�delayr|r}rr�rorSrs       @@@����rr^z@CoalesceCalls.coalesce_calls.<locals>.decorator.<locals>.wrapper�s����������z�z�&�)�)���<�"�1�3�3�D��#�6�#��/3�t�t�V�V� <�I�I� "�I�(,�
�
�
�y�y�y�A�	������� 
�
�
�
�
�
�
�
�
��i�i�k�k���$�.�6�1�2�2��(�4�14�D�4E�0F�0F�-��)�0�0�2�2�2�,0��)����@�2�"� �N������K�K�@�!��	���)-���$�d�D�&�)�)�D�%�%�%��^�s�G�G�G�G�t�~��/F�G�G�G�G�G�!�^�f�4��;�E��(�4����>�&�"�!�
����� $�0�8�8�8����H�%�!�"����-1�L�L� �N�V�3�(� � �"�-�-��)�)�)��N�N�9�!��������r)rarb)r�r^rorSrs` ���r�	decoratorz/CoalesceCalls.coalesce_calls.<locals>.decorator�sQ����
�_�T�
"�
"�c
�c
�c
�c
�c
�c
�c
�#�
"�c
�J�Nrr*)rrSror�s``` r�coalesce_callszCoalesceCalls.coalesce_calls�s8�����8g	�g	�g	�g	�g	�g	�g	�R�r)r0r1r2rr�r*rrrjrj�sH������!�!�!�7;�E�E�E�E�E�E�Errjc��tj��}|�'|����d��rtj��S|S)ziReturns readable name of the server.

    It is sent to CLN and allows user to sort out his servers.
    N�	localhost)�socket�getfqdn�lower�
startswith�gethostname)�hostnames r�get_hostnamer�sH��
�~���H���8�>�>�+�+�6�6�{�C�C���!�#�#�#��Orc�>�eZdZdZd
d�Zd�Zd�Zd�Zd�Zd�Z	d	�Z
dS)�Versionz�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.
    Nc�8�|r|�|��dSdSr)�parse)r�vstrings  rrzVersion.__init__3s,���	 ��J�J�w������	 �	 rc�\�d�|jjt|����S)Nz	{} ('{}'))�format�	__class__r0r�rs r�__repr__zVersion.__repr__7s#���!�!�$�.�"9�3�t�9�9�E�E�Erc�N�|�|��}|tur|S|dkS�Nr��_cmp�NotImplemented�r�other�cs   r�__eq__zVersion.__eq__:�,���I�I�e���������H��A�v�
rc�N�|�|��}|tur|S|dkSr�r�r�s   r�__lt__zVersion.__lt__@�,���I�I�e���������H��1�u�rc�N�|�|��}|tur|S|dkSr�r�r�s   r�__le__zVersion.__le__Fr�rc�N�|�|��}|tur|S|dkSr�r�r�s   r�__gt__zVersion.__gt__Lr�rc�N�|�|��}|tur|S|dkSr�r�r�s   r�__ge__zVersion.__ge__Rr�rr)r0r1r2rHrr�r�r�r�r�r�r*rrr�r�,s��������� � � � �F�F�F�����������������rr�c�V�eZdZdZejdej��Zd�Zd�Z	d�Z
d�ZdS)�LooseVersiona�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").
    z(\d+ | [a-z]+ | \.)c���||_d�|j�|��D��}t|��D](\}}	t	|��||<�#t
$rY�%wxYw||_dS)Nc�"�g|]}|r|dk�
|��
S)�.r*)�.0�xs  r�
<listcomp>z&LooseVersion.parse.<locals>.<listcomp>�s,��
�
�
��1�
�AB�c���A���r)r��component_re�split�	enumerate�int�
ValueError�version)rr��
components�ir(s     rr�zLooseVersion.parse}s������
�
��(�.�.�w�7�7�
�
�
�
� �
�+�+�	�	�F�A�s�
� #�C���
�1�
�
���
�
�
���
����"����s�A�
A!� A!c��|jSr)r�rs r�__str__zLooseVersion.__str__�s
���|�rc�&�dt|��zS)NzLooseVersion ('%s'))r�rs rr�zLooseVersion.__repr__�s��$�s�4�y�y�0�0rc���t|t��rt|��}nt|t��stS|j|jkrdS|j|jkrdS|j|jkrdSdS)Nr���r)�
isinstancer�r�r�r�)rr�s  rr�zLooseVersion._cmp�s���e�S�!�!�	"� ��'�'�E�E��E�<�0�0�	"�!�!��<�5�=�(�(��1��<�%�-�'�'��2��<�%�-�'�'��1�(�'rN)r0r1r2rH�re�compile�VERBOSEr�r�r�r�r�r*rrr�r�Zsr��������>�2�:�4�b�j�A�A�L�"�"�"� ���1�1�1�����rr�c�R�tj�|��\}}tjdkr|dkr|dz}tj�|��r|S|�[tj�dd��}|�9	tjd��}n##ttf$rtj}YnwxYw|sdS|�tj
��}|D]E}tj�||��}tj�|��r|cS�FdS)z�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.
    �win32z.exeN�PATH�CS_PATH)�os�path�splitext�sys�platform�isfile�environr��confstr�AttributeErrorr��defpathr��pathsep�join)�
executabler��_�ext�paths�p�fs       r�find_executabler��s)���W�
�
�j�
)�
)�F�A�s������c�V�m�m��&�(�
�	�w�~�~�j�!�!�����|��z�~�~�f�d�+�+���<�
"��z�)�,�,����"�J�/�
"�
"�
"��z����
"�������t��J�J�r�z�"�"�E�
�����G�L�L��J�'�'��
�7�>�>�!���	��H�H�H�	��4s�B�B9�8B9r)rc�datetimera�loggingr�rfr�r�r��	timedelta�
total_seconds�MINUTE�HOUR�DAY�WEEK�	getLoggerr0rrIr	�	ExceptionrGrL�
rate_limitrj�webserver_gracefull_restartr�r�r�r�r*rr�<module>r�sL������������������
�
�
�
�����	�	�	�	�	�	�	�	�
�
�
�
�	��	�A�	&�	&�	&�	4�	4�	6�	6���x���"�"�"�0�0�2�2���h��a� � � �.�.�0�0���x���"�"�"�0�0�2�2��	��	�8�	$�	$��D�D�D�D�D�&�D�D�D�N	�	�	�	�	�y�	�	�	�)O�)O�)O�)O�)O�)O�)O�)O�X�
�J�J�J�J�J�J�J�J�Z,�m�o�o�����*�*�*�*�*�*�*�*�\D�D�D�D�D�7�D�D�D�P"�"�"�"�"�"rdefence360agent/utils/__pycache__/common.cpython-311.pyc0000644000000000000000000005300700000000000020115 0ustar  �

�j8wȀ���N�ddlZddlZddlZddlZddlZddlZddlZddlZddlZej	d����
��Zej	d����
��Zej	d����
��Z
ej	d����
��Zeje��ZGd�de��ZGd	�d
e��ZGd�d��ZeZGd
�d��Ze��Zd�ZGd�d��ZGd�de��Zdd�ZdS)�N�)�minutes)�hours)�days)�weeksc��eZdZdZd�Zd�Zd�Zd�Zd�Zd�Z	Gd�d	e
��ZGd
�de��ZGd�d
e��Z
Gd�de��ZdS)�ServiceBasezBase service class.c�d�||_d|_d|_|�|��|_dS�NF)�_loop�_should_stop�
_main_task�StoppedState�_state)�self�loops  �Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/common.py�__init__zServiceBase.__init__s1����
�!�������'�'��-�-�����c�4�|j���S�N)r�start�rs rrzServiceBase.starts���{� � �"�"�"rc�4�|j���Sr)r�should_stoprs rrzServiceBase.should_stops���{�&�&�(�(�(rc��DK�|j����d{V��Sr)r�waitrs rrzServiceBase.wait"s,�����[�%�%�'�'�'�'�'�'�'�'�'rc�4�|j���Sr)r�
is_runningrs rrzServiceBase.is_running%s���{�%�%�'�'�'rc��K�t�r)�NotImplementedErrorrs r�_runzServiceBase._run(s����!�!rc�,�eZdZd�Zd�Zd�Zd�Zd�ZdS)�ServiceBase.Statec��||_dS)z:type obj: ServiceBaseN)�_obj�r�objs  rrzServiceBase.State.__init__,s
���D�I�I�Irc��dSr�rs rrzServiceBase.State.start0����Drc��dSrr*rs rrzServiceBase.State.should_stop3r+rc��:K�|jj}|r
|�d{V��dSdSr)r&r)r�tasks  rrzServiceBase.State.wait6s7�����9�'�D��
��
�
�
�
�
�
�
�
�
�
�
rc��dSrr*rs rrzServiceBase.State.is_running;s���5rN)�__name__�
__module__�__qualname__rrrrrr*rr�Stater$+s_������	�	�	�	�	�	�	�	�	�	�	�	�
	�	�	�	�	rr3c��eZdZd�Zd�ZdS)�ServiceBase.StoppedStatec�p�t�|j��|j_d|j_dSr)r	rr&rr
)r�futures  r�_on_stopz!ServiceBase.StoppedState._on_stop?s,��*�7�7��	�B�B�D�I��%*�D�I�"�"�"rc���|j}|j�|�����|_|j�|j��t�|��|_	dSr)
r&r�create_taskr"r�add_done_callbackr8r	�RunningStaterr's  rrzServiceBase.StoppedState.startCsY���)�C� �Y�2�2�3�8�8�:�:�>�>�C�N��N�,�,�T�]�;�;�;�$�1�1�#�6�6�C�J�J�JrN)r0r1r2r8rr*rrrr5>s2������	+�	+�	+�	7�	7�	7�	7�	7rrc��eZdZd�Zd�ZdS)�ServiceBase.RunningStatec��|j}d|_|j���t�|��|_dS�NT)r&r
r�cancelr	�
StoppingStaterr's  rrz$ServiceBase.RunningState.should_stopJs>���)�C�#�C���N�!�!�#�#�#�$�2�2�3�7�7�C�J�J�Jrc��dSr@r*rs rrz#ServiceBase.RunningState.is_runningPs���4rN)r0r1r2rrr*rrr<r>Is2������	8�	8�	8�	�	�	�	�	rr<c��eZdZd�ZdS)�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�	7�u�	7�	7�	7������u��������������rr	c��eZdZdS)rGN)r0r1r2r*rrrGrGZs�������DrrGc�H�eZdZdZejfdd�d�Zed���Zd�Z	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)r�period�timerrMs    rrzRateLimit.__init__is#��#�����������
�
�
rc�N�|jdup|j|���kSr)rOrQrs r�should_be_calledzRateLimit.should_be_calledos,��
� �D�(�
5��#�t�{�{�}�}�4�	
rc����tj�����fd���}tj�����fd���}tj���r|n|S)Nc����jr)�����jz�_�|i|��S�j�
�j|i|��SdSr�rVrQrPrOrR��args�kwargs�funcrs  ��r�wrapperz#RateLimit.__call__.<locals>.wrapperwsa����$�
6�'+�{�{�}�}�t�|�'C��$��t�T�,�V�,�,�,���*�$�t�}�d�5�f�5�5�5�+�*rc���K��jr/�����jz�_�|i|���d{V��S�j�
�j|i|��SdSrrYrZs  ��r�
async_wrapperz)RateLimit.__call__.<locals>.async_wrappersw������$�
6�'+�{�{�}�}�t�|�'C��$�!�T�4�2�6�2�2�2�2�2�2�2�2�2���*�$�t�}�d�5�f�5�5�5�+�*r)�	functools�wraps�asyncio�iscoroutinefunction)rr]r^r`s``  r�__call__zRateLimit.__call__vs�����	���	�	�	6�	6�	6�	6�	6�
�	�	6�
���	�	�	6�	6�	6�	6�	6�
�	�	6�!(� ;�D� A� A�N�}�}�w�Nr)
r0r1r2rH�time�	monotonicr�propertyrVrer*rrrLrL^st��������&*�^� �� � � � � ��
�
��X�
�O�O�O�O�OrrLc� �eZdZd�Zdd�d�ZdS)�
CoalesceCallsc�<�td��|_d|_dS)Nz-inf)�float�	call_time�delayed_callrs rrzCoalesceCalls.__init__�s���v����� ����rN)�
done_callbackc��������fd�}|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<c  |
        |----------------+--------------------------------+------------+------|
        | no p. call     | call soon                      | call at c+T| warn |
        | p. call at c+T | cancel the call/warn, call soon| drop call  | warn |
        c�N���tj�������fd���}|S)Nc������	K�|�d���	�	�tj���	|s|r	d|�d|��}nd}�
j�d|�d����	fd������	�
fd�}�	���}|�
j�zkr��
j�Wt�
j��}�
j���d�
_t�
d	|��
j|��t�d
�����	�|�
||���
_dS�
j|cxkr�
j�zkr�nn��
j�z|z
}�
j�t�d��|��dS�
j�J�t�d�|����	�
�
j�z|�
||���
_dSt�
d
��
j|��dS)Nr�*z, **��(�)c���|���sD|����2��d�z|���|d���dSdSdS)z�Log task's error
                       if any with event's loop exception handler.

                    CancelledError is not logged.
                    NzUnhandled exception during )�message�	exceptionr.)�	cancelledry�call_exception_handler)r.�	call_reprrs ��r�
log_exceptionzWCoalesceCalls.coalesce_calls.<locals>.decorator.<locals>.wrapper.<locals>.log_exception�s}��� �>�>�+�+�����0@�0@�0L��3�3�+H�"+�,,�-1�^�^�-=�-=�(,�	���������0L�0Lrc����t�d��������_d�_��||i|����}|����n���dS)z*Call & schedule the delayed coroutine now.zSchedule call %sN)�logger�inforfrmrnr:r;)	�coror[r\r.r|ror}rrs	    �����r�call_delayedzVCoalesceCalls.coalesce_calls.<locals>.decorator.<locals>.wrapper.<locals>.call_delayed�s�����K�K� 2�I�>�>�>�%)�Y�Y�[�[�D�N�(,�D�%��+�+�D�D�$�,A�&�,A�,A�B�B�D��*�*�(�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)�getrc�get_event_loopr0rfrmrn�strrAr�warningr��	call_soon�call_at)r[r\�	args_reprr��now�old_delayed_call_repr�delayr|r}rr�rorSrs       @@@����rr^z@CoalesceCalls.coalesce_calls.<locals>.decorator.<locals>.wrapper�s����������z�z�&�)�)���<�"�1�3�3�D��#�6�#��/3�t�t�V�V� <�I�I� "�I�(,�
�
�
�y�y�y�A�	������� 
�
�
�
�
�
�
�
�
��i�i�k�k���$�.�6�1�2�2��(�4�14�D�4E�0F�0F�-��)�0�0�2�2�2�,0��)����@�2�"� �N������K�K�@�!��	���)-���$�d�D�&�)�)�D�%�%�%��^�s�G�G�G�G�t�~��/F�G�G�G�G�G�!�^�f�4��;�E��(�4����>�&�"�!�
����� $�0�8�8�8����H�%�!�"����-1�L�L� �N�V�3�(� � �"�-�-��)�)�)��N�N�9�!��������r)rarb)r�r^rorSrs` ���r�	decoratorz/CoalesceCalls.coalesce_calls.<locals>.decorator�sQ����
�_�T�
"�
"�c
�c
�c
�c
�c
�c
�c
�#�
"�c
�J�Nrr*)rrSror�s``` r�coalesce_callszCoalesceCalls.coalesce_calls�s8�����8g	�g	�g	�g	�g	�g	�g	�R�r)r0r1r2rr�r*rrrjrj�sH������!�!�!�7;�E�E�E�E�E�E�Errjc��tj��}|�'|����d��rtj��S|S)ziReturns readable name of the server.

    It is sent to CLN and allows user to sort out his servers.
    N�	localhost)�socket�getfqdn�lower�
startswith�gethostname)�hostnames r�get_hostnamer�sH��
�~���H���8�>�>�+�+�6�6�{�C�C���!�#�#�#��Orc�>�eZdZdZd
d�Zd�Zd�Zd�Zd�Zd�Z	d	�Z
dS)�Versionz�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.
    Nc�8�|r|�|��dSdSr)�parse)r�vstrings  rrzVersion.__init__3s,���	 ��J�J�w������	 �	 rc�\�d�|jjt|����S)Nz	{} ('{}'))�format�	__class__r0r�rs r�__repr__zVersion.__repr__7s#���!�!�$�.�"9�3�t�9�9�E�E�Erc�N�|�|��}|tur|S|dkS�Nr��_cmp�NotImplemented�r�other�cs   r�__eq__zVersion.__eq__:�,���I�I�e���������H��A�v�
rc�N�|�|��}|tur|S|dkSr�r�r�s   r�__lt__zVersion.__lt__@�,���I�I�e���������H��1�u�rc�N�|�|��}|tur|S|dkSr�r�r�s   r�__le__zVersion.__le__Fr�rc�N�|�|��}|tur|S|dkSr�r�r�s   r�__gt__zVersion.__gt__Lr�rc�N�|�|��}|tur|S|dkSr�r�r�s   r�__ge__zVersion.__ge__Rr�rr)r0r1r2rHrr�r�r�r�r�r�r*rrr�r�,s��������� � � � �F�F�F�����������������rr�c�V�eZdZdZejdej��Zd�Zd�Z	d�Z
d�ZdS)�LooseVersiona�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").
    z(\d+ | [a-z]+ | \.)c���||_d�|j�|��D��}t|��D](\}}	t	|��||<�#t
$rY�%wxYw||_dS)Nc�"�g|]}|r|dk�
|��
S)�.r*)�.0�xs  r�
<listcomp>z&LooseVersion.parse.<locals>.<listcomp>�s,��
�
�
��1�
�AB�c���A���r)r��component_re�split�	enumerate�int�
ValueError�version)rr��
components�ir(s     rr�zLooseVersion.parse}s������
�
��(�.�.�w�7�7�
�
�
�
� �
�+�+�	�	�F�A�s�
� #�C���
�1�
�
���
�
�
���
����"����s�A�
A!� A!c��|jSr)r�rs r�__str__zLooseVersion.__str__�s
���|�rc�&�dt|��zS)NzLooseVersion ('%s'))r�rs rr�zLooseVersion.__repr__�s��$�s�4�y�y�0�0rc���t|t��rt|��}nt|t��stS|j|jkrdS|j|jkrdS|j|jkrdSdS)Nr���r)�
isinstancer�r�r�r�)rr�s  rr�zLooseVersion._cmp�s���e�S�!�!�	"� ��'�'�E�E��E�<�0�0�	"�!�!��<�5�=�(�(��1��<�%�-�'�'��2��<�%�-�'�'��1�(�'rN)r0r1r2rH�re�compile�VERBOSEr�r�r�r�r�r*rrr�r�Zsr��������>�2�:�4�b�j�A�A�L�"�"�"� ���1�1�1�����rr�c�R�tj�|��\}}tjdkr|dkr|dz}tj�|��r|S|�[tj�dd��}|�9	tjd��}n##ttf$rtj}YnwxYw|sdS|�tj
��}|D]E}tj�||��}tj�|��r|cS�FdS)z�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.
    �win32z.exeN�PATH�CS_PATH)�os�path�splitext�sys�platform�isfile�environr��confstr�AttributeErrorr��defpathr��pathsep�join)�
executabler��_�ext�paths�p�fs       r�find_executabler��s)���W�
�
�j�
)�
)�F�A�s������c�V�m�m��&�(�
�	�w�~�~�j�!�!�����|��z�~�~�f�d�+�+���<�
"��z�)�,�,����"�J�/�
"�
"�
"��z����
"�������t��J�J�r�z�"�"�E�
�����G�L�L��J�'�'��
�7�>�>�!���	��H�H�H�	��4s�B�B9�8B9r)rc�datetimera�loggingr�rfr�r�r��	timedelta�
total_seconds�MINUTE�HOUR�DAY�WEEK�	getLoggerr0rrIr	�	ExceptionrGrL�
rate_limitrj�webserver_gracefull_restartr�r�r�r�r*rr�<module>r�sL������������������
�
�
�
�����	�	�	�	�	�	�	�	�
�
�
�
�	��	�A�	&�	&�	&�	4�	4�	6�	6���x���"�"�"�0�0�2�2���h��a� � � �.�.�0�0���x���"�"�"�0�0�2�2��	��	�8�	$�	$��D�D�D�D�D�&�D�D�D�N	�	�	�	�	�y�	�	�	�)O�)O�)O�)O�)O�)O�)O�)O�X�
�J�J�J�J�J�J�J�J�Z,�m�o�o�����*�*�*�*�*�*�*�*�\D�D�D�D�D�7�D�D�D�P"�"�"�"�"�"rdefence360agent/utils/__pycache__/completions.cpython-311.opt-1.pyc0000644000000000000000000004100100000000000022107 0ustar  �

�YU���	�2�dZddlZddlZddlmZmZmZdedefd�Zdej	deeedfeeffd	�Z
dej	deefd
�Zdej	deedfdeeedfeeffd
�Zdeeedfeefdeedfdeefd�Z
	ddej	dedefd�Z	ddej	dedefd�Z	ddej	dedefd�Zeeed�Zee�����Z	ddej	dededefd�ZdS)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)�Dict�List�Tuple�prog�returnc�.�tjdd|��S)zDConvert a prog name to a safe shell identifier (letters, digits, _).z[^a-zA-Z0-9]�_)�re�sub)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)r�results  r�_collect_commandsrs ��02�F����V�$�$�$��Mrc���g}|jD]W}t|tj��r�t|tj��r�8|jD]}|�|����Xt|��S)z:Extract all optional flags from a parser (excluding help).)�_actions�
isinstance�argparse�_HelpAction�_SubParsersAction�option_strings�append�sorted)r�flags�action�opts    r�
_get_flagsr!s����E��/�����f�h�2�3�3�	���f�h�8�9�9�	���(�	�	�C��L�L������	��%�=�=�r�pathrc���t|��}|||<|jD]P}t|tj��r4|j���D]\}}t|||fz|����QdS)z>Recursively walk subparsers and collect command paths + flags.N)r!rrrr�choices�itemsr)rr"rrr�name�	subparsers       rrr(s���
�v���E��F�4�L��/�@�@���f�h�8�9�9�	@�#)�>�#7�#7�#9�#9�
@�
@���i��Y���w���?�?�?�?��@�@r�commands�prefixc��t��}|D][}t|��t|��dzkr6|dt|���|kr|�|d���\t|��S)z,Get immediate subcommands of a given prefix.�N���)�set�len�addr)r(r)�subsr"s    r�_get_subcommandsr17ss��
�5�5�D������t�9�9��F���a��'�'�D��3�v�;�;��,?�6�,I�,I��H�H�T�"�X������$�<�<�r�imunify360-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��|�d��t|���d����}|D]�}|s�t||��}||}d�||z��}d�|��}	|�d|	�d���|�d |�d!���|�d"����t|d#��}
|�d#g��}d�|
|z��}|�d$��|�d |�d!���|�d"��|�d%��|�d&��|�d��|�d't|���d(|����|�d��d)�|��S)*z"Generate a bash completion script.z# bash completion for �# Auto-generated by z completions bash�r	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|��|fS�N�r.��ps r�<lambda>zgenerate_bash.<locals>.<lambda>g����A���w��l�r��key� �	        "�")z%            COMPREPLY=($(compgen -W "z
" -- "$cur"))z            return ;;r�        "")�    esac�}z
complete -F _z
_completions �
)rrr
r�keysr1�join�get)
rrr(�lines�	all_pathsr"r0r�completions�pattern�	root_subs�
root_flags�root_completionss
             r�
generate_bashrQCs��!��(�(�H��E�	�L�L�0�$�0�0�1�1�1�	�L�L�?��?�?�?�@�@�@�	�L�L�����	�L�L�>�%�d�+�+�>�>�>�?�?�?�	�L�L�1�2�2�2�	�L�L�A�B�B�B�	�L�L�5�6�6�6�	�L�L�����	�L�L�'�(�(�(�	�L�L�:�;�;�;�	�L�L�=�>�>�>�	�L�L�5�6�6�6�	�L�L�,�-�-�-�	�L�L�����	�L�L�����	�L�L�:�;�;�;�	�L�L�(�)�)�)�	�L�L�����	�L�L�8�9�9�9�	�L�L�0�1�1�1�	�L�L�.�/�/�/�	�L�L�L����
�L�L�� � � �	�L�L�����	�L�L�����	�L�L�*�+�+�+��x�}�}���,B�,B�C�C�C�I��.�.���	����$�/�/�������h�h�t�e�|�,�,���(�(�4�.�.��
���,��,�,�,�-�-�-�
���N�K�N�N�N�	
�	
�	
�	���,�-�-�-�-�!��2�.�.�I����b�"�%�%�J��x�x�	�J� 6�7�7��	�L�L�����	�L�L�O�0@�O�O�O����
�L�L�(�)�)�)�	�L�L�����	�L�L�����	�L�L�����	�L�L�L�!1�$�!7�!7�L�L�d�L�L�M�M�M�	�L�L������9�9�U���rc�2�t|��}dt|����}g}|�d|����|�d|����|�d|�d���|�d��|�|�d���|�d��|�d	��|�d��|�d
��|�d��|�d��|�d
��|�d��|�d��|�d��|�d��|�d��t|���d����}|D�]}|s�t||��}||}d�|��}	|�d|	�d���|r8d�d�|D����}
|�d|
�d���|r8d�d�|D����}|�d|�d���|�|rdndd�|���d���|�d����t|d ��}|�d g��}
d�d!�|D����}|�d"��|�d|�d���|
r8d�d#�|
D����}|�d|�d���|�d��|�d��|�d$��|�d%��|�d��|�|���|�d��d&�|��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.<locals>.<lambda>�r=rr>r@rArBc3�"K�|]
}d|�d�V��dS��"Nr��.0�ss  r�	<genexpr>zgenerate_zsh.<locals>.<genexpr>�s*���� 8� 8�a��Q���� 8� 8� 8� 8� 8� 8rz            commands=(�)c3�"K�|]
}d|�d�V��dSrUr�rX�fs  rrZzgenerate_zsh.<locals>.<genexpr>�s*���� 9� 9�a��Q���� 9� 9� 9� 9� 9� 9rz            flags=(z;            _describe 'command' commands -- flags && returnz            compadd -- z
 && returnz            ;;rc3�"K�|]
}d|�d�V��dSrUrrWs  rrZzgenerate_zsh.<locals>.<genexpr>�s*����5�5�a��Q����5�5�5�5�5�5rrCc3�"K�|]
}d|�d�V��dSrUrr]s  rrZzgenerate_zsh.<locals>.<genexpr>�s*����:�:�!�X��X�X�X�:�:�:�:�:�:rrDrErF)rr
rrrGr1rHrI)rrr(�	func_namerJrKr"r0rrM�	desc_list�	flag_listrNrO�	root_descs               r�generate_zshre�s;��!��(�(�H�,�$�T�*�*�,�,�I��E�	�L�L�#�T�#�#�$�$�$�	�L�L�/��/�/�0�0�0�	�L�L�>��>�>�>�?�?�?�	�L�L�����	�L�L�I�$�$�$�%�%�%�	�L�L�.�/�/�/�	�L�L�%�&�&�&�	�L�L�����	�L�L�6�7�7�7�	�L�L�"�#�#�#�	�L�L�5�6�6�6�	�L�L�8�9�9�9�	�L�L�H�I�I�I�	�L�L�,�-�-�-�	�L�L�����	�L�L�����	�L�L�/�0�0�0��x�}�}���,B�,B�C�C�C�I��'�'���	����$�/�/�������(�(�4�.�.��
���,��,�,�,�-�-�-��	@���� 8� 8�4� 8� 8� 8�8�8�I��L�L�>�)�>�>�>�?�?�?��	=���� 9� 9�5� 9� 9� 9�9�9�I��L�L�;�y�;�;�;�<�<�<�
����
G�I�I�F�3�8�8�E�?�?�F�F�F�	
�	
�	
�
	���%�&�&�&�&�!��2�.�.�I����b�"�%�%�J����5�5�9�5�5�5�5�5�I�	�L�L�����	�L�L�6�)�6�6�6�7�7�7��9��H�H�:�:�z�:�:�:�:�:�	�
���7�9�7�7�7�8�8�8�	�L�L�N�O�O�O�	�L�L�!�"�"�"�	�L�L�����	�L�L�����	�L�L�����	�L�L�I�� � � �	�L�L������9�9�U���rc��t|��}g}|�d|����|�d|�d���|�d��t|���d����D�]�}t	||��}||}|s�dd�t	|d	������}|D]!}|�d
|�d|�d|�d
����"|D]{}	|	�d��r(|�d
|�d|�d|	dd��d
����?|	�d��r'|�d
|�d|�d|	dd��d
����|��g}
|D]}|
�d|�����d�|
��}|}|r|dd�|����z
}|D]!}|�d
|�d|�d|�d
����"|D]{}	|	�d��r(|�d
|�d|�d|	dd��d
����?|	�d��r'|�d
|�d|�d|	dd��d
����|���|�d��d�|��S)z"Generate a fish completion script.z# fish completion for r4z completions fishr5c�$�t|��|fSr8r9r:s rr<zgenerate_fish.<locals>.<lambda>�s��s�1�v�v�q�k�rr>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_fishrp�s���!��(�(�H��E�	�L�L�0�$�0�0�1�1�1�	�L�L�?��?�?�?�@�@�@�	�L�L������x�}�}���,A�,A�B�B�B�1�1����$�/�/�������-	�?��H�H�-�h��;�;�<�<�?�?�
��
�
�����H�4�H�H�i�H�H�#�H�H�H������
�
���?�?�4�(�(���L�L�N�t�N�N�)�N�N�4����8�N�N�N������_�_�S�)�)���L�L�N�t�N�N�)�N�N�4����8�N�N�N�����

��J��
F�
F���!�!�"D��"D�"D�E�E�E�E����J�/�/�I��J��
��/�����,�,�/�/��	�
�
�
�����H�4�H�H�i�H�H�#�H�H�H������
�
���?�?�4�(�(���L�L�N�t�N�N�)�N�N�4����8�N�N�N������_�_�S�)�)���L�L�N�t�N�N�)�N�N�4����8�N�N�N�����

�
�L�L������9�9�U���r)�bash�zsh�fish�shellc��t�|��}|�-td|�dd�t�������|||��S)zfGenerate completion script for the given shell.

    Raises ValueError if shell is not supported.
    NzUnsupported shell: z. Supported shells: z, )�
GENERATORSrI�
ValueErrorrH�SUPPORTED_SHELLS)rrtr�	generators    r�generate_completionsrzsl�����u�%�%�I����
?�%�
?�
?�!%���+;�!<�!<�
?�
?�
�
�	
��9�V�T�"�"�"r)r2)�__doc__rr
�typingrrr�strr
�ArgumentParserrr!rr1rQrerprvrrGrxrzrrr�<module>rs���������	�	�	�	�$�$�$�$�$�$�$�$�$�$�.�3�.�3�.�.�.�.�
��#��	�%��S��/�4��9�
$�%�����
�x�.�
�4��9�
�
�
�
�@��#�@�
��S��/�@�
��s�C�x��$�s�)�+�,�@�@�@�@�	��5��c��?�D��I�-�.�	��#�s�(�O�	�
�#�Y�	�	�	�	�2D�@�@��#�@�+.�@��@�@�@�@�H2D�>�>��#�>�+.�>��>�>�>�>�D2D�@�@��#�@�+.�@��@�@�@�@�H
�����
��6�*�/�/�+�+�,�,��#�#�#��#�#��#��#�	�	#�#�#�#�#�#rdefence360agent/utils/__pycache__/completions.cpython-311.pyc0000644000000000000000000004100100000000000021150 0ustar  �

�YU���	�2�dZddlZddlZddlmZmZmZdedefd�Zdej	deeedfeeffd	�Z
dej	deefd
�Zdej	deedfdeeedfeeffd
�Zdeeedfeefdeedfdeefd�Z
	ddej	dedefd�Z	ddej	dedefd�Z	ddej	dedefd�Zeeed�Zee�����Z	ddej	dededefd�ZdS)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)�Dict�List�Tuple�prog�returnc�.�tjdd|��S)zDConvert a prog name to a safe shell identifier (letters, digits, _).z[^a-zA-Z0-9]�_)�re�sub)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)r�results  r�_collect_commandsrs ��02�F����V�$�$�$��Mrc���g}|jD]W}t|tj��r�t|tj��r�8|jD]}|�|����Xt|��S)z:Extract all optional flags from a parser (excluding help).)�_actions�
isinstance�argparse�_HelpAction�_SubParsersAction�option_strings�append�sorted)r�flags�action�opts    r�
_get_flagsr!s����E��/�����f�h�2�3�3�	���f�h�8�9�9�	���(�	�	�C��L�L������	��%�=�=�r�pathrc���t|��}|||<|jD]P}t|tj��r4|j���D]\}}t|||fz|����QdS)z>Recursively walk subparsers and collect command paths + flags.N)r!rrrr�choices�itemsr)rr"rrr�name�	subparsers       rrr(s���
�v���E��F�4�L��/�@�@���f�h�8�9�9�	@�#)�>�#7�#7�#9�#9�
@�
@���i��Y���w���?�?�?�?��@�@r�commands�prefixc��t��}|D][}t|��t|��dzkr6|dt|���|kr|�|d���\t|��S)z,Get immediate subcommands of a given prefix.�N���)�set�len�addr)r(r)�subsr"s    r�_get_subcommandsr17ss��
�5�5�D������t�9�9��F���a��'�'�D��3�v�;�;��,?�6�,I�,I��H�H�T�"�X������$�<�<�r�imunify360-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��|�d��t|���d����}|D]�}|s�t||��}||}d�||z��}d�|��}	|�d|	�d���|�d |�d!���|�d"����t|d#��}
|�d#g��}d�|
|z��}|�d$��|�d |�d!���|�d"��|�d%��|�d&��|�d��|�d't|���d(|����|�d��d)�|��S)*z"Generate a bash completion script.z# bash completion for �# Auto-generated by z completions bash�r	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|��|fS�N�r.��ps r�<lambda>zgenerate_bash.<locals>.<lambda>g����A���w��l�r��key� �	        "�")z%            COMPREPLY=($(compgen -W "z
" -- "$cur"))z            return ;;r�        "")�    esac�}z
complete -F _z
_completions �
)rrr
r�keysr1�join�get)
rrr(�lines�	all_pathsr"r0r�completions�pattern�	root_subs�
root_flags�root_completionss
             r�
generate_bashrQCs��!��(�(�H��E�	�L�L�0�$�0�0�1�1�1�	�L�L�?��?�?�?�@�@�@�	�L�L�����	�L�L�>�%�d�+�+�>�>�>�?�?�?�	�L�L�1�2�2�2�	�L�L�A�B�B�B�	�L�L�5�6�6�6�	�L�L�����	�L�L�'�(�(�(�	�L�L�:�;�;�;�	�L�L�=�>�>�>�	�L�L�5�6�6�6�	�L�L�,�-�-�-�	�L�L�����	�L�L�����	�L�L�:�;�;�;�	�L�L�(�)�)�)�	�L�L�����	�L�L�8�9�9�9�	�L�L�0�1�1�1�	�L�L�.�/�/�/�	�L�L�L����
�L�L�� � � �	�L�L�����	�L�L�����	�L�L�*�+�+�+��x�}�}���,B�,B�C�C�C�I��.�.���	����$�/�/�������h�h�t�e�|�,�,���(�(�4�.�.��
���,��,�,�,�-�-�-�
���N�K�N�N�N�	
�	
�	
�	���,�-�-�-�-�!��2�.�.�I����b�"�%�%�J��x�x�	�J� 6�7�7��	�L�L�����	�L�L�O�0@�O�O�O����
�L�L�(�)�)�)�	�L�L�����	�L�L�����	�L�L�����	�L�L�L�!1�$�!7�!7�L�L�d�L�L�M�M�M�	�L�L������9�9�U���rc�2�t|��}dt|����}g}|�d|����|�d|����|�d|�d���|�d��|�|�d���|�d��|�d	��|�d��|�d
��|�d��|�d��|�d
��|�d��|�d��|�d��|�d��|�d��t|���d����}|D�]}|s�t||��}||}d�|��}	|�d|	�d���|r8d�d�|D����}
|�d|
�d���|r8d�d�|D����}|�d|�d���|�|rdndd�|���d���|�d����t|d ��}|�d g��}
d�d!�|D����}|�d"��|�d|�d���|
r8d�d#�|
D����}|�d|�d���|�d��|�d��|�d$��|�d%��|�d��|�|���|�d��d&�|��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.<locals>.<lambda>�r=rr>r@rArBc3�"K�|]
}d|�d�V��dS��"Nr��.0�ss  r�	<genexpr>zgenerate_zsh.<locals>.<genexpr>�s*���� 8� 8�a��Q���� 8� 8� 8� 8� 8� 8rz            commands=(�)c3�"K�|]
}d|�d�V��dSrUr�rX�fs  rrZzgenerate_zsh.<locals>.<genexpr>�s*���� 9� 9�a��Q���� 9� 9� 9� 9� 9� 9rz            flags=(z;            _describe 'command' commands -- flags && returnz            compadd -- z
 && returnz            ;;rc3�"K�|]
}d|�d�V��dSrUrrWs  rrZzgenerate_zsh.<locals>.<genexpr>�s*����5�5�a��Q����5�5�5�5�5�5rrCc3�"K�|]
}d|�d�V��dSrUrr]s  rrZzgenerate_zsh.<locals>.<genexpr>�s*����:�:�!�X��X�X�X�:�:�:�:�:�:rrDrErF)rr
rrrGr1rHrI)rrr(�	func_namerJrKr"r0rrM�	desc_list�	flag_listrNrO�	root_descs               r�generate_zshre�s;��!��(�(�H�,�$�T�*�*�,�,�I��E�	�L�L�#�T�#�#�$�$�$�	�L�L�/��/�/�0�0�0�	�L�L�>��>�>�>�?�?�?�	�L�L�����	�L�L�I�$�$�$�%�%�%�	�L�L�.�/�/�/�	�L�L�%�&�&�&�	�L�L�����	�L�L�6�7�7�7�	�L�L�"�#�#�#�	�L�L�5�6�6�6�	�L�L�8�9�9�9�	�L�L�H�I�I�I�	�L�L�,�-�-�-�	�L�L�����	�L�L�����	�L�L�/�0�0�0��x�}�}���,B�,B�C�C�C�I��'�'���	����$�/�/�������(�(�4�.�.��
���,��,�,�,�-�-�-��	@���� 8� 8�4� 8� 8� 8�8�8�I��L�L�>�)�>�>�>�?�?�?��	=���� 9� 9�5� 9� 9� 9�9�9�I��L�L�;�y�;�;�;�<�<�<�
����
G�I�I�F�3�8�8�E�?�?�F�F�F�	
�	
�	
�
	���%�&�&�&�&�!��2�.�.�I����b�"�%�%�J����5�5�9�5�5�5�5�5�I�	�L�L�����	�L�L�6�)�6�6�6�7�7�7��9��H�H�:�:�z�:�:�:�:�:�	�
���7�9�7�7�7�8�8�8�	�L�L�N�O�O�O�	�L�L�!�"�"�"�	�L�L�����	�L�L�����	�L�L�����	�L�L�I�� � � �	�L�L������9�9�U���rc��t|��}g}|�d|����|�d|�d���|�d��t|���d����D�]�}t	||��}||}|s�dd�t	|d	������}|D]!}|�d
|�d|�d|�d
����"|D]{}	|	�d��r(|�d
|�d|�d|	dd��d
����?|	�d��r'|�d
|�d|�d|	dd��d
����|��g}
|D]}|
�d|�����d�|
��}|}|r|dd�|����z
}|D]!}|�d
|�d|�d|�d
����"|D]{}	|	�d��r(|�d
|�d|�d|	dd��d
����?|	�d��r'|�d
|�d|�d|	dd��d
����|���|�d��d�|��S)z"Generate a fish completion script.z# fish completion for r4z completions fishr5c�$�t|��|fSr8r9r:s rr<zgenerate_fish.<locals>.<lambda>�s��s�1�v�v�q�k�rr>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_fishrp�s���!��(�(�H��E�	�L�L�0�$�0�0�1�1�1�	�L�L�?��?�?�?�@�@�@�	�L�L������x�}�}���,A�,A�B�B�B�1�1����$�/�/�������-	�?��H�H�-�h��;�;�<�<�?�?�
��
�
�����H�4�H�H�i�H�H�#�H�H�H������
�
���?�?�4�(�(���L�L�N�t�N�N�)�N�N�4����8�N�N�N������_�_�S�)�)���L�L�N�t�N�N�)�N�N�4����8�N�N�N�����

��J��
F�
F���!�!�"D��"D�"D�E�E�E�E����J�/�/�I��J��
��/�����,�,�/�/��	�
�
�
�����H�4�H�H�i�H�H�#�H�H�H������
�
���?�?�4�(�(���L�L�N�t�N�N�)�N�N�4����8�N�N�N������_�_�S�)�)���L�L�N�t�N�N�)�N�N�4����8�N�N�N�����

�
�L�L������9�9�U���r)�bash�zsh�fish�shellc��t�|��}|�-td|�dd�t�������|||��S)zfGenerate completion script for the given shell.

    Raises ValueError if shell is not supported.
    NzUnsupported shell: z. Supported shells: z, )�
GENERATORSrI�
ValueErrorrH�SUPPORTED_SHELLS)rrtr�	generators    r�generate_completionsrzsl�����u�%�%�I����
?�%�
?�
?�!%���+;�!<�!<�
?�
?�
�
�	
��9�V�T�"�"�"r)r2)�__doc__rr
�typingrrr�strr
�ArgumentParserrr!rr1rQrerprvrrGrxrzrrr�<module>rs���������	�	�	�	�$�$�$�$�$�$�$�$�$�$�.�3�.�3�.�.�.�.�
��#��	�%��S��/�4��9�
$�%�����
�x�.�
�4��9�
�
�
�
�@��#�@�
��S��/�@�
��s�C�x��$�s�)�+�,�@�@�@�@�	��5��c��?�D��I�-�.�	��#�s�(�O�	�
�#�Y�	�	�	�	�2D�@�@��#�@�+.�@��@�@�@�@�H2D�>�>��#�>�+.�>��>�>�>�>�D2D�@�@��#�@�+.�@��@�@�@�@�H
�����
��6�*�/�/�+�+�,�,��#�#�#��#�#��#��#�	�	#�#�#�#�#�#rdefence360agent/utils/__pycache__/config.cpython-311.opt-1.pyc0000644000000000000000000000577300000000000021040 0ustar  �

�'F6a}���ddlZddlZddlmZddlmZmZddlmZej	j
dzZee��Z
dZdZd�Zd	�Zdd
�ZdS)�N)�	getLogger)�config�messages)�checkers��
KERNELCARE�edfc��t|�tt����vrt�d��dSdS)NzYConfiguration update with an obsolete kernelcare option 'edf'. This option has no effect.)�OBSOLETE_OPTION�get�OBSOLETE_SECTION�dict�logger�warning)�datas �Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/config.py�warn_obsolete_optionrsM���$�(�(�#3�T�V�V�<�<�<�<����
*�	
�	
�	
�	
�	
�=�<�c�|�|�d��}t|t��sdStj���tjjkrX|�d��durCtjjs4|�	dd��|s|�	dd��dSdSdSdSdS)N�	WORDPRESS�waf_enabledT)
r�
isinstancerr�caller_type�UserType�NON_ROOT�	Wordpress�WAF_DEFAULT�pop)r�	wordpresss  r�enforce_waf_optin_policyr s�������%�%�I��i��&�&�������� � �F�O�$<�<�<��M�M�-�(�(�D�0�0�� �,�
1�	�
�
�m�T�*�*�*��	(��H�H�[�$�'�'�'�'�'�
	=�<�0�0�0�0�	(�	(rc���K�t|��tj||��t|��t	j|��}|�|d���tj��}|�	tj|tj��|������d{V��tj
|���t����d{V��dS)NT)�without_defaults)�conf�	timestamp�event)�timeout)rr�config_validationr r�
ConfigFile�dict_to_config�asyncio�Event�process_messager�ConfigUpdate�time�wait_for�wait�CONFIG_UPDATE_TIMEOUT)�sinkr�userr#�updateds     r�
update_configr5&s������������t�T�*�*�*��T�"�"�"���T�"�"�D�����t��4�4�4��m�o�o�G�
�
�
���4�4�9�;�;�g�N�N�N�����������
�7�<�<�>�>�3H�
I�
I�
I�I�I�I�I�I�I�I�I�Ir)N)r*r.�loggingr�defence360agent.contractsrr�"defence360agent.feature_managementr�	SimpleRpc�CLIENT_TIMEOUTr1�__name__rr
rrr r5�rr�<module>r=s�����������������6�6�6�6�6�6�6�6�7�7�7�7�7�7��(�7�!�;��	��8�	�	������
�
�
�(�(�(�
J�
J�
J�
J�
J�
Jrdefence360agent/utils/__pycache__/config.cpython-311.pyc0000644000000000000000000000577300000000000020101 0ustar  �

�'F6a}���ddlZddlZddlmZddlmZmZddlmZej	j
dzZee��Z
dZdZd�Zd	�Zdd
�ZdS)�N)�	getLogger)�config�messages)�checkers��
KERNELCARE�edfc��t|�tt����vrt�d��dSdS)NzYConfiguration update with an obsolete kernelcare option 'edf'. This option has no effect.)�OBSOLETE_OPTION�get�OBSOLETE_SECTION�dict�logger�warning)�datas �Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/config.py�warn_obsolete_optionrsM���$�(�(�#3�T�V�V�<�<�<�<����
*�	
�	
�	
�	
�	
�=�<�c�|�|�d��}t|t��sdStj���tjjkrX|�d��durCtjjs4|�	dd��|s|�	dd��dSdSdSdSdS)N�	WORDPRESS�waf_enabledT)
r�
isinstancerr�caller_type�UserType�NON_ROOT�	Wordpress�WAF_DEFAULT�pop)r�	wordpresss  r�enforce_waf_optin_policyr s�������%�%�I��i��&�&�������� � �F�O�$<�<�<��M�M�-�(�(�D�0�0�� �,�
1�	�
�
�m�T�*�*�*��	(��H�H�[�$�'�'�'�'�'�
	=�<�0�0�0�0�	(�	(rc���K�t|��tj||��t|��t	j|��}|�|d���tj��}|�	tj|tj��|������d{V��tj
|���t����d{V��dS)NT)�without_defaults)�conf�	timestamp�event)�timeout)rr�config_validationr r�
ConfigFile�dict_to_config�asyncio�Event�process_messager�ConfigUpdate�time�wait_for�wait�CONFIG_UPDATE_TIMEOUT)�sinkr�userr#�updateds     r�
update_configr5&s������������t�T�*�*�*��T�"�"�"���T�"�"�D�����t��4�4�4��m�o�o�G�
�
�
���4�4�9�;�;�g�N�N�N�����������
�7�<�<�>�>�3H�
I�
I�
I�I�I�I�I�I�I�I�I�Ir)N)r*r.�loggingr�defence360agent.contractsrr�"defence360agent.feature_managementr�	SimpleRpc�CLIENT_TIMEOUTr1�__name__rr
rrr r5�rr�<module>r=s�����������������6�6�6�6�6�6�6�6�7�7�7�7�7�7��(�7�!�;��	��8�	�	������
�
�
�(�(�(�
J�
J�
J�
J�
J�
Jrdefence360agent/utils/__pycache__/cronjob.cpython-311.opt-1.pyc0000644000000000000000000000406600000000000021221 0ustar  �

���.9f��2�ddlmZmZGd�de��ZdS)�)�Union�Optionalc�r�eZdZdZdeeedfdeeedfdeefd�Zd�Z	e
d���ZdS)	�CronJob��minute�hour�cmdrNr	r
c�0�||_||_||_dS)Nr)�selfrr	r
s    �R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/cronjob.py�__init__zCronJob.__init__s�������	������c�8�d|j�d|j�d|j�d�S)Nz6# DO NOT EDIT. AUTOMATICALLY GENERATED BY IMUNIFY360.
� z * * * root �
r)rs r
�__str__zCronJob.__str__sF��
C���
C�
C�#�y�
C�
C�6:�h�
C�
C�
C�	
rc��dx}x}}d�|���D��}|rH|d�d��}|d}|d}d�|dd���}t|||���S)Nc�*�g|]}|ddk�|��S)r�#�)�.0�xs  r
�
<listcomp>z$CronJob.from_str.<locals>.<listcomp>s!��=�=�=�q��1��������rrr��r)�
splitlines�split�joinr)�cls�datarr	r
�lines�line_memberss       r
�from_strzCronJob.from_strs���"�"��"���=�=�D�O�O�-�-�=�=�=���	-� ��8�>�>�#�.�.�L�!�!�_�F���?�D��(�(�<����+�,�,�C��f�4�S�9�9�9�9r)�__name__�
__module__�__qualname__�	__slots__r�int�strrrr�classmethodr$rrr
rrs�������'�I�	��c�3��n�%�	��C��d�N�#�		�
�c�]�	�	�	�	�
�
�
��:�:��[�:�:�:rrN)�typingrr�objectrrrr
�<module>r.sQ��"�"�"�"�"�"�"�"�:�:�:�:�:�f�:�:�:�:�:rdefence360agent/utils/__pycache__/cronjob.cpython-311.pyc0000644000000000000000000000406600000000000020262 0ustar  �

���.9f��2�ddlmZmZGd�de��ZdS)�)�Union�Optionalc�r�eZdZdZdeeedfdeeedfdeefd�Zd�Z	e
d���ZdS)	�CronJob��minute�hour�cmdrNr	r
c�0�||_||_||_dS)Nr)�selfrr	r
s    �R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/cronjob.py�__init__zCronJob.__init__s�������	������c�8�d|j�d|j�d|j�d�S)Nz6# DO NOT EDIT. AUTOMATICALLY GENERATED BY IMUNIFY360.
� z * * * root �
r)rs r
�__str__zCronJob.__str__sF��
C���
C�
C�#�y�
C�
C�6:�h�
C�
C�
C�	
rc��dx}x}}d�|���D��}|rH|d�d��}|d}|d}d�|dd���}t|||���S)Nc�*�g|]}|ddk�|��S)r�#�)�.0�xs  r
�
<listcomp>z$CronJob.from_str.<locals>.<listcomp>s!��=�=�=�q��1��������rrr��r)�
splitlines�split�joinr)�cls�datarr	r
�lines�line_memberss       r
�from_strzCronJob.from_strs���"�"��"���=�=�D�O�O�-�-�=�=�=���	-� ��8�>�>�#�.�.�L�!�!�_�F���?�D��(�(�<����+�,�,�C��f�4�S�9�9�9�9r)�__name__�
__module__�__qualname__�	__slots__r�int�strrrr�classmethodr$rrr
rrs�������'�I�	��c�3��n�%�	��C��d�N�#�		�
�c�]�	�	�	�	�
�
�
��:�:��[�:�:�:rrN)�typingrr�objectrrrr
�<module>r.sQ��"�"�"�"�"�"�"�"�:�:�:�:�:�f�:�:�:�:�:rdefence360agent/utils/__pycache__/doctor.cpython-311.opt-1.pyc0000644000000000000000000002424300000000000021056 0ustar  �

���������ddlZddlZddlZddlZddlZddlZddlZddlm	Z	ddl
mZddlm
Z
ddlmZddlmZmZdZeje��ZdZd	ezZed
zZe	d��Ze	d��e	d
��fZdee	fd�Zdede	ddfd�Zd�Zde	ddfd�Z de	de!ddfd�Z"dede	ddfd�Z#dee	fd�Z$defd�Z%defd�Z&d�Z'dS)�N)�Path)�Optional)�	Packaging)�
save_state)�
CheckRunError�	check_run�zimunify-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.gpg�returnc��tD]F}|���r0tjt	|��tj��r|cS�GdS�N)�
_PUBKEY_PATHS�is_file�os�access�str�R_OK��ps �Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/doctor.py�_find_pubkeyrsK��
�����9�9�;�;�	�2�9�S��V�V�R�W�5�5�	��H�H�H���4��url�dstc�F�tj�|��}tj�|t���5}|�d��5}t
j||��ddd��n#1swxYwYddd��dS#1swxYwYdS)N)�timeout�wb)�urllib�request�Request�urlopen�
_HTTP_TIMEOUT�open�shutil�copyfileobj)rr�req�resp�fps     r�_blocking_downloadr)&s��
�.�
 �
 ��
%�
%�C�	��	�	��]�	�	;�	;�%�t�S�X�X��F�F�%�	���4��$�$�$�%�%�%�%�%�%�%�%�%�%�%����%�%�%�%�%�%�%�%�%�%�%�%�%�%�%�%����%�%�%�%�%�%s6�B�A>�2B�>B	�B�B	�B�B�Bc�`�t��}|�tjd��sdS	t�ddd���tt
jdtt�������}n9#t$r,}t�dt|��Yd}~dSd}~wwxYw	|���}tj|j��s5tj|j��r|jt%j��kr%tjt|��d�	��dS|d
z�d���nV#t$rI}t�d|��tjt|��d�	��Yd}~dSd}~wwxYw||fS)
z�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.
    N�gpg�T)�mode�parents�exist_okzimunify-doctor.)�prefix�dirz#cannot prepare workdir under %s: %s��
ignore_errors�gnupg)r-zworkdir setup failed: %s)rr$�which�_TMPDIR�mkdirr�tempfile�mkdtempr�OSError�logger�info�lstat�stat�S_ISLNK�st_mode�S_ISDIR�st_uidr�geteuid�rmtree)�pubkey�workdir�exc�sts    r�_blocking_setup_workdirrI.s����^�^�F�
�~�V�\�%�0�0�~��t���
�
�5�$��
�>�>�>����$5�3�w�<�<�H�H�H�
�
�����������9�7�C�H�H�H��t�t�t�t�t����������
�]�]�_�_���L���$�$�	��<��
�+�+�	��y�B�J�L�L�(�(��M�#�g�,�,�d�;�;�;�;��4�	�7�	�!�!�u�!�-�-�-�-���������.��4�4�4��
�c�'�l�l�$�7�7�7�7��t�t�t�t�t����������7�?�s7�AA;�;
B1�!B,�,B1�5BE�<E�
F)� >F$�$F)rc�L�tjt|��d���dS)NTr2)r$rDrrs r�_blocking_rmtreerKTs#��
�M�#�a�&�&��-�-�-�-�-�-rr-c�0�|�|��dSr
)�chmod)rr-s  r�_blocking_chmodrNXs���G�G�D�M�M�M�M�Mrc��vK�tj��}|�dt||���d{V��dS)z�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.
    N)�asyncio�get_event_loop�run_in_executorr))rr�loops   r�	_downloadrT\sI�����!�#�#�D�
�
�
�t�%7��c�
B�
B�B�B�B�B�B�B�B�B�Brc
���K�tj��}|�dt���d{V��}|�dS|\}}|tz}|tdzz}|dz}d}	tt|���d{V��tt|���d{V��ttj
t|�����}tdddd	t|��g|�
���d{V��tddddt|��t|��g|�
���d{V��|�dt|d���d{V��d
}||s#|�dt|���d{V��SS#tt f$rL}	t"�d|	��Yd}	~	|s$|�dt|���d{V��dSdSd}	~	wwxYw#|s#|�dt|���d{V��wwxYw)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_URL�dictr�environrrrNrKrr:r;r<)
rS�setuprErF�script�sig�gpghome�successrWrGs
          r�_verified_remote_scriptrbfs������!�#�#�D��&�&�t�-D�E�E�E�E�E�E�E�E�E��}��t��O�F�G�
�|�
#�F�
�\�F�*�
+�C����G��G�H���V�,�,�,�,�,�,�,�,�,���#�&�&�&�&�&�&�&�&�&��2�:��W���6�6�6���
�I�y�*�c�&�k�k�B��
�
�
�	
�	
�	
�	
�	
�	
�	
��
�I�y�*�c�#�h�h��F���L��
�
�
�	
�	
�	
�	
�	
�	
�	
��"�"�4��&�%�H�H�H�H�H�H�H�H�H����
�	H��&�&�t�-=�w�G�G�G�G�G�G�G�G�G�G�	H��	
�7�#�������;�S�A�A�A��t�t�t��	H��&�&�t�-=�w�G�G�G�G�G�G�G�G�G�G�G�	H�	H�����	������	H��&�&�t�-=�w�G�G�G�G�G�G�G�G�G�G�	H���s+�C$E)�)G�:G�G	�G�G	�	'G0c���K�t���d{V��}|�td���tj��}	t	t|��g���d{V��}|�dt|j���d{V��n,#|�dt|j���d{V��wxYw|�	���
��}|std���|S)Nz)Signed remote doctor script not availablezDoctor key is empty)rb�
ValueErrorrPrQrrrRrK�parent�decode�strip)r^rS�out�keys    r�_repo_get_doctor_keyrj�s
����*�,�,�
,�
,�
,�
,�
,�
,�F�
�~��D�E�E�E��!�#�#�D�J��s�6�{�{�m�,�,�,�,�,�,�,�,���"�"�4�)9�6�=�I�I�I�I�I�I�I�I�I�I��d�"�"�4�)9�6�=�I�I�I�I�I�I�I�I�I�I����

�*�*�,�,�
�
�
�
�C��0��.�/�/�/��Js�#B�)B0c��K�tj}t|�����sd}t	t|dt
��g���d{V��}|������}|S)Nz%/opt/imunify360/venv/share/imunify360�scripts)r�DATADIRr�is_dirrrXrfrg)�dir_rhris   r�_package_get_doctor_keyrp�sy������D���:�:�����7�6���4��i��>�>�?�@�@�
@�
@�
@�
@�
@�
@�C�

�*�*�,�,�
�
�
�
�C��Jrc��K�	t���d{V��}n1#tttf$rt	���d{V��}YnwxYwtdd|i��|S)N�
doctor_key)rjrrdr:rpr)ris r�get_doctor_keyrs�s�����.�(�*�*�*�*�*�*�*�*�����:�w�/�.�.�.�+�-�-�-�-�-�-�-�-����.�����|�l�C�0�1�1�1��Js��+A�A)(rP�loggingrr$r>r8�urllib.requestr�pathlibr�typingr� defence360agent.contracts.configr�'defence360agent.subsys.persistent_stater�defence360agent.utilsrrr"�	getLogger�__name__r;rXrYrZr6rrrr)rIrK�intrNrTrbrjrprs�rr�<module>rsV����������	�	�	�	�
�
�
�
�������������������������6�6�6�6�6�6�>�>�>�>�>�>�:�:�:�:�:�:�:�:��
�	��	�8�	$�	$��"��8�<�G������
�$�$�
%�
%���D�	:�;�;��D�	<�=�=��
��h�t�n�����%�C�%�d�%�t�%�%�%�%�#�#�#�L.��.��.�.�.�.��t��3��4�����C��C�4�C�D�C�C�C�C�&H�x��~�&H�&H�&H�&H�R�C������s���������rdefence360agent/utils/__pycache__/doctor.cpython-311.pyc0000644000000000000000000002424300000000000020117 0ustar  �

���������ddlZddlZddlZddlZddlZddlZddlZddlm	Z	ddl
mZddlm
Z
ddlmZddlmZmZdZeje��ZdZd	ezZed
zZe	d��Ze	d��e	d
��fZdee	fd�Zdede	ddfd�Zd�Zde	ddfd�Z de	de!ddfd�Z"dede	ddfd�Z#dee	fd�Z$defd�Z%defd�Z&d�Z'dS)�N)�Path)�Optional)�	Packaging)�
save_state)�
CheckRunError�	check_run�zimunify-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.gpg�returnc��tD]F}|���r0tjt	|��tj��r|cS�GdS�N)�
_PUBKEY_PATHS�is_file�os�access�str�R_OK��ps �Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/doctor.py�_find_pubkeyrsK��
�����9�9�;�;�	�2�9�S��V�V�R�W�5�5�	��H�H�H���4��url�dstc�F�tj�|��}tj�|t���5}|�d��5}t
j||��ddd��n#1swxYwYddd��dS#1swxYwYdS)N)�timeout�wb)�urllib�request�Request�urlopen�
_HTTP_TIMEOUT�open�shutil�copyfileobj)rr�req�resp�fps     r�_blocking_downloadr)&s��
�.�
 �
 ��
%�
%�C�	��	�	��]�	�	;�	;�%�t�S�X�X��F�F�%�	���4��$�$�$�%�%�%�%�%�%�%�%�%�%�%����%�%�%�%�%�%�%�%�%�%�%�%�%�%�%�%����%�%�%�%�%�%s6�B�A>�2B�>B	�B�B	�B�B�Bc�`�t��}|�tjd��sdS	t�ddd���tt
jdtt�������}n9#t$r,}t�dt|��Yd}~dSd}~wwxYw	|���}tj|j��s5tj|j��r|jt%j��kr%tjt|��d�	��dS|d
z�d���nV#t$rI}t�d|��tjt|��d�	��Yd}~dSd}~wwxYw||fS)
z�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.
    N�gpg�T)�mode�parents�exist_okzimunify-doctor.)�prefix�dirz#cannot prepare workdir under %s: %s��
ignore_errors�gnupg)r-zworkdir setup failed: %s)rr$�which�_TMPDIR�mkdirr�tempfile�mkdtempr�OSError�logger�info�lstat�stat�S_ISLNK�st_mode�S_ISDIR�st_uidr�geteuid�rmtree)�pubkey�workdir�exc�sts    r�_blocking_setup_workdirrI.s����^�^�F�
�~�V�\�%�0�0�~��t���
�
�5�$��
�>�>�>����$5�3�w�<�<�H�H�H�
�
�����������9�7�C�H�H�H��t�t�t�t�t����������
�]�]�_�_���L���$�$�	��<��
�+�+�	��y�B�J�L�L�(�(��M�#�g�,�,�d�;�;�;�;��4�	�7�	�!�!�u�!�-�-�-�-���������.��4�4�4��
�c�'�l�l�$�7�7�7�7��t�t�t�t�t����������7�?�s7�AA;�;
B1�!B,�,B1�5BE�<E�
F)� >F$�$F)rc�L�tjt|��d���dS)NTr2)r$rDrrs r�_blocking_rmtreerKTs#��
�M�#�a�&�&��-�-�-�-�-�-rr-c�0�|�|��dSr
)�chmod)rr-s  r�_blocking_chmodrNXs���G�G�D�M�M�M�M�Mrc��vK�tj��}|�dt||���d{V��dS)z�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.
    N)�asyncio�get_event_loop�run_in_executorr))rr�loops   r�	_downloadrT\sI�����!�#�#�D�
�
�
�t�%7��c�
B�
B�B�B�B�B�B�B�B�B�Brc
���K�tj��}|�dt���d{V��}|�dS|\}}|tz}|tdzz}|dz}d}	tt|���d{V��tt|���d{V��ttj
t|�����}tdddd	t|��g|�
���d{V��tddddt|��t|��g|�
���d{V��|�dt|d���d{V��d
}||s#|�dt|���d{V��SS#tt f$rL}	t"�d|	��Yd}	~	|s$|�dt|���d{V��dSdSd}	~	wwxYw#|s#|�dt|���d{V��wwxYw)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_URL�dictr�environrrrNrKrr:r;r<)
rS�setuprErF�script�sig�gpghome�successrWrGs
          r�_verified_remote_scriptrbfs������!�#�#�D��&�&�t�-D�E�E�E�E�E�E�E�E�E��}��t��O�F�G�
�|�
#�F�
�\�F�*�
+�C����G��G�H���V�,�,�,�,�,�,�,�,�,���#�&�&�&�&�&�&�&�&�&��2�:��W���6�6�6���
�I�y�*�c�&�k�k�B��
�
�
�	
�	
�	
�	
�	
�	
�	
��
�I�y�*�c�#�h�h��F���L��
�
�
�	
�	
�	
�	
�	
�	
�	
��"�"�4��&�%�H�H�H�H�H�H�H�H�H����
�	H��&�&�t�-=�w�G�G�G�G�G�G�G�G�G�G�	H��	
�7�#�������;�S�A�A�A��t�t�t��	H��&�&�t�-=�w�G�G�G�G�G�G�G�G�G�G�G�	H�	H�����	������	H��&�&�t�-=�w�G�G�G�G�G�G�G�G�G�G�	H���s+�C$E)�)G�:G�G	�G�G	�	'G0c���K�t���d{V��}|�td���tj��}	t	t|��g���d{V��}|�dt|j���d{V��n,#|�dt|j���d{V��wxYw|�	���
��}|std���|S)Nz)Signed remote doctor script not availablezDoctor key is empty)rb�
ValueErrorrPrQrrrRrK�parent�decode�strip)r^rS�out�keys    r�_repo_get_doctor_keyrj�s
����*�,�,�
,�
,�
,�
,�
,�
,�F�
�~��D�E�E�E��!�#�#�D�J��s�6�{�{�m�,�,�,�,�,�,�,�,���"�"�4�)9�6�=�I�I�I�I�I�I�I�I�I�I��d�"�"�4�)9�6�=�I�I�I�I�I�I�I�I�I�I����

�*�*�,�,�
�
�
�
�C��0��.�/�/�/��Js�#B�)B0c��K�tj}t|�����sd}t	t|dt
��g���d{V��}|������}|S)Nz%/opt/imunify360/venv/share/imunify360�scripts)r�DATADIRr�is_dirrrXrfrg)�dir_rhris   r�_package_get_doctor_keyrp�sy������D���:�:�����7�6���4��i��>�>�?�@�@�
@�
@�
@�
@�
@�
@�C�

�*�*�,�,�
�
�
�
�C��Jrc��K�	t���d{V��}n1#tttf$rt	���d{V��}YnwxYwtdd|i��|S)N�
doctor_key)rjrrdr:rpr)ris r�get_doctor_keyrs�s�����.�(�*�*�*�*�*�*�*�*�����:�w�/�.�.�.�+�-�-�-�-�-�-�-�-����.�����|�l�C�0�1�1�1��Js��+A�A)(rP�loggingrr$r>r8�urllib.requestr�pathlibr�typingr� defence360agent.contracts.configr�'defence360agent.subsys.persistent_stater�defence360agent.utilsrrr"�	getLogger�__name__r;rXrYrZr6rrrr)rIrK�intrNrTrbrjrprs�rr�<module>rsV����������	�	�	�	�
�
�
�
�������������������������6�6�6�6�6�6�>�>�>�>�>�>�:�:�:�:�:�:�:�:��
�	��	�8�	$�	$��"��8�<�G������
�$�$�
%�
%���D�	:�;�;��D�	<�=�=��
��h�t�n�����%�C�%�d�%�t�%�%�%�%�#�#�#�L.��.��.�.�.�.��t��3��4�����C��C�4�C�D�C�C�C�C�&H�x��~�&H�&H�&H�&H�R�C������s���������rdefence360agent/utils/__pycache__/fd_ops.cpython-311.opt-1.pyc0000644000000000000000000002430600000000000021036 0ustar  �

��*�6j������dZddlZddlZddlZddlZddlmZmZddlm	Z	ej
e��Zdd�Z
defd�Zeejfdd�d	���Zed
���Zded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)�contextmanager�suppress)�Path�returnc��|dfg}	|�r$|d\}}d}tj|��5}|D]�}|�d���r`tj|jtjtjztjz|���}|�||jf��d}ntj	|j|�����ddd��n#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.
    N���F)�follow_symlinks��dir_fdT)�os�scandir�is_dir�open�name�O_RDONLY�O_DIRECTORY�
O_NOFOLLOW�append�unlink�pop�close�rmdir�
BaseException)r�stack�
current_fd�_�pushed�entries�entry�child_fd�fdr�	parent_fds           �Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/fd_ops.py�	rmtree_fdr$s���"�d�^��E���	5�!�"�I�M�J���F���J�'�'�
A�7�$�A�A�E��|�|�E�|�:�:�
A�#%�7�!�J��K�"�.�8�2�=�H�#-�$�$�$��
���h��
�%;�<�<�<�!%�����	�%�*�Z�@�@�@�@�@�
A�
A�
A�
A�
A�
A�
A�
A�
A�
A�
A����
A�
A�
A�
A��
5� �9�9�;�;���D��#��H�R�L�L�L�#(��9�L�I�q��H�T�)�4�4�4�4�/�	5�	5�	5�	5�	5��0�����	�	�H�B�����������
����s0�$D0�BC�D0�C�D0�C�AD0�0*Ec��tj�tj|����}t	|��j}tj|dtjtjz��}	|dd�D]S}tj|tjtjztj	z|���}tj
|��|}�T|S#t$rtj
|���wxYw)a�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.
    r�Nr
)r�path�abspath�fspathr�partsrrrrrr)r'r*r!�part�new_fds     r#�open_dir_no_symlinksr-Hs����7�?�?�2�9�T�?�?�+�+�D���J�J��E�	���q��2�;���7�	8�	8�B���!�"�"�I�	�	�D��W����b�n�,�r�}�<�����F�

�H�R�L�L�L��B�B��	������
������
����s
�9AC� C9r
c#��K�|�d|ini}tjt|��|tjzfi|��}	|V�tj|��dS#tj|��wxYw)z�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.
    Nr)rr�strrr)r'�flagsr�kwr!s     r#�
open_nofollowr2ess���� &�1�(�F�	�	�r�B�	���T���E�B�M�1�	8�	8�R�	8�	8�B������
������������������s�A�A)c#�K�t|��}	|V�tj|��dS#tj|��wxYw)z�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.
    N)r-rr)r'r!s  r#�safe_dirr4wsF����
�d�	#�	#�B������
������������������s	�-�A�datarc��tj�|��\}}	tj|tjtjz|���}	tj|	d��5}
|
�t|��dz��}ddd��n#1swxYwY||krdSn9#t$rYn-t$r!}|jtjkrn�Yd}~nd}~wwxYw|s |st�d||��dS|��	tj||d���}
tj|
j��r7ttjtjtj��|���tj|
j��}n>#t$r1tjd��}tj|��d	|z}YnwxYwd}d
}t+d��D]�}|�dtjd
������d�}	tj|tjtjztjztjzd|���}n#t6$rY��wxYwt7d���	t9|��}d}|t|��kr3|tj|||d���z
}|t|��k�3|�|�tj|||��tj||��tj |��tj!|��d
}tj"||||���d}|dkrtj!|��|�BtGt��5tj$||���ddd��n#1swxYwYnd#|dkrtj!|��|�CtGt��5tj$||���ddd��w#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)rr	ri�r�dr�z	.i360editi�z.Could not create temporary file (100 attempts))�
src_dir_fd�
dst_dir_fdT)%rr'�splitrrr�fdopen�read�len�FileNotFoundError�OSError�errno�ELOOP�logger�error�stat�S_ISLNK�st_mode�strerror�S_IMODE�umask�range�urandom�hex�O_WRONLY�O_CREAT�O_EXCL�FileExistsError�
memoryview�write�chown�chmod�fsyncr�renamerr)�filenamer5�uid�gid�allow_empty_content�permissionsrr�basename�
content_fd�f�old_content�exc�st�
current_umask�tmp_basename�tmp_fd�view�writtens                   r#�atomic_rewrite_fdri�s���"�'�-�-��)�)�K�A�x���W��b�k�B�M�1�&�
�
�
�
��Y�z�4�
(�
(�	0�A��&�&��T���Q��/�/�K�	0�	0�	0�	0�	0�	0�	0�	0�	0�	0�	0����	0�	0�	0�	0��$����5����
�
�
��������9���#�#���
�D�D�D�D�����������t�����5�t�X�F�F�F��u���	1����&�%�H�H�H�B��|�B�J�'�'�
O��e�k�2�;�u�{�+C�+C�X�N�N�N��,�r�z�2�2�K�K�� �	1�	1�	1��H�Q�K�K�M��H�]�#�#�#��=�.�0�K�K�K�	1�����L�
�F�
�3�Z�Z�
P�
P��"�C�C�R�Z��]�]�%6�%6�%8�%8�C�C�C��		��W����b�j�(�2�9�4�r�}�D���	���F�
�E���	�	�	��H�	�����N�O�O�O�7��$��������D�	�	�!�!��r�x���W�X�X��7�7�7�G���D�	�	�!�!��?�s���H�V�S�#�&�&�&�
����%�%�%�
������
��������
�	�,��V��O�O�O�O����Q�;�;��H�V�����#��+�,�,�
7�
7��	�,�v�6�6�6�6�
7�
7�
7�
7�
7�
7�
7�
7�
7�
7�
7����
7�
7�
7�
7����Q�;�;��H�V�����#��+�,�,�
7�
7��	�,�v�6�6�6�6�
7�
7�
7�
7�
7�
7�
7�
7�
7�
7�
7����
7�
7�
7�
7�
7�$�����4s��AB-�'&B�
B-�B�B-� B�!	B-�-
C#�9	C#�C�C#�BF�8G�G�A	I�
I$�#I$�7C
N �1N�N�N� 1P�O4�(P�4O8�8P�;O8�<P)rN)�__doc__rB�loggingrrF�
contextlibrr�pathlibr�	getLogger�__name__rDr$�intr-rr2r4�bytes�boolri��r#�<module>rus0����
��������	�	�	�	�����/�/�/�/�/�/�/�/�������	��	�8�	$�	$��0�0�0�0�f�#�����:� �k��T��������"�
�
���
�[�
�[�
�[�
�[�[�[�[�[�[rtdefence360agent/utils/__pycache__/fd_ops.cpython-311.pyc0000644000000000000000000002430600000000000020077 0ustar  �

��*�6j������dZddlZddlZddlZddlZddlmZmZddlm	Z	ej
e��Zdd�Z
defd�Zeejfdd�d	���Zed
���Zded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)�contextmanager�suppress)�Path�returnc��|dfg}	|�r$|d\}}d}tj|��5}|D]�}|�d���r`tj|jtjtjztjz|���}|�||jf��d}ntj	|j|�����ddd��n#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.
    N���F)�follow_symlinks��dir_fdT)�os�scandir�is_dir�open�name�O_RDONLY�O_DIRECTORY�
O_NOFOLLOW�append�unlink�pop�close�rmdir�
BaseException)r�stack�
current_fd�_�pushed�entries�entry�child_fd�fdr�	parent_fds           �Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/fd_ops.py�	rmtree_fdr$s���"�d�^��E���	5�!�"�I�M�J���F���J�'�'�
A�7�$�A�A�E��|�|�E�|�:�:�
A�#%�7�!�J��K�"�.�8�2�=�H�#-�$�$�$��
���h��
�%;�<�<�<�!%�����	�%�*�Z�@�@�@�@�@�
A�
A�
A�
A�
A�
A�
A�
A�
A�
A�
A����
A�
A�
A�
A��
5� �9�9�;�;���D��#��H�R�L�L�L�#(��9�L�I�q��H�T�)�4�4�4�4�/�	5�	5�	5�	5�	5��0�����	�	�H�B�����������
����s0�$D0�BC�D0�C�D0�C�AD0�0*Ec��tj�tj|����}t	|��j}tj|dtjtjz��}	|dd�D]S}tj|tjtjztj	z|���}tj
|��|}�T|S#t$rtj
|���wxYw)a�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.
    r�Nr
)r�path�abspath�fspathr�partsrrrrrr)r'r*r!�part�new_fds     r#�open_dir_no_symlinksr-Hs����7�?�?�2�9�T�?�?�+�+�D���J�J��E�	���q��2�;���7�	8�	8�B���!�"�"�I�	�	�D��W����b�n�,�r�}�<�����F�

�H�R�L�L�L��B�B��	������
������
����s
�9AC� C9r
c#��K�|�d|ini}tjt|��|tjzfi|��}	|V�tj|��dS#tj|��wxYw)z�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.
    Nr)rr�strrr)r'�flagsr�kwr!s     r#�
open_nofollowr2ess���� &�1�(�F�	�	�r�B�	���T���E�B�M�1�	8�	8�R�	8�	8�B������
������������������s�A�A)c#�K�t|��}	|V�tj|��dS#tj|��wxYw)z�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.
    N)r-rr)r'r!s  r#�safe_dirr4wsF����
�d�	#�	#�B������
������������������s	�-�A�datarc��tj�|��\}}	tj|tjtjz|���}	tj|	d��5}
|
�t|��dz��}ddd��n#1swxYwY||krdSn9#t$rYn-t$r!}|jtjkrn�Yd}~nd}~wwxYw|s |st�d||��dS|��	tj||d���}
tj|
j��r7ttjtjtj��|���tj|
j��}n>#t$r1tjd��}tj|��d	|z}YnwxYwd}d
}t+d��D]�}|�dtjd
������d�}	tj|tjtjztjztjzd|���}n#t6$rY��wxYwt7d���	t9|��}d}|t|��kr3|tj|||d���z
}|t|��k�3|�|�tj|||��tj||��tj |��tj!|��d
}tj"||||���d}|dkrtj!|��|�BtGt��5tj$||���ddd��n#1swxYwYnd#|dkrtj!|��|�CtGt��5tj$||���ddd��w#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)rr	ri�r�dr�z	.i360editi�z.Could not create temporary file (100 attempts))�
src_dir_fd�
dst_dir_fdT)%rr'�splitrrr�fdopen�read�len�FileNotFoundError�OSError�errno�ELOOP�logger�error�stat�S_ISLNK�st_mode�strerror�S_IMODE�umask�range�urandom�hex�O_WRONLY�O_CREAT�O_EXCL�FileExistsError�
memoryview�write�chown�chmod�fsyncr�renamerr)�filenamer5�uid�gid�allow_empty_content�permissionsrr�basename�
content_fd�f�old_content�exc�st�
current_umask�tmp_basename�tmp_fd�view�writtens                   r#�atomic_rewrite_fdri�s���"�'�-�-��)�)�K�A�x���W��b�k�B�M�1�&�
�
�
�
��Y�z�4�
(�
(�	0�A��&�&��T���Q��/�/�K�	0�	0�	0�	0�	0�	0�	0�	0�	0�	0�	0����	0�	0�	0�	0��$����5����
�
�
��������9���#�#���
�D�D�D�D�����������t�����5�t�X�F�F�F��u���	1����&�%�H�H�H�B��|�B�J�'�'�
O��e�k�2�;�u�{�+C�+C�X�N�N�N��,�r�z�2�2�K�K�� �	1�	1�	1��H�Q�K�K�M��H�]�#�#�#��=�.�0�K�K�K�	1�����L�
�F�
�3�Z�Z�
P�
P��"�C�C�R�Z��]�]�%6�%6�%8�%8�C�C�C��		��W����b�j�(�2�9�4�r�}�D���	���F�
�E���	�	�	��H�	�����N�O�O�O�7��$��������D�	�	�!�!��r�x���W�X�X��7�7�7�G���D�	�	�!�!��?�s���H�V�S�#�&�&�&�
����%�%�%�
������
��������
�	�,��V��O�O�O�O����Q�;�;��H�V�����#��+�,�,�
7�
7��	�,�v�6�6�6�6�
7�
7�
7�
7�
7�
7�
7�
7�
7�
7�
7����
7�
7�
7�
7����Q�;�;��H�V�����#��+�,�,�
7�
7��	�,�v�6�6�6�6�
7�
7�
7�
7�
7�
7�
7�
7�
7�
7�
7����
7�
7�
7�
7�
7�$�����4s��AB-�'&B�
B-�B�B-� B�!	B-�-
C#�9	C#�C�C#�BF�8G�G�A	I�
I$�#I$�7C
N �1N�N�N� 1P�O4�(P�4O8�8P�;O8�<P)rN)�__doc__rB�loggingrrF�
contextlibrr�pathlibr�	getLogger�__name__rDr$�intr-rr2r4�bytes�boolri��r#�<module>rus0����
��������	�	�	�	�����/�/�/�/�/�/�/�/�������	��	�8�	$�	$��0�0�0�0�f�#�����:� �k��T��������"�
�
���
�[�
�[�
�[�
�[�[�[�[�[�[rtdefence360agent/utils/__pycache__/hyperscan.cpython-311.opt-1.pyc0000644000000000000000000000134700000000000021560 0ustar  �

C�J�܋	��B�ddlZejd���d���ZdS)�N�)�maxsizec��td��5}d|���vcddd��S#1swxYwYdS)Nz
/proc/cpuinfo�ssse3)�open�read)�fs �T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/hyperscan.py�is_ssse3_supportedrs���	
�o�	�	�#�!��!�&�&�(�(�"�#�#�#�#�#�#�#�#�#�#�#�#����#�#�#�#�#�#s�3�7�7)�	functools�	lru_cacher��r
�<module>rsG���������Q����#�#� ��#�#�#rdefence360agent/utils/__pycache__/hyperscan.cpython-311.pyc0000644000000000000000000000134700000000000020621 0ustar  �

C�J�܋	��B�ddlZejd���d���ZdS)�N�)�maxsizec��td��5}d|���vcddd��S#1swxYwYdS)Nz
/proc/cpuinfo�ssse3)�open�read)�fs �T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/hyperscan.py�is_ssse3_supportedrs���	
�o�	�	�#�!��!�&�&�(�(�"�#�#�#�#�#�#�#�#�#�#�#�#����#�#�#�#�#�#s�3�7�7)�	functools�	lru_cacher��r
�<module>rsG���������Q����#�#� ��#�#�#rdefence360agent/utils/__pycache__/importer.cpython-311.opt-1.pyc0000644000000000000000000001165000000000000021423 0ustar  �

��5eh������dZddlZddlZddlZddlZddlmZddlmZm	Z	m
Z
eje��Z
dede
eefddfd	�Zd
e	e
eefdedfd�Zddeddfd�Zddeddfd�Zd�Zd�ZGd�d��ZdS)z>
Provides utilities for dynamically loading packages/modules.
�N)�Path)�	Generator�List�Union�module_name�	file_path�return�modulec��tj�||��}tj�|��}|j�|��|S)z4
    Execute and return module from *file_path*
    )�	importlib�util�spec_from_file_location�module_from_spec�loader�exec_module)rr�specr
s    �S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/importer.py�get_module_by_pathrsK���>�1�1�+�y�I�I�D�
�^�
,�
,�T�
2�
2�F��K���F�#�#�#��M��paths)r
NNc#�K�tj|��D]D}|js;t|jj��|j�d�z}t|j|��V��EdS)z)
    Yields all modules from *paths*
    z.pyN)�pkgutil�iter_modules�ispkgr�
module_finder�path�namer)rr
rs   rrrsr�����&�u�-�-�8�8���|�	8���,�1�2�2���5H�5H�5H�H�D�$�V�[�$�7�7�7�7�7��8�8rFrc�R�	tj�|��}n#t$r|s�YdSwxYwtj|��|j�|j��r<|}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_spec�ModuleNotFoundError�
import_moduler�
is_packagerrr�submodule_search_locations)r�
missing_okr�packager
s     r�loadr''s�����~�'�'��-�-���������	���������
��D�!�!�!��{���d�i�(�(�@����*�4�+J�K�K�	@�	@�F��#�w�$>�$>���$>�$>�?�?�?�?�@�@�	@�	@s�"�
3�3�packagesc�2�|D]}t||����dS)N)r%)r')r(r%r&s   r�
load_packagesr*<s1���-�-���W��,�,�,�,�,�-�-rc�t�	tj|��}n#t$r|cYSwxYwt|||��S)zh
    Return object with *name* from specific *module*.
    If object was not found return *default*
    )rr"�ImportError�getattr)r
r�default�ms    r�getr0AsO��
��#�F�+�+�����������������1�d�G�$�$�$s��&�&c�n�	tj�|��}n#t$rYdSwxYw|duS)NF)rr
r r!)rrs  r�existsr2MsK����~�'�'��-�-���������u�u������t��s�"�
0�0c�6�eZdZdefd�Zed���Zd�ZdS)�
LazyImportrc�"�||_d|_dS�N)�_module_name�_module)�selfrs  r�__init__zLazyImport.__init__Vs��'�������rc�Z�|j�tj|j��|_|jSr6)r8rr"r7)r9s rr
zLazyImport.moduleZs'���<��$�2�4�3D�E�E�D�L��|�rc�,�t|j|��Sr6)r-r
)r9�attrs  r�__getattr__zLazyImport.__getattr__`s���t�{�D�)�)�)rN)�__name__�
__module__�__qualname__�strr:�propertyr
r>�rrr4r4UsY�������C���������X��
*�*�*�*�*rr4)F)�__doc__r�importlib.util�loggingr�pathlibr�typingrrr�	getLoggerr?�loggerrBrrr'�tupler*r0r2r4rDrr�<module>rMs���������������������������)�)�)�)�)�)�)�)�)�)�	��	�8�	$�	$��
��
�!&�s�D�y�!1�
�
�
�
�
�
�	8���c�4�i� �!�	8��#�$�	8�	8�	8�	8�@�@�s�@��@�@�@�@�*-�-�E�-��-�-�-�-�
	%�	%�	%����*�*�*�*�*�*�*�*�*�*rdefence360agent/utils/__pycache__/importer.cpython-311.pyc0000644000000000000000000001165000000000000020464 0ustar  �

��5eh������dZddlZddlZddlZddlZddlmZddlmZm	Z	m
Z
eje��Z
dede
eefddfd	�Zd
e	e
eefdedfd�Zddeddfd�Zddeddfd�Zd�Zd�ZGd�d��ZdS)z>
Provides utilities for dynamically loading packages/modules.
�N)�Path)�	Generator�List�Union�module_name�	file_path�return�modulec��tj�||��}tj�|��}|j�|��|S)z4
    Execute and return module from *file_path*
    )�	importlib�util�spec_from_file_location�module_from_spec�loader�exec_module)rr�specr
s    �S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/importer.py�get_module_by_pathrsK���>�1�1�+�y�I�I�D�
�^�
,�
,�T�
2�
2�F��K���F�#�#�#��M��paths)r
NNc#�K�tj|��D]D}|js;t|jj��|j�d�z}t|j|��V��EdS)z)
    Yields all modules from *paths*
    z.pyN)�pkgutil�iter_modules�ispkgr�
module_finder�path�namer)rr
rs   rrrsr�����&�u�-�-�8�8���|�	8���,�1�2�2���5H�5H�5H�H�D�$�V�[�$�7�7�7�7�7��8�8rFrc�R�	tj�|��}n#t$r|s�YdSwxYwtj|��|j�|j��r<|}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_spec�ModuleNotFoundError�
import_moduler�
is_packagerrr�submodule_search_locations)r�
missing_okr�packager
s     r�loadr''s�����~�'�'��-�-���������	���������
��D�!�!�!��{���d�i�(�(�@����*�4�+J�K�K�	@�	@�F��#�w�$>�$>���$>�$>�?�?�?�?�@�@�	@�	@s�"�
3�3�packagesc�2�|D]}t||����dS)N)r%)r')r(r%r&s   r�
load_packagesr*<s1���-�-���W��,�,�,�,�,�-�-rc�t�	tj|��}n#t$r|cYSwxYwt|||��S)zh
    Return object with *name* from specific *module*.
    If object was not found return *default*
    )rr"�ImportError�getattr)r
r�default�ms    r�getr0AsO��
��#�F�+�+�����������������1�d�G�$�$�$s��&�&c�n�	tj�|��}n#t$rYdSwxYw|duS)NF)rr
r r!)rrs  r�existsr2MsK����~�'�'��-�-���������u�u������t��s�"�
0�0c�6�eZdZdefd�Zed���Zd�ZdS)�
LazyImportrc�"�||_d|_dS�N)�_module_name�_module)�selfrs  r�__init__zLazyImport.__init__Vs��'�������rc�Z�|j�tj|j��|_|jSr6)r8rr"r7)r9s rr
zLazyImport.moduleZs'���<��$�2�4�3D�E�E�D�L��|�rc�,�t|j|��Sr6)r-r
)r9�attrs  r�__getattr__zLazyImport.__getattr__`s���t�{�D�)�)�)rN)�__name__�
__module__�__qualname__�strr:�propertyr
r>�rrr4r4UsY�������C���������X��
*�*�*�*�*rr4)F)�__doc__r�importlib.util�loggingr�pathlibr�typingrrr�	getLoggerr?�loggerrBrrr'�tupler*r0r2r4rDrr�<module>rMs���������������������������)�)�)�)�)�)�)�)�)�)�	��	�8�	$�	$��
��
�!&�s�D�y�!1�
�
�
�
�
�
�	8���c�4�i� �!�	8��#�$�	8�	8�	8�	8�@�@�s�@��@�@�@�@�*-�-�E�-��-�-�-�-�
	%�	%�	%����*�*�*�*�*�*�*�*�*�*rdefence360agent/utils/__pycache__/ipecho.cpython-311.opt-1.pyc0000644000000000000000000001312500000000000021030 0ustar  �

�9v�?_E����dZddlZddlZddlZddlZddlZddlmZddlm	Z	ddl
mZddlm
Z
mZddlmZddlmZmZeje��Zd	Zd
Zed��dzZGd
�de
��ZdS)z<IPEchoAPI - returns real IP address of the host (behind NAT)�N)�Path)�Optional)�
alru_cache)�API�APIError)�atomic_rewrite)�IP�	IPVersion�i0*z/var/imunify360�ipecho_cachec�:�eZdZdZdZeed���ddedee	fd�����Z
eejd	���d
�����Z
e	ddedee	fd���Zedee	fd���Zed
e	ddfd���Zed���ZdS)�	IPEchoAPIz2Make requests to the API for obtain own IP addressz/api/ip�)�maxsizeN�
ip_version�returnc��<K�|�|���d{V��S�z5Return cached result for resolved IP from echo ip APIN)�ip_for_version)�clsrs  �Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/ipecho.py�get_ipzIPEchoAPI.get_ips.����
�'�'�
�3�3�3�3�3�3�3�3�3��c�`�	|���S#t$r
}t|�d}~wwxYwr)�_get_ip�	Exceptionr)r�es  r�	server_ipzIPEchoAPI.server_ip$s<��	"��;�;�=�=� ���	"�	"�	"���!�����	"���s��
-�(�-c��@K�tj��}	tj|�d|j��t
����d{V��}t
j|��|krtd���|S#tj	tf$r
}t|�d}~wwxYw)z#Return resolved IP from echo ip APIN)�timeoutz
Wrong ip type)�asyncio�get_event_loop�wait_for�run_in_executorr�TIMEOUT_FOR_IPECHO_REQUESTr	�type_of�
ValueError�TimeoutErrorr)rr�loop�iprs     rrzIPEchoAPI.ip_for_version-s������%�'�'��		"��'��$�$�T�3�;�7�7�2����������B��z�"�~�~��+�+� ��1�1�1��I���$�j�1�	"�	"�	"���!�����	"���s�A"A:�:B�B�Bc��	t���sdSt���j}t	j��|z
}|dkrdS|t
kr-t������}|SdS#t$r&}t�
d|��Yd}~dSd}~wwxYw)NrzIPEchoAPI cache read error: %s)�CACHE_FILE_PATH�exists�stat�st_mtime�time�CACHE_TTL_SECONDS�	read_text�stripr�logger�error)r�mtime�	cache_ager+rs     r�_load_cachezIPEchoAPI._load_cache?s���	�"�)�)�+�+�
��t�#�(�(�*�*�3�E��	���e�+�I��1�}�}��t��,�,�,�$�.�.�0�0�6�6�8�8���	��t���	�	�	��L�L�9�1�=�=�=��4�4�4�4�4�����	���s"�B�:B�7B�
C�B>�>Cr+c��	tt|dd���dS#t$r&}t�d|��Yd}~dSd}~wwxYw)NFi�)�backup�permissionszIPEchoAPI cache write error: %s)rr-rr5r6)rr+rs   r�_save_cachezIPEchoAPI._save_cacheTs|��	?�����!�	
�
�
�
�
�
���	?�	?�	?��L�L�:�A�>�>�>�>�>�>�>�>�>�����	?���s��
A�A�Ac�`�|���}|�|Stj�|j|jz��}|�|��}|�d��dkrtd���|�d��}|r|�|��|S)zIGet IP from file-based cache or send request to API and process response.N�status�okzUnexpected API errorr+)	r9�urllib�request�Request�	_BASE_URL�URL�getrr=)r�	cached_iprB�responser+s     rrzIPEchoAPI._get_ip`s����O�O�%�%�	�� ����.�(�(�����)@�A�A���;�;�w�'�'���<�<��!�!�T�)�)��1�2�2�2�
�\�\�$�
�
��
�	 ��O�O�B�����	r)N)�__name__�
__module__�__qualname__�__doc__rE�classmethodrr
r�strr�	functools�	lru_cacherrr9r=r�rrrrsR������<�<�
�C���Z�����4�4�i�4�8�C�=�4�4�4����[�4�
��Y���#�#�#�"�"�$�#��[�"��%)�"�"�"�"�	�#��"�"�"��[�"�"��H�S�M�����[��(�	?�S�	?�T�	?�	?�	?��[�	?�����[���rr)rLr"rO�loggingr1rA�pathlibr�typingr�	async_lrur�defence360agent.api.serverrr�defence360agent.utilsr�defence360agent.utils.validater	r
�	getLoggerrIr5r&r2r-rrQrr�<module>rZs$��B�B�����������������
�
�
�
������������� � � � � � �4�4�4�4�4�4�4�4�0�0�0�0�0�0�8�8�8�8�8�8�8�8�	��	�8�	$�	$�������$�(�)�)�N�:��X�X�X�X�X��X�X�X�X�Xrdefence360agent/utils/__pycache__/ipecho.cpython-311.pyc0000644000000000000000000001312500000000000020071 0ustar  �

�9v�?_E����dZddlZddlZddlZddlZddlZddlmZddlm	Z	ddl
mZddlm
Z
mZddlmZddlmZmZeje��Zd	Zd
Zed��dzZGd
�de
��ZdS)z<IPEchoAPI - returns real IP address of the host (behind NAT)�N)�Path)�Optional)�
alru_cache)�API�APIError)�atomic_rewrite)�IP�	IPVersion�i0*z/var/imunify360�ipecho_cachec�:�eZdZdZdZeed���ddedee	fd�����Z
eejd	���d
�����Z
e	ddedee	fd���Zedee	fd���Zed
e	ddfd���Zed���ZdS)�	IPEchoAPIz2Make requests to the API for obtain own IP addressz/api/ip�)�maxsizeN�
ip_version�returnc��<K�|�|���d{V��S�z5Return cached result for resolved IP from echo ip APIN)�ip_for_version)�clsrs  �Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/ipecho.py�get_ipzIPEchoAPI.get_ips.����
�'�'�
�3�3�3�3�3�3�3�3�3��c�`�	|���S#t$r
}t|�d}~wwxYwr)�_get_ip�	Exceptionr)r�es  r�	server_ipzIPEchoAPI.server_ip$s<��	"��;�;�=�=� ���	"�	"�	"���!�����	"���s��
-�(�-c��@K�tj��}	tj|�d|j��t
����d{V��}t
j|��|krtd���|S#tj	tf$r
}t|�d}~wwxYw)z#Return resolved IP from echo ip APIN)�timeoutz
Wrong ip type)�asyncio�get_event_loop�wait_for�run_in_executorr�TIMEOUT_FOR_IPECHO_REQUESTr	�type_of�
ValueError�TimeoutErrorr)rr�loop�iprs     rrzIPEchoAPI.ip_for_version-s������%�'�'��		"��'��$�$�T�3�;�7�7�2����������B��z�"�~�~��+�+� ��1�1�1��I���$�j�1�	"�	"�	"���!�����	"���s�A"A:�:B�B�Bc��	t���sdSt���j}t	j��|z
}|dkrdS|t
kr-t������}|SdS#t$r&}t�
d|��Yd}~dSd}~wwxYw)NrzIPEchoAPI cache read error: %s)�CACHE_FILE_PATH�exists�stat�st_mtime�time�CACHE_TTL_SECONDS�	read_text�stripr�logger�error)r�mtime�	cache_ager+rs     r�_load_cachezIPEchoAPI._load_cache?s���	�"�)�)�+�+�
��t�#�(�(�*�*�3�E��	���e�+�I��1�}�}��t��,�,�,�$�.�.�0�0�6�6�8�8���	��t���	�	�	��L�L�9�1�=�=�=��4�4�4�4�4�����	���s"�B�:B�7B�
C�B>�>Cr+c��	tt|dd���dS#t$r&}t�d|��Yd}~dSd}~wwxYw)NFi�)�backup�permissionszIPEchoAPI cache write error: %s)rr-rr5r6)rr+rs   r�_save_cachezIPEchoAPI._save_cacheTs|��	?�����!�	
�
�
�
�
�
���	?�	?�	?��L�L�:�A�>�>�>�>�>�>�>�>�>�����	?���s��
A�A�Ac�`�|���}|�|Stj�|j|jz��}|�|��}|�d��dkrtd���|�d��}|r|�|��|S)zIGet IP from file-based cache or send request to API and process response.N�status�okzUnexpected API errorr+)	r9�urllib�request�Request�	_BASE_URL�URL�getrr=)r�	cached_iprB�responser+s     rrzIPEchoAPI._get_ip`s����O�O�%�%�	�� ����.�(�(�����)@�A�A���;�;�w�'�'���<�<��!�!�T�)�)��1�2�2�2�
�\�\�$�
�
��
�	 ��O�O�B�����	r)N)�__name__�
__module__�__qualname__�__doc__rE�classmethodrr
r�strr�	functools�	lru_cacherrr9r=r�rrrrsR������<�<�
�C���Z�����4�4�i�4�8�C�=�4�4�4����[�4�
��Y���#�#�#�"�"�$�#��[�"��%)�"�"�"�"�	�#��"�"�"��[�"�"��H�S�M�����[��(�	?�S�	?�T�	?�	?�	?��[�	?�����[���rr)rLr"rO�loggingr1rA�pathlibr�typingr�	async_lrur�defence360agent.api.serverrr�defence360agent.utilsr�defence360agent.utils.validater	r
�	getLoggerrIr5r&r2r-rrQrr�<module>rZs$��B�B�����������������
�
�
�
������������� � � � � � �4�4�4�4�4�4�4�4�0�0�0�0�0�0�8�8�8�8�8�8�8�8�	��	�8�	$�	$�������$�(�)�)�N�:��X�X�X�X�X��X�X�X�X�Xrdefence360agent/utils/__pycache__/json.cpython-311.opt-1.pyc0000644000000000000000000000460100000000000020531 0ustar  �

���Wc�8����dZddlZddlmZmZmZmZddlmZddl	m
Z
defd�ZGd�d	ej
��ZGd
�de��ZdS)z6JSON encoders to help with sending messages to server.�N)�IPv4Address�IPv4Network�IPv6Address�IPv6Network)�
model_to_dict)�Model�returnc�p�t|j��st|j��St|��S)zn
    IPv4Network('192.168.1.1/32') -> '192.168.1.1'
    IPv4Network('192.168.1.0/24') -> '192.168.1.0/24'
    )�int�hostmask�str�network_address)�nets �O/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/json.py�ip_net_to_stringr
s2��
�s�|���(��3�&�'�'�'��s�8�8�O�c��eZdZd�ZdS)�	IPEncoderc���t|ttf��rt|��St|tt
f��rt
|��Stj�	||��S�N)
�
isinstancerrrrrr
�json�JSONEncoder�default)�self�objs  rrzIPEncoder.defaultsb���c�K��5�6�6�	)�#�C�(�(�(��c�K��5�6�6�	��s�8�8�O���'�'��c�2�2�2rN)�__name__�
__module__�__qualname__r�rrrrs#������3�3�3�3�3rrc���eZdZ�fd�Z�xZS)�ServerJSONEncoderc���t|t��rt|��St���|��Sr)rrr�superr)rr�	__class__s  �rrzServerJSONEncoder.defaults9����c�5�!�!�	&� ��%�%�%��w�w���s�#�#�#r)rrrr�
__classcell__)r%s@rr"r"s8�������$�$�$�$�$�$�$�$�$rr")�__doc__r�	ipaddressrrrr�playhouse.shortcutsr�defence360agent.modelrr
rrrr"r rr�<module>r+s���<�<�����H�H�H�H�H�H�H�H�H�H�H�H�-�-�-�-�-�-�'�'�'�'�'�'��S�����3�3�3�3�3�� �3�3�3�$�$�$�$�$�	�$�$�$�$�$rdefence360agent/utils/__pycache__/json.cpython-311.pyc0000644000000000000000000000460100000000000017572 0ustar  �

���Wc�8����dZddlZddlmZmZmZmZddlmZddl	m
Z
defd�ZGd�d	ej
��ZGd
�de��ZdS)z6JSON encoders to help with sending messages to server.�N)�IPv4Address�IPv4Network�IPv6Address�IPv6Network)�
model_to_dict)�Model�returnc�p�t|j��st|j��St|��S)zn
    IPv4Network('192.168.1.1/32') -> '192.168.1.1'
    IPv4Network('192.168.1.0/24') -> '192.168.1.0/24'
    )�int�hostmask�str�network_address)�nets �O/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/json.py�ip_net_to_stringr
s2��
�s�|���(��3�&�'�'�'��s�8�8�O�c��eZdZd�ZdS)�	IPEncoderc���t|ttf��rt|��St|tt
f��rt
|��Stj�	||��S�N)
�
isinstancerrrrrr
�json�JSONEncoder�default)�self�objs  rrzIPEncoder.defaultsb���c�K��5�6�6�	)�#�C�(�(�(��c�K��5�6�6�	��s�8�8�O���'�'��c�2�2�2rN)�__name__�
__module__�__qualname__r�rrrrs#������3�3�3�3�3rrc���eZdZ�fd�Z�xZS)�ServerJSONEncoderc���t|t��rt|��St���|��Sr)rrr�superr)rr�	__class__s  �rrzServerJSONEncoder.defaults9����c�5�!�!�	&� ��%�%�%��w�w���s�#�#�#r)rrrr�
__classcell__)r%s@rr"r"s8�������$�$�$�$�$�$�$�$�$rr")�__doc__r�	ipaddressrrrr�playhouse.shortcutsr�defence360agent.modelrr
rrrr"r rr�<module>r+s���<�<�����H�H�H�H�H�H�H�H�H�H�H�H�-�-�-�-�-�-�'�'�'�'�'�'��S�����3�3�3�3�3�� �3�3�3�$�$�$�$�$�	�$�$�$�$�$rdefence360agent/utils/__pycache__/kwconfig.cpython-311.opt-1.pyc0000644000000000000000000000667500000000000021404 0ustar  �

�3.zI�p��\�ddlZddlmZddlmZGd�d��ZGd�de��ZdS)�N)�Optional)�atomic_rewritec�r�eZdZdZdxZxZZdZd
d�Zde	e
fd�Zde	e
fd�Z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��|jsJ�tj|j�|��tj��|_|p|j|_||_dS�N)	�SEARCH_PATTERN�re�compile�format�	MULTILINE�_pattern�DEFAULT_FILENAME�	_filename�_name)�self�name�filenames   �S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/kwconfig.py�__init__zKWConfig.__init__sX���"�"�"�"��
���&�&�t�,�,�b�l�
�
��
�"�:�T�%:�����
�
�
��returnc���|jsJ�t|j��5}|���}ddd��n#1swxYwY|�|��}|�*|d|j�|j|��zdzz
}n9|j�|j�|j|��|��}t|j||j
���|S)N�
)�allow_empty_content)�
WRITE_PATTERN�openr�read�_parser
rr�subr�ALLOW_EMPTY_CONFIG)r�value�f�content�	old_values     r�setzKWConfig.sets)���!�!�!�!�
�$�.�
!�
!�	�Q��f�f�h�h�G�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	��K�K��(�(�	�����t�)�0�0���U�C�C�C�d�J�
�G�G��m�'�'��"�)�)�$�*�e�<�<�g���G�	��N�� $� 7�	
�	
�	
�	
�
�s�?�A�Ac��t|j��5}|���}ddd��n#1swxYwY|�|��Sr	)rrrr )rr$r%s   r�getzKWConfig.get2s���
�$�.�
!�
!�	�Q��f�f�h�h�G�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	��{�{�7�#�#�#s�6�:�:c�d�|j�|��}|o|�d��S)N�)r�search�group)rr%�matchs   rr zKWConfig._parse7s,���
�$�$�W�-�-���'����Q���'rr	)�__name__�
__module__�__qualname__�__doc__r
rrr"rr�strr'r)r �rrrrs���������9;�:�N�:�%�
��������H�S�M�����0$�X�c�]�$�$�$�$�
(��#��(�(�(�(�(�(rrc��eZdZdZdZdZdS)�PureFTPBaseConfigz^\s*?{}\s+(.*?)\s*?$z{} {}z/etc/pure-ftpd.confN)r/r0r1r
rrr4rrr6r6<s ������,�N��M�,���rr6)r�typingr�defence360agent.utilsrrr6r4rr�<module>r9s���	�	�	�	�������0�0�0�0�0�0�2(�2(�2(�2(�2(�2(�2(�2(�j-�-�-�-�-��-�-�-�-�-rdefence360agent/utils/__pycache__/kwconfig.cpython-311.pyc0000644000000000000000000000667500000000000020445 0ustar  �

�3.zI�p��\�ddlZddlmZddlmZGd�d��ZGd�de��ZdS)�N)�Optional)�atomic_rewritec�r�eZdZdZdxZxZZdZd
d�Zde	e
fd�Zde	e
fd�Z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��|jsJ�tj|j�|��tj��|_|p|j|_||_dS�N)	�SEARCH_PATTERN�re�compile�format�	MULTILINE�_pattern�DEFAULT_FILENAME�	_filename�_name)�self�name�filenames   �S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/kwconfig.py�__init__zKWConfig.__init__sX���"�"�"�"��
���&�&�t�,�,�b�l�
�
��
�"�:�T�%:�����
�
�
��returnc���|jsJ�t|j��5}|���}ddd��n#1swxYwY|�|��}|�*|d|j�|j|��zdzz
}n9|j�|j�|j|��|��}t|j||j
���|S)N�
)�allow_empty_content)�
WRITE_PATTERN�openr�read�_parser
rr�subr�ALLOW_EMPTY_CONFIG)r�value�f�content�	old_values     r�setzKWConfig.sets)���!�!�!�!�
�$�.�
!�
!�	�Q��f�f�h�h�G�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	��K�K��(�(�	�����t�)�0�0���U�C�C�C�d�J�
�G�G��m�'�'��"�)�)�$�*�e�<�<�g���G�	��N�� $� 7�	
�	
�	
�	
�
�s�?�A�Ac��t|j��5}|���}ddd��n#1swxYwY|�|��Sr	)rrrr )rr$r%s   r�getzKWConfig.get2s���
�$�.�
!�
!�	�Q��f�f�h�h�G�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	��{�{�7�#�#�#s�6�:�:c�d�|j�|��}|o|�d��S)N�)r�search�group)rr%�matchs   rr zKWConfig._parse7s,���
�$�$�W�-�-���'����Q���'rr	)�__name__�
__module__�__qualname__�__doc__r
rrr"rr�strr'r)r �rrrrs���������9;�:�N�:�%�
��������H�S�M�����0$�X�c�]�$�$�$�$�
(��#��(�(�(�(�(�(rrc��eZdZdZdZdZdS)�PureFTPBaseConfigz^\s*?{}\s+(.*?)\s*?$z{} {}z/etc/pure-ftpd.confN)r/r0r1r
rrr4rrr6r6<s ������,�N��M�,���rr6)r�typingr�defence360agent.utilsrrr6r4rr�<module>r9s���	�	�	�	�������0�0�0�0�0�0�2(�2(�2(�2(�2(�2(�2(�2(�j-�-�-�-�-��-�-�-�-�-rdefence360agent/utils/__pycache__/net.cpython-311.opt-1.pyc0000644000000000000000000000245000000000000020346 0ustar  �

;�'L��O����ddlmZmZmZmZddlmZmZdZd\Z	Z
deeeffd�Zdeeefdeeeeffd	�Z
d
S)�)�IPv4Address�IPv4Network�IPv6Address�IPv6Network)�Tuple�Union�tcp)�in�out�
ip_addressc��|jdkr*t�|jdd�dd���St|��S)N���bigT)�signed)�version�int�
from_bytes�packed)rs �N/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/net.py�pack_ip_addressrs@����Q����~�~�j�/����3�U�4�~�H�H�H��:�����
ip_network�returnc�f�t|j��}t|j��}|||jfS)N)r�network_address�netmaskr)r�net�masks   r�pack_ip_networkr s4���*�4�
5�
5�C��:�-�.�.�D���j�(�(�(rN)�	ipaddressrrrr�typingrr�TCP�IN�OUTrrr �rr�<module>r's���H�H�H�H�H�H�H�H�H�H�H�H�����������
���C���k�;�&>� ?�����)��k�;�.�/�)�
�3��S�=��)�)�)�)�)�)rdefence360agent/utils/__pycache__/net.cpython-311.pyc0000644000000000000000000000245000000000000017407 0ustar  �

;�'L��O����ddlmZmZmZmZddlmZmZdZd\Z	Z
deeeffd�Zdeeefdeeeeffd	�Z
d
S)�)�IPv4Address�IPv4Network�IPv6Address�IPv6Network)�Tuple�Union�tcp)�in�out�
ip_addressc��|jdkr*t�|jdd�dd���St|��S)N���bigT)�signed)�version�int�
from_bytes�packed)rs �N/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/net.py�pack_ip_addressrs@����Q����~�~�j�/����3�U�4�~�H�H�H��:�����
ip_network�returnc�f�t|j��}t|j��}|||jfS)N)r�network_address�netmaskr)r�net�masks   r�pack_ip_networkr s4���*�4�
5�
5�C��:�-�.�.�D���j�(�(�(rN)�	ipaddressrrrr�typingrr�TCP�IN�OUTrrr �rr�<module>r's���H�H�H�H�H�H�H�H�H�H�H�H�����������
���C���k�;�&>� ?�����)��k�;�.�/�)�
�3��S�=��)�)�)�)�)�)rdefence360agent/utils/__pycache__/net_transport.cpython-311.opt-1.pyc0000644000000000000000000004657400000000000022501 0ustar  �

)���z�X�����dZddlZddlZddlZddlZddlZddlZddlZ	ddl
mZmZddl
mZddlmZmZmZmZerddlZee��ZdZdedefd	�ZGd
�de��ZGd�d
��ZGd�de��ZGd�de��ZGd�dejj ��Z!Gd�dejj"��Z#Gd�de	j$j%��Z&Gd�de	j$j'��Z(Gd�d��Z)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)�ABC�abstractmethod)�	getLogger)�Dict�Optional�Tuple�
TYPE_CHECKINGg�r@�ip�returnc�~�	ttj|��tj��S#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��
��)�.�r�2�2�I�4I�J�J�J�������u�u����s�+.�
<�<c�J�eZdZdZedededefd���Zdededefd�ZdS)�	IpChooserz�Select an IP address to connect to for a given hostname and port.

    Implementations may be stateful and can keep caches/metrics inside.
    �hostname�portrc��t�)z6Return an IP address (v4 or v6) for *hostname*:*port*.)�NotImplementedError��selfrrs   r�choosezIpChooser.chooseTs
��"�!�c�.�|�||��S�N)rrs   r�__call__zIpChooser.__call__Ys���{�{�8�T�*�*�*rN)	�__name__�
__module__�__qualname__�__doc__r�str�intrr �rrrrNs|��������
�"�s�"�#�"�#�"�"�"��^�"�+��+�C�+�C�+�+�+�+�+�+rrc�X�eZdZdZejed�dedefd�Z	de
dedee
d	ffd
�ZdS)�DnsCacheResolverz�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.
    )�family�ttl_secondsr*r+c�`�||_||_i|_tj��|_dSr)�_family�_ttl_seconds�_cache�	threading�Lock�_lock)rr*r+s   r�__init__zDnsCacheResolver.__init__ds4�����'���
�	
���^�%�%��
�
�
rrrr.c�,�|||jf}tj��}|j5|j�|��}|�;|\}}||kr0t
�d|||j��|cddd��Sddd��n#1swxYwYt
�d|||j��tj|||jtj	��}g}|D])\}	}	}	}	}
|
d}||vr|�
|���*|s#td�||�����t|��}|j5||jz|f|j|<ddd��n#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/�get�logger�debug�socket�getaddrinfo�SOCK_STREAM�append�OSError�format�tupler.)
rrr�key�now�cached�
expires_at�ips�infos�_�sockaddrr
�ips_ts
             r�get_ipszDnsCacheResolver.get_ipsqsn����t�|�,���i�k�k��
�Z�	�	��[�_�_�S�)�)�F��!�"(��
�C���#�#��L�L�J� ����	����	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	���G����L�		
�	
�	
��"����L���	
�
����$)�	�	� �A�q�!�Q���!��B���}�}��
�
�2������	N��5�<�<�X�t�L�L�M�M�M��c�
�
��
�Z�	@�	@� #�d�&7� 7��?�D�K���	@�	@�	@�	@�	@�	@�	@�	@�	@�	@�	@����	@�	@�	@�	@�	���?����L��	
�	
�	
��s$�AB	�	B
�B
�E&�&E*�-E*N)
r!r"r#r$r9�	AF_UNSPEC�_DNS_DEFAULT_TTL_SECONDSr&�floatr3r%rrIr'rrr)r)]s����������&�5�	&�&�&��&��	&�&�&�&�3��3�3�3�5��c��?�3�3�3�3�3�3rr)c��eZdZdZdddd�deedeejdefd�Z	dd
�Z
dd�Zd	efd�Zd	ee
fd
�Zd	efd�Zde
ded	e
fd�ZdS)�RandomIpChooserWithIPv6Togglez�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
    NT)�resolver�rng�ipv6_enabledrOrPrQc��|p
t��|_|ptj��|_||_d|_dSr)r)�	_resolver�random�Random�_rng�
_ipv6_enabled�_last_ip)rrOrPrQs    rr3z&RandomIpChooserWithIPv6Toggle.__init__�s<��"�7�%5�%7�%7����*�6�=�?�?��	�)���'+��
�
�
rrc��d|_dS)NT�rW�rs r�enable_ipv6z)RandomIpChooserWithIPv6Toggle.enable_ipv6�s��!����rc��d|_dS)NFrZr[s r�disable_ipv6z*RandomIpChooserWithIPv6Toggle.disable_ipv6�s��"����rc��|jSrrZr[s r�is_ipv6_enabledz-RandomIpChooserWithIPv6Toggle.is_ipv6_enabled�s���!�!rc��|jSr)rXr[s r�last_ipz%RandomIpChooserWithIPv6Toggle.last_ip�s
���}�rc�<�t|j��od|jvS)N�:)�boolrXr[s r�last_ip_was_ipv6z.RandomIpChooserWithIPv6Toggle.last_ip_was_ipv6�s���D�M�"�"�=��t�}�(<�=rrrc���|j�||��}|jr@|j�|��}||_t�d|||��|Std�|D����}|s#td�
||�����|j�|��}||_t�d|||��|S)NzERandomIpChooserWithIPv6Toggle selected IP %s for %s:%s (IPv6 enabled)c3�8K�|]}t|���|V��dSr)r)�.0r
s  r�	<genexpr>z7RandomIpChooserWithIPv6Toggle.choose.<locals>.<genexpr>�s-����:�:��X�b�\�\�:��:�:�:�:�:�:rzNo IPv4 IPs resolved for {}:{}zKRandomIpChooserWithIPv6Toggle selected IPv4 IP %s for %s:%s (IPv6 disabled))rSrIrWrV�choicerXr7r8r?r=r>)rrrrD�chosen�ipv4_ipss      rrz$RandomIpChooserWithIPv6Toggle.choose�s����n�$�$�X�t�4�4����
	��Y�%�%�c�*�*�F�"�D�M��L�L�!����
�
�
��M��:�:�c�:�:�:�:�:���	��0�7�7��$�G�G���
���!�!�(�+�+����
����
����	
�	
�	
��
r�rN)r!r"r#r$rr)rTrUrer3r\r^r`r%rbrfr&rr'rrrNrN�s��������04�'+�!�
,�
,�
,��+�,�
,��f�m�
$�	
,�
�
,�
,�
,�
,�"�"�"�"�#�#�#�#�"��"�"�"�"���#������>�$�>�>�>�>��s��#��#������rrNc�`�eZdZdZddd�deedeejfd�Zde	de
d	e	fd
�ZdS)�RandomIpChooserzAResolve hostname and select a random IP from resolved candidates.N)rOrPrOrPc�d�|p
t��|_|ptj��|_dSr)r)rSrTrUrV)rrOrPs   rr3zRandomIpChooser.__init__�s-��"�7�%5�%7�%7����*�6�=�?�?��	�	�	rrrrc��|j�||��}|j�|��}t�d|||��|S)Nz(RandomIpChooser selected IP %s for %s:%s)rSrIrVrkr7r8)rrrrDrls     rrzRandomIpChooser.choose�sW���n�$�$�X�t�4�4����!�!�#�&�&�����6����		
�	
�	
��
r)r!r"r#r$rr)rTrUr3r%r&rr'rrrprp�s�������K�K�
04�'+�	+�+�+��+�,�+��f�m�
$�	+�+�+�+�	�s�	�#�	�#�	�	�	�	�	�	rrpc�Z��eZdZdZ	d
ejdd�dedeede	f�fd�Z
dd	�Z�xZS)�ForcedIPHTTPConnectionz�HTTPConnection that connects to a chosen IP.

    Important: urllib builds the request URL with the original hostname,
    therefore the Host header stays correct.
    N��timeout�source_addressrr�
ip_chooserc�`��t���||||���||_dS)N)rrvrw��superr3�_ip_chooser)rrrrxrvrw�	__class__s      �rr3zForcedIPHTTPConnection.__init__	sB���	��������)�		�	
�	
�	
�&����rrc���|jpd}|j�|j|��}t�d|||j��t
j||f|j|j	��|_
dS)N�Pz:ForcedIPHTTPConnection connecting to %s:%s for hostname %s)rr|r�hostr7r8r9�create_connectionrvrw�sock)rrr
s   r�connectzForcedIPHTTPConnection.connectsw���y��B��
�
�
$�
$�T�Y��
5�
5�����H����I�		
�	
�	
��,�
��J��L���
�
��	�	�	rrrn�
r!r"r#r$r9�_GLOBAL_DEFAULT_TIMEOUTr%rr&rr3r��
__classcell__�r}s@rrtrts����������#�&��.��&�&�&��&��s�m�&�
�&�&�&�&�&�&�"
�
�
�
�
�
�
�
rrtc
�^��eZdZdZ	dejdd�dedeede	ddf�fd	�Z
d
d�Z�xZS)�ForcedIPHTTPSConnectionz�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
    Nrurrrx�context�ssl.SSLContextc�b��t���|||||���||_dS)N)rr�rvrwrz)rrrrxr�rvrwr}s       �rr3z ForcedIPHTTPSConnection.__init__3sE���	���������)�	�	
�	
�	
�&����rrc�~�|jpd}|j�|j|��}t�d|||j��t
j||f|j|j	��}|j
r"||_|���|j}|j
�||j���|_dS)Ni�z;ForcedIPHTTPSConnection connecting to %s:%s for hostname %s)�server_hostname)rr|rr�r7r8r9r�rvrw�_tunnel_hostr��_tunnel�_context�wrap_socket)rrr
�raw_socks    rr�zForcedIPHTTPSConnection.connectFs����y��C��
�
�
$�
$�T�Y��
5�
5�����I����I�		
�	
�	
��+�
��J��L���
�
����	!� �D�I��L�L�N�N�N��y�H��M�-�-�� �I�.�
�
��	�	�	rrrnr�r�s@rr�r�+s����������#�&��.��&�&�&��&��s�m�&�
�&�"�
&�&�&�&�&�&�&
�
�
�
�
�
�
�
rr�c�H��eZdZdZdef�fd�Zdejjfd�Z	�xZ
S)�ForcedIPHTTPHandlerz3urllib handler that creates ForcedIPHTTPConnection.rxc�V��t�����||_dSrrz)rrxr}s  �rr3zForcedIPHTTPHandler.__init__ds'���
��������%����rrc�:���fd�}��||��S)Nc�X��t|�j|�d�����S)Nrv)rxrv)rtr|r6�r��kwargsrs  �r�factoryz.ForcedIPHTTPHandler.http_open.<locals>.factoryis2���)���+��
�
�9�-�-����
r��do_open�r�reqr�s`  r�	http_openzForcedIPHTTPHandler.http_openhs2���	�	�	�	�	��|�|�G�S�)�)�)r)r!r"r#r$rr3�http�client�HTTPResponser�r�r�s@rr�r�asj�������=�=�&�i�&�&�&�&�&�&�*��� 8�*�*�*�*�*�*�*�*rr�c�L��eZdZdZdeddf�fd�Zdejjfd�Z	�xZ
S)�ForcedIPHTTPSHandlerz4urllib handler that creates ForcedIPHTTPSConnection.rxr�r�c�h��t���|���||_||_dS)N)r�)r{r3r|r�)rrxr�r}s   �rr3zForcedIPHTTPSHandler.__init__vs1���
�������)�)�)�%�����
�
�
rrc�:���fd�}��||��S)Nc�d��t|�j�j|�d�����S)Nrv)rxr�rv)r�r|r�r6r�s  �rr�z0ForcedIPHTTPSHandler.https_open.<locals>.factory|s7���*���+��
��
�
�9�-�-�	���
rr�r�s`  r�
https_openzForcedIPHTTPSHandler.https_open{s2���	�	�	�	�	��|�|�G�S�)�)�)r)r!r"r#r$rr3r�r�r�r�r�r�s@rr�r�ssr�������>�>� �i� �:J� � � � � � �
	*���!9�	*�	*�	*�	*�	*�	*�	*�	*rr�c��eZdZdZddd�deededfd�Zdd�d	ejj	d
ee
dejj
fd�ZdS)
�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.
    N)rx�ssl_contextrxr�r�c� �ddl}|p|���|_|�%tj���|_dStj�t|���t||j�����|_dS)Nr)rx)rxr�)	�ssl�create_default_context�_ssl_context�urllib�request�build_opener�_openerr�r�)rrxr��_ssls    rr3zUrlTransport.__init__�s���	����'�H�4�+F�+F�+H�+H�����!�>�6�6�8�8�D�L�L�L�!�>�6�6�#�z�:�:�:�$�)� �-������D�L�L�Lr�rvr�rvrc�r�|�|j�|��S|j�||���S)Nr�)r��open)rr�rvs   rr�zUrlTransport.open�s:���?��<�$�$�S�)�)�)��|� � ��g� �6�6�6r)r!r"r#r$rrr3r�r��RequestrLr�r�r�r�r'rrr�r��s���������+/�26�	����Y�'���.�/�	����2$(�	7�7�7�
�^�
#�7��%��	7�

��	!�7�7�7�7�7�7rr�)*r$�http.clientr�rrTr9r0r5�urllib.requestr��abcrr�loggingr�typingrrrr	r�r!r7rKr%rerrr)rNrpr��HTTPConnectionrt�HTTPSConnectionr�r��HTTPHandlerr��HTTPSHandlerr�r�r'rr�<module>r�s���,�,�\��������
�
�
�
�
�
�
�
�������������#�#�#�#�#�#�#�#�������7�7�7�7�7�7�7�7�7�7�7�7����J�J�J�	��8�	�	��!����������+�+�+�+�+��+�+�+�G�G�G�G�G�G�G�G�T@�@�@�@�@�I�@�@�@�F�����i����0&
�&
�&
�&
�&
�T�[�7�&
�&
�&
�R3
�3
�3
�3
�3
�d�k�9�3
�3
�3
�l*�*�*�*�*�&�.�4�*�*�*�$*�*�*�*�*�6�>�6�*�*�*�(%7�%7�%7�%7�%7�%7�%7�%7�%7�%7rdefence360agent/utils/__pycache__/net_transport.cpython-311.pyc0000644000000000000000000004657400000000000021542 0ustar  �

)���z�X�����dZddlZddlZddlZddlZddlZddlZddlZ	ddl
mZmZddl
mZddlmZmZmZmZerddlZee��ZdZdedefd	�ZGd
�de��ZGd�d
��ZGd�de��ZGd�de��ZGd�dejj ��Z!Gd�dejj"��Z#Gd�de	j$j%��Z&Gd�de	j$j'��Z(Gd�d��Z)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)�ABC�abstractmethod)�	getLogger)�Dict�Optional�Tuple�
TYPE_CHECKINGg�r@�ip�returnc�~�	ttj|��tj��S#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��
��)�.�r�2�2�I�4I�J�J�J�������u�u����s�+.�
<�<c�J�eZdZdZedededefd���Zdededefd�ZdS)�	IpChooserz�Select an IP address to connect to for a given hostname and port.

    Implementations may be stateful and can keep caches/metrics inside.
    �hostname�portrc��t�)z6Return an IP address (v4 or v6) for *hostname*:*port*.)�NotImplementedError��selfrrs   r�choosezIpChooser.chooseTs
��"�!�c�.�|�||��S�N)rrs   r�__call__zIpChooser.__call__Ys���{�{�8�T�*�*�*rN)	�__name__�
__module__�__qualname__�__doc__r�str�intrr �rrrrNs|��������
�"�s�"�#�"�#�"�"�"��^�"�+��+�C�+�C�+�+�+�+�+�+rrc�X�eZdZdZejed�dedefd�Z	de
dedee
d	ffd
�ZdS)�DnsCacheResolverz�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.
    )�family�ttl_secondsr*r+c�`�||_||_i|_tj��|_dSr)�_family�_ttl_seconds�_cache�	threading�Lock�_lock)rr*r+s   r�__init__zDnsCacheResolver.__init__ds4�����'���
�	
���^�%�%��
�
�
rrrr.c�,�|||jf}tj��}|j5|j�|��}|�;|\}}||kr0t
�d|||j��|cddd��Sddd��n#1swxYwYt
�d|||j��tj|||jtj	��}g}|D])\}	}	}	}	}
|
d}||vr|�
|���*|s#td�||�����t|��}|j5||jz|f|j|<ddd��n#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/�get�logger�debug�socket�getaddrinfo�SOCK_STREAM�append�OSError�format�tupler.)
rrr�key�now�cached�
expires_at�ips�infos�_�sockaddrr
�ips_ts
             r�get_ipszDnsCacheResolver.get_ipsqsn����t�|�,���i�k�k��
�Z�	�	��[�_�_�S�)�)�F��!�"(��
�C���#�#��L�L�J� ����	����	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	���G����L�		
�	
�	
��"����L���	
�
����$)�	�	� �A�q�!�Q���!��B���}�}��
�
�2������	N��5�<�<�X�t�L�L�M�M�M��c�
�
��
�Z�	@�	@� #�d�&7� 7��?�D�K���	@�	@�	@�	@�	@�	@�	@�	@�	@�	@�	@����	@�	@�	@�	@�	���?����L��	
�	
�	
��s$�AB	�	B
�B
�E&�&E*�-E*N)
r!r"r#r$r9�	AF_UNSPEC�_DNS_DEFAULT_TTL_SECONDSr&�floatr3r%rrIr'rrr)r)]s����������&�5�	&�&�&��&��	&�&�&�&�3��3�3�3�5��c��?�3�3�3�3�3�3rr)c��eZdZdZdddd�deedeejdefd�Z	dd
�Z
dd�Zd	efd�Zd	ee
fd
�Zd	efd�Zde
ded	e
fd�ZdS)�RandomIpChooserWithIPv6Togglez�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
    NT)�resolver�rng�ipv6_enabledrOrPrQc��|p
t��|_|ptj��|_||_d|_dSr)r)�	_resolver�random�Random�_rng�
_ipv6_enabled�_last_ip)rrOrPrQs    rr3z&RandomIpChooserWithIPv6Toggle.__init__�s<��"�7�%5�%7�%7����*�6�=�?�?��	�)���'+��
�
�
rrc��d|_dS)NT�rW�rs r�enable_ipv6z)RandomIpChooserWithIPv6Toggle.enable_ipv6�s��!����rc��d|_dS)NFrZr[s r�disable_ipv6z*RandomIpChooserWithIPv6Toggle.disable_ipv6�s��"����rc��|jSrrZr[s r�is_ipv6_enabledz-RandomIpChooserWithIPv6Toggle.is_ipv6_enabled�s���!�!rc��|jSr)rXr[s r�last_ipz%RandomIpChooserWithIPv6Toggle.last_ip�s
���}�rc�<�t|j��od|jvS)N�:)�boolrXr[s r�last_ip_was_ipv6z.RandomIpChooserWithIPv6Toggle.last_ip_was_ipv6�s���D�M�"�"�=��t�}�(<�=rrrc���|j�||��}|jr@|j�|��}||_t�d|||��|Std�|D����}|s#td�
||�����|j�|��}||_t�d|||��|S)NzERandomIpChooserWithIPv6Toggle selected IP %s for %s:%s (IPv6 enabled)c3�8K�|]}t|���|V��dSr)r)�.0r
s  r�	<genexpr>z7RandomIpChooserWithIPv6Toggle.choose.<locals>.<genexpr>�s-����:�:��X�b�\�\�:��:�:�:�:�:�:rzNo IPv4 IPs resolved for {}:{}zKRandomIpChooserWithIPv6Toggle selected IPv4 IP %s for %s:%s (IPv6 disabled))rSrIrWrV�choicerXr7r8r?r=r>)rrrrD�chosen�ipv4_ipss      rrz$RandomIpChooserWithIPv6Toggle.choose�s����n�$�$�X�t�4�4����
	��Y�%�%�c�*�*�F�"�D�M��L�L�!����
�
�
��M��:�:�c�:�:�:�:�:���	��0�7�7��$�G�G���
���!�!�(�+�+����
����
����	
�	
�	
��
r�rN)r!r"r#r$rr)rTrUrer3r\r^r`r%rbrfr&rr'rrrNrN�s��������04�'+�!�
,�
,�
,��+�,�
,��f�m�
$�	
,�
�
,�
,�
,�
,�"�"�"�"�#�#�#�#�"��"�"�"�"���#������>�$�>�>�>�>��s��#��#������rrNc�`�eZdZdZddd�deedeejfd�Zde	de
d	e	fd
�ZdS)�RandomIpChooserzAResolve hostname and select a random IP from resolved candidates.N)rOrPrOrPc�d�|p
t��|_|ptj��|_dSr)r)rSrTrUrV)rrOrPs   rr3zRandomIpChooser.__init__�s-��"�7�%5�%7�%7����*�6�=�?�?��	�	�	rrrrc��|j�||��}|j�|��}t�d|||��|S)Nz(RandomIpChooser selected IP %s for %s:%s)rSrIrVrkr7r8)rrrrDrls     rrzRandomIpChooser.choose�sW���n�$�$�X�t�4�4����!�!�#�&�&�����6����		
�	
�	
��
r)r!r"r#r$rr)rTrUr3r%r&rr'rrrprp�s�������K�K�
04�'+�	+�+�+��+�,�+��f�m�
$�	+�+�+�+�	�s�	�#�	�#�	�	�	�	�	�	rrpc�Z��eZdZdZ	d
ejdd�dedeede	f�fd�Z
dd	�Z�xZS)�ForcedIPHTTPConnectionz�HTTPConnection that connects to a chosen IP.

    Important: urllib builds the request URL with the original hostname,
    therefore the Host header stays correct.
    N��timeout�source_addressrr�
ip_chooserc�`��t���||||���||_dS)N)rrvrw��superr3�_ip_chooser)rrrrxrvrw�	__class__s      �rr3zForcedIPHTTPConnection.__init__	sB���	��������)�		�	
�	
�	
�&����rrc���|jpd}|j�|j|��}t�d|||j��t
j||f|j|j	��|_
dS)N�Pz:ForcedIPHTTPConnection connecting to %s:%s for hostname %s)rr|r�hostr7r8r9�create_connectionrvrw�sock)rrr
s   r�connectzForcedIPHTTPConnection.connectsw���y��B��
�
�
$�
$�T�Y��
5�
5�����H����I�		
�	
�	
��,�
��J��L���
�
��	�	�	rrrn�
r!r"r#r$r9�_GLOBAL_DEFAULT_TIMEOUTr%rr&rr3r��
__classcell__�r}s@rrtrts����������#�&��.��&�&�&��&��s�m�&�
�&�&�&�&�&�&�"
�
�
�
�
�
�
�
rrtc
�^��eZdZdZ	dejdd�dedeede	ddf�fd	�Z
d
d�Z�xZS)�ForcedIPHTTPSConnectionz�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
    Nrurrrx�context�ssl.SSLContextc�b��t���|||||���||_dS)N)rr�rvrwrz)rrrrxr�rvrwr}s       �rr3z ForcedIPHTTPSConnection.__init__3sE���	���������)�	�	
�	
�	
�&����rrc�~�|jpd}|j�|j|��}t�d|||j��t
j||f|j|j	��}|j
r"||_|���|j}|j
�||j���|_dS)Ni�z;ForcedIPHTTPSConnection connecting to %s:%s for hostname %s)�server_hostname)rr|rr�r7r8r9r�rvrw�_tunnel_hostr��_tunnel�_context�wrap_socket)rrr
�raw_socks    rr�zForcedIPHTTPSConnection.connectFs����y��C��
�
�
$�
$�T�Y��
5�
5�����I����I�		
�	
�	
��+�
��J��L���
�
����	!� �D�I��L�L�N�N�N��y�H��M�-�-�� �I�.�
�
��	�	�	rrrnr�r�s@rr�r�+s����������#�&��.��&�&�&��&��s�m�&�
�&�"�
&�&�&�&�&�&�&
�
�
�
�
�
�
�
rr�c�H��eZdZdZdef�fd�Zdejjfd�Z	�xZ
S)�ForcedIPHTTPHandlerz3urllib handler that creates ForcedIPHTTPConnection.rxc�V��t�����||_dSrrz)rrxr}s  �rr3zForcedIPHTTPHandler.__init__ds'���
��������%����rrc�:���fd�}��||��S)Nc�X��t|�j|�d�����S)Nrv)rxrv)rtr|r6�r��kwargsrs  �r�factoryz.ForcedIPHTTPHandler.http_open.<locals>.factoryis2���)���+��
�
�9�-�-����
r��do_open�r�reqr�s`  r�	http_openzForcedIPHTTPHandler.http_openhs2���	�	�	�	�	��|�|�G�S�)�)�)r)r!r"r#r$rr3�http�client�HTTPResponser�r�r�s@rr�r�asj�������=�=�&�i�&�&�&�&�&�&�*��� 8�*�*�*�*�*�*�*�*rr�c�L��eZdZdZdeddf�fd�Zdejjfd�Z	�xZ
S)�ForcedIPHTTPSHandlerz4urllib handler that creates ForcedIPHTTPSConnection.rxr�r�c�h��t���|���||_||_dS)N)r�)r{r3r|r�)rrxr�r}s   �rr3zForcedIPHTTPSHandler.__init__vs1���
�������)�)�)�%�����
�
�
rrc�:���fd�}��||��S)Nc�d��t|�j�j|�d�����S)Nrv)rxr�rv)r�r|r�r6r�s  �rr�z0ForcedIPHTTPSHandler.https_open.<locals>.factory|s7���*���+��
��
�
�9�-�-�	���
rr�r�s`  r�
https_openzForcedIPHTTPSHandler.https_open{s2���	�	�	�	�	��|�|�G�S�)�)�)r)r!r"r#r$rr3r�r�r�r�r�r�s@rr�r�ssr�������>�>� �i� �:J� � � � � � �
	*���!9�	*�	*�	*�	*�	*�	*�	*�	*rr�c��eZdZdZddd�deededfd�Zdd�d	ejj	d
ee
dejj
fd�ZdS)
�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.
    N)rx�ssl_contextrxr�r�c� �ddl}|p|���|_|�%tj���|_dStj�t|���t||j�����|_dS)Nr)rx)rxr�)	�ssl�create_default_context�_ssl_context�urllib�request�build_opener�_openerr�r�)rrxr��_ssls    rr3zUrlTransport.__init__�s���	����'�H�4�+F�+F�+H�+H�����!�>�6�6�8�8�D�L�L�L�!�>�6�6�#�z�:�:�:�$�)� �-������D�L�L�Lr�rvr�rvrc�r�|�|j�|��S|j�||���S)Nr�)r��open)rr�rvs   rr�zUrlTransport.open�s:���?��<�$�$�S�)�)�)��|� � ��g� �6�6�6r)r!r"r#r$rrr3r�r��RequestrLr�r�r�r�r'rrr�r��s���������+/�26�	����Y�'���.�/�	����2$(�	7�7�7�
�^�
#�7��%��	7�

��	!�7�7�7�7�7�7rr�)*r$�http.clientr�rrTr9r0r5�urllib.requestr��abcrr�loggingr�typingrrrr	r�r!r7rKr%rerrr)rNrpr��HTTPConnectionrt�HTTPSConnectionr�r��HTTPHandlerr��HTTPSHandlerr�r�r'rr�<module>r�s���,�,�\��������
�
�
�
�
�
�
�
�������������#�#�#�#�#�#�#�#�������7�7�7�7�7�7�7�7�7�7�7�7����J�J�J�	��8�	�	��!����������+�+�+�+�+��+�+�+�G�G�G�G�G�G�G�G�T@�@�@�@�@�I�@�@�@�F�����i����0&
�&
�&
�&
�&
�T�[�7�&
�&
�&
�R3
�3
�3
�3
�3
�d�k�9�3
�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  �

�4�� �����$�ddlZddlZddlZddlmZmZddlmZddlm	Z	m
Z
mZmZm
Z
mZddlmZddlmZddlmZddlmZdd	lmZGd
�d��Zd�ZGd
�d��Zd�Zd�Zd�Zd�Z d�Z!d�Z"ed���d���Z#d�Z$dS)�N)�	lru_cache�partial)�chain)�Any�Dict�Iterable�Iterator�Mapping�Tuple)�app)�Core)�prepare_schema)�	RpcClient)�EXITCODE_NOT_FOUNDc��eZdZeeeefZd�Ze	defd���Z
e	d���Zdefd�Zdefd�Z
defd�Zdefd�Zdefd	�Zd
�ZdS)�SchemaToArgparsec��||_|�d��|_|�d��|_|�dd��|_|�d��|_|�dd��|_|�d��|_|�dd��|_|�d	��|_	dS)
N�allowed�default�envvarF�help�
positional�rename�required�type)
�	_argument�get�_allowed�_default�_envvar�_help�_positional�_rename�	_required�_type)�self�argument�optionss   �R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/parsers.py�__init__zSchemaToArgparse.__init__s���&���")�+�+�i�"8�"8��
�$�[�[��3�3��
�#�K�K��%�8�8���!�+�+�f�-�-��
�!(���\�5�!A�!A���#�K�K��1�1���&�{�{�:�u�=�=���!�+�+�f�-�-��
�
�
��returnc�Z�|jr|jSd|j�dd��zS)N�--�_�-)r"r�replace�r&s r)�argnamezSchemaToArgparse.argnames2����	"��>�!��d�n�,�,�S�#�6�6�6�6r+c� �tt|���|���|���|���|���|�������}|S�N)�dictr�choicesrr�metavar�nargsr)r&�argparse_optionss  r)r(zSchemaToArgparse.options%si���������������	�	���������
�
����
�
���

�
�	
�	
�� �r+c#�K�d}|jdkr%|js|jr|js|dfV�dS|dfV�dS|jr|js|j�
|dfV�dSdSdS)Nr9�list�+�*�?)r%r"r$r r�r&�options  r)r9zSchemaToArgparse.nargs3s��������:�����#�
"���
"�t�|�
"��c�k�!�!�!�!�!��c�k�!�!�!�!�!�
�
�	�4�<�	�4�=�3H��#�+������	�	�3H�3Hr+c#� K�d|jfV�dS)Nr7)rr2s r)r7zSchemaToArgparse.choices?s�������&�&�&�&�&�&r+c#� K�d|jfV�dS)Nr)r!r2s r)rzSchemaToArgparse.helpBs�����d�j� � � � � � r+c#�K�d}|jr||j���fV�dS|jdkr||j���fV�dSdS)Nr8r<)r#�upperr%rr@s  r)r8zSchemaToArgparse.metavarEst�������<�	1��$�,�,�,�.�.�.�.�.�.�.�.�
�Z�6�
!�
!��$�.�.�.�0�0�0�0�0�0�0�0�"�
!r+c#�lK�|j�&|js!|jdks|jsd|jfV�dSdSdSdS)Nr<r)rr r%r"r2s r)rzSchemaToArgparse.defaultLsZ�����M�%��L�
&���v�%�%�T�-=�%��T�]�*�*�*�*�*�*�	
&�%�%�%�%�%r+c#�bK�|jr|jdkr|js|jsdV�dSdSdSdSdS)Nr<)rT)r$r%r r"r2s r)rzSchemaToArgparse.requiredTsd�����N�	#��
�f�$�$��L�%��$�%�
#�"�"�"�"�"�	#�	#�$�$�$�$�$�$r+N)�__name__�
__module__�__qualname__r	r�strr�
OptionTyper*�propertyr3r(r9r7rr8rr�r+r)rrs
�������%��S��/�*�J�
.�
.�
.��7��7�7�7��X�7�
� � ��X� �
�z�
�
�
�
�'��'�'�'�'�!�j�!�!�!�!�1��1�1�1�1�+��+�+�+�+�#�#�#�#�#r+rc��|�d��dkr�|�d��o|�dd��}|�|���}|�d|�dd	��z|d
���|�d|�dd	��z|d
���|jdi||�d��i��dSt||��}|j|jfi|j��dS)Nr�booleanrrF)rr.r/r0�
store_true)�dest�actionz--no-�store_falserrN)r�add_mutually_exclusive_group�add_argumentr1�set_defaultsrr3r()�parserr'r(r�bool_parser�	converters      r)�schema_to_argparser[_s6���{�{�6���i�'�'��;�;�z�*�*�O�7�;�;�x��3O�3O�/O���9�9�8�9�L�L��� � ��8�#�#�C��-�-�-���	!�	
�	
�	
�
	� � ��h�&�&�s�C�0�0�0�� �	!�	
�	
�	
�
	!�� �F�F�H�g�k�k�)�.D�.D�#E�F�F�F�F�F�$�X�w�7�7�	����I�-�C�C��1B�C�C�C�C�Cr+c	��eZdZedefd���Zed���Zededee	de
e	e	ffd���Zed���Zd	S)
�	EnvParser�envvar_parameter_optionsc���|sdSd��d�d��fd�|���D������S)N�c�@�d|vr|d�d|d��S|dS)Nrrz		rN)r(s r)�
format_argz)EnvParser.format_help.<locals>.format_argys5���� � �!�(�+�B�B����B�B�B��8�$�$r+z
environment variables: 
  {}z
  c3�.�K�|]}�|��V��dSr5rN)�.0r(rbs  �r)�	<genexpr>z(EnvParser.format_help.<locals>.<genexpr>sA���������
�7�#�#������r+)�format�join�values)r^rbs @r)�format_helpzEnvParser.format_helpts|���'�	��2�	%�	%�	%�
1�7�7��K�K�����7�>�>�@�@����
�
�
�
�	
r+c�p�d|vr1	|�d��n#t$r
d|�d|�d�fcYSwxYwdS)N�isascii�asciizerror: �=z  must only contain ascii symbols)�encode�UnicodeEncodeError)r�valuer(s   r)�	_validatezEnvParser._validate�ss������
����W�%�%�%�%��%�
�
�
�N�f�N�N�u�N�N�N�����
�����ts��3�3�environ�excluder,c	�l�i}|���D�]\}}||vr�|d}	||x}	||<|�||	|��x}
rK|�|||
��}t|tj���t	jt����#t$r�d|vr
|d||<Y��|�	d��sY��|�||d�
|����}t|tj���t	jt��Y��wxYw|S)Nr)�filerrz-error: environment variable {} is not defined)�itemsrq�
_format_error�print�sys�stderr�exitr�KeyErrorrrf)�clsrr�commandr^rs�kwargs�	parameterr(�envvar_namerp�err�msgs            r)�parsezEnvParser.parse�sz����":�"@�"@�"B�"B�	1�	1��I�w��G�#�#��!�(�+�K�
1�,3�K�,@�@���y�)�"�-�-��U�G�D�D�D�3�1��+�+��!9�3���C��#�C�J�/�/�/�/��H�/�0�0�0���+�
-�
-�
-���'�'�(/�	�(:�F�9�%��H��{�{�:�.�.���H��'�'��,�C�J�J�#�������c��
�+�+�+�+���+�,�,�,�,�,�
-����,�
s�
B�D1�8D1�AD1�0D1c�~�d�d�|��|�|��|���S)Nz{command}:
{help}

{message}� )r~r�message)rfrgri)r}r~r^r�s    r)rwzEnvParser._format_error�sA��0�7�7��H�H�W�%�%����!9�:�:��8�
�
�	
r+N)
rHrIrJ�staticmethodr
rirq�classmethodrrKrr�rwrNr+r)r]r]ss��������
�g�
�
�
��\�
� ����\���$��$�
�#��$�
�c�3�h��
$�$�$��[�$�L�
�
��[�
�
�
r+r]c�^�	tj|��n#tj$rYdSwxYwdS)NFT)�	ipaddress�IPv4Address�AddressValueError)�addrs r)�is_valid_ipv4_addrr��sE�����d�#�#�#�#���&�����u�u������4s��*�*c#�K�|���D]7\}}||�di���dg��vr||fV��8dS)N�cli�users)rvr)�schema�user�keyrhs    r)�_filter_userr��se�����|�|�~�~�����V��6�:�:�e�R�(�(�,�,�W�b�9�9�9�9��v�+������r+c�>�t|���j|�di|��S)N)�require_svc_is_runningrN)r�cmd)r~�require_rpc�paramss   r)�rpc_endpointr��s9��F�<�9�K�8�8�8�<�g�F���
���r+c�p�i}|D]0}|�dd��}t||d��}|�|||<�1|S)Nr0r/)r1�getattr)�arg_parser_namespace�	argumentsrr'�arg_parser_argumentrps      r)�generate_endpoint_paramsr��sU��
�F��%�%��&�.�.�s�C�8�8���,�.A�4�H�H����$�F�8����Mr+c���i}t|�����}|D�]��|�}t�ttf��sJ�d}|}t���D]�\}}|t
���dz
krj|�||�d��tj
���}t�fd�|D����r|�d���|�<��t�d|dz���}	|�|	��}
|
sE|�||�d������dd�	��x}||	<��|
}��|s
Jd
���i}|�di���
��D]c\}}
d|
vr|
||<|
�d
d��r�%d|
vr)|
jdi|d|
d��d|
d<d|
d<t|||
���dt �|��|_|�ddd���|�ddd���|�di���dd��}|�t+t,�|��t+t.|�di��������|�������dS) N�r)�namer�formatter_classc3�Z�K�|]%}|�ko�|dt����kV��&dSr5)�len)rd�c�methodss  �r)rezapply_parser.<locals>.<genexpr>�sT���������'�\�B�g��>�S��\�\�>�1B�&B������r+�Available commands�r)r�rT)rrzparser is not definedr�r�envvar_onlyFrrrz--jsonrQzreturn data in JSON format)rSrz	--verbosez-v�count)rSr�r��running)r�)�endpointr�r^r~rN)�sorted�keys�
isinstance�tupler<�	enumerater��
add_parserr�argparse�RawDescriptionHelpFormatter�any�add_subparsersrv�updater[r]ri�epilogrVrWrr�r�)�
subparsersr��_subparsers�commandsrhrX�	subparser�ir~�hashable�exists_subparserr^r'r(r�r�s               @r)�apply_parserr��sy����K��f�k�k�m�m�$�$�H��P
�P
�������'�E�4�=�1�1�1�1�1����	�#�G�,�,�"	1�"	1�J�A�w��C��L�L�1�$�$�$�"�-�-� ����F�+�+�$,�$H�.����
�����%�������,2�+@�+@�1�,A�,�,�K��(��!���1�q�5��!1�2�2��#.�?�?�8�#<�#<� �'�1�8A�8L�8L�$�#�Z�Z��/�/�9M�9�9�%�n�*>��n�N�N�O�I��H� 5� 5�
!1�I�I��.�.�.�.�.�v�$&� �!'���H�b�!9�!9�!?�!?�!A�!A�		:�		:��H�g��7�"�"�5<�(��2��;�;�}�e�4�4����7�"�"����E�E���!1�'�(�2C�!D�E�E�E�&+��
�#�(-���%��v�x��9�9�9�9�!�-�-�.F�G�G��
�����\�0L�	�	
�	
�	
�	���K��g��>�>�>��j�j���+�+�/�/�
�y�I�I������\�7�K�@�@�%,�(� �*�*�X�r�2�2�7�7�9�9�&�&�&�&>��	�
	
�
	
�
	
�
	
�GP
�P
r+c��ttttj��|����}t||��dSr5)r6r�rr�SCHEMA_PATHSr�)r�r�r�s   r)�_apply_subparsersr�5s:��
�,�~�c�.>�?�?��F�F�
G�
G�F���V�$�$�$�$�$r+r�)�maxsizec�H�tjdtjz���}|�dd���|�dgd�d�	��|�d
d�d�
��|�d���}t
|d��t|��|S)NzCLI for %s.)�descriptionz--log-configzlogging config filenamer�z--console-log-level)�ERROR�WARNING�INFO�DEBUGz%Level of logging input to the console�r7rz
--remote-addrc�(�t|��r|ndSr5)r�)�ips r)�<lambda>z#create_cli_parser.<locals>.<lambda>Fs��0��4�4�>���$�r+z2Client's IP address for adding it to the whitelist)rrr��root)r��ArgumentParser�Config�NAMErVr�r��_apply_completions_parser)rXr�s  r)�create_cli_parserr�:s���
�
$����1L�
M�
M�
M�F�
����-F��G�G�G�
����5�5�5�
4�����
����
>�
>�
A�����
�&�&�,@�&�A�A�J��j�&�)�)�)��j�)�)�)��Mr+c��ddlm}|�dd���}|�d|d���|�d	�
��dS)Nr)�SUPPORTED_SHELLS�completionsz&Generate shell auto-completion scriptsr��shellz!Shell to generate completions forr�T)�completions_command)�!defence360agent.utils.completionsr�r�rVrW)r�r��completions_parsers   r)r�r�Os|��B�B�B�B�B�B�#�.�.��
5�/�����#�#�� �
0�$����
�#�#��#�=�=�=�=�=r+)%r�r�ry�	functoolsrr�	itertoolsr�typingrrrr	r
r�defence360agent.applicationr� defence360agent.contracts.configr
r��defence360agent.rpc_tools.utilsr�defence360agent.simple_rpcr�defence360agent.utils.clirrr[r]r�r�r�r�r�r�r�r�rNr+r)�<module>r�s�����������
�
�
�
�(�(�(�(�(�(�(�(�������@�@�@�@�@�@�@�@�@�@�@�@�@�@�@�@�+�+�+�+�+�+�;�;�;�;�;�;�:�:�:�:�:�:�0�0�0�0�0�0�8�8�8�8�8�8�M#�M#�M#�M#�M#�M#�M#�M#�`D�D�D�(J
�J
�J
�J
�J
�J
�J
�J
�Z������������S
�S
�S
�l%�%�%�
��1���������(>�>�>�>�>r+defence360agent/utils/__pycache__/parsers.cpython-311.pyc0000644000000000000000000004160300000000000020303 0ustar  �

�4�� �����$�ddlZddlZddlZddlmZmZddlmZddlm	Z	m
Z
mZmZm
Z
mZddlmZddlmZddlmZddlmZdd	lmZGd
�d��Zd�ZGd
�d��Zd�Zd�Zd�Zd�Z d�Z!d�Z"ed���d���Z#d�Z$dS)�N)�	lru_cache�partial)�chain)�Any�Dict�Iterable�Iterator�Mapping�Tuple)�app)�Core)�prepare_schema)�	RpcClient)�EXITCODE_NOT_FOUNDc��eZdZeeeefZd�Ze	defd���Z
e	d���Zdefd�Zdefd�Z
defd�Zdefd�Zdefd	�Zd
�ZdS)�SchemaToArgparsec��||_|�d��|_|�d��|_|�dd��|_|�d��|_|�dd��|_|�d��|_|�dd��|_|�d	��|_	dS)
N�allowed�default�envvarF�help�
positional�rename�required�type)
�	_argument�get�_allowed�_default�_envvar�_help�_positional�_rename�	_required�_type)�self�argument�optionss   �R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/parsers.py�__init__zSchemaToArgparse.__init__s���&���")�+�+�i�"8�"8��
�$�[�[��3�3��
�#�K�K��%�8�8���!�+�+�f�-�-��
�!(���\�5�!A�!A���#�K�K��1�1���&�{�{�:�u�=�=���!�+�+�f�-�-��
�
�
��returnc�Z�|jr|jSd|j�dd��zS)N�--�_�-)r"r�replace�r&s r)�argnamezSchemaToArgparse.argnames2����	"��>�!��d�n�,�,�S�#�6�6�6�6r+c� �tt|���|���|���|���|���|�������}|S�N)�dictr�choicesrr�metavar�nargsr)r&�argparse_optionss  r)r(zSchemaToArgparse.options%si���������������	�	���������
�
����
�
���

�
�	
�	
�� �r+c#�K�d}|jdkr%|js|jr|js|dfV�dS|dfV�dS|jr|js|j�
|dfV�dSdSdS)Nr9�list�+�*�?)r%r"r$r r�r&�options  r)r9zSchemaToArgparse.nargs3s��������:�����#�
"���
"�t�|�
"��c�k�!�!�!�!�!��c�k�!�!�!�!�!�
�
�	�4�<�	�4�=�3H��#�+������	�	�3H�3Hr+c#� K�d|jfV�dS)Nr7)rr2s r)r7zSchemaToArgparse.choices?s�������&�&�&�&�&�&r+c#� K�d|jfV�dS)Nr)r!r2s r)rzSchemaToArgparse.helpBs�����d�j� � � � � � r+c#�K�d}|jr||j���fV�dS|jdkr||j���fV�dSdS)Nr8r<)r#�upperr%rr@s  r)r8zSchemaToArgparse.metavarEst�������<�	1��$�,�,�,�.�.�.�.�.�.�.�.�
�Z�6�
!�
!��$�.�.�.�0�0�0�0�0�0�0�0�"�
!r+c#�lK�|j�&|js!|jdks|jsd|jfV�dSdSdSdS)Nr<r)rr r%r"r2s r)rzSchemaToArgparse.defaultLsZ�����M�%��L�
&���v�%�%�T�-=�%��T�]�*�*�*�*�*�*�	
&�%�%�%�%�%r+c#�bK�|jr|jdkr|js|jsdV�dSdSdSdSdS)Nr<)rT)r$r%r r"r2s r)rzSchemaToArgparse.requiredTsd�����N�	#��
�f�$�$��L�%��$�%�
#�"�"�"�"�"�	#�	#�$�$�$�$�$�$r+N)�__name__�
__module__�__qualname__r	r�strr�
OptionTyper*�propertyr3r(r9r7rr8rr�r+r)rrs
�������%��S��/�*�J�
.�
.�
.��7��7�7�7��X�7�
� � ��X� �
�z�
�
�
�
�'��'�'�'�'�!�j�!�!�!�!�1��1�1�1�1�+��+�+�+�+�#�#�#�#�#r+rc��|�d��dkr�|�d��o|�dd��}|�|���}|�d|�dd	��z|d
���|�d|�dd	��z|d
���|jdi||�d��i��dSt||��}|j|jfi|j��dS)Nr�booleanrrF)rr.r/r0�
store_true)�dest�actionz--no-�store_falserrN)r�add_mutually_exclusive_group�add_argumentr1�set_defaultsrr3r()�parserr'r(r�bool_parser�	converters      r)�schema_to_argparser[_s6���{�{�6���i�'�'��;�;�z�*�*�O�7�;�;�x��3O�3O�/O���9�9�8�9�L�L��� � ��8�#�#�C��-�-�-���	!�	
�	
�	
�
	� � ��h�&�&�s�C�0�0�0�� �	!�	
�	
�	
�
	!�� �F�F�H�g�k�k�)�.D�.D�#E�F�F�F�F�F�$�X�w�7�7�	����I�-�C�C��1B�C�C�C�C�Cr+c	��eZdZedefd���Zed���Zededee	de
e	e	ffd���Zed���Zd	S)
�	EnvParser�envvar_parameter_optionsc���|sdSd��d�d��fd�|���D������S)N�c�@�d|vr|d�d|d��S|dS)Nrrz		rN)r(s r)�
format_argz)EnvParser.format_help.<locals>.format_argys5���� � �!�(�+�B�B����B�B�B��8�$�$r+z
environment variables: 
  {}z
  c3�.�K�|]}�|��V��dSr5rN)�.0r(rbs  �r)�	<genexpr>z(EnvParser.format_help.<locals>.<genexpr>sA���������
�7�#�#������r+)�format�join�values)r^rbs @r)�format_helpzEnvParser.format_helpts|���'�	��2�	%�	%�	%�
1�7�7��K�K�����7�>�>�@�@����
�
�
�
�	
r+c�p�d|vr1	|�d��n#t$r
d|�d|�d�fcYSwxYwdS)N�isascii�asciizerror: �=z  must only contain ascii symbols)�encode�UnicodeEncodeError)r�valuer(s   r)�	_validatezEnvParser._validate�ss������
����W�%�%�%�%��%�
�
�
�N�f�N�N�u�N�N�N�����
�����ts��3�3�environ�excluder,c	�l�i}|���D�]\}}||vr�|d}	||x}	||<|�||	|��x}
rK|�|||
��}t|tj���t	jt����#t$r�d|vr
|d||<Y��|�	d��sY��|�||d�
|����}t|tj���t	jt��Y��wxYw|S)Nr)�filerrz-error: environment variable {} is not defined)�itemsrq�
_format_error�print�sys�stderr�exitr�KeyErrorrrf)�clsrr�commandr^rs�kwargs�	parameterr(�envvar_namerp�err�msgs            r)�parsezEnvParser.parse�sz����":�"@�"@�"B�"B�	1�	1��I�w��G�#�#��!�(�+�K�
1�,3�K�,@�@���y�)�"�-�-��U�G�D�D�D�3�1��+�+��!9�3���C��#�C�J�/�/�/�/��H�/�0�0�0���+�
-�
-�
-���'�'�(/�	�(:�F�9�%��H��{�{�:�.�.���H��'�'��,�C�J�J�#�������c��
�+�+�+�+���+�,�,�,�,�,�
-����,�
s�
B�D1�8D1�AD1�0D1c�~�d�d�|��|�|��|���S)Nz{command}:
{help}

{message}� )r~r�message)rfrgri)r}r~r^r�s    r)rwzEnvParser._format_error�sA��0�7�7��H�H�W�%�%����!9�:�:��8�
�
�	
r+N)
rHrIrJ�staticmethodr
rirq�classmethodrrKrr�rwrNr+r)r]r]ss��������
�g�
�
�
��\�
� ����\���$��$�
�#��$�
�c�3�h��
$�$�$��[�$�L�
�
��[�
�
�
r+r]c�^�	tj|��n#tj$rYdSwxYwdS)NFT)�	ipaddress�IPv4Address�AddressValueError)�addrs r)�is_valid_ipv4_addrr��sE�����d�#�#�#�#���&�����u�u������4s��*�*c#�K�|���D]7\}}||�di���dg��vr||fV��8dS)N�cli�users)rvr)�schema�user�keyrhs    r)�_filter_userr��se�����|�|�~�~�����V��6�:�:�e�R�(�(�,�,�W�b�9�9�9�9��v�+������r+c�>�t|���j|�di|��S)N)�require_svc_is_runningrN)r�cmd)r~�require_rpc�paramss   r)�rpc_endpointr��s9��F�<�9�K�8�8�8�<�g�F���
���r+c�p�i}|D]0}|�dd��}t||d��}|�|||<�1|S)Nr0r/)r1�getattr)�arg_parser_namespace�	argumentsrr'�arg_parser_argumentrps      r)�generate_endpoint_paramsr��sU��
�F��%�%��&�.�.�s�C�8�8���,�.A�4�H�H����$�F�8����Mr+c���i}t|�����}|D�]��|�}t�ttf��sJ�d}|}t���D]�\}}|t
���dz
krj|�||�d��tj
���}t�fd�|D����r|�d���|�<��t�d|dz���}	|�|	��}
|
sE|�||�d������dd�	��x}||	<��|
}��|s
Jd
���i}|�di���
��D]c\}}
d|
vr|
||<|
�d
d��r�%d|
vr)|
jdi|d|
d��d|
d<d|
d<t|||
���dt �|��|_|�ddd���|�ddd���|�di���dd��}|�t+t,�|��t+t.|�di��������|�������dS) N�r)�namer�formatter_classc3�Z�K�|]%}|�ko�|dt����kV��&dSr5)�len)rd�c�methodss  �r)rezapply_parser.<locals>.<genexpr>�sT���������'�\�B�g��>�S��\�\�>�1B�&B������r+�Available commands�r)r�rT)rrzparser is not definedr�r�envvar_onlyFrrrz--jsonrQzreturn data in JSON format)rSrz	--verbosez-v�count)rSr�r��running)r�)�endpointr�r^r~rN)�sorted�keys�
isinstance�tupler<�	enumerater��
add_parserr�argparse�RawDescriptionHelpFormatter�any�add_subparsersrv�updater[r]ri�epilogrVrWrr�r�)�
subparsersr��_subparsers�commandsrhrX�	subparser�ir~�hashable�exists_subparserr^r'r(r�r�s               @r)�apply_parserr��sy����K��f�k�k�m�m�$�$�H��P
�P
�������'�E�4�=�1�1�1�1�1����	�#�G�,�,�"	1�"	1�J�A�w��C��L�L�1�$�$�$�"�-�-� ����F�+�+�$,�$H�.����
�����%�������,2�+@�+@�1�,A�,�,�K��(��!���1�q�5��!1�2�2��#.�?�?�8�#<�#<� �'�1�8A�8L�8L�$�#�Z�Z��/�/�9M�9�9�%�n�*>��n�N�N�O�I��H� 5� 5�
!1�I�I��.�.�.�.�.�v�$&� �!'���H�b�!9�!9�!?�!?�!A�!A�		:�		:��H�g��7�"�"�5<�(��2��;�;�}�e�4�4����7�"�"����E�E���!1�'�(�2C�!D�E�E�E�&+��
�#�(-���%��v�x��9�9�9�9�!�-�-�.F�G�G��
�����\�0L�	�	
�	
�	
�	���K��g��>�>�>��j�j���+�+�/�/�
�y�I�I������\�7�K�@�@�%,�(� �*�*�X�r�2�2�7�7�9�9�&�&�&�&>��	�
	
�
	
�
	
�
	
�GP
�P
r+c��ttttj��|����}t||��dSr5)r6r�rr�SCHEMA_PATHSr�)r�r�r�s   r)�_apply_subparsersr�5s:��
�,�~�c�.>�?�?��F�F�
G�
G�F���V�$�$�$�$�$r+r�)�maxsizec�H�tjdtjz���}|�dd���|�dgd�d�	��|�d
d�d�
��|�d���}t
|d��t|��|S)NzCLI for %s.)�descriptionz--log-configzlogging config filenamer�z--console-log-level)�ERROR�WARNING�INFO�DEBUGz%Level of logging input to the console�r7rz
--remote-addrc�(�t|��r|ndSr5)r�)�ips r)�<lambda>z#create_cli_parser.<locals>.<lambda>Fs��0��4�4�>���$�r+z2Client's IP address for adding it to the whitelist)rrr��root)r��ArgumentParser�Config�NAMErVr�r��_apply_completions_parser)rXr�s  r)�create_cli_parserr�:s���
�
$����1L�
M�
M�
M�F�
����-F��G�G�G�
����5�5�5�
4�����
����
>�
>�
A�����
�&�&�,@�&�A�A�J��j�&�)�)�)��j�)�)�)��Mr+c��ddlm}|�dd���}|�d|d���|�d	�
��dS)Nr)�SUPPORTED_SHELLS�completionsz&Generate shell auto-completion scriptsr��shellz!Shell to generate completions forr�T)�completions_command)�!defence360agent.utils.completionsr�r�rVrW)r�r��completions_parsers   r)r�r�Os|��B�B�B�B�B�B�#�.�.��
5�/�����#�#�� �
0�$����
�#�#��#�=�=�=�=�=r+)%r�r�ry�	functoolsrr�	itertoolsr�typingrrrr	r
r�defence360agent.applicationr� defence360agent.contracts.configr
r��defence360agent.rpc_tools.utilsr�defence360agent.simple_rpcr�defence360agent.utils.clirrr[r]r�r�r�r�r�r�r�r�rNr+r)�<module>r�s�����������
�
�
�
�(�(�(�(�(�(�(�(�������@�@�@�@�@�@�@�@�@�@�@�@�@�@�@�@�+�+�+�+�+�+�;�;�;�;�;�;�:�:�:�:�:�:�0�0�0�0�0�0�8�8�8�8�8�8�M#�M#�M#�M#�M#�M#�M#�M#�`D�D�D�(J
�J
�J
�J
�J
�J
�J
�J
�Z������������S
�S
�S
�l%�%�%�
��1���������(>�>�>�>�>r+defence360agent/utils/__pycache__/resource_limits.cpython-311.opt-1.pyc0000644000000000000000000000760500000000000022777 0ustar  �

�W�����
��ddlZddlZddlmZddlmZddlmZddlm	Z	ddl
mZeje
��ZdZed��Zed	��ZGd
�de��Zdefd
�Zde	ededededejjf
d�Zdefd�Zdefd�ZdS)�N)�Enum)�fsdecode)�Path)�List)�
OsReleaseInfoz/usr/libexec/run-with-intensityz/usr/sbin/lvectlz/proc/lve/listc��eZdZdZdZdZdS)�LimitsMethod�nice�lve�cgroupsN)�__name__�
__module__�__qualname__�NICE�LVE�CGROUPS���Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/resource_limits.pyr	r	s�������D�
�C��G�G�Grr	�returnc���K�tjtdtjjtjj����d{V��}|����d{V��\}}t
|�����}|dkrtj	S|dkrtj
S|dkrtjStd�
|t
|����������)z6Returns limit method, used in run-with-intensity tool.�show)�stdout�stderrNr
rrz>Parsing of used limitation method failed
stdout: {}
stderr: {})�asyncio�create_subprocess_exec�RUN_WITH_INTENSITY�
subprocess�PIPE�communicater�stripr	rrr�LookupError�format)�procrrs   r�get_current_methodr%s�����/����!�&��!�&�	���������D� �+�+�-�-�-�-�-�-�-�-�N�F�F�
�f�
�
�
#�
#�
%�
%�F�
����� � �
�������
�����#�#�
�J�	�����(�(�.�.�0�0�	1�	1���r�cmd�key�
intensity_cpu�intensity_ioc��K�tddt|��dt|��g}|�d|g��tj||zi|���d{V��S)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�str�extendrr)r&r'r(r)�subprocess_kwargs�
limits_cmds      r�create_subprocessr02s�����$	�
���M�����L���
�J����w��n�%�%�%��/�
�s�
��0���������rc�Z�t���otj��S)z1Checks that LVE-utils is active resource limiter.)�PROC_LVE_LIST_PATH�existsr�
is_cloudlinuxrrr�
is_lve_activer5Rs$���$�$�&�&�H�=�+F�+H�+H�Hrc�4�t���S)z#Checks that LVE-utils is installed.)�LVECTL_BIN_PATHr3rrr�
has_lvectlr8Ys���!�!�#�#�#r)r�logging�enumr�osr�pathlibr�typingr�defence360agent.utilsr�	getLoggerr
�loggerrr7r2r	r%r,�intr�Processr0�boolr5r8rrr�<module>rDs�����������������������������������/�/�/�/�/�/�	��	�8�	$�	$��7���$�)�*�*���T�*�+�+�������4�����,�����0�	
�c���	�����	�
���
����@I�t�I�I�I�I�$�D�$�$�$�$�$�$rdefence360agent/utils/__pycache__/resource_limits.cpython-311.pyc0000644000000000000000000000760500000000000022040 0ustar  �

�W�����
��ddlZddlZddlmZddlmZddlmZddlm	Z	ddl
mZeje
��ZdZed��Zed	��ZGd
�de��Zdefd
�Zde	ededededejjf
d�Zdefd�Zdefd�ZdS)�N)�Enum)�fsdecode)�Path)�List)�
OsReleaseInfoz/usr/libexec/run-with-intensityz/usr/sbin/lvectlz/proc/lve/listc��eZdZdZdZdZdS)�LimitsMethod�nice�lve�cgroupsN)�__name__�
__module__�__qualname__�NICE�LVE�CGROUPS���Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/resource_limits.pyr	r	s�������D�
�C��G�G�Grr	�returnc���K�tjtdtjjtjj����d{V��}|����d{V��\}}t
|�����}|dkrtj	S|dkrtj
S|dkrtjStd�
|t
|����������)z6Returns limit method, used in run-with-intensity tool.�show)�stdout�stderrNr
rrz>Parsing of used limitation method failed
stdout: {}
stderr: {})�asyncio�create_subprocess_exec�RUN_WITH_INTENSITY�
subprocess�PIPE�communicater�stripr	rrr�LookupError�format)�procrrs   r�get_current_methodr%s�����/����!�&��!�&�	���������D� �+�+�-�-�-�-�-�-�-�-�N�F�F�
�f�
�
�
#�
#�
%�
%�F�
����� � �
�������
�����#�#�
�J�	�����(�(�.�.�0�0�	1�	1���r�cmd�key�
intensity_cpu�intensity_ioc��K�tddt|��dt|��g}|�d|g��tj||zi|���d{V��S)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�str�extendrr)r&r'r(r)�subprocess_kwargs�
limits_cmds      r�create_subprocessr02s�����$	�
���M�����L���
�J����w��n�%�%�%��/�
�s�
��0���������rc�Z�t���otj��S)z1Checks that LVE-utils is active resource limiter.)�PROC_LVE_LIST_PATH�existsr�
is_cloudlinuxrrr�
is_lve_activer5Rs$���$�$�&�&�H�=�+F�+H�+H�Hrc�4�t���S)z#Checks that LVE-utils is installed.)�LVECTL_BIN_PATHr3rrr�
has_lvectlr8Ys���!�!�#�#�#r)r�logging�enumr�osr�pathlibr�typingr�defence360agent.utilsr�	getLoggerr
�loggerrr7r2r	r%r,�intr�Processr0�boolr5r8rrr�<module>rDs�����������������������������������/�/�/�/�/�/�	��	�8�	$�	$��7���$�)�*�*���T�*�+�+�������4�����,�����0�	
�c���	�����	�
���
����@I�t�I�I�I�I�$�D�$�$�$�$�$�$rdefence360agent/utils/__pycache__/safe_fileops.cpython-311.opt-1.pyc0000644000000000000000000004413700000000000022227 0ustar  �

YȈN�x'�	�@�UddlZddlZddlZddlZddlZddlZddlZddlZddlZddl	m
Z
ddlmZm
Z
ddlmZddlmZmZmZddlmZejZejejzejzZeje��Ze��Z ee
e!d<dej"fd	�Z#d)d�Z$ej%e$��d�Z&Gd
�de'��Z(de)d
dfd�Z*d�Z+d*d�Z,de)fd�Z-de)de)fd�Z.de)de)fd�Z/de)fd�Z0e,ej1��Z1e,ej2��Z2ed+d���Z3ed���Z4ede)fd���Z5e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)�contextmanager�suppress)�chain)�Set�Tuple�Union)�utils�
_active_pools�loopc���K�td���}t�|��	|j|g|�R��d{V��		|�d���t�|��S#t�|��wxYw#	|�d���t�|��w#t�|��wxYwxYw)N�)�max_workersF)�wait)rr�add�run_in_executor�shutdown�discard)r�args�pools   �W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/safe_fileops.py�_run_in_fresh_executorrs������1�-�-�-�D����d����(�)�T�)�$�6��6�6�6�6�6�6�6�6�6�6�	(��M�M�u�M�%�%�%��!�!�$�'�'�'�'��M�!�!�$�'�'�'�'�����	(��M�M�u�M�%�%�%��!�!�$�'�'�'�'��M�!�!�$�'�'�'�'������s/�B�A4�4B�C%�C�+C%�C"�"C%�returnc��tt��D]L}	|�dd����#t$r%}t�d|��Yd}~�Ed}~wwxYwt���dS)z�Shutdown all tracked ProcessPoolExecutors.

    Should be called during agent shutdown to ensure clean process termination.
    FT)r�cancel_futuresz+Error shutting down ProcessPoolExecutor: %sN)�listrr�	Exception�logger�warning�clear)r�es  r�shutdown_process_poolsr")s���
�]�#�#�M�M��	M��M�M�u�T�M�:�:�:�:���	M�	M�	M��N�N�H�!�L�L�L�L�L�L�L�L�����	M�����������s�0�
A�A�Ac��tjg��tj|��tj|��||�S�N)�os�	setgroups�setgid�setuid)�fun�uid�gidrs    r�dropr,:s8���L������I�c�N�N�N��I�c�N�N�N��3��:��c��eZdZdS)�UnsafeFileOperationN)�__name__�
__module__�__qualname__�r-rr/r/As�������Dr-r/�pathc��tj|��}tj|j��sYt
�d|tj|j����tj|��td|�����dS)z�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.
    z:Identity file %s is not a regular file (mode=%s), removingz#Removed non-regular identity file: N)
r%�lstat�stat�S_ISREG�st_moderr�filemode�unlink�FileNotFoundError)r4�sts  r�ensure_regular_filer>Es���
��$���B��<��
�#�#�N����H���M�"�*�%�%�	
�	
�	
�
	�	�$����� L�d� L� L�M�M�M�N�Nr-c��tjt|����}|jt	j��krt
dt|��z���dS)Nz The file belongs to admin user: T)r%r7�str�st_uidr
�get_min_uidr/)�filer=s  r�check_non_admin_filerDWsT��	���T���	�	�B�	�y�5�$�&�&�&�&�!�.��T���:�
�
�	
��4r-Fc����fd�}|S)Nc�P���tj���dd���fd�
��}|S)N)rc��B�K�tj�|��s�
std|z���t	j|��}t
t|j��|g��}�
rt|j��}|D]I}tj	t|����}|jdkr|jdkr|j|j}}n �Jtdt|��z���|ptj��}t!|t"�	|||g|�R��d{V��S)NzNo such file or directory: rz"Unsafe file operation under root: )r%r4�existsr<�pathlib�Pathr�reversed�parentsr7r@rA�st_gidr/�asyncio�get_event_looprr,)�filenamerrr4�paths�pr=r*r+r)�
missing_oks         ��r�wrapperz$safe.<locals>._safe.<locals>.wrapperbsQ������7�>�>�(�+�+�
�J�
�'�1�H�<�����<��)�)�D��(�4�<�0�0�4�&�9�9�E��
/� ���.�.���
�
���W�S��V�V�_�_���9��>�>�b�i�1�n�n�!�y�"�)��C��E��)�8�3�t�9�9�D�����3�7�1�3�3�D�/�������
�����������
r-)�	functools�wraps)r)rTrSs` �r�_safezsafe.<locals>._safeasK����	���	�	�04�	�	�	�	�	�	�	�
�	�	�>�r-r3)rSrWs` r�saferX`s$���!�!�!�!�!�F�Lr-rPc�R�tj|�����dSr$)rIrJ�touch�rPs r�_touchr\�s$���L���� � �"�"�"�"�"r-�datac�T�tj|���|��dSr$)rIrJ�
write_text�rPr]s  r�_write_textra�s&���L����%�%�d�+�+�+�+�+r-c��bK�td���t��||���d{V��S�NT)rS)rXrar`s  rr_r_�s@����3�&���&�&�&�{�3�3�H�d�C�C�C�C�C�C�C�C�Cr-c��`K�td���t��|���d{V��Src)rXr\r[s rrZrZ�s>����.�&���&�&�&�v�.�.�x�8�8�8�8�8�8�8�8�8r-Tc#�LK�d|vrtd���t||��5}tj|�����}tj|��}tjd|�������}t|��}||ks|j	|j
krtd|�����|rEtj|j
��tj|��jvrtd|�d����|V�ddd��dS#1swxYwYdS)N�wz'w' mode is not permittedz/proc/self/fd/zUnable to safely read z. File is not in user homedir)r/�openr%�fstat�fileno�pwd�getpwnam�readlinkr@rA�pw_uidrIrJ�pw_dirrL)	rP�mode�user�respect_homedir�fr=�passwd�	real_path�filename_strs	         r�safe_open_filerv�sz����
�d�{�{�!�"=�>�>�>�	
�h��	�	���
�X�a�h�h�j�j�
!�
!����d�#�#���K� =������ =� =�>�>�	��8�}�}��
�I�%�%�2�9��
�+E�+E�%�&M�|�&M�&M�N�N�N�
�	���V�]�+�+��<��-�-�5�6�6�&�.��.�.�.���
�����-��������������������s�C&D�D� Dc/�BK�tj|i|��}	|V�tt��5tj|��ddd��dS#1swxYwYdS#tt��5tj|��ddd��w#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%rgr�OSError�close)r�kwargs�fds   r�open_fdr|�s!����
��$�	!�&�	!�	!�B������
�g�
�
�	�	��H�R�L�L�L�	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	�	��X�g�
�
�	�	��H�R�L�L�L�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	���s@�A�A�A�A�B�1B�B�B�B�B�B�namec/��K�t|g|�Rdtji|��5}tjd�|����}||krtd���|V�ddd��dS#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_DIRECTORYrl�formatr/)r}rrz�dir_fd�reals     r�
opendir_fdr��s�����
��	=��	=�	=�	=�B�N�	=�f�	=�	=����{�-�4�4�V�<�<�=�=���4�<�<�%�&M�N�N�N�����	��������������������s�AA-�-A1�4A1rrc	#�K�d}t|t��r�tt��5t	j||���}t	j||jt
jzt
j	z|���ddd��n#1swxYwYt	j
|||���}t||���5}|pt	j|��|_	|V�|rGtt��5t	j||j���ddd��n#1swxYwYnO#|rHtt��5t	j||j���ddd��w#1swxYwYwwxYwddd��dS#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
    N�r�)ror�)rr��ro)�
isinstancer@rrxr%r7�chmodr9�S_IRUSR�S_IWUSRrgr=)rrr�rror=�fos      r�	open_fobjr��s�����
�B��!�S���	3�
�g�
�
�	�	����6�*�*�*�B��H���
�T�\�1�D�L�@��
�
�
�
�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�
�G�A�U�6�2�2�2��	
�a�d�	�	�	�1�r�� �b�g�a�j�j���	1��H�H�H��
1��g�&�&�1�1��H�Q�R�Z�0�0�0�0�1�1�1�1�1�1�1�1�1�1�1����1�1�1�1����
1��g�&�&�1�1��H�Q�R�Z�0�0�0�0�1�1�1�1�1�1�1�1�1�1�1����1�1�1�1�1�
1����1�1�1�1�1�1�1�1�1�1�1�1����1�1�1�1�1�1s��A
B�B�B�:F�D%�F�1D�
F�D	�F� D	�!F�%E1�<E$	�E1�$E(
�(E1�+E(
�,E1�1F�F�Fr�r�is_safec#�K�|r3t|||���5}|dfV�ddd��dS#1swxYwYdS||fV�dS)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
    )r�rN)r|)r}r�rr�r{s     r�
safe_tupler�s�������
�T�&��
6�
6�
6�	�"��d�(�N�N�N�	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	�	��F�l�����s�+�/�/�src�dstc�\�|\}}|\}}t|rdntjz}	t||td���5}
t|||	d���5}|r|d��tj|
|��t|t��r2tj	|�
��|
jj���ddd��n#1swxYwY|r=t|t��r(|r|d��tj
||���ddd��dS#1swxYwYdS)Nr�rb)r�rro�wbr�rr�)�W_FLAGSr%�O_EXCLr��R_FLAGS�shutil�copyfileobjr�r@r�rir=r9r;)r�r��
src_unlink�
dst_overwrite�racecall�src_f�
src_dir_fd�dst_f�
dst_dir_fd�w_flags�src_fo�dst_fos            r�_mover�
s�����E�:���E�:��m�:�����;�G�	�
�j��d�
�
�
�0�	�
��*�G�$�
�
�
�		B�
��
�����
�
�
���v�v�.�.�.��%��%�%�
B��������v�y�/@�A�A�A�A�		B�		B�		B�		B�		B�		B�		B�		B�		B�		B�		B����		B�		B�		B�		B��	0�*�U�C�0�0�	0��
�����
�
�
��I�e�J�/�/�/�/�%0�0�0�0�0�0�0�0�0�0�0�0����0�0�0�0�0�0s7�D!�A/C
�>D!�
C	�D!�C	�AD!�!D%�(D%c��zK�tj�|��\}}tj�|��\}	}
t|��5}t|	��5}t	||t
|��5}
t	|
|t|��5}tj||���}tj	��}t|tt|j
|j|
||||�
�
�d{V��|r*|r(|r|d��tj||���|r>tj|
|j
|j|���tj|
|j|���ddd��n#1swxYwYddd��n#1swxYwYddd��n#1swxYwYddd��dS#1swxYwYdS)Nr�r)r%r4�splitr�r�r�r�r7rNrOrr,r�rArMr;�chownr�r9)r�r��safe_src�safe_dstr�r�r��src_dir�src_name�dst_dir�dst_namer�r��	src_tuple�	dst_tuple�src_strs                 r�	safe_mover�.s������
�
�c�*�*��G�X���
�
�c�*�*��G�X�	�G�	�	�B�
�J��-�-�B�	�Z��*�g�x���B�
�J��*�g�x���	B�
����*�5�5�5���%�'�'��$�����M��M������
�
�	
�	
�	
�	
�	
�	
�	
��	3�(�	3��
�����
�
�
��I�h�z�2�2�2�2��	B��H�X�v�}�f�m�J�O�O�O�O��H�X�v�~�j�A�A�A�A�=B�B�B�B�B�B�B�B�B�B�B����B�B�B�B�B�B�B�B�B�B�B�B�B�B�B����B�B�B�B�B�B�B�B�B�B�B�B�B�B�B����B�B�B�B�B�B�B�B�B�B�B�B�B�B�B�B����B�B�B�B�B�Bs�F0�&F�>F�CE*	�F�*E.
�.F�1E.
�2F�5F�F�F�F�	F�F0�F	�F0�F	� F0�0F4�7F4)rN)F)T)NrN)FFTFN)<rN�atexitrU�loggingr%rIrjr�r7�concurrent.futuresr�
contextlibrr�	itertoolsr�typingrrr	�defence360agentr
�O_RDONLYr��O_TRUNC�O_CREAT�O_WRONLYr��	getLoggerr0r�setr�__annotations__�AbstractEventLooprr"�registerr,rr/r@r>rDrXr\rar_rZr�r;rvr|r��intr��boolr�r�r�r3r-r�<module>r�s��������
�
�
�
���������	�	�	�	�����
�
�
�
�
�
�
�
�����2�2�2�2�2�2�/�/�/�/�/�/�/�/�������$�$�$�$�$�$�$�$�$�$�!�!�!�!�!�!�
�+��
�*�r�z�
!�B�K�
/��	��	�8�	$�	$��+.�#�%�%�
�s�&�'�/�/�/�	(�w�'@�	(�	(�	(�	(�
�
�
�
����&�'�'�'����	�	�	�	�	�)�	�	�	�N�c�N�d�N�N�N�N�$���$�$�$�$�N#�S�#�#�#�#�,�#�,�S�,�,�,�,�D�s�D�#�D�D�D�D�9�#�9�9�9�9�	
��R�X����	
��b�i�����������8�������
�S�
�
�
���
� � 1� 1��s�C�x�� 1� 1� 1��� 1�F�	�S�	�#�	�c�	�D�	�	�	���	�0�	�u�S�#�X���c�4�i� 0�0�	1�0�	�u�S�#�X���c�4�i� 0�0�	1�0�0�0�0�H�
���
�*B�*B�	�*B�	�*B�*B�*B�*B�*B�*Br-defence360agent/utils/__pycache__/safe_fileops.cpython-311.pyc0000644000000000000000000004413700000000000021270 0ustar  �

YȈN�x'�	�@�UddlZddlZddlZddlZddlZddlZddlZddlZddlZddl	m
Z
ddlmZm
Z
ddlmZddlmZmZmZddlmZejZejejzejzZeje��Ze��Z ee
e!d<dej"fd	�Z#d)d�Z$ej%e$��d�Z&Gd
�de'��Z(de)d
dfd�Z*d�Z+d*d�Z,de)fd�Z-de)de)fd�Z.de)de)fd�Z/de)fd�Z0e,ej1��Z1e,ej2��Z2ed+d���Z3ed���Z4ede)fd���Z5e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)�contextmanager�suppress)�chain)�Set�Tuple�Union)�utils�
_active_pools�loopc���K�td���}t�|��	|j|g|�R��d{V��		|�d���t�|��S#t�|��wxYw#	|�d���t�|��w#t�|��wxYwxYw)N�)�max_workersF)�wait)rr�add�run_in_executor�shutdown�discard)r�args�pools   �W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/safe_fileops.py�_run_in_fresh_executorrs������1�-�-�-�D����d����(�)�T�)�$�6��6�6�6�6�6�6�6�6�6�6�	(��M�M�u�M�%�%�%��!�!�$�'�'�'�'��M�!�!�$�'�'�'�'�����	(��M�M�u�M�%�%�%��!�!�$�'�'�'�'��M�!�!�$�'�'�'�'������s/�B�A4�4B�C%�C�+C%�C"�"C%�returnc��tt��D]L}	|�dd����#t$r%}t�d|��Yd}~�Ed}~wwxYwt���dS)z�Shutdown all tracked ProcessPoolExecutors.

    Should be called during agent shutdown to ensure clean process termination.
    FT)r�cancel_futuresz+Error shutting down ProcessPoolExecutor: %sN)�listrr�	Exception�logger�warning�clear)r�es  r�shutdown_process_poolsr")s���
�]�#�#�M�M��	M��M�M�u�T�M�:�:�:�:���	M�	M�	M��N�N�H�!�L�L�L�L�L�L�L�L�����	M�����������s�0�
A�A�Ac��tjg��tj|��tj|��||�S�N)�os�	setgroups�setgid�setuid)�fun�uid�gidrs    r�dropr,:s8���L������I�c�N�N�N��I�c�N�N�N��3��:��c��eZdZdS)�UnsafeFileOperationN)�__name__�
__module__�__qualname__�r-rr/r/As�������Dr-r/�pathc��tj|��}tj|j��sYt
�d|tj|j����tj|��td|�����dS)z�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.
    z:Identity file %s is not a regular file (mode=%s), removingz#Removed non-regular identity file: N)
r%�lstat�stat�S_ISREG�st_moderr�filemode�unlink�FileNotFoundError)r4�sts  r�ensure_regular_filer>Es���
��$���B��<��
�#�#�N����H���M�"�*�%�%�	
�	
�	
�
	�	�$����� L�d� L� L�M�M�M�N�Nr-c��tjt|����}|jt	j��krt
dt|��z���dS)Nz The file belongs to admin user: T)r%r7�str�st_uidr
�get_min_uidr/)�filer=s  r�check_non_admin_filerDWsT��	���T���	�	�B�	�y�5�$�&�&�&�&�!�.��T���:�
�
�	
��4r-Fc����fd�}|S)Nc�P���tj���dd���fd�
��}|S)N)rc��B�K�tj�|��s�
std|z���t	j|��}t
t|j��|g��}�
rt|j��}|D]I}tj	t|����}|jdkr|jdkr|j|j}}n �Jtdt|��z���|ptj��}t!|t"�	|||g|�R��d{V��S)NzNo such file or directory: rz"Unsafe file operation under root: )r%r4�existsr<�pathlib�Pathr�reversed�parentsr7r@rA�st_gidr/�asyncio�get_event_looprr,)�filenamerrr4�paths�pr=r*r+r)�
missing_oks         ��r�wrapperz$safe.<locals>._safe.<locals>.wrapperbsQ������7�>�>�(�+�+�
�J�
�'�1�H�<�����<��)�)�D��(�4�<�0�0�4�&�9�9�E��
/� ���.�.���
�
���W�S��V�V�_�_���9��>�>�b�i�1�n�n�!�y�"�)��C��E��)�8�3�t�9�9�D�����3�7�1�3�3�D�/�������
�����������
r-)�	functools�wraps)r)rTrSs` �r�_safezsafe.<locals>._safeasK����	���	�	�04�	�	�	�	�	�	�	�
�	�	�>�r-r3)rSrWs` r�saferX`s$���!�!�!�!�!�F�Lr-rPc�R�tj|�����dSr$)rIrJ�touch�rPs r�_touchr\�s$���L���� � �"�"�"�"�"r-�datac�T�tj|���|��dSr$)rIrJ�
write_text�rPr]s  r�_write_textra�s&���L����%�%�d�+�+�+�+�+r-c��bK�td���t��||���d{V��S�NT)rS)rXrar`s  rr_r_�s@����3�&���&�&�&�{�3�3�H�d�C�C�C�C�C�C�C�C�Cr-c��`K�td���t��|���d{V��Src)rXr\r[s rrZrZ�s>����.�&���&�&�&�v�.�.�x�8�8�8�8�8�8�8�8�8r-Tc#�LK�d|vrtd���t||��5}tj|�����}tj|��}tjd|�������}t|��}||ks|j	|j
krtd|�����|rEtj|j
��tj|��jvrtd|�d����|V�ddd��dS#1swxYwYdS)N�wz'w' mode is not permittedz/proc/self/fd/zUnable to safely read z. File is not in user homedir)r/�openr%�fstat�fileno�pwd�getpwnam�readlinkr@rA�pw_uidrIrJ�pw_dirrL)	rP�mode�user�respect_homedir�fr=�passwd�	real_path�filename_strs	         r�safe_open_filerv�sz����
�d�{�{�!�"=�>�>�>�	
�h��	�	���
�X�a�h�h�j�j�
!�
!����d�#�#���K� =������ =� =�>�>�	��8�}�}��
�I�%�%�2�9��
�+E�+E�%�&M�|�&M�&M�N�N�N�
�	���V�]�+�+��<��-�-�5�6�6�&�.��.�.�.���
�����-��������������������s�C&D�D� Dc/�BK�tj|i|��}	|V�tt��5tj|��ddd��dS#1swxYwYdS#tt��5tj|��ddd��w#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%rgr�OSError�close)r�kwargs�fds   r�open_fdr|�s!����
��$�	!�&�	!�	!�B������
�g�
�
�	�	��H�R�L�L�L�	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	�	��X�g�
�
�	�	��H�R�L�L�L�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	���s@�A�A�A�A�B�1B�B�B�B�B�B�namec/��K�t|g|�Rdtji|��5}tjd�|����}||krtd���|V�ddd��dS#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_DIRECTORYrl�formatr/)r}rrz�dir_fd�reals     r�
opendir_fdr��s�����
��	=��	=�	=�	=�B�N�	=�f�	=�	=����{�-�4�4�V�<�<�=�=���4�<�<�%�&M�N�N�N�����	��������������������s�AA-�-A1�4A1rrc	#�K�d}t|t��r�tt��5t	j||���}t	j||jt
jzt
j	z|���ddd��n#1swxYwYt	j
|||���}t||���5}|pt	j|��|_	|V�|rGtt��5t	j||j���ddd��n#1swxYwYnO#|rHtt��5t	j||j���ddd��w#1swxYwYwwxYwddd��dS#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
    N�r�)ror�)rr��ro)�
isinstancer@rrxr%r7�chmodr9�S_IRUSR�S_IWUSRrgr=)rrr�rror=�fos      r�	open_fobjr��s�����
�B��!�S���	3�
�g�
�
�	�	����6�*�*�*�B��H���
�T�\�1�D�L�@��
�
�
�
�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�
�G�A�U�6�2�2�2��	
�a�d�	�	�	�1�r�� �b�g�a�j�j���	1��H�H�H��
1��g�&�&�1�1��H�Q�R�Z�0�0�0�0�1�1�1�1�1�1�1�1�1�1�1����1�1�1�1����
1��g�&�&�1�1��H�Q�R�Z�0�0�0�0�1�1�1�1�1�1�1�1�1�1�1����1�1�1�1�1�
1����1�1�1�1�1�1�1�1�1�1�1�1����1�1�1�1�1�1s��A
B�B�B�:F�D%�F�1D�
F�D	�F� D	�!F�%E1�<E$	�E1�$E(
�(E1�+E(
�,E1�1F�F�Fr�r�is_safec#�K�|r3t|||���5}|dfV�ddd��dS#1swxYwYdS||fV�dS)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
    )r�rN)r|)r}r�rr�r{s     r�
safe_tupler�s�������
�T�&��
6�
6�
6�	�"��d�(�N�N�N�	�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	�	�	��F�l�����s�+�/�/�src�dstc�\�|\}}|\}}t|rdntjz}	t||td���5}
t|||	d���5}|r|d��tj|
|��t|t��r2tj	|�
��|
jj���ddd��n#1swxYwY|r=t|t��r(|r|d��tj
||���ddd��dS#1swxYwYdS)Nr�rb)r�rro�wbr�rr�)�W_FLAGSr%�O_EXCLr��R_FLAGS�shutil�copyfileobjr�r@r�rir=r9r;)r�r��
src_unlink�
dst_overwrite�racecall�src_f�
src_dir_fd�dst_f�
dst_dir_fd�w_flags�src_fo�dst_fos            r�_mover�
s�����E�:���E�:��m�:�����;�G�	�
�j��d�
�
�
�0�	�
��*�G�$�
�
�
�		B�
��
�����
�
�
���v�v�.�.�.��%��%�%�
B��������v�y�/@�A�A�A�A�		B�		B�		B�		B�		B�		B�		B�		B�		B�		B�		B����		B�		B�		B�		B��	0�*�U�C�0�0�	0��
�����
�
�
��I�e�J�/�/�/�/�%0�0�0�0�0�0�0�0�0�0�0�0����0�0�0�0�0�0s7�D!�A/C
�>D!�
C	�D!�C	�AD!�!D%�(D%c��zK�tj�|��\}}tj�|��\}	}
t|��5}t|	��5}t	||t
|��5}
t	|
|t|��5}tj||���}tj	��}t|tt|j
|j|
||||�
�
�d{V��|r*|r(|r|d��tj||���|r>tj|
|j
|j|���tj|
|j|���ddd��n#1swxYwYddd��n#1swxYwYddd��n#1swxYwYddd��dS#1swxYwYdS)Nr�r)r%r4�splitr�r�r�r�r7rNrOrr,r�rArMr;�chownr�r9)r�r��safe_src�safe_dstr�r�r��src_dir�src_name�dst_dir�dst_namer�r��	src_tuple�	dst_tuple�src_strs                 r�	safe_mover�.s������
�
�c�*�*��G�X���
�
�c�*�*��G�X�	�G�	�	�B�
�J��-�-�B�	�Z��*�g�x���B�
�J��*�g�x���	B�
����*�5�5�5���%�'�'��$�����M��M������
�
�	
�	
�	
�	
�	
�	
�	
��	3�(�	3��
�����
�
�
��I�h�z�2�2�2�2��	B��H�X�v�}�f�m�J�O�O�O�O��H�X�v�~�j�A�A�A�A�=B�B�B�B�B�B�B�B�B�B�B����B�B�B�B�B�B�B�B�B�B�B�B�B�B�B����B�B�B�B�B�B�B�B�B�B�B�B�B�B�B����B�B�B�B�B�B�B�B�B�B�B�B�B�B�B�B����B�B�B�B�B�Bs�F0�&F�>F�CE*	�F�*E.
�.F�1E.
�2F�5F�F�F�F�	F�F0�F	�F0�F	� F0�0F4�7F4)rN)F)T)NrN)FFTFN)<rN�atexitrU�loggingr%rIrjr�r7�concurrent.futuresr�
contextlibrr�	itertoolsr�typingrrr	�defence360agentr
�O_RDONLYr��O_TRUNC�O_CREAT�O_WRONLYr��	getLoggerr0r�setr�__annotations__�AbstractEventLooprr"�registerr,rr/r@r>rDrXr\rar_rZr�r;rvr|r��intr��boolr�r�r�r3r-r�<module>r�s��������
�
�
�
���������	�	�	�	�����
�
�
�
�
�
�
�
�����2�2�2�2�2�2�/�/�/�/�/�/�/�/�������$�$�$�$�$�$�$�$�$�$�!�!�!�!�!�!�
�+��
�*�r�z�
!�B�K�
/��	��	�8�	$�	$��+.�#�%�%�
�s�&�'�/�/�/�	(�w�'@�	(�	(�	(�	(�
�
�
�
����&�'�'�'����	�	�	�	�	�)�	�	�	�N�c�N�d�N�N�N�N�$���$�$�$�$�N#�S�#�#�#�#�,�#�,�S�,�,�,�,�D�s�D�#�D�D�D�D�9�#�9�9�9�9�	
��R�X����	
��b�i�����������8�������
�S�
�
�
���
� � 1� 1��s�C�x�� 1� 1� 1��� 1�F�	�S�	�#�	�c�	�D�	�	�	���	�0�	�u�S�#�X���c�4�i� 0�0�	1�0�	�u�S�#�X���c�4�i� 0�0�	1�0�0�0�0�H�
���
�*B�*B�	�*B�	�*B�*B�*B�*B�*B�*Br-defence360agent/utils/__pycache__/safe_sequence.cpython-311.opt-1.pyc0000644000000000000000000000146200000000000022370 0ustar  �

�-�Q�����ddlZdefd�ZdS)�N�pc�z�	|���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
    )�encode�UnicodeEncodeError�os�fsencode)rs �X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/safe_sequence.py�pathr
sL���	���
�
�
�
�������{�1�~�~��������
�Hs��8�8)r�strr
��r	�<module>rs3��	�	�	�	�
�C�
�
�
�
�
�
r
defence360agent/utils/__pycache__/safe_sequence.cpython-311.pyc0000644000000000000000000000146200000000000021431 0ustar  �

�-�Q�����ddlZdefd�ZdS)�N�pc�z�	|���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
    )�encode�UnicodeEncodeError�os�fsencode)rs �X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/safe_sequence.py�pathr
sL���	���
�
�
�
�������{�1�~�~��������
�Hs��8�8)r�strr
��r	�<module>rs3��	�	�	�	�
�C�
�
�
�
�
�
r
defence360agent/utils/__pycache__/serialization.cpython-311.opt-1.pyc0000644000000000000000000001306300000000000022437 0ustar  �

2F���]����dZddlZddlZddlZddlZddlZddlmZddlm	Z	m
Z
mZeje
��Zde	de	fd�Zd�Zd	ed
efd�Zdd�d	ed
ee
effd�ZdS)zLJSON persistence helpers for small agent state files (no pickle at runtime).�N)�iscoroutinefunction)�Any�Callable�Union�obj�returnc��t|tj��rd�|D��St|t��rd�|���D��St|t
tf��rd�|D��S|S)Nc�,�g|]}t|����S���_to_jsonable��.0�items  �X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/serialization.py�
<listcomp>z _to_jsonable.<locals>.<listcomp>� ��3�3�3�t��T�"�"�3�3�3�c�4�i|]\}}|t|����Srr)r�k�vs   r�
<dictcomp>z _to_jsonable.<locals>.<dictcomp>s$��;�;�;�t�q�!��<��?�?�;�;�;rc�,�g|]}t|����Srrrs  rrz _to_jsonable.<locals>.<listcomp>rr)�
isinstance�collections�deque�dict�items�list�tuple)rs rr
r
s����#�{�(�)�)�4�3�3�s�3�3�3�3��#�t���<�;�;�s�y�y�{�{�;�;�;�;��#��e�}�%�%�4�3�3�s�3�3�3�3��Jrc��tjt|����}d�|��}t	|dd���5}|�|��ddd��n#1swxYwYt
j||��dS)z-Atomically write ``obj`` to ``path`` as JSON.z{}.tmp�w�utf-8��encodingN)�json�dumpsr
�format�open�write�os�replace)�pathr�payload�tmpr"s     r�_dumpr0s����j��c�*�*�+�+�G�
�/�/�$�
�
�C�	
�c�3��	)�	)�	)��Q�	��������������������������J�s�D�����s�	A+�+A/�2A/r-�attrc������fd�}|S)zZDecorator: after the wrapped method runs, persist ``self.<attr>``
    to ``path`` as JSON.c����tj������fd���}tj������fd���}t���r|S|S)Nc����|g|�Ri|��}t|���}t�d|���t�|��|S�NzWrite %r to %r��getattr�logger�debugr0��self�args�kwargs�resultrr1�fr-s     ���r�wrapperz2serialize_attr.<locals>.decorator.<locals>.wrapper&s]����Q�t�-�d�-�-�-�f�-�-�F��$��%�%�C��L�L�)�3��5�5�5��$������Mrc���K��|g|�Ri|���d{V��}t|���}t�d|���t�|��|Sr5r6r:s     ���r�
async_wrapperz8serialize_attr.<locals>.decorator.<locals>.async_wrapper.ss������1�T�3�D�3�3�3�F�3�3�3�3�3�3�3�3�F��$��%�%�C��L�L�)�3��5�5�5��$������Mr)�	functools�wrapsr)r?r@rBr1r-s`  ��r�	decoratorz!serialize_attr.<locals>.decorator%s�����	���	�	�	�	�	�	�	�	�
�	�	�
���	�	�	�	�	�	�	�	�
�	�	��q�!�!�	!� � ��rr)r-r1rEs`` r�serialize_attrrF!s*����������*�r)�fallbackrGc��	t|dd���5}tj|��}ddd��n#1swxYwYt|t��rtj|��S|S#t$rt�	d|��Yn1t$r%}t�d|��Yd}~nd}~wwxYwt|��r
|��n|S)z�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).�rr#r$NzCan't find %s to unserializez.Unserialize failed with %r. Returning fallback)
r)r&�loadrrrr�FileNotFoundErrorr8�warning�	Exception�error�callable)r-rGrIr�es     r�unserializerQ=s:��
�
�$��g�
.�
.�
.�	�!��)�A�,�,�C�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	��c�4� � �	*��$�S�)�)�)��
���=�=�=����5�t�<�<�<�<�<��J�J�J����E�q�I�I�I�I�I�I�I�I�����J����"�(�+�+�9�8�8�:�:�:��9s8�A+�5�A+�9�A+�9�A+�+%C�	C�B;�;C)�__doc__rrCr&�loggingr+�asyncior�typingrrr�	getLogger�__name__r8r
r0�strrF�objectrQrrr�<module>rZs��R�R�����������������	�	�	�	�'�'�'�'�'�'�'�'�'�'�'�'�'�'�'�'�	��	�8�	$�	$���c��c���������C��s�����8CG�:�:�:��:��h��.>�(?�:�:�:�:�:�:rdefence360agent/utils/__pycache__/serialization.cpython-311.pyc0000644000000000000000000001306300000000000021500 0ustar  �

2F���]����dZddlZddlZddlZddlZddlZddlmZddlm	Z	m
Z
mZeje
��Zde	de	fd�Zd�Zd	ed
efd�Zdd�d	ed
ee
effd�ZdS)zLJSON persistence helpers for small agent state files (no pickle at runtime).�N)�iscoroutinefunction)�Any�Callable�Union�obj�returnc��t|tj��rd�|D��St|t��rd�|���D��St|t
tf��rd�|D��S|S)Nc�,�g|]}t|����S���_to_jsonable��.0�items  �X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/serialization.py�
<listcomp>z _to_jsonable.<locals>.<listcomp>� ��3�3�3�t��T�"�"�3�3�3�c�4�i|]\}}|t|����Srr)r�k�vs   r�
<dictcomp>z _to_jsonable.<locals>.<dictcomp>s$��;�;�;�t�q�!��<��?�?�;�;�;rc�,�g|]}t|����Srrrs  rrz _to_jsonable.<locals>.<listcomp>rr)�
isinstance�collections�deque�dict�items�list�tuple)rs rr
r
s����#�{�(�)�)�4�3�3�s�3�3�3�3��#�t���<�;�;�s�y�y�{�{�;�;�;�;��#��e�}�%�%�4�3�3�s�3�3�3�3��Jrc��tjt|����}d�|��}t	|dd���5}|�|��ddd��n#1swxYwYt
j||��dS)z-Atomically write ``obj`` to ``path`` as JSON.z{}.tmp�w�utf-8��encodingN)�json�dumpsr
�format�open�write�os�replace)�pathr�payload�tmpr"s     r�_dumpr0s����j��c�*�*�+�+�G�
�/�/�$�
�
�C�	
�c�3��	)�	)�	)��Q�	��������������������������J�s�D�����s�	A+�+A/�2A/r-�attrc������fd�}|S)zZDecorator: after the wrapped method runs, persist ``self.<attr>``
    to ``path`` as JSON.c����tj������fd���}tj������fd���}t���r|S|S)Nc����|g|�Ri|��}t|���}t�d|���t�|��|S�NzWrite %r to %r��getattr�logger�debugr0��self�args�kwargs�resultrr1�fr-s     ���r�wrapperz2serialize_attr.<locals>.decorator.<locals>.wrapper&s]����Q�t�-�d�-�-�-�f�-�-�F��$��%�%�C��L�L�)�3��5�5�5��$������Mrc���K��|g|�Ri|���d{V��}t|���}t�d|���t�|��|Sr5r6r:s     ���r�
async_wrapperz8serialize_attr.<locals>.decorator.<locals>.async_wrapper.ss������1�T�3�D�3�3�3�F�3�3�3�3�3�3�3�3�F��$��%�%�C��L�L�)�3��5�5�5��$������Mr)�	functools�wrapsr)r?r@rBr1r-s`  ��r�	decoratorz!serialize_attr.<locals>.decorator%s�����	���	�	�	�	�	�	�	�	�
�	�	�
���	�	�	�	�	�	�	�	�
�	�	��q�!�!�	!� � ��rr)r-r1rEs`` r�serialize_attrrF!s*����������*�r)�fallbackrGc��	t|dd���5}tj|��}ddd��n#1swxYwYt|t��rtj|��S|S#t$rt�	d|��Yn1t$r%}t�d|��Yd}~nd}~wwxYwt|��r
|��n|S)z�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).�rr#r$NzCan't find %s to unserializez.Unserialize failed with %r. Returning fallback)
r)r&�loadrrrr�FileNotFoundErrorr8�warning�	Exception�error�callable)r-rGrIr�es     r�unserializerQ=s:��
�
�$��g�
.�
.�
.�	�!��)�A�,�,�C�	�	�	�	�	�	�	�	�	�	�	����	�	�	�	��c�4� � �	*��$�S�)�)�)��
���=�=�=����5�t�<�<�<�<�<��J�J�J����E�q�I�I�I�I�I�I�I�I�����J����"�(�+�+�9�8�8�:�:�:��9s8�A+�5�A+�9�A+�9�A+�+%C�	C�B;�;C)�__doc__rrCr&�loggingr+�asyncior�typingrrr�	getLogger�__name__r8r
r0�strrF�objectrQrrr�<module>rZs��R�R�����������������	�	�	�	�'�'�'�'�'�'�'�'�'�'�'�'�'�'�'�'�	��	�8�	$�	$���c��c���������C��s�����8CG�:�:�:��:��h��.>�(?�:�:�:�:�:�:rdefence360agent/utils/__pycache__/sshutil.cpython-311.opt-1.pyc0000644000000000000000000005177500000000000021271 0ustar  �

��JkO�����ddlZddlZddlZddlZddlZddlZddlZddlZddl	m
Z
ddlmZddl
mZddlmZmZddlmZe
e��ZdZdZed	��Zed
��Zejd��Zded
efd�ZdZdZdZ d�Z!d#d�Z"d
e#fd�Z$d$ddd
efd�Z%ejd��Z&d
e'fd�Z(dede'd
efd�Z)defd�Z*d�Z+d%d!�Z,d%d
e'fd"�Z-dS)&�N)�	getLogger)�URLError)�Path)�BACKUP_EXTENSION�atomic_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}\Z�username�returnc��t|t��rt�|��st	d|�����|dkrtd��S	t
j|��j}n%#t$r}t	d|����|�d}~wwxYw|rtj�|��st	d|�d|�����ttj�
|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 �: �.ssh�authorized_keys)�
isinstance�str�_USERNAME_RE�match�
ValueErrorr�pwd�getpwnam�pw_dir�KeyError�os�path�isabs�join)r	�home�es   �R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/sshutil.py�_resolve_authorized_keysr s���h��$�$�?�L�,>�,>�x�,H�,H�?��j�8�8�=�>�>�>��6����0�1�1�1�B��|�H�%�%�,�����B�B�B��j�x�x�9�:�:��A�����B�����
�r�w�}�}�T�*�*�
��j�8@���$�$�G�
�
�	
������T�6�+<�=�=�>�>�>s�A2�2
B�<B�B��%IMUNIFY_ASSISTED_CLEANUP_KEY_TTL_DAYSzrestrict,ptyc��6K�d}	tg}t���r:|�t	t�d������t
|��D]�}	|������D]�}|�	��}|�
d��r]|�
d��sH	t|���d��}|cc|cS#ttf$rY��wxYw����#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*.conf�Port �#�zFailed to read r
NzFailed to get SSH port: )�SSH_CONFIG_PATH�SSH_CONFIG_DIR�exists�extend�sorted�glob�reversed�	read_text�
splitlines�strip�
startswith�int�split�
IndexErrorr�IOError�logger�warning�	Exception)�port�config_files�config_file�liners     r�get_ssh_portr>9s�����
�D��'�(��� � �"�"�	G�����~�':�':�8�'D�'D� E� E�F�F�F�$�L�1�1�	�	�K�

�'�1�1�3�3�>�>�@�@�	%�	%�D��:�:�<�<�D����w�/�/�%�����8L�8L�%�%�$'�t�z�z�|�|�A��#7�#7�D�#'�K�K�K�K������!+�J�7�%�%�%�$�H�%�����	%���
�
�
����C��C�C��C�C�D�D�D����������
����	���7�7�7����5�!�5�5�6�6�6�6�6�6�6�6�����7�������t��������s��A,E�3A&D#�(D
�D#�E�
D�D#�D�D#�"E�#
E�- E�
E�E�E�F�
F�%F�F�F�F�Fr$c��rK�	tjd|���d{V��\}}	tj|���d����d{V��}|�dd������}t
jd|��rRt�	d	|�d
|�d���	|�
��|����d{V��dSt�d	|�d
|�d���	|�
��|����d{V��dS#tj
$rOt�d|����Y|�
��|����d{V��dSwxYw#|�
��|����d{V��wxYw#ttf$r+}t�d|�d|����Yd}~dSd}~wt $r+}t�d|�d|����Yd}~dSd}~wwxYw)zBTest if port is actually an SSH port by checking the server bannerz	127.0.0.1Ng@��timeout�utf-8�ignore��errorsz^SSH-[12]\.r%z is confirmed as SSH (banner: �)Tz is open but not SSH (got: Fz'Timeout waiting for SSH banner on port zFailed to connect to port r
z#Unexpected error checking SSH port )�asyncio�open_connection�wait_for�readline�decoder1�rerr7�info�close�wait_closedr8�TimeoutError�ConnectionRefusedError�OSErrorr9)r:�reader�writer�bannerrs     r�check_ssh_connectionrV\s������&�6�{�D�I�I�I�I�I�I�I�I����	'�"�+�F�O�O�,=�,=�s�K�K�K�K�K�K�K�K�K�F��]�]�7�8�]�<�<�B�B�D�D�F��x���/�/�	
����I�D�I�I��I�I�I�����
�L�L�N�N�N��$�$�&�&�&�&�&�&�&�&�&�&�&����F�D�F�F�V�F�F�F�����
�L�L�N�N�N��$�$�&�&�&�&�&�&�&�&�&�&�&���#�	�	�	��N�N�K�T�K�K�L�L�L���L�L�N�N�N��$�$�&�&�&�&�&�&�&�&�&�&�&�	�����
�L�L�N�N�N��$�$�&�&�&�&�&�&�&�&�&�&�����"�G�,�������?�D�?�?�A�?�?�@�@�@��u�u�u�u�u������������H�T�H�H�Q�H�H�I�I�I��u�u�u�u�u��������se�G�B
D3�1.G�!!D3�.G�3,F�F� .G�F�F�0G�G�H6� G>�>
H6� H1�1H6c��tj�td��}	t	|��}|dkr|Sn#t
tf$rYnwxYwtS)zDRead the assisted-cleanup key TTL from env, falling back to default.�r)r�environ�get�KEY_TTL_ENV_VARr3�	TypeErrorr�DEFAULT_KEY_TTL_DAYS)�raw�ttls  r�
_key_ttl_daysr`~se��
�*�.�.��"�
-�
-�C�
��#�h�h����7�7��J����z�"�
�
�
���
�����s�?�A�A�nowzdatetime.datetime | Nonec��|p-tj�tjj��}|���tjt
�����z}|�d��S)N)�daysz
%Y%m%d%H%M)�datetimera�timezone�utc�
astimezone�	timedeltar`�strftime)ra�base�expirys   r�_expiry_timestamprl�s]���>�(�#�'�'��(9�(=�>�>�D�
�_�_�
�
��!3����!I�!I�!I�
I�F��?�?�<�(�(�(�zOpenSSH_(\d+)\.(\d+)c���K�	tjddtjjtjj����d{V��}tj|���d����d{V��\}}n?#ttjf$r&}t�	d|��Yd}~dSd}~wwxYw|pd�
d	d
���p|pd�
d	d
���}t�|��}|s%t�	d|dd
���dSt|�d����t|�d����}}||fdkS)N�sshz-V)�stdout�stderr�r@zssh -V probe failed: %sFrmrBrCrDz0ssh -V did not match OpenSSH version pattern: %r��r'�)r!r!)rG�create_subprocess_exec�
subprocess�PIPErI�communicaterRrPr7r8rK�_OPENSSH_VERSION_RE�searchr3�group)�procrprqr�outputr�major�minors        r�_sshd_supports_expiry_timer��s�����
��3����%�*��%�*�	
�
�
�
�
�
�
�
�
�� '�/��0@�0@�0B�0B�A�N�N�N�N�N�N�N�N�N�������W�)�*�������0�!�4�4�4��u�u�u�u�u����������m��
#�
#�G�H�
#�
=�
=�'��
�#��f�W�X�f�&�&��
 �&�&�v�.�.�E������>��t��t��	
�	
�	
��u��u�{�{�1�~�~�&�&��E�K�K��N�N�(;�(;�5�E��5�>�V�#�#s�A-A2�2B.�B)�)B.�pub_key�supports_expiryc�z�|rt�dt���d�}nt}|�d|�����S)Nz,expiry-time="�"� )�KEY_OPTIONS_BASErlr1)r�r��optionss   r�build_authorized_key_liner��sJ���#�%�K�K�5F�5H�5H�K�K�K���"���)�)��
�
���)�)�)rmc��|dkrdS	tj|��}n,#t$rt�d|��YdSwxYw|j|jfS)z�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.
    r)NNz>user %r not found; leaving authorized_keys ownership untouched)rrrr7r8�pw_uid�pw_gid)r	�pws  r�_target_uid_gidr��s{���6����z��
�\�(�
#�
#�����������L��	
�	
�	
��z�z������9�b�i��s��%A�Ac��d}|r+	tjdd|���d}n#t$rYnwxYwtjdtjtjztjz|���}|rT	|�|�tj|||��tj|d��n##t$rtj
|���wxYw|S)z@O_NOFOLLOW fd for .ssh under *home_fd*; a symlinked .ssh raises.Fri�)�mode�dir_fdT�r�)r�mkdir�FileExistsError�open�O_RDONLY�O_DIRECTORY�
O_NOFOLLOW�chown�fchmod�
BaseExceptionrN)�home_fd�uid�gid�create�created�ssh_fds      r�
_open_ssh_dirr��s����G�
��	��H�V�%��8�8�8�8��G�G���	�	�	��D�	����
�W��
��b�n�$�r�}�4�����F�
��	���3�?�����c�*�*�*��I�f�e�$�$�$�$���	�	�	��H�V�����	�����Ms� �
-�-�./B� B>rc	��hK�		t|��}n3#t$r&}t�d|��Yd}~dSd}~wwxYwt	j��dkrt�d��dS	tj�t���
��������}n5#t$r(}t�d|����Yd}~dSd}~wwxYwd|vsd|vrt�d��dSt|t���d{V���	��}t!|��\}}tj�|jj��}	t)|��}n8#t*$r+}t�d
|�d|����Yd}~dSd}~wwxYw		t-|||d�
��}	nQ#t*$rD}t�d|j�d|����Yd}~t	j|��dSd}~wwxYw	d}
	t	jdtjtjz|	���}t	j|d��5}|�
��}
ddd��n#1swxYwYn[#t8$rd}
d}
YnKt*$r?}|jt:jkr�t�d|��d}
d}
Yd}~nd}~wwxYwtAj!dtDzdzd|
��}|}|r|�#d��s|dz
}||dzz
}tId|d|||
|	���t	j|	��n#t	j|	��wxYw	t	j|��n#t	j|��wxYwt�%d||�&dd��d��dS#tN$r(}t�d|����Yd}~dSd}~wwxYw)Nzinstall_pub_key: %sFrzFunction must be run as rootzFailed to download public key: �
�
z*Downloaded public key spans multiple lines)r��Cannot open home directory r
T�r�zFailed to prepare directory rr��rrXi�zReplacing symlinked %s�.*�.*\n?��backupr�r��permissionsr�z/Installed assisted-cleanup key for user %s (%s)r�r'zFailed to install public key: )(r rr7�errorr�geteuid�urllib�request�urlopen�ANALYST_PUB_KEY_URL�readrKr1rr�r�r�r�realpath�parentrrRr�rNr�r�r��fdopen�FileNotFoundError�errno�ELOOPr8rL�sub�KEY_PATTERN�endswithrrMr4r9)r	�auth_keys_pathrr��guarded_liner�r�rr�r�r��keys_fd�f�existing�stripped�new_contents                r�install_pub_keyr��s!����l�	�5�h�?�?�N�N���	�	�	��L�L�.��2�2�2��5�5�5�5�5�����	����
�:�<�<�1����L�L�7�8�8�8��5�		���&�&�':�;�;�������������	
�G���	�	�	��L�L�>�1�>�>�?�?�?��5�5�5�5�5�����	�����7�?�?�d�g�o�o��L�L�E�F�F�F��5�0��"<�">�">�>�>�>�>�>�>�
�
�
��#�8�,�,���S�
�w���� 5� <�=�=��	�*�4�0�0�G�G���	�	�	��L�L�B�t�B�B�q�B�B�C�C�C��5�5�5�5�5�����	����6	�
�&�w��S��F�F�F�����
�
�
����O�>�3H�O�O�A�O�O�����u�u�u�^
�H�W����������g
����
,
!�"��,� �g�)���b�m�3�%����G���7�C�0�0�,�A�#$�6�6�8�8��,�,�,�,�,�,�,�,�,�,�,����,�,�,�,���)�(�(�(�!�H�"'�K�K�K��(�(�(��w�%�+�-�-���N�N�#;�^�L�L�L�!�H�"'�K�K�K�K�K�K�����(�����6��K�'�(�2������
'���(�{�';�';�D�'A�'A�(��4�'�K��|�d�2�2���%�� ��� +�!�������� � � � ����� � � � ���� ��H�W������B�H�W�����������=�����s�A�&�&�q�)�	
�	
�	
�
�t���������9�a�9�9�:�:�:��u�u�u�u�u��������sG��O?�
A�A�O?�A�4O?�<AC�O?�
D	�!D�>O?�D	�	%O?�0AO?�F�O?�
G�) G�	O?�G�O?�G-�,N.�-
H;�7%H6�N.� O?�6H;�;N.�?M?�.J2�0M?�J&�M?�&J*�*M?�-J*�.M?�2L
�M?�	L
�5L�M?�L
�
A M?�*N.�?N�N.�O?�.O�9O?�?
P1�	P,�,P1c	��		t|��}n3#t$r&}t�d|��Yd}~dSd}~wwxYwt	|��\}}t
j�|jj��}	t|��}n8#t$r+}t�d|�d|����Yd}~dSd}~wwxYw		t|||d���}nQ#t$rD}t�d|j�d|����Yd}~tj
|��dSd}~wwxYw		tjdt
jt
jz|�	��}n`#t$rS}t�d
|�d|����Yd}~tj
|��tj
|��dSd}~wwxYwtj|d��5}	t%jtj|	�����j��}
|	���}ddd��n#1swxYwYt1jt4|��sHt�d|����	tj
|��tj
|��dSt1jd
t4zdzd|��}|���st�d|�d���t=dt>z|d|||
|���t=d|d|||���t�d|����	tj
|��tj
|��dS#tj
|��wxYw#tj
|��wxYw#t@$r(}t�d|����Yd}~dSd}~wwxYw)z�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.
    zremove_pub_key: %sNFr�r
r�zCannot open directory rr�zCannot open r�z Analyst public key not found in r�r�rXzFile z will be empty after removalr�)r�r�r�r�z-Successfully removed analyst public key from TzFailed to remove public key: )!r rr7r�r�rrr�r�rrRr8r�rNr�r�r�r��stat�S_IMODE�fstat�fileno�st_moder�rLrzr�rMr�r1rrr9)
r	r�rr�r�rr�r�r�r�r��contentr�s
             r�remove_pub_keyr�Ws���S�	�5�h�?�?�N�N���	�	�	��L�L�-�q�1�1�1��5�5�5�5�5�����	����#�8�,�,���S��w���� 5� <�=�=��	�*�4�0�0�G�G���	�	�	��N�N�D��D�D��D�D�E�E�E��5�5�5�5�5�����	����B	�
�&�w��S��G�G�G�����
�
�
����I�^�-B�I�I�a�I�I�����u�u�u�v
�H�W����������
����
8
!�!� �g�)���b�m�3�%����G�G��
�!�!�!��N�N�#G�.�#G�#G�A�#G�#G�H�H�H� �5�5�5�^��� � � ��H�W����������g!�����Y�w��,�,�'��"&�,�r�x����
�
�/C�/C�/K�"L�"L�K��f�f�h�h�G�'�'�'�'�'�'�'�'�'�'�'����'�'�'�'��y��g�6�6�!��K�K�K�>�K�K����!�L��� � � ��H�W������K!�f��K�'�(�2�B�����#�(�(�*�*���K�K�L��L�L�L�����%�(8�8�� ��� +�!������%�� ���!�
�������)�&�)�)�������� � � ��H�W���������� � � � ������H�W����������������8�Q�8�8�9�9�9��u�u�u�u�u��������s ��N"�
A�>�N"�A�>N"�B�N"�
C� C�<N"�C�N"�C �N	� 
D.�*%D)�N	�N"�)D.�.N	�3.E"�!M0�"
F?�, F:�M0�N	�$N"�:F?�?M0�AH5�)M0�5H9�9M0�<H9�=:M0�8N	�N"�"B#M0�N	�N"�0N�N	�	N�N"�"
O�,O�O)r$)N)r).rGrdr�rrLr��urllib.requestr�r�loggingr�urllib.errorr�pathlibr�defence360agent.utilsrr�defence360agent.utils.fd_opsr�__name__r7r�r�r(r)�compilerrr r]r[r�r>rVr3r`rlry�boolr�r�r�r�r�r��rmr�<module>r�sp��������������
�
�
�
�	�	�	�	���������	�	�	�	�������!�!�!�!�!�!�������B�B�B�B�B�B�B�B�=�=�=�=�=�=�	��8�	�	��M��3���$�-�.�.����.�/�/���r�z�6�7�7��?�s�?�t�?�?�?�?�,��9��!�� � � �F����D	 �s�	 �	 �	 �	 �)�)�5�)��)�)�)�)�!�b�j�!8�9�9��$�$�$�$�$�$�6*�s�*��*��*�*�*�*� �c� � � � �(���2o�o�o�o�dZ�Z�t�Z�Z�Z�Z�Z�Zrmdefence360agent/utils/__pycache__/sshutil.cpython-311.pyc0000644000000000000000000005177500000000000020332 0ustar  �

��JkO�����ddlZddlZddlZddlZddlZddlZddlZddlZddl	m
Z
ddlmZddl
mZddlmZmZddlmZe
e��ZdZdZed	��Zed
��Zejd��Zded
efd�ZdZdZdZ d�Z!d#d�Z"d
e#fd�Z$d$ddd
efd�Z%ejd��Z&d
e'fd�Z(dede'd
efd�Z)defd�Z*d�Z+d%d!�Z,d%d
e'fd"�Z-dS)&�N)�	getLogger)�URLError)�Path)�BACKUP_EXTENSION�atomic_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}\Z�username�returnc��t|t��rt�|��st	d|�����|dkrtd��S	t
j|��j}n%#t$r}t	d|����|�d}~wwxYw|rtj�|��st	d|�d|�����ttj�
|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 �: �.ssh�authorized_keys)�
isinstance�str�_USERNAME_RE�match�
ValueErrorr�pwd�getpwnam�pw_dir�KeyError�os�path�isabs�join)r	�home�es   �R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/sshutil.py�_resolve_authorized_keysr s���h��$�$�?�L�,>�,>�x�,H�,H�?��j�8�8�=�>�>�>��6����0�1�1�1�B��|�H�%�%�,�����B�B�B��j�x�x�9�:�:��A�����B�����
�r�w�}�}�T�*�*�
��j�8@���$�$�G�
�
�	
������T�6�+<�=�=�>�>�>s�A2�2
B�<B�B��%IMUNIFY_ASSISTED_CLEANUP_KEY_TTL_DAYSzrestrict,ptyc��6K�d}	tg}t���r:|�t	t�d������t
|��D]�}	|������D]�}|�	��}|�
d��r]|�
d��sH	t|���d��}|cc|cS#ttf$rY��wxYw����#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*.conf�Port �#�zFailed to read r
NzFailed to get SSH port: )�SSH_CONFIG_PATH�SSH_CONFIG_DIR�exists�extend�sorted�glob�reversed�	read_text�
splitlines�strip�
startswith�int�split�
IndexErrorr�IOError�logger�warning�	Exception)�port�config_files�config_file�liners     r�get_ssh_portr>9s�����
�D��'�(��� � �"�"�	G�����~�':�':�8�'D�'D� E� E�F�F�F�$�L�1�1�	�	�K�

�'�1�1�3�3�>�>�@�@�	%�	%�D��:�:�<�<�D����w�/�/�%�����8L�8L�%�%�$'�t�z�z�|�|�A��#7�#7�D�#'�K�K�K�K������!+�J�7�%�%�%�$�H�%�����	%���
�
�
����C��C�C��C�C�D�D�D����������
����	���7�7�7����5�!�5�5�6�6�6�6�6�6�6�6�����7�������t��������s��A,E�3A&D#�(D
�D#�E�
D�D#�D�D#�"E�#
E�- E�
E�E�E�F�
F�%F�F�F�F�Fr$c��rK�	tjd|���d{V��\}}	tj|���d����d{V��}|�dd������}t
jd|��rRt�	d	|�d
|�d���	|�
��|����d{V��dSt�d	|�d
|�d���	|�
��|����d{V��dS#tj
$rOt�d|����Y|�
��|����d{V��dSwxYw#|�
��|����d{V��wxYw#ttf$r+}t�d|�d|����Yd}~dSd}~wt $r+}t�d|�d|����Yd}~dSd}~wwxYw)zBTest if port is actually an SSH port by checking the server bannerz	127.0.0.1Ng@��timeout�utf-8�ignore��errorsz^SSH-[12]\.r%z is confirmed as SSH (banner: �)Tz is open but not SSH (got: Fz'Timeout waiting for SSH banner on port zFailed to connect to port r
z#Unexpected error checking SSH port )�asyncio�open_connection�wait_for�readline�decoder1�rerr7�info�close�wait_closedr8�TimeoutError�ConnectionRefusedError�OSErrorr9)r:�reader�writer�bannerrs     r�check_ssh_connectionrV\s������&�6�{�D�I�I�I�I�I�I�I�I����	'�"�+�F�O�O�,=�,=�s�K�K�K�K�K�K�K�K�K�F��]�]�7�8�]�<�<�B�B�D�D�F��x���/�/�	
����I�D�I�I��I�I�I�����
�L�L�N�N�N��$�$�&�&�&�&�&�&�&�&�&�&�&����F�D�F�F�V�F�F�F�����
�L�L�N�N�N��$�$�&�&�&�&�&�&�&�&�&�&�&���#�	�	�	��N�N�K�T�K�K�L�L�L���L�L�N�N�N��$�$�&�&�&�&�&�&�&�&�&�&�&�	�����
�L�L�N�N�N��$�$�&�&�&�&�&�&�&�&�&�&�����"�G�,�������?�D�?�?�A�?�?�@�@�@��u�u�u�u�u������������H�T�H�H�Q�H�H�I�I�I��u�u�u�u�u��������se�G�B
D3�1.G�!!D3�.G�3,F�F� .G�F�F�0G�G�H6� G>�>
H6� H1�1H6c��tj�td��}	t	|��}|dkr|Sn#t
tf$rYnwxYwtS)zDRead the assisted-cleanup key TTL from env, falling back to default.�r)r�environ�get�KEY_TTL_ENV_VARr3�	TypeErrorr�DEFAULT_KEY_TTL_DAYS)�raw�ttls  r�
_key_ttl_daysr`~se��
�*�.�.��"�
-�
-�C�
��#�h�h����7�7��J����z�"�
�
�
���
�����s�?�A�A�nowzdatetime.datetime | Nonec��|p-tj�tjj��}|���tjt
�����z}|�d��S)N)�daysz
%Y%m%d%H%M)�datetimera�timezone�utc�
astimezone�	timedeltar`�strftime)ra�base�expirys   r�_expiry_timestamprl�s]���>�(�#�'�'��(9�(=�>�>�D�
�_�_�
�
��!3����!I�!I�!I�
I�F��?�?�<�(�(�(�zOpenSSH_(\d+)\.(\d+)c���K�	tjddtjjtjj����d{V��}tj|���d����d{V��\}}n?#ttjf$r&}t�	d|��Yd}~dSd}~wwxYw|pd�
d	d
���p|pd�
d	d
���}t�|��}|s%t�	d|dd
���dSt|�d����t|�d����}}||fdkS)N�sshz-V)�stdout�stderr�r@zssh -V probe failed: %sFrmrBrCrDz0ssh -V did not match OpenSSH version pattern: %r��r'�)r!r!)rG�create_subprocess_exec�
subprocess�PIPErI�communicaterRrPr7r8rK�_OPENSSH_VERSION_RE�searchr3�group)�procrprqr�outputr�major�minors        r�_sshd_supports_expiry_timer��s�����
��3����%�*��%�*�	
�
�
�
�
�
�
�
�
�� '�/��0@�0@�0B�0B�A�N�N�N�N�N�N�N�N�N�������W�)�*�������0�!�4�4�4��u�u�u�u�u����������m��
#�
#�G�H�
#�
=�
=�'��
�#��f�W�X�f�&�&��
 �&�&�v�.�.�E������>��t��t��	
�	
�	
��u��u�{�{�1�~�~�&�&��E�K�K��N�N�(;�(;�5�E��5�>�V�#�#s�A-A2�2B.�B)�)B.�pub_key�supports_expiryc�z�|rt�dt���d�}nt}|�d|�����S)Nz,expiry-time="�"� )�KEY_OPTIONS_BASErlr1)r�r��optionss   r�build_authorized_key_liner��sJ���#�%�K�K�5F�5H�5H�K�K�K���"���)�)��
�
���)�)�)rmc��|dkrdS	tj|��}n,#t$rt�d|��YdSwxYw|j|jfS)z�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.
    r)NNz>user %r not found; leaving authorized_keys ownership untouched)rrrr7r8�pw_uid�pw_gid)r	�pws  r�_target_uid_gidr��s{���6����z��
�\�(�
#�
#�����������L��	
�	
�	
��z�z������9�b�i��s��%A�Ac��d}|r+	tjdd|���d}n#t$rYnwxYwtjdtjtjztjz|���}|rT	|�|�tj|||��tj|d��n##t$rtj
|���wxYw|S)z@O_NOFOLLOW fd for .ssh under *home_fd*; a symlinked .ssh raises.Fri�)�mode�dir_fdT�r�)r�mkdir�FileExistsError�open�O_RDONLY�O_DIRECTORY�
O_NOFOLLOW�chown�fchmod�
BaseExceptionrN)�home_fd�uid�gid�create�created�ssh_fds      r�
_open_ssh_dirr��s����G�
��	��H�V�%��8�8�8�8��G�G���	�	�	��D�	����
�W��
��b�n�$�r�}�4�����F�
��	���3�?�����c�*�*�*��I�f�e�$�$�$�$���	�	�	��H�V�����	�����Ms� �
-�-�./B� B>rc	��hK�		t|��}n3#t$r&}t�d|��Yd}~dSd}~wwxYwt	j��dkrt�d��dS	tj�t���
��������}n5#t$r(}t�d|����Yd}~dSd}~wwxYwd|vsd|vrt�d��dSt|t���d{V���	��}t!|��\}}tj�|jj��}	t)|��}n8#t*$r+}t�d
|�d|����Yd}~dSd}~wwxYw		t-|||d�
��}	nQ#t*$rD}t�d|j�d|����Yd}~t	j|��dSd}~wwxYw	d}
	t	jdtjtjz|	���}t	j|d��5}|�
��}
ddd��n#1swxYwYn[#t8$rd}
d}
YnKt*$r?}|jt:jkr�t�d|��d}
d}
Yd}~nd}~wwxYwtAj!dtDzdzd|
��}|}|r|�#d��s|dz
}||dzz
}tId|d|||
|	���t	j|	��n#t	j|	��wxYw	t	j|��n#t	j|��wxYwt�%d||�&dd��d��dS#tN$r(}t�d|����Yd}~dSd}~wwxYw)Nzinstall_pub_key: %sFrzFunction must be run as rootzFailed to download public key: �
�
z*Downloaded public key spans multiple lines)r��Cannot open home directory r
T�r�zFailed to prepare directory rr��rrXi�zReplacing symlinked %s�.*�.*\n?��backupr�r��permissionsr�z/Installed assisted-cleanup key for user %s (%s)r�r'zFailed to install public key: )(r rr7�errorr�geteuid�urllib�request�urlopen�ANALYST_PUB_KEY_URL�readrKr1rr�r�r�r�realpath�parentrrRr�rNr�r�r��fdopen�FileNotFoundError�errno�ELOOPr8rL�sub�KEY_PATTERN�endswithrrMr4r9)r	�auth_keys_pathrr��guarded_liner�r�rr�r�r��keys_fd�f�existing�stripped�new_contents                r�install_pub_keyr��s!����l�	�5�h�?�?�N�N���	�	�	��L�L�.��2�2�2��5�5�5�5�5�����	����
�:�<�<�1����L�L�7�8�8�8��5�		���&�&�':�;�;�������������	
�G���	�	�	��L�L�>�1�>�>�?�?�?��5�5�5�5�5�����	�����7�?�?�d�g�o�o��L�L�E�F�F�F��5�0��"<�">�">�>�>�>�>�>�>�
�
�
��#�8�,�,���S�
�w���� 5� <�=�=��	�*�4�0�0�G�G���	�	�	��L�L�B�t�B�B�q�B�B�C�C�C��5�5�5�5�5�����	����6	�
�&�w��S��F�F�F�����
�
�
����O�>�3H�O�O�A�O�O�����u�u�u�^
�H�W����������g
����
,
!�"��,� �g�)���b�m�3�%����G���7�C�0�0�,�A�#$�6�6�8�8��,�,�,�,�,�,�,�,�,�,�,����,�,�,�,���)�(�(�(�!�H�"'�K�K�K��(�(�(��w�%�+�-�-���N�N�#;�^�L�L�L�!�H�"'�K�K�K�K�K�K�����(�����6��K�'�(�2������
'���(�{�';�';�D�'A�'A�(��4�'�K��|�d�2�2���%�� ��� +�!�������� � � � ����� � � � ���� ��H�W������B�H�W�����������=�����s�A�&�&�q�)�	
�	
�	
�
�t���������9�a�9�9�:�:�:��u�u�u�u�u��������sG��O?�
A�A�O?�A�4O?�<AC�O?�
D	�!D�>O?�D	�	%O?�0AO?�F�O?�
G�) G�	O?�G�O?�G-�,N.�-
H;�7%H6�N.� O?�6H;�;N.�?M?�.J2�0M?�J&�M?�&J*�*M?�-J*�.M?�2L
�M?�	L
�5L�M?�L
�
A M?�*N.�?N�N.�O?�.O�9O?�?
P1�	P,�,P1c	��		t|��}n3#t$r&}t�d|��Yd}~dSd}~wwxYwt	|��\}}t
j�|jj��}	t|��}n8#t$r+}t�d|�d|����Yd}~dSd}~wwxYw		t|||d���}nQ#t$rD}t�d|j�d|����Yd}~tj
|��dSd}~wwxYw		tjdt
jt
jz|�	��}n`#t$rS}t�d
|�d|����Yd}~tj
|��tj
|��dSd}~wwxYwtj|d��5}	t%jtj|	�����j��}
|	���}ddd��n#1swxYwYt1jt4|��sHt�d|����	tj
|��tj
|��dSt1jd
t4zdzd|��}|���st�d|�d���t=dt>z|d|||
|���t=d|d|||���t�d|����	tj
|��tj
|��dS#tj
|��wxYw#tj
|��wxYw#t@$r(}t�d|����Yd}~dSd}~wwxYw)z�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.
    zremove_pub_key: %sNFr�r
r�zCannot open directory rr�zCannot open r�z Analyst public key not found in r�r�rXzFile z will be empty after removalr�)r�r�r�r�z-Successfully removed analyst public key from TzFailed to remove public key: )!r rr7r�r�rrr�r�rrRr8r�rNr�r�r�r��stat�S_IMODE�fstat�fileno�st_moder�rLrzr�rMr�r1rrr9)
r	r�rr�r�rr�r�r�r�r��contentr�s
             r�remove_pub_keyr�Ws���S�	�5�h�?�?�N�N���	�	�	��L�L�-�q�1�1�1��5�5�5�5�5�����	����#�8�,�,���S��w���� 5� <�=�=��	�*�4�0�0�G�G���	�	�	��N�N�D��D�D��D�D�E�E�E��5�5�5�5�5�����	����B	�
�&�w��S��G�G�G�����
�
�
����I�^�-B�I�I�a�I�I�����u�u�u�v
�H�W����������
����
8
!�!� �g�)���b�m�3�%����G�G��
�!�!�!��N�N�#G�.�#G�#G�A�#G�#G�H�H�H� �5�5�5�^��� � � ��H�W����������g!�����Y�w��,�,�'��"&�,�r�x����
�
�/C�/C�/K�"L�"L�K��f�f�h�h�G�'�'�'�'�'�'�'�'�'�'�'����'�'�'�'��y��g�6�6�!��K�K�K�>�K�K����!�L��� � � ��H�W������K!�f��K�'�(�2�B�����#�(�(�*�*���K�K�L��L�L�L�����%�(8�8�� ��� +�!������%�� ���!�
�������)�&�)�)�������� � � ��H�W���������� � � � ������H�W����������������8�Q�8�8�9�9�9��u�u�u�u�u��������s ��N"�
A�>�N"�A�>N"�B�N"�
C� C�<N"�C�N"�C �N	� 
D.�*%D)�N	�N"�)D.�.N	�3.E"�!M0�"
F?�, F:�M0�N	�$N"�:F?�?M0�AH5�)M0�5H9�9M0�<H9�=:M0�8N	�N"�"B#M0�N	�N"�0N�N	�	N�N"�"
O�,O�O)r$)N)r).rGrdr�rrLr��urllib.requestr�r�loggingr�urllib.errorr�pathlibr�defence360agent.utilsrr�defence360agent.utils.fd_opsr�__name__r7r�r�r(r)�compilerrr r]r[r�r>rVr3r`rlry�boolr�r�r�r�r�r��rmr�<module>r�sp��������������
�
�
�
�	�	�	�	���������	�	�	�	�������!�!�!�!�!�!�������B�B�B�B�B�B�B�B�=�=�=�=�=�=�	��8�	�	��M��3���$�-�.�.����.�/�/���r�z�6�7�7��?�s�?�t�?�?�?�?�,��9��!�� � � �F����D	 �s�	 �	 �	 �	 �)�)�5�)��)�)�)�)�!�b�j�!8�9�9��$�$�$�$�$�$�6*�s�*��*��*�*�*�*� �c� � � � �(���2o�o�o�o�dZ�Z�t�Z�Z�Z�Z�Z�Zrmdefence360agent/utils/__pycache__/subprocess.cpython-311.opt-1.pyc0000644000000000000000000000415200000000000021751 0ustar  �

�Z@5S�
���Z�dZddlZddlZddlmZgd�ZGd�dej��Zd�ZdS)z0General utilities for working with subprocesses.�N)�PIPE)r�CalledProcessError�check_outputc��eZdZdZd�ZdS)rz'Add stdout,stderr to str representationc�&�|jrn|jdkrcd|j�dtj|j���d|j�d|j��S#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)�
returncode�cmd�signal�Signals�stdout�stderr�
ValueError)�selfs �U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/subprocess.py�__str__zCalledProcessError.__str__s����?�	�t���2�2�
��H�H�H��N���(������K�K�K��K�K�
����
�
�
�/��x�$�/�!1�4�;���L�M����
����+��8�T�_�d�k�4�;�G�H�
s�4A�(A3�2A3N)�__name__�
__module__�__qualname__�__doc__r��rrr	s)������1�1�����rrc��	tj|i|��S#tj$r,}t|j|j|j|j��d�d}~wwxYw)z_A wrapper for stdlib subprocess.check_output.

    Include stdout/stderr in error message.
    N)�
subprocessrrrr	rr
)�args�kwargs�es   rrr%se��
��&��7��7�7�7���(���� �
�L�!�%���1�8�
�
��	��������s��A�'A	�	A)rr
rr�__all__rrrrr�<module>rs���6�6�
�
�
�
�����������
8�
8�
8��������6����8
�
�
�
�
rdefence360agent/utils/__pycache__/subprocess.cpython-311.pyc0000644000000000000000000000415200000000000021012 0ustar  �

�Z@5S�
���Z�dZddlZddlZddlmZgd�ZGd�dej��Zd�ZdS)z0General utilities for working with subprocesses.�N)�PIPE)r�CalledProcessError�check_outputc��eZdZdZd�ZdS)rz'Add stdout,stderr to str representationc�&�|jrn|jdkrcd|j�dtj|j���d|j�d|j��S#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)�
returncode�cmd�signal�Signals�stdout�stderr�
ValueError)�selfs �U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/subprocess.py�__str__zCalledProcessError.__str__s����?�	�t���2�2�
��H�H�H��N���(������K�K�K��K�K�
����
�
�
�/��x�$�/�!1�4�;���L�M����
����+��8�T�_�d�k�4�;�G�H�
s�4A�(A3�2A3N)�__name__�
__module__�__qualname__�__doc__r��rrr	s)������1�1�����rrc��	tj|i|��S#tj$r,}t|j|j|j|j��d�d}~wwxYw)z_A wrapper for stdlib subprocess.check_output.

    Include stdout/stderr in error message.
    N)�
subprocessrrrr	rr
)�args�kwargs�es   rrr%se��
��&��7��7�7�7���(���� �
�L�!�%���1�8�
�
��	��������s��A�'A	�	A)rr
rr�__all__rrrrr�<module>rs���6�6�
�
�
�
�����������
8�
8�
8��������6����8
�
�
�
�
rdefence360agent/utils/__pycache__/support.cpython-311.opt-1.pyc0000644000000000000000000001723100000000000021277 0ustar  �

�z�B#����ddlZddlZddlZddlZddlZddlZddlmZddl	m
Z
ddlmZddl
mZe
e��ZGd�de��ZdZd	Zd
ZdZdZd
Z			dd�Zd�Zd�Zddd�defd�Zd�Zd�ZdS)�N)�partial)�	getLogger)�Path)�ANTIVIRUS_MODEc���eZdZ�fd�Z�xZS)�ZendeskAPIErrorc�t��||_||_||_t���|��dS)N)�error�description�details�super�__init__)�selfr
rr�	__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/{}iq��i���iQd�l�VA�,c��K�t|���d{V��}t|���}|�|g|d<t||���}ttrdndd�tdd�g}	|r|	�t|d���|r|	�t|d���t||||	�	��}
t|
���d{V��S)
z?
    Send request to support of Imunify360 via Zendesk API
    N)�body�uploads)�name�email�
pr_imunify_av�pr_im360)�id�valueT)�	requester�subject�comment�
custom_fields)	�_upload_attachments�dict�_PRODUCT_IDr�_PRIVACY_POLICY_ID�append�
_DOCTOR_ID�_CLN_ID�_post_support_request)�sender_emailr!r�
doctor_key�cln�attachments�upload_tokenr"r r#�requests           r�send_requestr2"s����-�[�9�9�9�9�9�9�9�9�L���$�$�$�G���*�^��	���,�l�;�;�;�I�
�(6�F�_�_�J�	
�	
�"�D�1�1��M��F����J��D�D�E�E�E�
�<����G�c�:�:�;�;�;�����#�	���G�'�w�/�/�/�/�/�/�/�/�/rc��tjtj||j�d�������S)Nzutf-8)�encoding)�json�load�io�
TextIOWrapper�headers�get_content_charset)�responses r�decode_as_jsonr<SsB���9�
����%�9�9�'�B�B�	
�	
�	
���rc� �tj�|��}|j}|r|dz
}|tj�|��z
}tj�|j|j|j|j	||j
f��}|S)N�&)�urllib�parse�urlparse�query�	urlencode�
urlunparse�scheme�netloc�path�params�fragment)rH�url�prBs    r�parse_paramsrL\s�������c�"�"�A�
�G�E���
����	�V�\�
#�
#�F�
+�
+�+�E�
�,�
!�
!�	
��1�8�Q�V�Q�X�u�a�j�A���C��Jr)rH�timeout�datac���|rt||��}	tj�tj�|||���|���5}|jt
|��fcddd��S#1swxYwYdS#tj$rt�t$r:}t|d��s�|j|j�t
|��nifcYd}~Sd}~wwxYw)z�HTTP POST *data* to *url* with given *headers*.

    Add query *params* to the *url* if given.

    Return (http_status, decoded_json_response) tuple.
    )rNr9)rMN�code)
rLr?r1�urlopen�RequestrPr<�socketrM�TimeoutError�OSError�hasattr�fp)rJrNr9rHrMr;�es       r�
_post_datarYhsU���(��6�3�'�'��G�
�^�
#�
#��N�"�"�3�T�7�"�C�C��$�
�
�	;���=�.��":�":�:�		;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;����	;�	;�	;�	;�	;�	;��
�>�������G�G�G��q�&�!�!�	���v�Q�T�-=��q�)�)�)�2�F�F�F�F�F�F�F�����	G���sB�AB�A8�+B�8A<�<B�?A<�B�C�$/C�C�Cc��K�t�d��}ddi}tjt	|���d����d��}t
j��}|�dt|||���d{V��\}}|d	krs|�
d
��}|r t�|d��Sd|���vrdStd
dd�|�����t|�
dd��|�
d��|�
di�����)z�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.
    z
requests.json�Content-Typezapplication/json)r1T)�	sort_keys�asciiN��r1r�suspended_ticketzResponse errorz
UNKNOWN ERRORz{!r}r
rr)�
_API_URL_TMPL�formatr5�dumpsr%�encode�asyncio�get_event_loop�run_in_executorrY�get�_HC_URL_TMPL�keysr)r1rJr9rN�loop�status�result�request_datas        rr+r+�sb�����
�
��
/�
/�C��1�2�G��:�d�7�+�+�+�t�<�<�<�C�C�G�L�L�D��!�#�#�D��/�/��j�#�t�W���������N�F�F���}�}��z�z�)�,�,���	��&�&�|�D�'9�:�:�:�
�6�;�;�=�=�
0�
0��4�!� �/�6�=�=��3H�3H���
���J�J�w��0�0��J�J�}�%�%��J�J�y�"�%�%�
�
�	
rc��K�d}|�|Stj��}|D]�}t|��}d|ji}|�||d<|�dttt�d��|�	��ddi|������d{V��\}}|dkr#t�d||d	����|�|d
d}��|S)N�filename�tokenzuploads.jsonr[zapplication/binary)rNr9rHr^z'Failed to upload file %s to Zendesk: %sr
�upload)rdrerrrfrrYr`ra�
read_bytes�logger�warning)r/r0rj�
attachmentrGrHrkrls        rr$r$�s$�����L������!�#�#�D�!�5�5�
��J�����d�i�(���#�*�F�7�O�#�3�3�����$�$�^�4�4��_�_�&�&�'�)=�>��
�
�
�	 
�	 
�	
�	
�	
�	
�	
�	
�����S�=�=��N�N�9���w��
�
�
�

���!�(�+�G�4�L���r)NNN) rdr7r5rS�urllib.parser?�urllib.request�	functoolsr�loggingr�pathlibr� defence360agent.contracts.configrrrs�	Exceptionrr`rhr&r)r*r'r2r<rL�bytesrYr+r$�rr�<module>rs~������	�	�	�	�����
�
�
�
���������������������������;�;�;�;�;�;�	��8�	�	��&�&�&�&�&�i�&�&�&�;�
�>����
�
�
��#�����
.0�.0�.0�.0�b���	�	�	�59�$�G�G�G�%�G�G�G�G�2
�
�
�:!�!�!�!�!rdefence360agent/utils/__pycache__/support.cpython-311.pyc0000644000000000000000000001723100000000000020340 0ustar  �

�z�B#����ddlZddlZddlZddlZddlZddlZddlmZddl	m
Z
ddlmZddl
mZe
e��ZGd�de��ZdZd	Zd
ZdZdZd
Z			dd�Zd�Zd�Zddd�defd�Zd�Zd�ZdS)�N)�partial)�	getLogger)�Path)�ANTIVIRUS_MODEc���eZdZ�fd�Z�xZS)�ZendeskAPIErrorc�t��||_||_||_t���|��dS)N)�error�description�details�super�__init__)�selfr
rr�	__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/{}iq��i���iQd�l�VA�,c��K�t|���d{V��}t|���}|�|g|d<t||���}ttrdndd�tdd�g}	|r|	�t|d���|r|	�t|d���t||||	�	��}
t|
���d{V��S)
z?
    Send request to support of Imunify360 via Zendesk API
    N)�body�uploads)�name�email�
pr_imunify_av�pr_im360)�id�valueT)�	requester�subject�comment�
custom_fields)	�_upload_attachments�dict�_PRODUCT_IDr�_PRIVACY_POLICY_ID�append�
_DOCTOR_ID�_CLN_ID�_post_support_request)�sender_emailr!r�
doctor_key�cln�attachments�upload_tokenr"r r#�requests           r�send_requestr2"s����-�[�9�9�9�9�9�9�9�9�L���$�$�$�G���*�^��	���,�l�;�;�;�I�
�(6�F�_�_�J�	
�	
�"�D�1�1��M��F����J��D�D�E�E�E�
�<����G�c�:�:�;�;�;�����#�	���G�'�w�/�/�/�/�/�/�/�/�/rc��tjtj||j�d�������S)Nzutf-8)�encoding)�json�load�io�
TextIOWrapper�headers�get_content_charset)�responses r�decode_as_jsonr<SsB���9�
����%�9�9�'�B�B�	
�	
�	
���rc� �tj�|��}|j}|r|dz
}|tj�|��z
}tj�|j|j|j|j	||j
f��}|S)N�&)�urllib�parse�urlparse�query�	urlencode�
urlunparse�scheme�netloc�path�params�fragment)rH�url�prBs    r�parse_paramsrL\s�������c�"�"�A�
�G�E���
����	�V�\�
#�
#�F�
+�
+�+�E�
�,�
!�
!�	
��1�8�Q�V�Q�X�u�a�j�A���C��Jr)rH�timeout�datac���|rt||��}	tj�tj�|||���|���5}|jt
|��fcddd��S#1swxYwYdS#tj$rt�t$r:}t|d��s�|j|j�t
|��nifcYd}~Sd}~wwxYw)z�HTTP POST *data* to *url* with given *headers*.

    Add query *params* to the *url* if given.

    Return (http_status, decoded_json_response) tuple.
    )rNr9)rMN�code)
rLr?r1�urlopen�RequestrPr<�socketrM�TimeoutError�OSError�hasattr�fp)rJrNr9rHrMr;�es       r�
_post_datarYhsU���(��6�3�'�'��G�
�^�
#�
#��N�"�"�3�T�7�"�C�C��$�
�
�	;���=�.��":�":�:�		;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;�	;����	;�	;�	;�	;�	;�	;��
�>�������G�G�G��q�&�!�!�	���v�Q�T�-=��q�)�)�)�2�F�F�F�F�F�F�F�����	G���sB�AB�A8�+B�8A<�<B�?A<�B�C�$/C�C�Cc��K�t�d��}ddi}tjt	|���d����d��}t
j��}|�dt|||���d{V��\}}|d	krs|�
d
��}|r t�|d��Sd|���vrdStd
dd�|�����t|�
dd��|�
d��|�
di�����)z�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.
    z
requests.json�Content-Typezapplication/json)r1T)�	sort_keys�asciiN��r1r�suspended_ticketzResponse errorz
UNKNOWN ERRORz{!r}r
rr)�
_API_URL_TMPL�formatr5�dumpsr%�encode�asyncio�get_event_loop�run_in_executorrY�get�_HC_URL_TMPL�keysr)r1rJr9rN�loop�status�result�request_datas        rr+r+�sb�����
�
��
/�
/�C��1�2�G��:�d�7�+�+�+�t�<�<�<�C�C�G�L�L�D��!�#�#�D��/�/��j�#�t�W���������N�F�F���}�}��z�z�)�,�,���	��&�&�|�D�'9�:�:�:�
�6�;�;�=�=�
0�
0��4�!� �/�6�=�=��3H�3H���
���J�J�w��0�0��J�J�}�%�%��J�J�y�"�%�%�
�
�	
rc��K�d}|�|Stj��}|D]�}t|��}d|ji}|�||d<|�dttt�d��|�	��ddi|������d{V��\}}|dkr#t�d||d	����|�|d
d}��|S)N�filename�tokenzuploads.jsonr[zapplication/binary)rNr9rHr^z'Failed to upload file %s to Zendesk: %sr
�upload)rdrerrrfrrYr`ra�
read_bytes�logger�warning)r/r0rj�
attachmentrGrHrkrls        rr$r$�s$�����L������!�#�#�D�!�5�5�
��J�����d�i�(���#�*�F�7�O�#�3�3�����$�$�^�4�4��_�_�&�&�'�)=�>��
�
�
�	 
�	 
�	
�	
�	
�	
�	
�	
�����S�=�=��N�N�9���w��
�
�
�

���!�(�+�G�4�L���r)NNN) rdr7r5rS�urllib.parser?�urllib.request�	functoolsr�loggingr�pathlibr� defence360agent.contracts.configrrrs�	Exceptionrr`rhr&r)r*r'r2r<rL�bytesrYr+r$�rr�<module>rs~������	�	�	�	�����
�
�
�
���������������������������;�;�;�;�;�;�	��8�	�	��&�&�&�&�&�i�&�&�&�;�
�>����
�
�
��#�����
.0�.0�.0�.0�b���	�	�	�59�$�G�G�G�%�G�G�G�G�2
�
�
�:!�!�!�!�!rdefence360agent/utils/__pycache__/threads.cpython-311.opt-1.pyc0000644000000000000000000000302100000000000021205 0ustar  �

�f�k���0�dZddlZddlZddlmZdZd�ZdS)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_threadc��K�tj��}tj��}t	j|j|g|�Ri|��}|�d|���d{V��S)a�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*
    N)r�get_running_loop�contextvars�copy_context�	functools�partial�run�run_in_executor)�func�args�kwargs�loop�ctx�	func_calls      �R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/threads.pyrrsp�����"�$�$�D�
�
"�
$�
$�C��!�#�'�4�A�$�A�A�A�&�A�A�I��%�%�d�I�6�6�6�6�6�6�6�6�6�)�__doc__r	r�asyncior�__all__r�rr�<module>rs[��������������������7�7�7�7�7rdefence360agent/utils/__pycache__/threads.cpython-311.pyc0000644000000000000000000000302100000000000020246 0ustar  �

�f�k���0�dZddlZddlZddlmZdZd�ZdS)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_threadc��K�tj��}tj��}t	j|j|g|�Ri|��}|�d|���d{V��S)a�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*
    N)r�get_running_loop�contextvars�copy_context�	functools�partial�run�run_in_executor)�func�args�kwargs�loop�ctx�	func_calls      �R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/threads.pyrrsp�����"�$�$�D�
�
"�
$�
$�C��!�#�'�4�A�$�A�A�A�&�A�A�I��%�%�d�I�6�6�6�6�6�6�6�6�6�)�__doc__r	r�asyncior�__all__r�rr�<module>rs[��������������������7�7�7�7�7rdefence360agent/utils/__pycache__/validate.cpython-311.opt-1.pyc0000644000000000000000000001775000000000000021362 0ustar  �


�O��L�����ddlmZddlmZmZmZmZmZmZm	Z	m
Z
mZddlm
Z
mZmZe
dZGd�dee��ZGd�dee��Zd	�Zdd�ZGd�d
��ZdS)�)�Enum)	�
IPV4LENGTH�
IPV6LENGTH�AddressValueError�IPv4Address�IPv4Network�IPv6Address�IPv6Network�
ip_address�
ip_network)�Literal�Optional�Union)�ipv4�ipv6c��eZdZdZdZd�ZdS)�LocalhostIPz	127.0.0.1z::1c��|jS�N)�value��selfs �S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/validate.py�__str__zLocalhostIP.__str__s
���z��N)�__name__�
__module__�__qualname__rrr�rrrrs-�������D��D�����rrc�X�eZdZdZdZdZd�Zedee	dedfd���Z
dS)	�NumericIPVersionz=Example: (IPListRecord.version==NumericIPVersion[ip_version])��c�*�t|j��Sr)�strrrs rrzNumericIPVersion.__str__ s���4�:���r�
ip_version�returnc�F�|�dS|tjkr|jn|jSr)�IP�V4rr)�clsr&s  r�from_ip_versionz NumericIPVersion.from_ip_version#s)�����4�%���.�.�s�x�x�C�H�<rN)rrr�__doc__rrr�classmethodr�	IPVersionr,rrrr!r!sn������G�G��D��D�����=�!�)�,�=�	�$�	%�=�=�=��[�=�=�=rr!c�6�t�|��Sr)r)�is_valid_ipv4_addr)�addrs rr1r1-s��
� � ��&�&�&rFc�8�t�||��Sr)r)�is_valid_ipv4_network)r2�stricts  rr4r41s��
�#�#�D�&�1�1�1rc���eZdZUdZeed<dZeed<ed���Zed���Z	ed���Z
ed���Zed	���Ze	dde
eeeffd���Ze	dde
eeeffd
���Zed���Zedd���Zede
eeeeefde
eeffd���Zede
eefdefd���Zede
eeefde
eeffd���ZdS)r)rr*r�V6c�R��t�fd�|j|jfD����S)Nc3�$�K�|]
}�|kV��dSrr)�.0�ver�versions  �r�	<genexpr>z"IP.check_ip_ver.<locals>.<genexpr>;s'�����>�>�c�7�c�>�>�>�>�>�>�>r)�anyr*r7)r+r<s `r�check_ip_verzIP.check_ip_ver9s/����>�>�>�>�c�f�c�f�-=�>�>�>�>�>�>rc�J�	t|��n#t$rYdSwxYwdS�NFT)r�
ValueError�r+r2s  r�is_valid_ipzIP.is_valid_ip=s?��	��t�������	�	�	��5�5�	�����t���
 � c�6�|j|i|��p|j|i|��Sr)r4�is_valid_ipv6_network)r+�args�kwargss   r�is_valid_ip_networkzIP.is_valid_ip_networkEs>��(�s�(�
�
��
�
�8�
&�S�
&��
7��
7�
7�	8rc�J�	t|��n#t$rYdSwxYwdSrA)rrrCs  rr1zIP.is_valid_ipv4_addrK�?��	�������� �	�	�	��5�5�	�����trEc�J�	t|��n#t$rYdSwxYwdSrA)r	rrCs  r�is_valid_ipv6_addrzIP.is_valid_ipv6_addrSrLrEFr2c�n�	t|��}n#t$rYdSwxYw|r|jtkSdSrA)rrB�	prefixlenr�r+r2r5�ips    rr4zIP.is_valid_ipv4_network[�T��	��T�"�"�B�B���	�	�	��5�5�	�����	.��<�:�-�-��trEc�n�	t|��}n#t$rYdSwxYw|r|jtkSdSrA)r
rBrPrrQs    rrGzIP.is_valid_ipv6_networkirSrEc��|�|��rtjS|�|��rtjStd���)NzInvalid ip address)r4r)r*rGr7rBrCs  r�type_ofz
IP.type_ofwsK���$�$�T�*�*�	��5�L�
�
&�
&�t�
,�
,�	��5�L��-�.�.�.r�/64c�H�t||zd���}t|��S)z�Conver ipv6 addr to ipv6 network with mask
        :param str ip: ip for converting
        :param str mask: ip network mask
        F)r5)r
r%)r+rR�mask�networks    r�convert_to_ipv6_networkzIP.convert_to_ipv6_network�s&���b�4�i��6�6�6���7�|�|�r�ip_argr'c��t|ttf��r|St|ttf��r7|jdkrtnt}tt|��|f��St|��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  r�adopt_to_ipvX_networkzIP.adopt_to_ipvX_network�ss���f�{�K�8�9�9�	8��M�
���k� :�
;�
;�	8�&,�n��&9�&9�
�
�z�I��s�6�{�{�I�6�7�7�7��&�!�!�!r�netc�p�t|j��st|j��St|��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  r�ip_net_to_stringzIP.ip_net_to_string�s2���3�<� � �	,��s�*�+�+�+��3�x�x�rrRc��t|t��r/t|�t	|������S|Sr)r^r	r
r[r%)r+rRs  r�ipv6_to_64networkzIP.ipv6_to_64network�s>���b�+�&�&�	E��s�:�:�3�r�7�7�C�C�D�D�D��	rN�F)rW)rrrr*r/�__annotations__r7r.r?rDrJr1rNrr%rr
r4rGrVr[�staticmethodrr	r`rergrrrr)r)5s6��������B�	�����B�	�����?�?��[�?�����[���8�8��[�8�
����[������[���@E�����k�;�6�7�����[���@E�����k�;�6�7�����[���/�/��[�/������[���
"��c�;��[�+�M�N�
"�	�{�K�'�	(�
"�
"�
"��\�
"���5��k�)A�#B��s�����[�����{�K��4�5��	�{�K�'�	(�����[���rr)Nrh)�enumr�	ipaddressrrrrrr	r
rr�typingr
rrr/r%rr_r!r1r4r)rrr�<module>rnsf��������
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�,�+�+�+�+�+�+�+�+�+��N�#�	������#�t����=�=�=�=�=�s�D�=�=�=�&'�'�'�2�2�2�2�u�u�u�u�u�u�u�u�u�urdefence360agent/utils/__pycache__/validate.cpython-311.pyc0000644000000000000000000001775000000000000020423 0ustar  �


�O��L�����ddlmZddlmZmZmZmZmZmZm	Z	m
Z
mZddlm
Z
mZmZe
dZGd�dee��ZGd�dee��Zd	�Zdd�ZGd�d
��ZdS)�)�Enum)	�
IPV4LENGTH�
IPV6LENGTH�AddressValueError�IPv4Address�IPv4Network�IPv6Address�IPv6Network�
ip_address�
ip_network)�Literal�Optional�Union)�ipv4�ipv6c��eZdZdZdZd�ZdS)�LocalhostIPz	127.0.0.1z::1c��|jS�N)�value��selfs �S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/validate.py�__str__zLocalhostIP.__str__s
���z��N)�__name__�
__module__�__qualname__rrr�rrrrs-�������D��D�����rrc�X�eZdZdZdZdZd�Zedee	dedfd���Z
dS)	�NumericIPVersionz=Example: (IPListRecord.version==NumericIPVersion[ip_version])��c�*�t|j��Sr)�strrrs rrzNumericIPVersion.__str__ s���4�:���r�
ip_version�returnc�F�|�dS|tjkr|jn|jSr)�IP�V4rr)�clsr&s  r�from_ip_versionz NumericIPVersion.from_ip_version#s)�����4�%���.�.�s�x�x�C�H�<rN)rrr�__doc__rrr�classmethodr�	IPVersionr,rrrr!r!sn������G�G��D��D�����=�!�)�,�=�	�$�	%�=�=�=��[�=�=�=rr!c�6�t�|��Sr)r)�is_valid_ipv4_addr)�addrs rr1r1-s��
� � ��&�&�&rFc�8�t�||��Sr)r)�is_valid_ipv4_network)r2�stricts  rr4r41s��
�#�#�D�&�1�1�1rc���eZdZUdZeed<dZeed<ed���Zed���Z	ed���Z
ed���Zed	���Ze	dde
eeeffd���Ze	dde
eeeffd
���Zed���Zedd���Zede
eeeeefde
eeffd���Zede
eefdefd���Zede
eeefde
eeffd���ZdS)r)rr*r�V6c�R��t�fd�|j|jfD����S)Nc3�$�K�|]
}�|kV��dSrr)�.0�ver�versions  �r�	<genexpr>z"IP.check_ip_ver.<locals>.<genexpr>;s'�����>�>�c�7�c�>�>�>�>�>�>�>r)�anyr*r7)r+r<s `r�check_ip_verzIP.check_ip_ver9s/����>�>�>�>�c�f�c�f�-=�>�>�>�>�>�>rc�J�	t|��n#t$rYdSwxYwdS�NFT)r�
ValueError�r+r2s  r�is_valid_ipzIP.is_valid_ip=s?��	��t�������	�	�	��5�5�	�����t���
 � c�6�|j|i|��p|j|i|��Sr)r4�is_valid_ipv6_network)r+�args�kwargss   r�is_valid_ip_networkzIP.is_valid_ip_networkEs>��(�s�(�
�
��
�
�8�
&�S�
&��
7��
7�
7�	8rc�J�	t|��n#t$rYdSwxYwdSrA)rrrCs  rr1zIP.is_valid_ipv4_addrK�?��	�������� �	�	�	��5�5�	�����trEc�J�	t|��n#t$rYdSwxYwdSrA)r	rrCs  r�is_valid_ipv6_addrzIP.is_valid_ipv6_addrSrLrEFr2c�n�	t|��}n#t$rYdSwxYw|r|jtkSdSrA)rrB�	prefixlenr�r+r2r5�ips    rr4zIP.is_valid_ipv4_network[�T��	��T�"�"�B�B���	�	�	��5�5�	�����	.��<�:�-�-��trEc�n�	t|��}n#t$rYdSwxYw|r|jtkSdSrA)r
rBrPrrQs    rrGzIP.is_valid_ipv6_networkirSrEc��|�|��rtjS|�|��rtjStd���)NzInvalid ip address)r4r)r*rGr7rBrCs  r�type_ofz
IP.type_ofwsK���$�$�T�*�*�	��5�L�
�
&�
&�t�
,�
,�	��5�L��-�.�.�.r�/64c�H�t||zd���}t|��S)z�Conver ipv6 addr to ipv6 network with mask
        :param str ip: ip for converting
        :param str mask: ip network mask
        F)r5)r
r%)r+rR�mask�networks    r�convert_to_ipv6_networkzIP.convert_to_ipv6_network�s&���b�4�i��6�6�6���7�|�|�r�ip_argr'c��t|ttf��r|St|ttf��r7|jdkrtnt}tt|��|f��St|��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  r�adopt_to_ipvX_networkzIP.adopt_to_ipvX_network�ss���f�{�K�8�9�9�	8��M�
���k� :�
;�
;�	8�&,�n��&9�&9�
�
�z�I��s�6�{�{�I�6�7�7�7��&�!�!�!r�netc�p�t|j��st|j��St|��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  r�ip_net_to_stringzIP.ip_net_to_string�s2���3�<� � �	,��s�*�+�+�+��3�x�x�rrRc��t|t��r/t|�t	|������S|Sr)r^r	r
r[r%)r+rRs  r�ipv6_to_64networkzIP.ipv6_to_64network�s>���b�+�&�&�	E��s�:�:�3�r�7�7�C�C�D�D�D��	rN�F)rW)rrrr*r/�__annotations__r7r.r?rDrJr1rNrr%rr
r4rGrVr[�staticmethodrr	r`rergrrrr)r)5s6��������B�	�����B�	�����?�?��[�?�����[���8�8��[�8�
����[������[���@E�����k�;�6�7�����[���@E�����k�;�6�7�����[���/�/��[�/������[���
"��c�;��[�+�M�N�
"�	�{�K�'�	(�
"�
"�
"��\�
"���5��k�)A�#B��s�����[�����{�K��4�5��	�{�K�'�	(�����[���rr)Nrh)�enumr�	ipaddressrrrrrr	r
rr�typingr
rrr/r%rr_r!r1r4r)rrr�<module>rnsf��������
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�
�,�+�+�+�+�+�+�+�+�+��N�#�	������#�t����=�=�=�=�=�s�D�=�=�=�&'�'�'�2�2�2�2�u�u�u�u�u�u�u�u�u�urdefence360agent/utils/__pycache__/whmcs.cpython-311.opt-1.pyc0000644000000000000000000003043400000000000020704 0ustar  �

��m`c�����ddlZddlZddlZddlmcmcmZddl	m
Z
ddlmZddl
mZddlmZmZddlmZmZmZe
e��ZeegZGd�d��Zd	�Zd
�Zd�Zd�Zd
�Zd�Zd�Z d�Z!d�Z"dS)�N)�	getLogger)�config)�
update_config)�update_users_protection�	MyImunify)�MU_PLUGIN_INSTALLATION�ADVICE_EMAIL_NOTIFICATION�WordPressMuPluginc�"�eZdZdZdZd�Zd�ZdS)�	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.jsonc��tj�|j��siS	t|jd��5}|���}ddd��n#1swxYwYnA#t
$r4}t�dt|����icYd}~Sd}~wwxYw	tj
|��}n9#tjtf$r t�d|��icYSwxYw|S)N�rz"Failed to read whmcs data file: %sz"Malformed file with whmcs data: %s)
�os�path�exists�open�read�IOError�logger�error�str�json�loads�JSONDecodeError�
ValueError)�self�f�raw_data�e�datas     �P/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/whmcs.pyrzWhmcsConf.read!s=���w�~�~�d�i�(�(�	��I�	��d�i��%�%�
$���6�6�8�8��
$�
$�
$�
$�
$�
$�
$�
$�
$�
$�
$����
$�
$�
$�
$����	�	�	��L�L�=�s�1�v�v�F�F�F��I�I�I�I�I�I�����	����	��:�h�'�'�D�D���$�j�1�	�	�	��L�L�=�x�H�H�H��I�I�I�	�����sR�A*�A�A*�A"�"A*�%A"�&A*�*
B(�4)B#�B(�#B(�,C�3C7�6C7c�d�|���}|�|��	t|jd��5}t	j||d���ddd��dS#1swxYwYdS#t$r3}t�dt|����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)
r�updaterrr�dumprrrr)rr �current_data�filers     r!�savezWhmcsConf.save4s���y�y�{�{�����D�!�!�!�	K��d�i��%�%�
8���	�,��Q�7�7�7�7�
8�
8�
8�
8�
8�
8�
8�
8�
8�
8�
8�
8����
8�
8�
8�
8�
8�
8���	K�	K�	K��L�L�A�3�q�6�6�J�J�J�J�J�J�J�J�J�����	K���s;�A2�A%�A2�%A)�)A2�,A)�-A2�2
B/�<(B*�*B/N)�__name__�
__module__�__qualname__�__doc__rrr*��r!rrsH��������-�D����&K�K�K�K�Kr0rc��pK�|�tj��}t||���d{V��S�N)�getr�MY_IMUNIFY_KEY�	mi_update)�sinkr �my_imunify_updatess   r!�sync_billing_datar8Es>�������&�"7�8�8���4�!3�4�4�4�4�4�4�4�4�4r0c�j�|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
    �status�enableTF)�active�inactive�
protection)�enabled�disabled�mu_plugin_installation�smart_advice_allowedr/��key�values  r!�convert_to_config_key_valuerFJs���
�h�����!�
�
��
�
�	
�

��	�	���!�
�
��
�
�	
�

�(�	(�	(�%�u�,�,���:�r0c�V�|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��
�h����e�;�(�(��<�<�	��	�	��5�@�i�i�j�A�A�	�&�	&�	&�'��.�.���:�r0c��\K�tj������d{V��Sr2)�hp�HostingPanel�	get_usersr/r0r!rLrLrs2������"�"�,�,�.�.�.�.�.�.�.�.�.r0c	��\�K�|st�d��dS|�d��}|r6t���d|�d��i��t||���d{V��t
���||�t����|�d��stg���d{V��St���d{V���|�dg��p�}�fd�|D��}|rZt�dt|����t||td|d��d���d{V��nt�d	��t|���d{V��S)
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>�usersc���g|]}|�v�|��	Sr/r/)�.0�user�	all_userss  �r!�
<listcomp>zmi_update.<locals>.<listcomp>�s*���������):�):��):�):�):r0z'Updating protection status for users=%s�z!No users to update protection for)r�infor3rr*�update_configsr
�"prepare_for_mu_plugin_installationr�get_current_whmcs_datarLrrrF�warning)r6�requested_myimunify_data�whmcs_activation_status�target_users�filtered_passed_usersrRs     @r!r5r5vs �����$�����5�6�6�6���6�:�:�8�D�D���M������(�$<�$@�$@��$J�$J�K�L�L�L�
��7�
8�
8�8�8�8�8�8�8�8����:�:�� �$�$�%;�<�<����
$�'�'��5�5�0�+�B�/�/�/�/�/�/�/�/�/��k�k�!�!�!�!�!�!�I�+�/�/���<�<�I�	�L�����%������
<����5��%�&�&�	
�	
�	
�&��!�'��6�|�D�
�
��
�
�
�	
�	
�	
�	
�	
�	
�	
�	
�	���:�;�;�;�'�(=�>�>�>�>�>�>�>�>�>r0c���K�tj��rdgnddg�t�fd�|���D����}td�|���D����}i}|r||tj<|r||d<|r@t
�dt|����t||���d{V��dSdS)N�purchase_page_urlr:c3�F�K�|]\}}|�v�	t||��V��dSr2)rF)rP�paramrE�mi_config_parameterss   �r!�	<genexpr>z!update_configs.<locals>.<genexpr>�sI��������E�5��(�(�(�	$�E�5�1�1�(�(�(�(��r0c3�NK�|] \}}|tv�t||��V��!dSr2)�MU_PLUGIN_KEYSrF�rPrarEs   r!rcz!update_configs.<locals>.<genexpr>�sG�������E�5��N�"�"�	$�E�5�1�1�"�"�"�"��r0�
CONTROL_PANELzUpdating config with data: %s)	r�is_mi_freemium_license�dict�itemsr4rrUrr)r6rZ�mi_config_data�mu_plugin_data�config_dictrbs     @r!rVrV�s1������(�*�*�	-�	���!�8�
,�������4�:�:�<�<������N����4�:�:�<�<������N��K��<�-;��F�)�*��6�'5��O�$��/����3�S��5E�5E�F�F�F��D�+�.�.�.�.�.�.�.�.�.�.�.�/�/r0c��K�|rUtj���tj�|�������n$tj�����}d�|D��S)zp
    Returns information from database based on passed users
    if no users passed - returns for all users
    c�X�g|]'}|dtd|d��dd���(S)rQr>rT)rQr>�rH)rP�items  r!rSz"get_users_info.<locals>.<listcomp>�sW�����
���L�7��d�<�0�����	
�	
���r0)r�select�whererQ�in_�dicts)rN�results  r!�get_users_inforw�s������	(�	���� � ���!3�!3�E�!:�!:�;�;�A�A�C�C�C�
�
�
�
�
%�
%�
'�
'��
������r0c���K�tj�����}td�|�tji�����D����}|�d��}td|�d����d|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||��V��dSr2rprfs   r!rcz)get_current_whmcs_data.<locals>.<genexpr>�sD�������E�5�	&�e�U�3�3������r0rgrBrTNr>)r�
ConfigFile�config_to_dictrir3r4rjrHrr	rw)rN�	conf_data�current_config�cp_datas    r!rXrX�s�����
�!�#�#�2�2�4�4�I����%�M�M�&�*?��D�D�J�J�L�L������N�
�m�m�O�,�,�G�-J�����,B� C� C�.�.��.	�N�)�*�18���!�1�1�N�,�-�*8��)>�)>�#>�#>�#>�#>�#>�#>�N�<� ��r0c
���tjj�d��dztj�ddd||tj���	��d���z}|S)N�/z/?�cloudlinux_advantage�provisioning�my_imunify_account_protection)�m�action�suite�username�domain�	server_ip)
r�MyImunifyConfig�PURCHASE_PAGE_URL�rstrip�urllib�parse�	urlencoderJrK�
get_server_ip)r�r��purchase_url_links   r!�get_upgrade_url_linkr��sx����0�7�7��<�<�
�	�
�,�
 �
 �+�(�8�$� ��_�.�.�<�<�>�>�

�
�	
�	
�	
���r0)#rr�urllib.parser��+defence360agent.subsys.panels.hosting_panel�subsys�panels�
hosting_panelrJ�loggingr�defence360agent.contractsr�defence360agent.utils.configr�defence360agent.myimunify.modelrr�)defence360agent.utils.wordpress_mu_pluginrr	r
r+rrerr8rFrHrLr5rVrwrXr�r/r0r!�<module>r�s�������	�	�	�	�����8�8�8�8�8�8�8�8�8�8�8�8�������,�,�,�,�,�,�6�6�6�6�6�6�N�N�N�N�N�N�N�N�����������
��8�	�	��(�*C�D��+K�+K�+K�+K�+K�+K�+K�+K�\5�5�5�
���4���/�/�/�-?�-?�-?�`/�/�/�B���*���.����r0defence360agent/utils/__pycache__/whmcs.cpython-311.pyc0000644000000000000000000003043400000000000017745 0ustar  �

��m`c�����ddlZddlZddlZddlmcmcmZddl	m
Z
ddlmZddl
mZddlmZmZddlmZmZmZe
e��ZeegZGd�d��Zd	�Zd
�Zd�Zd�Zd
�Zd�Zd�Z d�Z!d�Z"dS)�N)�	getLogger)�config)�
update_config)�update_users_protection�	MyImunify)�MU_PLUGIN_INSTALLATION�ADVICE_EMAIL_NOTIFICATION�WordPressMuPluginc�"�eZdZdZdZd�Zd�ZdS)�	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.jsonc��tj�|j��siS	t|jd��5}|���}ddd��n#1swxYwYnA#t
$r4}t�dt|����icYd}~Sd}~wwxYw	tj
|��}n9#tjtf$r t�d|��icYSwxYw|S)N�rz"Failed to read whmcs data file: %sz"Malformed file with whmcs data: %s)
�os�path�exists�open�read�IOError�logger�error�str�json�loads�JSONDecodeError�
ValueError)�self�f�raw_data�e�datas     �P/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/whmcs.pyrzWhmcsConf.read!s=���w�~�~�d�i�(�(�	��I�	��d�i��%�%�
$���6�6�8�8��
$�
$�
$�
$�
$�
$�
$�
$�
$�
$�
$����
$�
$�
$�
$����	�	�	��L�L�=�s�1�v�v�F�F�F��I�I�I�I�I�I�����	����	��:�h�'�'�D�D���$�j�1�	�	�	��L�L�=�x�H�H�H��I�I�I�	�����sR�A*�A�A*�A"�"A*�%A"�&A*�*
B(�4)B#�B(�#B(�,C�3C7�6C7c�d�|���}|�|��	t|jd��5}t	j||d���ddd��dS#1swxYwYdS#t$r3}t�dt|����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)
r�updaterrr�dumprrrr)rr �current_data�filers     r!�savezWhmcsConf.save4s���y�y�{�{�����D�!�!�!�	K��d�i��%�%�
8���	�,��Q�7�7�7�7�
8�
8�
8�
8�
8�
8�
8�
8�
8�
8�
8�
8����
8�
8�
8�
8�
8�
8���	K�	K�	K��L�L�A�3�q�6�6�J�J�J�J�J�J�J�J�J�����	K���s;�A2�A%�A2�%A)�)A2�,A)�-A2�2
B/�<(B*�*B/N)�__name__�
__module__�__qualname__�__doc__rrr*��r!rrsH��������-�D����&K�K�K�K�Kr0rc��pK�|�tj��}t||���d{V��S�N)�getr�MY_IMUNIFY_KEY�	mi_update)�sinkr �my_imunify_updatess   r!�sync_billing_datar8Es>�������&�"7�8�8���4�!3�4�4�4�4�4�4�4�4�4r0c�j�|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
    �status�enableTF)�active�inactive�
protection)�enabled�disabled�mu_plugin_installation�smart_advice_allowedr/��key�values  r!�convert_to_config_key_valuerFJs���
�h�����!�
�
��
�
�	
�

��	�	���!�
�
��
�
�	
�

�(�	(�	(�%�u�,�,���:�r0c�V�|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��
�h����e�;�(�(��<�<�	��	�	��5�@�i�i�j�A�A�	�&�	&�	&�'��.�.���:�r0c��\K�tj������d{V��Sr2)�hp�HostingPanel�	get_usersr/r0r!rLrLrs2������"�"�,�,�.�.�.�.�.�.�.�.�.r0c	��\�K�|st�d��dS|�d��}|r6t���d|�d��i��t||���d{V��t
���||�t����|�d��stg���d{V��St���d{V���|�dg��p�}�fd�|D��}|rZt�dt|����t||td|d��d���d{V��nt�d	��t|���d{V��S)
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>�usersc���g|]}|�v�|��	Sr/r/)�.0�user�	all_userss  �r!�
<listcomp>zmi_update.<locals>.<listcomp>�s*���������):�):��):�):�):r0z'Updating protection status for users=%s�z!No users to update protection for)r�infor3rr*�update_configsr
�"prepare_for_mu_plugin_installationr�get_current_whmcs_datarLrrrF�warning)r6�requested_myimunify_data�whmcs_activation_status�target_users�filtered_passed_usersrRs     @r!r5r5vs �����$�����5�6�6�6���6�:�:�8�D�D���M������(�$<�$@�$@��$J�$J�K�L�L�L�
��7�
8�
8�8�8�8�8�8�8�8����:�:�� �$�$�%;�<�<����
$�'�'��5�5�0�+�B�/�/�/�/�/�/�/�/�/��k�k�!�!�!�!�!�!�I�+�/�/���<�<�I�	�L�����%������
<����5��%�&�&�	
�	
�	
�&��!�'��6�|�D�
�
��
�
�
�	
�	
�	
�	
�	
�	
�	
�	
�	���:�;�;�;�'�(=�>�>�>�>�>�>�>�>�>r0c���K�tj��rdgnddg�t�fd�|���D����}td�|���D����}i}|r||tj<|r||d<|r@t
�dt|����t||���d{V��dSdS)N�purchase_page_urlr:c3�F�K�|]\}}|�v�	t||��V��dSr2)rF)rP�paramrE�mi_config_parameterss   �r!�	<genexpr>z!update_configs.<locals>.<genexpr>�sI��������E�5��(�(�(�	$�E�5�1�1�(�(�(�(��r0c3�NK�|] \}}|tv�t||��V��!dSr2)�MU_PLUGIN_KEYSrF�rPrarEs   r!rcz!update_configs.<locals>.<genexpr>�sG�������E�5��N�"�"�	$�E�5�1�1�"�"�"�"��r0�
CONTROL_PANELzUpdating config with data: %s)	r�is_mi_freemium_license�dict�itemsr4rrUrr)r6rZ�mi_config_data�mu_plugin_data�config_dictrbs     @r!rVrV�s1������(�*�*�	-�	���!�8�
,�������4�:�:�<�<������N����4�:�:�<�<������N��K��<�-;��F�)�*��6�'5��O�$��/����3�S��5E�5E�F�F�F��D�+�.�.�.�.�.�.�.�.�.�.�.�/�/r0c��K�|rUtj���tj�|�������n$tj�����}d�|D��S)zp
    Returns information from database based on passed users
    if no users passed - returns for all users
    c�X�g|]'}|dtd|d��dd���(S)rQr>rT)rQr>�rH)rP�items  r!rSz"get_users_info.<locals>.<listcomp>�sW�����
���L�7��d�<�0�����	
�	
���r0)r�select�whererQ�in_�dicts)rN�results  r!�get_users_inforw�s������	(�	���� � ���!3�!3�E�!:�!:�;�;�A�A�C�C�C�
�
�
�
�
%�
%�
'�
'��
������r0c���K�tj�����}td�|�tji�����D����}|�d��}td|�d����d|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||��V��dSr2rprfs   r!rcz)get_current_whmcs_data.<locals>.<genexpr>�sD�������E�5�	&�e�U�3�3������r0rgrBrTNr>)r�
ConfigFile�config_to_dictrir3r4rjrHrr	rw)rN�	conf_data�current_config�cp_datas    r!rXrX�s�����
�!�#�#�2�2�4�4�I����%�M�M�&�*?��D�D�J�J�L�L������N�
�m�m�O�,�,�G�-J�����,B� C� C�.�.��.	�N�)�*�18���!�1�1�N�,�-�*8��)>�)>�#>�#>�#>�#>�#>�#>�N�<� ��r0c
���tjj�d��dztj�ddd||tj���	��d���z}|S)N�/z/?�cloudlinux_advantage�provisioning�my_imunify_account_protection)�m�action�suite�username�domain�	server_ip)
r�MyImunifyConfig�PURCHASE_PAGE_URL�rstrip�urllib�parse�	urlencoderJrK�
get_server_ip)r�r��purchase_url_links   r!�get_upgrade_url_linkr��sx����0�7�7��<�<�
�	�
�,�
 �
 �+�(�8�$� ��_�.�.�<�<�>�>�

�
�	
�	
�	
���r0)#rr�urllib.parser��+defence360agent.subsys.panels.hosting_panel�subsys�panels�
hosting_panelrJ�loggingr�defence360agent.contractsr�defence360agent.utils.configr�defence360agent.myimunify.modelrr�)defence360agent.utils.wordpress_mu_pluginrr	r
r+rrerr8rFrHrLr5rVrwrXr�r/r0r!�<module>r�s�������	�	�	�	�����8�8�8�8�8�8�8�8�8�8�8�8�������,�,�,�,�,�,�6�6�6�6�6�6�N�N�N�N�N�N�N�N�����������
��8�	�	��(�*C�D��+K�+K�+K�+K�+K�+K�+K�+K�\5�5�5�
���4���/�/�/�-?�-?�-?�`/�/�/�B���*���.����r0defence360agent/utils/__pycache__/wordpress_mu_plugin.cpython-311.opt-1.pyc0000644000000000000000000000402600000000000023670 0ustar  �

�����	��Z�ddlZddlmZee��ZdZdZeegZGd�d��ZdS)�N)�	getLogger�mu_plugin_installation�advice_email_notificationc��eZdZd�ZdS)�WordPressMuPluginc�T�t|dk|g��s8t�dt|��t|����dS|s*t�dt|����dStj�d��std���dS)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)�all�logger�warning�str�os�path�exists�
ValueError)�self�activation_status�mu_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_installations����%��1�3C�D�E�E�	��N�N�N��%�&�&��$�%�%�	
�
�
�
�F��	��N�N�)��$�%�%�
�
�
�

�F��w�~�~�A�B�B�	��A���
�	�	�N)�__name__�
__module__�__qualname__r�rrrr
s#����������rr)	r�loggingrrr�MU_PLUGIN_INSTALLATION�ADVICE_EMAIL_NOTIFICATION�MU_PLUGIN_KEYSrrrr�<module>r sx��	�	�	�	�������	��8�	�	��1��7��(�*C�D�����������rdefence360agent/utils/__pycache__/wordpress_mu_plugin.cpython-311.pyc0000644000000000000000000000402600000000000022731 0ustar  �

�����	��Z�ddlZddlmZee��ZdZdZeegZGd�d��ZdS)�N)�	getLogger�mu_plugin_installation�advice_email_notificationc��eZdZd�ZdS)�WordPressMuPluginc�T�t|dk|g��s8t�dt|��t|����dS|s*t�dt|����dStj�d��std���dS)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)�all�logger�warning�str�os�path�exists�
ValueError)�self�activation_status�mu_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_installations����%��1�3C�D�E�E�	��N�N�N��%�&�&��$�%�%�	
�
�
�
�F��	��N�N�)��$�%�%�
�
�
�

�F��w�~�~�A�B�B�	��A���
�	�	�N)�__name__�
__module__�__qualname__r�rrrr
s#����������rr)	r�loggingrrr�MU_PLUGIN_INSTALLATION�ADVICE_EMAIL_NOTIFICATION�MU_PLUGIN_KEYSrrrr�<module>r sx��	�	�	�	�������	��8�	�	��1��7��(�*C�D�����������rdefence360agent/utils/__pycache__/zipsafe.cpython-311.opt-1.pyc0000644000000000000000000000254300000000000021224 0ustar  �

�c�z�+O��8�ddlZddlmZdejdeddfd�ZdS)�N)�Path�zf�dest�returnc��t|�����}|���D]�}|�d��rt	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: )r�resolve�namelist�
startswith�
ValueError�parts�parents�
extractall)rr�
dest_resolved�memberr�targets      �R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/zipsafe.py�safe_extractallrs�����J�J�&�&�(�(�M��+�+�-�-�
O�
O�����[�)�)�	P��*�f�f�N�O�O�O��V���"���5�=�=��*�AG��I���
� �&�(�1�1�3�3���]�"�"�}�F�N�'J�'J��*�V�V�M�N�N�N���M�M�-� � � � � �)�zipfile�pathlibr�ZipFiler�rr�<module>rsU������������
!���
!�t�
!��
!�
!�
!�
!�
!�
!rdefence360agent/utils/__pycache__/zipsafe.cpython-311.pyc0000644000000000000000000000254300000000000020265 0ustar  �

�c�z�+O��8�ddlZddlmZdejdeddfd�ZdS)�N)�Path�zf�dest�returnc��t|�����}|���D]�}|�d��rt	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: )r�resolve�namelist�
startswith�
ValueError�parts�parents�
extractall)rr�
dest_resolved�memberr�targets      �R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/zipsafe.py�safe_extractallrs�����J�J�&�&�(�(�M��+�+�-�-�
O�
O�����[�)�)�	P��*�f�f�N�O�O�O��V���"���5�=�=��*�AG��I���
� �&�(�1�1�3�3���]�"�"�}�F�N�'J�'J��*�V�V�M�N�N�N���M�M�-� � � � � �)�zipfile�pathlibr�ZipFiler�rr�<module>rsU������������
!���
!�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<c  |
        |----------------+--------------------------------+------------+------|
        | no p. call     | call soon                      | call at c+T| warn |
        | p. call at c+T | cancel the call/warn, call soon| drop call  | warn |
        """

        def decorator(coro):
            @functools.wraps(coro)
            async def wrapper(*args, **kwargs):
                loop = kwargs.get("loop")
                if loop is None:
                    loop = asyncio.get_event_loop()

                if args or kwargs:
                    args_repr = "*%r, **%r" % (args, kwargs)
                else:  # special case no args case
                    args_repr = ""
                call_repr = "%s(%s)" % (coro.__name__, args_repr)

                def log_exception(task):
                    """Log task's error
                       if any with event's loop exception handler.

                    CancelledError is not logged.
                    """
                    if not task.cancelled() and task.exception() is not None:
                        loop.call_exception_handler(
                            {
                                "message": "Unhandled exception during "
                                + call_repr,
                                "exception": task.exception(),
                                "task": task,
                            }
                        )

                def call_delayed(coro, args, kwargs):
                    """Call & schedule the delayed coroutine now."""
                    logger.info("Schedule call %s", call_repr)
                    self.call_time = loop.time()
                    self.delayed_call = None
                    task = loop.create_task(coro(*args, **kwargs))
                    task.add_done_callback(
                        log_exception
                        if done_callback is None
                        else done_callback
                    )

                now = loop.time()
                if now > (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.py0000644000000000000000000000264500000000000013534 0ustar  import asyncio
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)
    )
    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.py0000644000000000000000000002766600000000000015203 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,
    ):
        import ssl as _ssl

        self._ssl_context = ssl_context or _ssl.create_default_context()

        if ip_chooser is None:
            self._opener = urllib.request.build_opener()
        else:
            self._opener = urllib.request.build_opener(
                ForcedIPHTTPHandler(ip_chooser=ip_chooser),
                ForcedIPHTTPSHandler(
                    ip_chooser=ip_chooser,
                    context=self._ssl_context,
                ),
            )

    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.py0000644000000000000000000000445300000000000015476 0ustar  import asyncio
import logging

from enum import Enum
from os import fsdecode
from pathlib import Path
from typing import List

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")


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 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.<attr>``
    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  �

�:�SV���~�dZddlmZddlmZmZddlmZddlm	Z	ddl
mZmZm
Z
mZmZmZddlmZmZmZmZgd�Zd	S)
ztWordPress incident collection, sending logic, plugin management, and rules.

Available for both AV and IM360 modes.
�)�ChangelogProcessor)�IncidentCollector�IncidentRateLimiter)�IncidentSender)�IncidentFileParser)�WP_RULES_ZIP_FILENAME�WP_RULES_VERSION_FILENAME�find_file_in_index�extract_wp_rules_yaml�get_wp_rules_data�get_wp_ruleset_version)�PLUGIN_PATH�PLUGIN_SLUG�PLUGIN_VERSION_FILE�WP_CLI_WRAPPER_PATH)rrrrrrr	r
rrr
rrrrN)�__doc__�-defence360agent.wordpress.changelog_processorr�,defence360agent.wordpress.incident_collectorrr�)defence360agent.wordpress.incident_senderr�)defence360agent.wordpress.incident_parserr�"defence360agent.wordpress.wp_rulesrr	r
rrr
�#defence360agent.wordpress.constantsrrrr�__all__���W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/__init__.py�<module>rs����
��������������E�D�D�D�D�D�H�H�H�H�H�H����������������������������������rdefence360agent/wordpress/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000264000000000000021251 0ustar  �

�:�SV���~�dZddlmZddlmZmZddlmZddlm	Z	ddl
mZmZm
Z
mZmZmZddlmZmZmZmZgd�Zd	S)
ztWordPress incident collection, sending logic, plugin management, and rules.

Available for both AV and IM360 modes.
�)�ChangelogProcessor)�IncidentCollector�IncidentRateLimiter)�IncidentSender)�IncidentFileParser)�WP_RULES_ZIP_FILENAME�WP_RULES_VERSION_FILENAME�find_file_in_index�extract_wp_rules_yaml�get_wp_rules_data�get_wp_ruleset_version)�PLUGIN_PATH�PLUGIN_SLUG�PLUGIN_VERSION_FILE�WP_CLI_WRAPPER_PATH)rrrrrrr	r
rrr
rrrrN)�__doc__�-defence360agent.wordpress.changelog_processorr�,defence360agent.wordpress.incident_collectorrr�)defence360agent.wordpress.incident_senderr�)defence360agent.wordpress.incident_parserr�"defence360agent.wordpress.wp_rulesrr	r
rrr
�#defence360agent.wordpress.constantsrrrr�__all__���W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/__init__.py�<module>rs����
��������������E�D�D�D�D�D�H�H�H�H�H�H����������������������������������rdefence360agent/wordpress/__pycache__/bot_protection.cpython-311.opt-1.pyc0000644000000000000000000001374400000000000023512 0ustar  �

"+.r��R��
�^�dZddlZddlZddlZddlZddlmZeje��Z	dZ
dZdZej
dej��Zej
dej��Zej
d	ej��Zej
d
ej��Zdedefd
�Zdedefd�Zdedefd�Zdededededef
d�ZdS)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)�balanced�strict�monitorrizDdefine\(\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+)['"]�path�uidc�<�	tj|tjtjztjz��}n#t
$rYdSwxYw	tj|��}tj|j	��r|j
|kr	tj|��dStj|t���dd���tj|��S#t
$rYtj|��dSwxYw#tj|��wxYw)z�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.
    Nzutf-8�replace)�errors)�os�open�O_RDONLY�
O_NOFOLLOW�
O_NONBLOCK�OSError�fstat�stat�S_ISREG�st_mode�st_uid�close�read�
_MAX_BYTES�decode)rr�fd�infos    �]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/bot_protection.py�
_safe_readr.s���
�W�T�2�;���6���F�
G�
G���������t�t�������x��|�|���|�D�L�)�)�	�T�[�C�-?�-?��
	��������	�w�r�:�&�&�-�-�g�i�-�H�H�	�������������
�������������	���������s9�9<�
A
�	A
�8C �.C � 
D�*D�D�D�Dc��t||��}|�dSd}t�|��}|r+|�d�����dk}d}t
�|��}|rU|�d�����tvr'|�d�����}||fS)zoReturn (enabled, preset) from wp-config.php constants; each is None
    when the constant is absent or invalid.N)NN��true)r�_CONST_ENABLED�search�group�lower�
_CONST_PRESET�
VALID_PRESETS)rr�text�enabled�match�presets      r�_parse_wp_configr,Ds����d�C� � �D��|��z��G��!�!�$�'�'�E��3��+�+�a�.�.�&�&�(�(�F�2��
�F�� � ��&�&�E��(����Q���%�%�'�'�=�8�8����Q���%�%�'�'���F�?��c��t||��}|�dSt�|��}|r+|�d�����dknd}d}t
�|��}|rU|�d�����tvr'|�d�����}||fS)z�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.N)TNr r!T)r�_KV_ENABLEDr#r$r%�
_KV_PRESETr')rrr(r*r)r+s      r�_parse_bot_settingsr1Us����d�C� � �D��|��z����t�$�$�E�27�A�e�k�k�!�n�n�"�"�$�$��.�.�T�G�
�F����d�#�#�E��(����Q���%�%�'�'�=�8�8����Q���%�%�'�'���F�?�r-�docroot�data_dir�hoster_enabled�
hoster_presetc��tt|��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,rr1�boolr'�DEFAULT_PRESET)r2r3rr4r5�
const_enabled�const_preset�bot_enabled�
bot_presetr)�	candidates           r�resolve_ai_bot_protectionr>es���#3��W�
�
��'��#�#��M�<�2��X���+�+�S����K���>�"�"�2�{�G�������"�J�
�>�&�&�	��
�%�%��I�%�%�%�%�&��N�"�"r-)�__doc__�loggingr�rer�pathlibr�	getLogger�__name__�loggerr'r8r�compile�
IGNORECASEr"r&r/r0�intrr,r1�strr7r>�r-r�<module>rKs���������	�	�	�	�	�	�	�	�����������	��	�8�	$�	$��1�
����
����O��M������
���M���
��b�j�.��
�����R�Z�.��
���
�
�T�������,�4��c�����"
�d�
��
�
�
�
� #�
�#��#�

�#��	#�
�#�#�#�#�#�#r-defence360agent/wordpress/__pycache__/bot_protection.cpython-311.pyc0000644000000000000000000001374400000000000022553 0ustar  �

"+.r��R��
�^�dZddlZddlZddlZddlZddlmZeje��Z	dZ
dZdZej
dej��Zej
dej��Zej
d	ej��Zej
d
ej��Zdedefd
�Zdedefd�Zdedefd�Zdededededef
d�ZdS)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)�balanced�strict�monitorrizDdefine\(\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+)['"]�path�uidc�<�	tj|tjtjztjz��}n#t
$rYdSwxYw	tj|��}tj|j	��r|j
|kr	tj|��dStj|t���dd���tj|��S#t
$rYtj|��dSwxYw#tj|��wxYw)z�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.
    Nzutf-8�replace)�errors)�os�open�O_RDONLY�
O_NOFOLLOW�
O_NONBLOCK�OSError�fstat�stat�S_ISREG�st_mode�st_uid�close�read�
_MAX_BYTES�decode)rr�fd�infos    �]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/bot_protection.py�
_safe_readr.s���
�W�T�2�;���6���F�
G�
G���������t�t�������x��|�|���|�D�L�)�)�	�T�[�C�-?�-?��
	��������	�w�r�:�&�&�-�-�g�i�-�H�H�	�������������
�������������	���������s9�9<�
A
�	A
�8C �.C � 
D�*D�D�D�Dc��t||��}|�dSd}t�|��}|r+|�d�����dk}d}t
�|��}|rU|�d�����tvr'|�d�����}||fS)zoReturn (enabled, preset) from wp-config.php constants; each is None
    when the constant is absent or invalid.N)NN��true)r�_CONST_ENABLED�search�group�lower�
_CONST_PRESET�
VALID_PRESETS)rr�text�enabled�match�presets      r�_parse_wp_configr,Ds����d�C� � �D��|��z��G��!�!�$�'�'�E��3��+�+�a�.�.�&�&�(�(�F�2��
�F�� � ��&�&�E��(����Q���%�%�'�'�=�8�8����Q���%�%�'�'���F�?��c��t||��}|�dSt�|��}|r+|�d�����dknd}d}t
�|��}|rU|�d�����tvr'|�d�����}||fS)z�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.N)TNr r!T)r�_KV_ENABLEDr#r$r%�
_KV_PRESETr')rrr(r*r)r+s      r�_parse_bot_settingsr1Us����d�C� � �D��|��z����t�$�$�E�27�A�e�k�k�!�n�n�"�"�$�$��.�.�T�G�
�F����d�#�#�E��(����Q���%�%�'�'�=�8�8����Q���%�%�'�'���F�?�r-�docroot�data_dir�hoster_enabled�
hoster_presetc��tt|��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,rr1�boolr'�DEFAULT_PRESET)r2r3rr4r5�
const_enabled�const_preset�bot_enabled�
bot_presetr)�	candidates           r�resolve_ai_bot_protectionr>es���#3��W�
�
��'��#�#��M�<�2��X���+�+�S����K���>�"�"�2�{�G�������"�J�
�>�&�&�	��
�%�%��I�%�%�%�%�&��N�"�"r-)�__doc__�loggingr�rer�pathlibr�	getLogger�__name__�loggerr'r8r�compile�
IGNORECASEr"r&r/r0�intrr,r1�strr7r>�r-r�<module>rKs���������	�	�	�	�	�	�	�	�����������	��	�8�	$�	$��1�
����
����O��M������
���M���
��b�j�.��
�����R�Z�.��
���
�
�T�������,�4��c�����"
�d�
��
�
�
�
� #�
�#��#�

�#��	#�
�#�#�#�#�#�#r-defence360agent/wordpress/__pycache__/changelog_processor.cpython-311.opt-1.pyc0000644000000000000000000004147300000000000024506 0ustar  �

�H��������dZddlZddlZddlZddlmZddlmZddlm	Z	m
Z
ddlmZddl
mZmZddlmZdd	lmZdd
lmZddlmZddlmZeje��Zd
ZdZdZdZ Gd�d��Z!dS)a�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:
    <?php __halt_compiler();
    #{base64-encoded JSON for action 1}
    #{base64-encoded JSON for action 2}

Each JSON action has the form:
    {"action": "disable"|"enable", "rule_id": "xyz", "ts": ...}

The user_id stored with each action is the system UID of the WordPress site
owner (site.uid).
�N)�Path)�MessageType)�WP_WAF_RULES_EDIT�has_permission)�MessageSink)�WPSite�
WordpressSite)�WPDisabledRule)�
open_nofollow)�get_data_dir)�IncidentFileParser)�parse_php_with_embedded_jsonz
changelog.phpzdisabled-rules.php�disable�enablec�t�eZdZdZdd�Zdeededzdeefd�Zdededzde	fd	�Z
d
ededeefd�Z
d
edededzde	fd�Zd
ededede	fd�Zedededzfd���Zedededede	fd���Zdedede	fd�Zed
edededzdeddf
d���Zededede	fd���ZdS)�ChangelogProcessora�Process WordPress rule disable/enable changelog files.

    Reads changelog.php from each site's data directory, applies
    disable/enable actions to the WPDisabledRule database, reports events
    to the correlation server, and deletes the file after processing.

    If no changelog exists (or no new entries), checks whether
    disabled-rules.php has been modified externally (e.g. backup restore)
    and flags the domain for regeneration.
    �returnNc�,�t��|_dS)N)r
�parser)�selfs �b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/changelog_processor.py�__init__zChangelogProcessor.__init__:s��)�*�*������sites�sinkc���K�g}|D]3}|�||���d{V��r|�|���4|r(t�dt	|����|S)a-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).
        Nz(Changelog processing affected %d site(s))�
_process_site�append�logger�info�len)rrr�affected�sites     r�process_changelogs_for_sitesz/ChangelogProcessor.process_changelogs_for_sites?s�����"$���	&�	&�D��'�'��d�3�3�3�3�3�3�3�3�
&�����%�%�%���	��K�K�:��H�
�
�
�
�
�
�rr#c��~K�	t|���d{V��}|���sdS|tz}|���r|�|||���d{V��rdS|�||��rdSn8#t
$r+}t�d|j|��Yd}~nd}~wwxYwdS)z�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.
        NFTz*Error processing changelog for site %s: %s)	r�exists�CHANGELOG_FILENAME�_process_changelog_file�_is_disabled_rules_file_stale�	Exceptionr�error�docroot)rr#r�data_dir�changelog_path�es      rrz ChangelogProcessor._process_site\s(����	�)�$�/�/�/�/�/�/�/�/�H��?�?�$�$�
��u�%�(:�:�N��$�$�&�&�
 ��5�5�"�D�$��������� � �4��1�1�$��A�A�
��t�
���	�	�	��L�L�<����
�
�
�
�
�
�
�
�����	�����us"�)B�;B�,B�
B:�!B5�5B:r.c��	|j�|��	|�d���S#t$r+}t�d|j|��Yd}~Sd}~wwxYw#ttf$r|}t�d|j|��gcYd}~	|�d���S#t$r+}t�d|j|��Yd}~Sd}~wwxYwd}~wwxYw#	|�d���w#t$r+}t�d|j|��Yd}~wd}~wwxYwxYw)zsParse a changelog file and delete it.

        The file is deleted regardless of whether parsing succeeds.
        T)�
missing_okz*Failed to delete changelog for site %s: %sNz)Failed to parse changelog for site %s: %s)r�
parse_file�unlink�OSErrorrr+r,�
ValueError)rr.r#r/s    r�_consume_changelogz%ChangelogProcessor._consume_changelog�s���	��;�)�)�.�9�9�
��%�%��%�6�6�6�6���
�
�
����@��L��������������
�������$�	�	�	��L�L�;����
�
�
�
�I�I�I�I�I�
��%�%��%�6�6�6�6���
�
�
����@��L��������������
��������	�����
��%�%��%�6�6�6�6���
�
�
����@��L��������������
������s��A+�3�
A(�!A#�#A(�+C8�<"C3�C8�C;�$B;�;
C0�!C+�+C0�3C8�8C;�;E�=D�E�
E	�!E�?E�E	�	Ec��K�|�||��}|sdSttt|j�����d{V��s0t
�dt|��|j��dS|�	|��}d}|D�]8}	t|�dd����}|dkr.td|�dd���d	|j�����|�>||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 %s�tsrz: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)r6rr�str�uidrr r!r,�_get_last_sync_ts�float�getr5�_process_action�_report_action�warningr*r+)
rr.r#r�actions�last_sync_ts�changed�action�	timestampr/s
          rr(z*ChangelogProcessor._process_changelog_file�s����� �)�)�.�$�?�?���	��5�$�$5�s�4�8�}�}�E�E�E�E�E�E�E�E�	��K�K�3��G�����	
�
�
��5��-�-�d�3�3�����	�	�F�
�!�&�*�*�T�1�"5�"5�6�6�	���>�>�$�3�%+�Z�Z�	�3�%?�%?�3�3�$(�L�3�3����
 �+�	�\�0I�0I��K�K�@��
�
�9�c�2�2���!�$�
�����'�'���i�@�@�#�"�G��)�)�&�$��i�H�H�H�H�H�H�H�H�H�H���
J�
J�
J����E�q�I�I�I�I�I�I�I�I������
�
�
����K���L��	������������
����	���G��L���L�L��		
�	
�	
��s+�BE&�.7E&�&
G
�0F�
G
�"G�G
rGrHc�B�|�d��}|�d��}|r|std|�����|tkr|�|||��S|tkr|�||��Std|�d|�d|j�����)a�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.
        rGr9z.Missing action or rule_id in changelog entry: zUnknown changelog action 'z' for rule r;)r@r5�ACTION_DISABLE�_apply_disable�
ACTION_ENABLE�
_apply_enabler,)rrGr#rH�action_typer9s      rrAz"ChangelogProcessor._process_action�s���"�j�j��*�*���*�*�Y�'�'���	�'�	��I��I�I���
��.�(�(��&�&�w��i�@�@�@�
�M�
)�
)��%�%�g�t�4�4�4��>�[�>�>�$�>�>�/3�|�>�>���
rc�p�	tj|j��}|jS#tj$rYdSwxYw)z�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.
        N)r	�	get_by_idr,�disabled_rules_sync_ts�DoesNotExist)r#�db_sites  rr>z$ChangelogProcessor._get_last_sync_tssG��	�#�-�d�l�;�;�G��1�1���)�	�	�	��4�4�	���s�"�5�5r9c�j�tj||jgtj|j|���}|dkS)z�Apply a disable action from the changelog.

        Returns:
            True if a new disable entry was created (not a no-op).
        )r9�domains�source�user_idrHr)r
�store�domain�SOURCE_WORDPRESSr=)r9r#rH�counts    rrKz!ChangelogProcessor._apply_disables?���$���[�M�!�2��H��
�
�
���q�y�rc�F�tj||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�
�
�
���q�y�rc
��K�|�dS|d}|d}|tkr
tj}n|tkr
tj}ndS	|�|d||jg||jtj	������d{V��dS#t$r-}t�d||j
|��Yd}~dSd}~wwxYw)z�Send a rule change event to the correlation server.

        Must only be called for valid actions (after _process_action succeeds).
        NrGr9�	wordpress)�	plugin_id�rulerUrHrWrVz<Failed to report changelog action for rule %s on site %s: %s)rJr�WPRuleDisabledrL�
WPRuleEnabled�process_messagerYr=r
rZr*rr+r,)rGr#rrHrNr9�message_clsr/s        rrBz!ChangelogProcessor._report_action0s,�����<��F��X�&����#���.�(�(�%�4�K�K�
�M�
)�
)�%�3�K�K��F�	��&�&���)� �!�[�M�'� �H�)�:�
���	�	�	
�	
�	
�	
�	
�	
�	
�	
�	
���	�	�	��L�L�N�����	
�
�
�
�
�
�
�
�
�����	���s�
?B�
C�"B=�=Cr-c�b�|tz}	tt|����5}tjtj|��dd���5}|���}ddd��n#1swxYwYddd��n#1swxYwYnU#t$rYdSt$r<}|j	tj
krt�d||��Yd}~dSd}~wwxYw	t|��}t|�dd����}n@#tt f$r,}	t�d	|j|	��Yd}	~	dSd}	~	wwxYw	t'j|j��}
n#t&j$rYdSwxYw|
j}|dupt/||z
��d
kS)aCheck 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.
        �rzutf-8)�encodingNFzCannot open %s: %sr8rz.Cannot read disabled-rules.php for site %s: %sg�?)�DISABLED_RULES_FILENAMErr<�os�fdopen�dup�read�FileNotFoundErrorr4�errno�ELOOPr�debugrr?r@r5rCr,r	rPrRrQ�abs)r#r-�disabled_rules_path�fd�f�content�exc�data�file_tsr/rS�db_tss            rr)z0ChangelogProcessor._is_disabled_rules_file_stale[sV��'�)@�@��
	��s�#6�7�7�8�8�
'�B��Y�r�v�b�z�z�3��A�A�A�'�Q��f�f�h�h�G�'�'�'�'�'�'�'�'�'�'�'����'�'�'�'�
'�
'�
'�
'�
'�
'�
'�
'�
'�
'�
'����
'�
'�
'�
'���!�	�	�	��5�5��	�	�	��y�E�K�'�'����1�3F��L�L�L��5�5�5�5�5�����	����
		�/��8�8�D��D�H�H�T�1�-�-�.�.�G�G����$�	�	�	��N�N�@����
�
�
�
�5�5�5�5�5�����
	����	�#�-�d�l�;�;�G�G���)�	�	�	��5�5�	�����.����}�:��G�e�O� 4� 4�s� :�:s��B�*B
�A3�'B
�3A7	�7B
�:A7	�;B
�>B�
B�B�B�B�
C(�#	C(�,1C#�#C(�,2D�E�0!E�E� E:�:F
�F
)rN)�__name__�
__module__�__qualname__�__doc__r�listrrr$�boolrr�dictr6r(r?rA�staticmethodr>r<rKrMrBr)�rrrr.sw������	�	�+�+�+�+�
��F�|���D� ��
�f��	����:$��$��D� �$�
�	$�$�$�$�L�"��*0��	
�d������4G��G��G��D� �	G�

�G�G�G�G�R �� �"(� �5:� �	
� � � � �D�
��
�5�4�<�
�
�
��\�
��
��
�6�
�e�
��
�
�
��\�
�
�S�
��
�4�
�
�
�
��(��(��(��D� �(��	(�

�(�(�(��\�(�T�);��);��);�
�);�);�);��\�);�);�);rr)"r~ro�loggingrj�pathlibr�"defence360agent.contracts.messagesr�%defence360agent.contracts.permissionsrr�!defence360agent.contracts.pluginsr�defence360agent.model.wordpressrr	�&defence360agent.model.wp_disabled_ruler
�defence360agent.utils.fd_opsr�defence360agent.wordpress.clir�)defence360agent.wordpress.incident_parserr
�defence360agent.wordpress.utilsr�	getLoggerr{rr'rirJrLrr�rr�<module>r�sb����$
��������	�	�	�	�������:�:�:�:�:�:���������:�9�9�9�9�9�A�A�A�A�A�A�A�A�A�A�A�A�A�A�6�6�6�6�6�6�6�6�6�6�6�6�H�H�H�H�H�H�H�H�H�H�H�H�	��	�8�	$�	$��$��.�����
�W;�W;�W;�W;�W;�W;�W;�W;�W;�W;rdefence360agent/wordpress/__pycache__/changelog_processor.cpython-311.pyc0000644000000000000000000004147300000000000023547 0ustar  �

�H��������dZddlZddlZddlZddlmZddlmZddlm	Z	m
Z
ddlmZddl
mZmZddlmZdd	lmZdd
lmZddlmZddlmZeje��Zd
ZdZdZdZ Gd�d��Z!dS)a�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:
    <?php __halt_compiler();
    #{base64-encoded JSON for action 1}
    #{base64-encoded JSON for action 2}

Each JSON action has the form:
    {"action": "disable"|"enable", "rule_id": "xyz", "ts": ...}

The user_id stored with each action is the system UID of the WordPress site
owner (site.uid).
�N)�Path)�MessageType)�WP_WAF_RULES_EDIT�has_permission)�MessageSink)�WPSite�
WordpressSite)�WPDisabledRule)�
open_nofollow)�get_data_dir)�IncidentFileParser)�parse_php_with_embedded_jsonz
changelog.phpzdisabled-rules.php�disable�enablec�t�eZdZdZdd�Zdeededzdeefd�Zdededzde	fd	�Z
d
ededeefd�Z
d
edededzde	fd�Zd
ededede	fd�Zedededzfd���Zedededede	fd���Zdedede	fd�Zed
edededzdeddf
d���Zededede	fd���ZdS)�ChangelogProcessora�Process WordPress rule disable/enable changelog files.

    Reads changelog.php from each site's data directory, applies
    disable/enable actions to the WPDisabledRule database, reports events
    to the correlation server, and deletes the file after processing.

    If no changelog exists (or no new entries), checks whether
    disabled-rules.php has been modified externally (e.g. backup restore)
    and flags the domain for regeneration.
    �returnNc�,�t��|_dS)N)r
�parser)�selfs �b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/changelog_processor.py�__init__zChangelogProcessor.__init__:s��)�*�*������sites�sinkc���K�g}|D]3}|�||���d{V��r|�|���4|r(t�dt	|����|S)a-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).
        Nz(Changelog processing affected %d site(s))�
_process_site�append�logger�info�len)rrr�affected�sites     r�process_changelogs_for_sitesz/ChangelogProcessor.process_changelogs_for_sites?s�����"$���	&�	&�D��'�'��d�3�3�3�3�3�3�3�3�
&�����%�%�%���	��K�K�:��H�
�
�
�
�
�
�rr#c��~K�	t|���d{V��}|���sdS|tz}|���r|�|||���d{V��rdS|�||��rdSn8#t
$r+}t�d|j|��Yd}~nd}~wwxYwdS)z�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.
        NFTz*Error processing changelog for site %s: %s)	r�exists�CHANGELOG_FILENAME�_process_changelog_file�_is_disabled_rules_file_stale�	Exceptionr�error�docroot)rr#r�data_dir�changelog_path�es      rrz ChangelogProcessor._process_site\s(����	�)�$�/�/�/�/�/�/�/�/�H��?�?�$�$�
��u�%�(:�:�N��$�$�&�&�
 ��5�5�"�D�$��������� � �4��1�1�$��A�A�
��t�
���	�	�	��L�L�<����
�
�
�
�
�
�
�
�����	�����us"�)B�;B�,B�
B:�!B5�5B:r.c��	|j�|��	|�d���S#t$r+}t�d|j|��Yd}~Sd}~wwxYw#ttf$r|}t�d|j|��gcYd}~	|�d���S#t$r+}t�d|j|��Yd}~Sd}~wwxYwd}~wwxYw#	|�d���w#t$r+}t�d|j|��Yd}~wd}~wwxYwxYw)zsParse a changelog file and delete it.

        The file is deleted regardless of whether parsing succeeds.
        T)�
missing_okz*Failed to delete changelog for site %s: %sNz)Failed to parse changelog for site %s: %s)r�
parse_file�unlink�OSErrorrr+r,�
ValueError)rr.r#r/s    r�_consume_changelogz%ChangelogProcessor._consume_changelog�s���	��;�)�)�.�9�9�
��%�%��%�6�6�6�6���
�
�
����@��L��������������
�������$�	�	�	��L�L�;����
�
�
�
�I�I�I�I�I�
��%�%��%�6�6�6�6���
�
�
����@��L��������������
��������	�����
��%�%��%�6�6�6�6���
�
�
����@��L��������������
������s��A+�3�
A(�!A#�#A(�+C8�<"C3�C8�C;�$B;�;
C0�!C+�+C0�3C8�8C;�;E�=D�E�
E	�!E�?E�E	�	Ec��K�|�||��}|sdSttt|j�����d{V��s0t
�dt|��|j��dS|�	|��}d}|D�]8}	t|�dd����}|dkr.td|�dd���d	|j�����|�>||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 %s�tsrz: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)r6rr�str�uidrr r!r,�_get_last_sync_ts�float�getr5�_process_action�_report_action�warningr*r+)
rr.r#r�actions�last_sync_ts�changed�action�	timestampr/s
          rr(z*ChangelogProcessor._process_changelog_file�s����� �)�)�.�$�?�?���	��5�$�$5�s�4�8�}�}�E�E�E�E�E�E�E�E�	��K�K�3��G�����	
�
�
��5��-�-�d�3�3�����	�	�F�
�!�&�*�*�T�1�"5�"5�6�6�	���>�>�$�3�%+�Z�Z�	�3�%?�%?�3�3�$(�L�3�3����
 �+�	�\�0I�0I��K�K�@��
�
�9�c�2�2���!�$�
�����'�'���i�@�@�#�"�G��)�)�&�$��i�H�H�H�H�H�H�H�H�H�H���
J�
J�
J����E�q�I�I�I�I�I�I�I�I������
�
�
����K���L��	������������
����	���G��L���L�L��		
�	
�	
��s+�BE&�.7E&�&
G
�0F�
G
�"G�G
rGrHc�B�|�d��}|�d��}|r|std|�����|tkr|�|||��S|tkr|�||��Std|�d|�d|j�����)a�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.
        rGr9z.Missing action or rule_id in changelog entry: zUnknown changelog action 'z' for rule r;)r@r5�ACTION_DISABLE�_apply_disable�
ACTION_ENABLE�
_apply_enabler,)rrGr#rH�action_typer9s      rrAz"ChangelogProcessor._process_action�s���"�j�j��*�*���*�*�Y�'�'���	�'�	��I��I�I���
��.�(�(��&�&�w��i�@�@�@�
�M�
)�
)��%�%�g�t�4�4�4��>�[�>�>�$�>�>�/3�|�>�>���
rc�p�	tj|j��}|jS#tj$rYdSwxYw)z�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.
        N)r	�	get_by_idr,�disabled_rules_sync_ts�DoesNotExist)r#�db_sites  rr>z$ChangelogProcessor._get_last_sync_tssG��	�#�-�d�l�;�;�G��1�1���)�	�	�	��4�4�	���s�"�5�5r9c�j�tj||jgtj|j|���}|dkS)z�Apply a disable action from the changelog.

        Returns:
            True if a new disable entry was created (not a no-op).
        )r9�domains�source�user_idrHr)r
�store�domain�SOURCE_WORDPRESSr=)r9r#rH�counts    rrKz!ChangelogProcessor._apply_disables?���$���[�M�!�2��H��
�
�
���q�y�rc�F�tj||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�
�
�
���q�y�rc
��K�|�dS|d}|d}|tkr
tj}n|tkr
tj}ndS	|�|d||jg||jtj	������d{V��dS#t$r-}t�d||j
|��Yd}~dSd}~wwxYw)z�Send a rule change event to the correlation server.

        Must only be called for valid actions (after _process_action succeeds).
        NrGr9�	wordpress)�	plugin_id�rulerUrHrWrVz<Failed to report changelog action for rule %s on site %s: %s)rJr�WPRuleDisabledrL�
WPRuleEnabled�process_messagerYr=r
rZr*rr+r,)rGr#rrHrNr9�message_clsr/s        rrBz!ChangelogProcessor._report_action0s,�����<��F��X�&����#���.�(�(�%�4�K�K�
�M�
)�
)�%�3�K�K��F�	��&�&���)� �!�[�M�'� �H�)�:�
���	�	�	
�	
�	
�	
�	
�	
�	
�	
�	
���	�	�	��L�L�N�����	
�
�
�
�
�
�
�
�
�����	���s�
?B�
C�"B=�=Cr-c�b�|tz}	tt|����5}tjtj|��dd���5}|���}ddd��n#1swxYwYddd��n#1swxYwYnU#t$rYdSt$r<}|j	tj
krt�d||��Yd}~dSd}~wwxYw	t|��}t|�dd����}n@#tt f$r,}	t�d	|j|	��Yd}	~	dSd}	~	wwxYw	t'j|j��}
n#t&j$rYdSwxYw|
j}|dupt/||z
��d
kS)aCheck 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.
        �rzutf-8)�encodingNFzCannot open %s: %sr8rz.Cannot read disabled-rules.php for site %s: %sg�?)�DISABLED_RULES_FILENAMErr<�os�fdopen�dup�read�FileNotFoundErrorr4�errno�ELOOPr�debugrr?r@r5rCr,r	rPrRrQ�abs)r#r-�disabled_rules_path�fd�f�content�exc�data�file_tsr/rS�db_tss            rr)z0ChangelogProcessor._is_disabled_rules_file_stale[sV��'�)@�@��
	��s�#6�7�7�8�8�
'�B��Y�r�v�b�z�z�3��A�A�A�'�Q��f�f�h�h�G�'�'�'�'�'�'�'�'�'�'�'����'�'�'�'�
'�
'�
'�
'�
'�
'�
'�
'�
'�
'�
'����
'�
'�
'�
'���!�	�	�	��5�5��	�	�	��y�E�K�'�'����1�3F��L�L�L��5�5�5�5�5�����	����
		�/��8�8�D��D�H�H�T�1�-�-�.�.�G�G����$�	�	�	��N�N�@����
�
�
�
�5�5�5�5�5�����
	����	�#�-�d�l�;�;�G�G���)�	�	�	��5�5�	�����.����}�:��G�e�O� 4� 4�s� :�:s��B�*B
�A3�'B
�3A7	�7B
�:A7	�;B
�>B�
B�B�B�B�
C(�#	C(�,1C#�#C(�,2D�E�0!E�E� E:�:F
�F
)rN)�__name__�
__module__�__qualname__�__doc__r�listrrr$�boolrr�dictr6r(r?rA�staticmethodr>r<rKrMrBr)�rrrr.sw������	�	�+�+�+�+�
��F�|���D� ��
�f��	����:$��$��D� �$�
�	$�$�$�$�L�"��*0��	
�d������4G��G��G��D� �	G�

�G�G�G�G�R �� �"(� �5:� �	
� � � � �D�
��
�5�4�<�
�
�
��\�
��
��
�6�
�e�
��
�
�
��\�
�
�S�
��
�4�
�
�
�
��(��(��(��D� �(��	(�

�(�(�(��\�(�T�);��);��);�
�);�);�);��\�);�);�);rr)"r~ro�loggingrj�pathlibr�"defence360agent.contracts.messagesr�%defence360agent.contracts.permissionsrr�!defence360agent.contracts.pluginsr�defence360agent.model.wordpressrr	�&defence360agent.model.wp_disabled_ruler
�defence360agent.utils.fd_opsr�defence360agent.wordpress.clir�)defence360agent.wordpress.incident_parserr
�defence360agent.wordpress.utilsr�	getLoggerr{rr'rirJrLrr�rr�<module>r�sb����$
��������	�	�	�	�������:�:�:�:�:�:���������:�9�9�9�9�9�A�A�A�A�A�A�A�A�A�A�A�A�A�A�6�6�6�6�6�6�6�6�6�6�6�6�H�H�H�H�H�H�H�H�H�H�H�H�	��	�8�	$�	$��$��.�����
�W;�W;�W;�W;�W;�W;�W;�W;�W;�W;rdefence360agent/wordpress/__pycache__/cli.cpython-311.opt-1.pyc0000644000000000000000000003730700000000000021230 0ustar  �

&b|�[K���ddlZddlZddlZddlZddlmZddlmZddlm	Z	m
Z
mZddlm
Z
mZddlmZmZmZddlmZddlmZeje��Zd	ed
efd�Zded
efd
�Zded
efd�Zdefd�Zdefd�Z defd�Z!ded
efd�Z"defd�Z#defd�Z$defd�Z%defd�Z&defd�Z'ed���defd���Z(d�Z)defd�Z*dS)�N)�Path)�
StrictVersion)�	check_run�
CheckRunError�async_lru_cache)�PLUGIN_PATH�PLUGIN_SLUG)�build_command_for_user�get_php_binary_path�
wp_wrapper)�log_message)�WPSite�version_str�returnc��t|t��sdS|���}|sdS	t|��dS#t$rYdSwxYw)z1Validate if a string is a valid semantic version.FT)�
isinstance�str�stripr�
ValueError)r�trimmed_strs  �R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/cli.py�_validate_semverrsu���k�3�'�'���u��#�#�%�%�K����u���k�"�"�"��t�������u�u����s�A�
A�A�outputc�b�|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
    Nr����zMFailed to extract valid semver version from WP CLI output. Output: '{output}'r�warning�	wordpressz#wp-plugin-version-extraction-failed)�format_args�level�	component�fingerprint)�split�lenrrr
)r�parts�
first_part�	last_parts    r�_extract_version_from_outputr(+s������t�
�L�L�N�N�E����t��5�z�z�A�~�~��1�X�^�^�%�%�
��J�'�'�	����5�z�z�A�~�~��"�I�O�O�%�%�	��I�&�&�	����	��v�&���9�
�����4��sitec��K�t|���d{V��}|dzdzdz}|���sdStjd��}	t	|��5}|D][}|�|��}|rB|�d��}t|��r|ccddd��Sddd��dS�\	ddd��n#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
    N�plugins�imunify-securityzimunify-security.phpz\* Version:\s*([0-9.]+)rz=Failed to read plugin file to determine version number %s: %s)�get_content_dir�exists�re�compile�open�search�groupr�	Exception�logger�error)	r*�content_dir�plugin_file�version_pattern�f�line�match�version�es	         r�_parse_version_from_plugin_filer@Ws�����(��-�-�-�-�-�-�-�-�K��i��"4�4�7M�M���������t��j�!;�<�<�O��
�+�
�
�	$�!��
$�
$��'�.�.�t�4�4���$�#�k�k�!�n�n�G�'��0�0�$�&���
	$�	$�	$�	$�	$�	$�	$�	$� $�	$�	$�	$�	$�	$�	$�	$�	$�$�
$�	$�	$�	$�	$�	$�	$�	$�	$�	$�	$�	$����	$�	$�	$�	$����������K��
�	
�	
�	
�
�t�t�t�t�t�����
�����4sU�C�AC	� C�-C	�.C�;C	�=C�	C
�
C�C
�C�
D�D�Dc��\K�tj|j��j}t	||���d{V��}gt||j���d�d�tt���d�d�}t||��}t�d|����t|���d{V��dS)zFInstall the Imunify Security WordPress plugin on given WordPress site.N�plugin�install�
--activate�--forcezInstalling wp plugin �
�pwd�getpwuid�uid�pw_namerr�docrootrrr
r6�infor�r*�username�php_path�args�commands     r�plugin_installrR�s������|�D�H�%�%�-�H�(��x�8�8�8�8�8�8�8�8�H��	�H�d�l�	+�	+����	��	�K���	�
	��	�
�D�%�X�t�4�4�G�
�K�K�1��1�1�2�2�2�
�G�
�
���������r)c��\K�tj|j��j}t	||���d{V��}gt||j���d�d�tt���d�d�}t||��}t�d|����t|���d{V��dS)z�
    Update the Imunify Security WordPress plugin on given WordPress site.

    Currently, this is the same as install, but in the future it may differ.
    NrBrCrDrEzUpdating wp plugin rFrMs     r�
plugin_updaterT�s������|�D�H�%�%�-�H�(��x�8�8�8�8�8�8�8�8�H��	�H�d�l�	+�	+����	��	�K���	�
	��	�
�D�%�X�t�4�4�G�
�K�K�/�g�/�/�0�0�0�
�G�
�
���������r)c��>K�tj|j��j}t	||���d{V��}gt||j���d�d�t�d�}t||��}t�
d|����t|���d{V��dS)z<Uninstall the imunify-security wp plugin from given wp site.NrB�	uninstallz--deactivatezUninstalling wp plugin )rGrHrIrJrrrKr	r
r6rLrrMs     r�plugin_uninstallrW�s������|�D�H�%�%�-�H�(��x�8�8�8�8�8�8�8�8�H��	�H�d�l�	+�	+����	��	�	�
	��D�%�X�t�4�4�G�
�K�K�3�'�3�3�4�4�4�
�G�
�
���������r)c��K�	t|���d{V��dS#t$r'}t�d||��Yd}~dSd}~wwxYw)z�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.
    NTz5Failed to uninstall plugin from %s during cleanup: %sF)rWr5r6r)r*r7s  r�try_plugin_uninstallrY�s}����	��t�$�$�$�$�$�$�$�$�$��t���������C���	
�	
�	
�
�u�u�u�u�u�����
���s��
A�A�Ac��tK�tj|j��j}t	||���d{V��}gt||j���d�d�t�d�}t||��}t�
d|����	t|���d{V��}|�d���
��}t|��S#t$r+}t�d|j��Yd}~dSd}~wt$$r&}t�d|��Yd}~dSd}~wwxYw)	z�
    Get the version of the imunify-security wp plugin installed on given WordPress site.

    Uses WP CLI to get the version.
    NrB�getz--field=versionzGetting wp plugin version �utf-8z0Failed to get wp plugin version. Return code: %sz-Failed to decode wp plugin version output: %s)rGrHrIrJrrrKr	r
r6rLr�decoderr(rr7�
returncode�UnicodeDecodeError)r*rNrOrPrQ�resultrr?s        r�_get_plugin_versionra�s}�����|�D�H�%�%�-�H�(��x�8�8�8�8�8�8�8�8�H��	�H�d�l�	+�	+����	��	�	�
	��D�%�X�t�4�4�G�
�K�K�6�W�6�6�7�7�7�� ��)�)�)�)�)�)�)�)�����w�'�'�-�-�/�/��+�F�3�3�3���������>�
�L�	
�	
�	
��t�t�t�t�t������������D�a�H�H�H��t�t�t�t�t��������s%�	A
C�
D7� D�
D7�D2�2D7c��K�	t|���d{V��}|r|Sn2#t$r%}t�d|��Yd}~nd}~wwxYwt�d��t|���d{V��S)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   r�get_plugin_versionrc�s�����J�7��=�=�=�=�=�=�=�=���	��N�	���J�J�J����E�q�I�I�I�I�I�I�I�I�����J�����K�K�A�B�B�B�$�T�*�*�*�*�*�*�*�*�*s��
A
�A�A
c��`K�tj|j��j}t	||���d{V��}gt||j���d�d�t�}t||��}t�
d|����	t|���d{V��n#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)
rGrHrIrJrrrKr	r
r6rLrrrMs     r�is_plugin_installedrf	s������|�D�H�%�%�-�H�(��x�8�8�8�8�8�8�8�8�H��	�H�d�l�	+�	+����	��	�	�D�%�X�t�4�4�G�
�K�K�?�g�?�?�@�@�@���� � � � � � � � � � �������u�u�����
�4s�B�
B+�*B+c��RK�tj|j��j}t	||���d{V��}gt||j���d�d�}t||��}t�	d|����	t|���d{V��n#t$rYdSwxYwdS)zJCheck if WordPress is installed and given site is accessible using WP CLI.N�corerez#Checking if WordPress is installed FT)rGrHrIrJrrrKr
r6rLrrrMs     r�is_wordpress_installedri!s������|�D�H�%�%�-�H�(��x�8�8�8�8�8�8�8�8�H��	�H�d�l�	+�	+����	��D�
%�X�t�4�4�G�
�K�K�?�g�?�?�@�@�@���� � � � � � � � � � �������u�u������4s�B�
B$�#B$c���K�tj|j��j}t	||���d{V��}gt||j���d�d�}t||��}t�	d|����	tjt|��d����d{V��}|�
d�����S#tj$r$t�d|j��YdSt"$r+}t�d	|j��Yd}~dSd}~wt($r&}t�d
|��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.
    N�evalzecho 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)rGrHrIrJrrrKr
r6rL�asyncio�wait_forrr]r�TimeoutErrorrrr7r^r_)r*rNrOrPrQr`r?s       r�_get_content_directoryrq8s������|�D�H�%�%�-�H�(��x�8�8�8�8�8�8�8�8�H��	�H�d�l�	+�	+����	��D�
%�X�t�4�4�G�
�K�K�6�W�6�6�7�7�7���'�	�'�(:�(:�B�G�G�G�G�G�G�G�G�G���}�}�W�%�%�+�+�-�-�-����������?���	
�	
�	
��t�t��������>�
�L�	
�	
�	
��t�t�t�t�t������������D�a�H�H�H��t�t�t�t�t��������s+�AC�/E$�	E$� D1�1
E$�>E�E$�d)�maxsizec���K�t|j��dz}|���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*r8�wp_content_dirs   rr.r.\s�����t�|�$�$�|�3�K������/�{�'9�'9�';�';�/�5�d�;�;�;�;�;�;�;�;���	/��~�.�.�K��r)c�8�t���dS)z.Clear the async LRU cache for get_content_dir.N)r.�cache_clear�r)r�clear_get_content_dir_cacher{ts�����!�!�!�!�!r)c��K�t|���d{V��}|st|j��dz}t|��dzS)zO
    Get the Imunify Security data directory for the given WordPress site.
    Nrur-)r.rrK)r*r8s  r�get_data_dirr}ysW����(��-�-�-�-�-�-�-�-�K��8��4�<�(�(�<�7������1�1�1r))+rn�loggingrGr0�pathlibr�distutils.versionr�defence360agent.utilsrrr�#defence360agent.wordpress.constantsrr	�defence360agent.wordpress.utilsr
rr�defence360agent.sentryr
�defence360agent.model.wordpressr�	getLogger�__name__r6r�boolrr(r@rRrTrWrYrarcrfrirqr.r{r}rzr)r�<module>r�s�����������
�
�
�
�	�	�	�	�������+�+�+�+�+�+�����������
I�H�H�H�H�H�H�H�����������
/�.�.�.�.�.�2�2�2�2�2�2�	��	�8�	$�	$���#��$�����$)��)��)�)�)�)�X&��&�3�&�&�&�&�R�v�����*�f�����2������(�V�������$�F�����D+�6�+�+�+�+�&�F�����0�v�����.!�v�!�!�!�!�H��������������."�"�"�
2�V�2�2�2�2�2�2r)defence360agent/wordpress/__pycache__/cli.cpython-311.pyc0000644000000000000000000003730700000000000020271 0ustar  �

&b|�[K���ddlZddlZddlZddlZddlmZddlmZddlm	Z	m
Z
mZddlm
Z
mZddlmZmZmZddlmZddlmZeje��Zd	ed
efd�Zded
efd
�Zded
efd�Zdefd�Zdefd�Z defd�Z!ded
efd�Z"defd�Z#defd�Z$defd�Z%defd�Z&defd�Z'ed���defd���Z(d�Z)defd�Z*dS)�N)�Path)�
StrictVersion)�	check_run�
CheckRunError�async_lru_cache)�PLUGIN_PATH�PLUGIN_SLUG)�build_command_for_user�get_php_binary_path�
wp_wrapper)�log_message)�WPSite�version_str�returnc��t|t��sdS|���}|sdS	t|��dS#t$rYdSwxYw)z1Validate if a string is a valid semantic version.FT)�
isinstance�str�stripr�
ValueError)r�trimmed_strs  �R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/cli.py�_validate_semverrsu���k�3�'�'���u��#�#�%�%�K����u���k�"�"�"��t�������u�u����s�A�
A�A�outputc�b�|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
    Nr����zMFailed to extract valid semver version from WP CLI output. Output: '{output}'r�warning�	wordpressz#wp-plugin-version-extraction-failed)�format_args�level�	component�fingerprint)�split�lenrrr
)r�parts�
first_part�	last_parts    r�_extract_version_from_outputr(+s������t�
�L�L�N�N�E����t��5�z�z�A�~�~��1�X�^�^�%�%�
��J�'�'�	����5�z�z�A�~�~��"�I�O�O�%�%�	��I�&�&�	����	��v�&���9�
�����4��sitec��K�t|���d{V��}|dzdzdz}|���sdStjd��}	t	|��5}|D][}|�|��}|rB|�d��}t|��r|ccddd��Sddd��dS�\	ddd��n#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
    N�plugins�imunify-securityzimunify-security.phpz\* Version:\s*([0-9.]+)rz=Failed to read plugin file to determine version number %s: %s)�get_content_dir�exists�re�compile�open�search�groupr�	Exception�logger�error)	r*�content_dir�plugin_file�version_pattern�f�line�match�version�es	         r�_parse_version_from_plugin_filer@Ws�����(��-�-�-�-�-�-�-�-�K��i��"4�4�7M�M���������t��j�!;�<�<�O��
�+�
�
�	$�!��
$�
$��'�.�.�t�4�4���$�#�k�k�!�n�n�G�'��0�0�$�&���
	$�	$�	$�	$�	$�	$�	$�	$� $�	$�	$�	$�	$�	$�	$�	$�	$�$�
$�	$�	$�	$�	$�	$�	$�	$�	$�	$�	$�	$����	$�	$�	$�	$����������K��
�	
�	
�	
�
�t�t�t�t�t�����
�����4sU�C�AC	� C�-C	�.C�;C	�=C�	C
�
C�C
�C�
D�D�Dc��\K�tj|j��j}t	||���d{V��}gt||j���d�d�tt���d�d�}t||��}t�d|����t|���d{V��dS)zFInstall the Imunify Security WordPress plugin on given WordPress site.N�plugin�install�
--activate�--forcezInstalling wp plugin �
�pwd�getpwuid�uid�pw_namerr�docrootrrr
r6�infor�r*�username�php_path�args�commands     r�plugin_installrR�s������|�D�H�%�%�-�H�(��x�8�8�8�8�8�8�8�8�H��	�H�d�l�	+�	+����	��	�K���	�
	��	�
�D�%�X�t�4�4�G�
�K�K�1��1�1�2�2�2�
�G�
�
���������r)c��\K�tj|j��j}t	||���d{V��}gt||j���d�d�tt���d�d�}t||��}t�d|����t|���d{V��dS)z�
    Update the Imunify Security WordPress plugin on given WordPress site.

    Currently, this is the same as install, but in the future it may differ.
    NrBrCrDrEzUpdating wp plugin rFrMs     r�
plugin_updaterT�s������|�D�H�%�%�-�H�(��x�8�8�8�8�8�8�8�8�H��	�H�d�l�	+�	+����	��	�K���	�
	��	�
�D�%�X�t�4�4�G�
�K�K�/�g�/�/�0�0�0�
�G�
�
���������r)c��>K�tj|j��j}t	||���d{V��}gt||j���d�d�t�d�}t||��}t�
d|����t|���d{V��dS)z<Uninstall the imunify-security wp plugin from given wp site.NrB�	uninstallz--deactivatezUninstalling wp plugin )rGrHrIrJrrrKr	r
r6rLrrMs     r�plugin_uninstallrW�s������|�D�H�%�%�-�H�(��x�8�8�8�8�8�8�8�8�H��	�H�d�l�	+�	+����	��	�	�
	��D�%�X�t�4�4�G�
�K�K�3�'�3�3�4�4�4�
�G�
�
���������r)c��K�	t|���d{V��dS#t$r'}t�d||��Yd}~dSd}~wwxYw)z�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.
    NTz5Failed to uninstall plugin from %s during cleanup: %sF)rWr5r6r)r*r7s  r�try_plugin_uninstallrY�s}����	��t�$�$�$�$�$�$�$�$�$��t���������C���	
�	
�	
�
�u�u�u�u�u�����
���s��
A�A�Ac��tK�tj|j��j}t	||���d{V��}gt||j���d�d�t�d�}t||��}t�
d|����	t|���d{V��}|�d���
��}t|��S#t$r+}t�d|j��Yd}~dSd}~wt$$r&}t�d|��Yd}~dSd}~wwxYw)	z�
    Get the version of the imunify-security wp plugin installed on given WordPress site.

    Uses WP CLI to get the version.
    NrB�getz--field=versionzGetting wp plugin version �utf-8z0Failed to get wp plugin version. Return code: %sz-Failed to decode wp plugin version output: %s)rGrHrIrJrrrKr	r
r6rLr�decoderr(rr7�
returncode�UnicodeDecodeError)r*rNrOrPrQ�resultrr?s        r�_get_plugin_versionra�s}�����|�D�H�%�%�-�H�(��x�8�8�8�8�8�8�8�8�H��	�H�d�l�	+�	+����	��	�	�
	��D�%�X�t�4�4�G�
�K�K�6�W�6�6�7�7�7�� ��)�)�)�)�)�)�)�)�����w�'�'�-�-�/�/��+�F�3�3�3���������>�
�L�	
�	
�	
��t�t�t�t�t������������D�a�H�H�H��t�t�t�t�t��������s%�	A
C�
D7� D�
D7�D2�2D7c��K�	t|���d{V��}|r|Sn2#t$r%}t�d|��Yd}~nd}~wwxYwt�d��t|���d{V��S)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   r�get_plugin_versionrc�s�����J�7��=�=�=�=�=�=�=�=���	��N�	���J�J�J����E�q�I�I�I�I�I�I�I�I�����J�����K�K�A�B�B�B�$�T�*�*�*�*�*�*�*�*�*s��
A
�A�A
c��`K�tj|j��j}t	||���d{V��}gt||j���d�d�t�}t||��}t�
d|����	t|���d{V��n#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)
rGrHrIrJrrrKr	r
r6rLrrrMs     r�is_plugin_installedrf	s������|�D�H�%�%�-�H�(��x�8�8�8�8�8�8�8�8�H��	�H�d�l�	+�	+����	��	�	�D�%�X�t�4�4�G�
�K�K�?�g�?�?�@�@�@���� � � � � � � � � � �������u�u�����
�4s�B�
B+�*B+c��RK�tj|j��j}t	||���d{V��}gt||j���d�d�}t||��}t�	d|����	t|���d{V��n#t$rYdSwxYwdS)zJCheck if WordPress is installed and given site is accessible using WP CLI.N�corerez#Checking if WordPress is installed FT)rGrHrIrJrrrKr
r6rLrrrMs     r�is_wordpress_installedri!s������|�D�H�%�%�-�H�(��x�8�8�8�8�8�8�8�8�H��	�H�d�l�	+�	+����	��D�
%�X�t�4�4�G�
�K�K�?�g�?�?�@�@�@���� � � � � � � � � � �������u�u������4s�B�
B$�#B$c���K�tj|j��j}t	||���d{V��}gt||j���d�d�}t||��}t�	d|����	tjt|��d����d{V��}|�
d�����S#tj$r$t�d|j��YdSt"$r+}t�d	|j��Yd}~dSd}~wt($r&}t�d
|��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.
    N�evalzecho 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)rGrHrIrJrrrKr
r6rL�asyncio�wait_forrr]r�TimeoutErrorrrr7r^r_)r*rNrOrPrQr`r?s       r�_get_content_directoryrq8s������|�D�H�%�%�-�H�(��x�8�8�8�8�8�8�8�8�H��	�H�d�l�	+�	+����	��D�
%�X�t�4�4�G�
�K�K�6�W�6�6�7�7�7���'�	�'�(:�(:�B�G�G�G�G�G�G�G�G�G���}�}�W�%�%�+�+�-�-�-����������?���	
�	
�	
��t�t��������>�
�L�	
�	
�	
��t�t�t�t�t������������D�a�H�H�H��t�t�t�t�t��������s+�AC�/E$�	E$� D1�1
E$�>E�E$�d)�maxsizec���K�t|j��dz}|���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*r8�wp_content_dirs   rr.r.\s�����t�|�$�$�|�3�K������/�{�'9�'9�';�';�/�5�d�;�;�;�;�;�;�;�;���	/��~�.�.�K��r)c�8�t���dS)z.Clear the async LRU cache for get_content_dir.N)r.�cache_clear�r)r�clear_get_content_dir_cacher{ts�����!�!�!�!�!r)c��K�t|���d{V��}|st|j��dz}t|��dzS)zO
    Get the Imunify Security data directory for the given WordPress site.
    Nrur-)r.rrK)r*r8s  r�get_data_dirr}ysW����(��-�-�-�-�-�-�-�-�K��8��4�<�(�(�<�7������1�1�1r))+rn�loggingrGr0�pathlibr�distutils.versionr�defence360agent.utilsrrr�#defence360agent.wordpress.constantsrr	�defence360agent.wordpress.utilsr
rr�defence360agent.sentryr
�defence360agent.model.wordpressr�	getLogger�__name__r6r�boolrr(r@rRrTrWrYrarcrfrirqr.r{r}rzr)r�<module>r�s�����������
�
�
�
�	�	�	�	�������+�+�+�+�+�+�����������
I�H�H�H�H�H�H�H�����������
/�.�.�.�.�.�2�2�2�2�2�2�	��	�8�	$�	$���#��$�����$)��)��)�)�)�)�X&��&�3�&�&�&�&�R�v�����*�f�����2������(�V�������$�F�����D+�6�+�+�+�+�&�F�����0�v�����.!�v�!�!�!�!�H��������������."�"�"�
2�V�2�2�2�2�2�2r)defence360agent/wordpress/__pycache__/constants.cpython-311.opt-1.pyc0000644000000000000000000000124700000000000022467 0ustar  �

ш���w���\�dZddlmZed��ZdZed��Zed��ZdS)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_SLUG�PLUGIN_VERSION_FILE�WP_CLI_WRAPPER_PATH���X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/constants.py�<module>r
s`��%�%��������d�J�K�K�� ���d�?�����d�L�M�M���rdefence360agent/wordpress/__pycache__/constants.cpython-311.pyc0000644000000000000000000000124700000000000021530 0ustar  �

ш���w���\�dZddlmZed��ZdZed��Zed��ZdS)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_SLUG�PLUGIN_VERSION_FILE�WP_CLI_WRAPPER_PATH���X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/constants.py�<module>r
s`��%�%��������d�J�K�K�� ���d�?�����d�L�M�M���rdefence360agent/wordpress/__pycache__/exception.cpython-311.opt-1.pyc0000644000000000000000000000134700000000000022452 0ustar  �

o��xSX���"�Gd�de��ZdS)c���eZdZ�fd�Z�xZS)�PHPErrorc�J��t���|��dS)N)�super�__init__)�self�message�	__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�������"�"�"�"�"�"�"�"�"rrN)�	Exceptionr�rr
�<module>rs9��"�"�"�"�"�y�"�"�"�"�"rdefence360agent/wordpress/__pycache__/exception.cpython-311.pyc0000644000000000000000000000134700000000000021513 0ustar  �

o��xSX���"�Gd�de��ZdS)c���eZdZ�fd�Z�xZS)�PHPErrorc�J��t���|��dS)N)�super�__init__)�self�message�	__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�������"�"�"�"�"�"�"�"�"rrN)�	Exceptionr�rr
�<module>rs9��"�"�"�"�"�y�"�"�"�"�"rdefence360agent/wordpress/__pycache__/incident_collector.cpython-311.opt-1.pyc0000644000000000000000000004630500000000000024322 0ustar  �

~fǕ�������dZddlZddlZddlZddlZddlZddlZddlm	Z	ddl
mZddlm
Z
ddlmZddlmZddlmZmZmZmZeje��ZGd	�d
��ZGd�d��ZdS)
z1Collector for WordPress CVE protection incidents.�N)�Path)�defaultdict)�WPSite)�get_data_dir)�IncidentFileParser)�upsert_wordpress_incident�bulk_create_wordpress_incidents�build_incident_dict�country_readerc�h�eZdZdZ			ddededefd�Zd	�Zd
ededee	effd
�Z
d
edefd�ZdS)�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.
    �d��'�max_incidents_per_rule_per_ip�time_window_seconds�max_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_windowrr�list�incident_times�cleanup_interval�time�last_cleanup)�selfrrrs    �a/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/incident_collector.py�__init__zIncidentRateLimiter.__init__#sK��$A�� �.���"4���*�$�/�/��� "��� �I�K�K�����c�~�	�tj��}||jz
�	g}|j���D]5\}}�	fd�|D��}|r||j|<� |�|���6|D]
}|j|=�t|j��|jkr�t|j���d����}tdt|j��t|jdz��z
��}|d|�D]
\}}|j|=�t�d|j|��||_dS)zHRemove records older than the time window and enforce max entries limit.c� ��g|]
}|�k�|��S�r#��.0�ts�cutoffs  �r�
<listcomp>z<IncidentRateLimiter._cleanup_old_records.<locals>.<listcomp>D����=�=�=�R��f���b���r c�2�|dr|ddndS)N�rr#)�xs r�<lambda>z:IncidentRateLimiter._cleanup_old_records.<locals>.<lambda>Ss���1��4�a��d�1�g�g�1�r )�keyr+g�������?NzARate limiter exceeded max entries (%d), removed %d oldest entries)
rrr�items�append�lenr�sorted�max�int�logger�warningr)
r�now�keys_to_deleter.�
timestamps�recent�entries_by_age�
num_to_remove�_r's
         @r�_cleanup_old_recordsz(IncidentRateLimiter._cleanup_old_records;s�����i�k�k���t�'�'����#�2�8�8�:�:�	+�	+�O�C��=�=�=�=�:�=�=�=�F��
+�+1��#�C�(�(��%�%�c�*�*�*�*�!�	)�	)�C��#�C�(�(��t�"�#�#�d�&=�=�=�#��#�)�)�+�+�4�4����N� ���D�'�(�(�3�t�/F��/L�+M�+M�M���M�)��-��8�
-�
-���Q��'��,�,��N�N���'��	
�
�
� ����r �rule_id�attacker_ip�returnc��	�tj��|jz
|jkr|���tj��}||jz
�	||f}||jvrB|j|}�	fd�|D��}|r||j|<t
|��}n
|j|=d}nd}||jkr#|jdz}dd|�d|�d|�d|j�d	|�d
�fSdS)z�
        Check if adding an incident would exceed rate limits.

        Args:
            rule_id: Rule identifier
            attacker_ip: IP address of the attacker

        Returns:
            Tuple of (allowed: bool, reason: str)
        c� ��g|]
}|�k�|��Sr#r#r$s  �rr(z8IncidentRateLimiter.check_rate_limit.<locals>.<listcomp>�r)r rrFzRate limit exceeded for rule z	 from IP z: �/z within z minutes)T�OK)rrrr>rrr1r)
rr?r@r7r.r9r:�recent_count�window_minutesr's
         @r�check_rate_limitz$IncidentRateLimiter.check_rate_limitgsF����9�;�;��*�*�T�-B�B�B��%�%�'�'�'��i�k�k���t�'�'����$���$�%�%�%��,�S�1�J�=�=�=�=�:�=�=�=�F��
!�+1��#�C�(�"�6�{�{����'��,� ����L��4�3�3�3�!�-��3�N��1�G�1�1�#�1�1�$�1�1�'+�'?�1�1�'�1�1�1��
��zr c��tj��}||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)rrr1r�popr0)rr?r@r7r.r9s      r�record_incidentz#IncidentRateLimiter.record_incident�s����i�k�k����$���d�)�)�)�(+�u�D���$�$�$��,�S�1�J��:���$�":�:�:����q�!�!�!����c�"�"�"�"�"r N)rrr)�__name__�
__module__�__qualname__�__doc__r4rr>�str�tuple�boolrHrKr#r rr
r
s���������.1�#&�"'�	(�(�'*�(�!�(� �	(�(�(�(�0* �* �* �X2��2�),�2�	�t�S�y�	�2�2�2�2�h#�s�#��#�#�#�#�#�#r r
c�t�eZdZdZddedzfd�Z	ddededefd	�Z		dd
eededefd�Z
ededeefd
���Z
ejd��Zededefd���Zdededzdedefd�Zdededzfd�Zdeedededzdedef
d�Zdedededzdefd�Zdedededzdededefd�ZdS)�IncidentCollectorzM
    Collect and persist WordPress incidents from plugin incident files.
    N�rate_limiterc�V�|p
t��|_t��|_dS)z�
        Initialize the incident collector.

        Args:
            rate_limiter: Optional rate limiter (creates default if not provided)
        N)r
rUr�parser)rrUs  rrzIncidentCollector.__init__�s)��)�A�,?�,A�,A���(�*�*����r T�site�delete_after_processingrAc��K�g}	t|���d{V��}t�d||��|���st�d|��gS|�|��}t�d||��|st�d|��gSt�dt|��|��|�|��}|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 %s�Incident 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)rr5�debug�exists�_get_incident_filesr1�_get_site_username�
_process_file�extend�	Exception�error�info)
rrXrY�collected_incidents�data_dir�incident_files�username�
incident_file�file_incidents�es
          r�collect_incidents_for_sitez,IncidentCollector.collect_incidents_for_site�s����� !��%	�)�$�/�/�/�/�/�/�/�/�H��L�L�9�4��J�J�J��?�?�$�$�
����H�$�O�O�O��	�!�5�5�h�?�?�N��L�L�0�$��
�
�
�"�
����B�D�I�I�I��	��L�L�7��N�#�#��
�
�
��.�.�t�4�4�H�!/�
;�
;�
�'+�'9�'9�!���+�	(�(�"�"�"�"�"�"��$�*�*�>�:�:�:�:�
;���	�	�	��L�L�<���
�
�
�
�
�
�
�
�����	����	���2��#�$�$��	
�	
�	
�#�"s&�A!D/�(AD/�8A6D/�/
E�9E�E�sitesc���K�g}|D]3}|�||���d{V��}|�|���4|r6t�dt	|��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))rlrar5rdr1)rrmrY�all_collected_incidentsrX�site_incidentss      r�collect_incidents_for_sitesz-IncidentCollector.collect_incidents_for_sitess�����#%���	;�	;�D�#'�#B�#B��'�$�$�������N�
$�*�*�>�:�:�:�:�"�	��K�K�D��+�,�,��E�
�
�
�
�
�'�&r rfc��|dz}t�d||��|���r|���st�d|��gSg}|���D]k}	tj|��}n#t$rY�$wxYwtj	|j
��r*|�|��r|�|���lt�d||��|S)a�
        Get all incident files in the incidents directory.

        Only returns files older than one hour to give the WordPress plugin
        time to process and finalize the incident data before collection.

        Args:
            data_dir: Path to the imunify-security data directory

        Returns:
            List of incident file paths, sorted by modification time
        �	incidentsz#Incidents directory for site %s: %sz.Incidents directory does not exist for site %sr[)
r5r\r]�is_dir�iterdir�os�lstat�OSError�stat_module�S_ISREG�st_mode�_is_incident_filer0)�clsrf�
incidents_dirrg�f�sts      rr^z%IncidentCollector._get_incident_files#s0��!�;�.�
����1�8�]�	
�	
�	
��#�#�%�%�	�]�-A�-A�-C�-C�	��L�L�@�(�
�
�
��I����&�&�(�(�	)�	)�A�
��X�a�[�[�����
�
�
���
�����"�2�:�.�.�
)�3�3H�3H��3K�3K�
)��%�%�a�(�(�(�����,�h��	
�	
�	
��s�B�
B"�!B"z^\d{4}-\d{2}-\d{2}-\d{2}\.php$�	file_pathc�Z�t|j�|j����S)z�
        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
        )rR�
_FILE_PATTERN�match�name)r}r�s  rr|z#IncidentCollector._is_incident_fileMs%���C�%�+�+�I�N�;�;�<�<�<r rhc��6K�	|j�|��}|s:t�d|j��|r|�d���gSt�dt|��|j|��|�||||j��}|r6|�d���t�d|j��|S#t$r.}t�
d|j||��gcYd}~Sd}~wwxYw)NzNo valid incidents in file %sT)�
missing_okz)Parsed %d incident(s) from %s for site %szDeleted processed file %sz1Error processing incident file %s for site %s: %s)rW�
parse_filer5r6r��unlinkr\r1�_process_file_incidentsrbrc)rrirXrhrYrsrerks        rr`zIncidentCollector._process_fileZsT����(	���.�.�}�=�=�I��
����3�!�&����+�:�!�(�(�D�(�9�9�9��	��L�L�;��I����"��	
�
�
�#'�">�">�����"�	#�#��'�
N��$�$��$�5�5�5����8�-�:L�M�M�M�&�&���	�	�	��L�L�C��"���	
�
�
��I�I�I�I�I�I�����	���s%�AC �BC � 
D�*#D�
D�Dc��	tj|j��}|jS#t$r-}t
�d|j||��Yd}~dSd}~wwxYw)Nz.Failed to get username for uid=%d, site %s: %s)�pwd�getpwuid�uid�pw_namerbr5rc)rrX�	user_inforks    rr_z$IncidentCollector._get_site_username�ss��
	���T�X�.�.�I��$�$���	�	�	��L�L�@�����	
�
�
��4�4�4�4�4�����	���s�"�
A�"A�Ars�incident_file_namec��g}d}t��5}|D]�}|�dd��}	|�d��p|�dd��}
|j�|	|
��\}}|s"t�d||��|dz
}��|j|j||jd�}
t||
|�	��}|�
|��|j�|	|
����	ddd��n#1swxYwYg}|rD	t|��}n3#t$r&}t�d
||��Yd}~nd}~wwxYwt�d|t!|��|��|S)Nrr?�unknown�REMOTE_ADDRr@�#Rate limit exceeded for site %s: %sr+��domain�	site_pathrh�user_id)�
geo_readerz+Failed to bulk insert incidents from %s: %sz(Processed file %s: %d stored, %d dropped)r�getrUrHr5r6r��docrootr�r
r0rKr	rbrcrdr1)rrsrXrhr��incidents_to_insert�
dropped_countr��incidentr?r@�allowed�reason�	site_info�
incident_data�created_incidentsrks                 rr�z)IncidentCollector._process_file_incidents�sZ��!���
��
�
�!	H��%� 
H� 
H��"�,�,�y�)�<�<��&�l�l�=�9�9��X�\�\�!�9�>�>��#'�"3�"D�"D���#�#����
���N�N�=������
"�Q�&�M��#�k�!%�� (�#�x�	��	�!4��i�J�!�!�!�
�$�*�*�=�9�9�9��!�1�1�'�;�G�G�G�G�A 
H�!	H�!	H�!	H�!	H�!	H�!	H�!	H�!	H�!	H�!	H�!	H����!	H�!	H�!	H�!	H�H���
	�	
�$C�'�%�%�!�!���
�
�
����A�&��������������
����	���6���!�"�"��		
�	
�	
�!� s*�C!D�D�D�D!�!
E�+E�Er�c�4�|�dd��}|�d��p|�dd��}|j�||��\}}|st�d||��dS|�||||||��S)Nr?r�r�r@r�)r�rUrHr5r6�_store_incident)	rr�rXrhr�r?r@r�r�s	         r�_process_incidentz#IncidentCollector._process_incident�s����,�,�y�)�4�4���l�l�=�1�1�
�X�\�\��9�6
�6
���+�<�<���
�
����
�	��N�N�5���
�
�
�
�4��#�#�������

�
�	
r r?r@c���	|j|j||jd�}t||��}|j�||��|S#t$r'}t�d||��Yd}~dSd}~wwxYw)Nr�z$Failed to store incident from %s: %s)	r�r�r�rrUrKrbr5rc)	rr�rXrhr?r@r�r�rks	         rr�z!IncidentCollector._store_incident�s���	��+�!�\�$��8�	��I�1�����H�

��-�-�g�{�C�C�C��O���	�	�	��L�L�6�"��
�
�
�
�4�4�4�4�4�����
	���s�AA�
A6�A1�1A6)N)T)rLrMrNrOr
rrrRrrlrq�classmethodrr^�re�compiler�r|rPr`r_�dictr�r�r�r#r rrTrT�s��������+�+�%8�4�%?�+�+�+�+�)-�?#�?#��?#�"&�?#�
�	?#�?#�?#�?#�H)-�'�'��F�|�'�"&�'�
�	'�'�'�'�B�$�4�$�D��J�$�$�$��[�$�N�B�J�@�A�A�M��
=�$�
=�4�
=�
=�
=��[�
=�/��/���*�	/�
"&�/�
�
/�/�/�/�b�v��#��*�����C!���:�C!��C!���*�	C!�
 �C!�
�
C!�C!�C!�C!�J 
�� 
�� 
���*�	 
�
 � 
� 
� 
� 
�D�������*�	�
���
� ������r rT)rO�loggingrvr��statryrr��pathlibr�collectionsr�defence360agent.model.wordpressr�defence360agent.wordpress.clir�)defence360agent.wordpress.incident_parserr�(defence360agent.model.wordpress_incidentrr	r
r�	getLoggerrLr5r
rTr#r r�<module>r�s[��7�7�����	�	�	�	�
�
�
�
���������	�	�	�	�������#�#�#�#�#�#�2�2�2�2�2�2�6�6�6�6�6�6�H�H�H�H�H�H�������������
��	�8�	$�	$��V#�V#�V#�V#�V#�V#�V#�V#�rk�k�k�k�k�k�k�k�k�kr defence360agent/wordpress/__pycache__/incident_collector.cpython-311.pyc0000644000000000000000000004630500000000000023363 0ustar  �

~fǕ�������dZddlZddlZddlZddlZddlZddlZddlm	Z	ddl
mZddlm
Z
ddlmZddlmZddlmZmZmZmZeje��ZGd	�d
��ZGd�d��ZdS)
z1Collector for WordPress CVE protection incidents.�N)�Path)�defaultdict)�WPSite)�get_data_dir)�IncidentFileParser)�upsert_wordpress_incident�bulk_create_wordpress_incidents�build_incident_dict�country_readerc�h�eZdZdZ			ddededefd�Zd	�Zd
ededee	effd
�Z
d
edefd�ZdS)�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.
    �d��'�max_incidents_per_rule_per_ip�time_window_seconds�max_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_windowrr�list�incident_times�cleanup_interval�time�last_cleanup)�selfrrrs    �a/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/incident_collector.py�__init__zIncidentRateLimiter.__init__#sK��$A�� �.���"4���*�$�/�/��� "��� �I�K�K�����c�~�	�tj��}||jz
�	g}|j���D]5\}}�	fd�|D��}|r||j|<� |�|���6|D]
}|j|=�t|j��|jkr�t|j���d����}tdt|j��t|jdz��z
��}|d|�D]
\}}|j|=�t�d|j|��||_dS)zHRemove records older than the time window and enforce max entries limit.c� ��g|]
}|�k�|��S�r#��.0�ts�cutoffs  �r�
<listcomp>z<IncidentRateLimiter._cleanup_old_records.<locals>.<listcomp>D����=�=�=�R��f���b���r c�2�|dr|ddndS)N�rr#)�xs r�<lambda>z:IncidentRateLimiter._cleanup_old_records.<locals>.<lambda>Ss���1��4�a��d�1�g�g�1�r )�keyr+g�������?NzARate limiter exceeded max entries (%d), removed %d oldest entries)
rrr�items�append�lenr�sorted�max�int�logger�warningr)
r�now�keys_to_deleter.�
timestamps�recent�entries_by_age�
num_to_remove�_r's
         @r�_cleanup_old_recordsz(IncidentRateLimiter._cleanup_old_records;s�����i�k�k���t�'�'����#�2�8�8�:�:�	+�	+�O�C��=�=�=�=�:�=�=�=�F��
+�+1��#�C�(�(��%�%�c�*�*�*�*�!�	)�	)�C��#�C�(�(��t�"�#�#�d�&=�=�=�#��#�)�)�+�+�4�4����N� ���D�'�(�(�3�t�/F��/L�+M�+M�M���M�)��-��8�
-�
-���Q��'��,�,��N�N���'��	
�
�
� ����r �rule_id�attacker_ip�returnc��	�tj��|jz
|jkr|���tj��}||jz
�	||f}||jvrB|j|}�	fd�|D��}|r||j|<t
|��}n
|j|=d}nd}||jkr#|jdz}dd|�d|�d|�d|j�d	|�d
�fSdS)z�
        Check if adding an incident would exceed rate limits.

        Args:
            rule_id: Rule identifier
            attacker_ip: IP address of the attacker

        Returns:
            Tuple of (allowed: bool, reason: str)
        c� ��g|]
}|�k�|��Sr#r#r$s  �rr(z8IncidentRateLimiter.check_rate_limit.<locals>.<listcomp>�r)r rrFzRate limit exceeded for rule z	 from IP z: �/z within z minutes)T�OK)rrrr>rrr1r)
rr?r@r7r.r9r:�recent_count�window_minutesr's
         @r�check_rate_limitz$IncidentRateLimiter.check_rate_limitgsF����9�;�;��*�*�T�-B�B�B��%�%�'�'�'��i�k�k���t�'�'����$���$�%�%�%��,�S�1�J�=�=�=�=�:�=�=�=�F��
!�+1��#�C�(�"�6�{�{����'��,� ����L��4�3�3�3�!�-��3�N��1�G�1�1�#�1�1�$�1�1�'+�'?�1�1�'�1�1�1��
��zr c��tj��}||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)rrr1r�popr0)rr?r@r7r.r9s      r�record_incidentz#IncidentRateLimiter.record_incident�s����i�k�k����$���d�)�)�)�(+�u�D���$�$�$��,�S�1�J��:���$�":�:�:����q�!�!�!����c�"�"�"�"�"r N)rrr)�__name__�
__module__�__qualname__�__doc__r4rr>�str�tuple�boolrHrKr#r rr
r
s���������.1�#&�"'�	(�(�'*�(�!�(� �	(�(�(�(�0* �* �* �X2��2�),�2�	�t�S�y�	�2�2�2�2�h#�s�#��#�#�#�#�#�#r r
c�t�eZdZdZddedzfd�Z	ddededefd	�Z		dd
eededefd�Z
ededeefd
���Z
ejd��Zededefd���Zdededzdedefd�Zdededzfd�Zdeedededzdedef
d�Zdedededzdefd�Zdedededzdededefd�ZdS)�IncidentCollectorzM
    Collect and persist WordPress incidents from plugin incident files.
    N�rate_limiterc�V�|p
t��|_t��|_dS)z�
        Initialize the incident collector.

        Args:
            rate_limiter: Optional rate limiter (creates default if not provided)
        N)r
rUr�parser)rrUs  rrzIncidentCollector.__init__�s)��)�A�,?�,A�,A���(�*�*����r T�site�delete_after_processingrAc��K�g}	t|���d{V��}t�d||��|���st�d|��gS|�|��}t�d||��|st�d|��gSt�dt|��|��|�|��}|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 %s�Incident 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)rr5�debug�exists�_get_incident_filesr1�_get_site_username�
_process_file�extend�	Exception�error�info)
rrXrY�collected_incidents�data_dir�incident_files�username�
incident_file�file_incidents�es
          r�collect_incidents_for_sitez,IncidentCollector.collect_incidents_for_site�s����� !��%	�)�$�/�/�/�/�/�/�/�/�H��L�L�9�4��J�J�J��?�?�$�$�
����H�$�O�O�O��	�!�5�5�h�?�?�N��L�L�0�$��
�
�
�"�
����B�D�I�I�I��	��L�L�7��N�#�#��
�
�
��.�.�t�4�4�H�!/�
;�
;�
�'+�'9�'9�!���+�	(�(�"�"�"�"�"�"��$�*�*�>�:�:�:�:�
;���	�	�	��L�L�<���
�
�
�
�
�
�
�
�����	����	���2��#�$�$��	
�	
�	
�#�"s&�A!D/�(AD/�8A6D/�/
E�9E�E�sitesc���K�g}|D]3}|�||���d{V��}|�|���4|r6t�dt	|��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))rlrar5rdr1)rrmrY�all_collected_incidentsrX�site_incidentss      r�collect_incidents_for_sitesz-IncidentCollector.collect_incidents_for_sitess�����#%���	;�	;�D�#'�#B�#B��'�$�$�������N�
$�*�*�>�:�:�:�:�"�	��K�K�D��+�,�,��E�
�
�
�
�
�'�&r rfc��|dz}t�d||��|���r|���st�d|��gSg}|���D]k}	tj|��}n#t$rY�$wxYwtj	|j
��r*|�|��r|�|���lt�d||��|S)a�
        Get all incident files in the incidents directory.

        Only returns files older than one hour to give the WordPress plugin
        time to process and finalize the incident data before collection.

        Args:
            data_dir: Path to the imunify-security data directory

        Returns:
            List of incident file paths, sorted by modification time
        �	incidentsz#Incidents directory for site %s: %sz.Incidents directory does not exist for site %sr[)
r5r\r]�is_dir�iterdir�os�lstat�OSError�stat_module�S_ISREG�st_mode�_is_incident_filer0)�clsrf�
incidents_dirrg�f�sts      rr^z%IncidentCollector._get_incident_files#s0��!�;�.�
����1�8�]�	
�	
�	
��#�#�%�%�	�]�-A�-A�-C�-C�	��L�L�@�(�
�
�
��I����&�&�(�(�	)�	)�A�
��X�a�[�[�����
�
�
���
�����"�2�:�.�.�
)�3�3H�3H��3K�3K�
)��%�%�a�(�(�(�����,�h��	
�	
�	
��s�B�
B"�!B"z^\d{4}-\d{2}-\d{2}-\d{2}\.php$�	file_pathc�Z�t|j�|j����S)z�
        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
        )rR�
_FILE_PATTERN�match�name)r}r�s  rr|z#IncidentCollector._is_incident_fileMs%���C�%�+�+�I�N�;�;�<�<�<r rhc��6K�	|j�|��}|s:t�d|j��|r|�d���gSt�dt|��|j|��|�||||j��}|r6|�d���t�d|j��|S#t$r.}t�
d|j||��gcYd}~Sd}~wwxYw)NzNo valid incidents in file %sT)�
missing_okz)Parsed %d incident(s) from %s for site %szDeleted processed file %sz1Error processing incident file %s for site %s: %s)rW�
parse_filer5r6r��unlinkr\r1�_process_file_incidentsrbrc)rrirXrhrYrsrerks        rr`zIncidentCollector._process_fileZsT����(	���.�.�}�=�=�I��
����3�!�&����+�:�!�(�(�D�(�9�9�9��	��L�L�;��I����"��	
�
�
�#'�">�">�����"�	#�#��'�
N��$�$��$�5�5�5����8�-�:L�M�M�M�&�&���	�	�	��L�L�C��"���	
�
�
��I�I�I�I�I�I�����	���s%�AC �BC � 
D�*#D�
D�Dc��	tj|j��}|jS#t$r-}t
�d|j||��Yd}~dSd}~wwxYw)Nz.Failed to get username for uid=%d, site %s: %s)�pwd�getpwuid�uid�pw_namerbr5rc)rrX�	user_inforks    rr_z$IncidentCollector._get_site_username�ss��
	���T�X�.�.�I��$�$���	�	�	��L�L�@�����	
�
�
��4�4�4�4�4�����	���s�"�
A�"A�Ars�incident_file_namec��g}d}t��5}|D]�}|�dd��}	|�d��p|�dd��}
|j�|	|
��\}}|s"t�d||��|dz
}��|j|j||jd�}
t||
|�	��}|�
|��|j�|	|
����	ddd��n#1swxYwYg}|rD	t|��}n3#t$r&}t�d
||��Yd}~nd}~wwxYwt�d|t!|��|��|S)Nrr?�unknown�REMOTE_ADDRr@�#Rate limit exceeded for site %s: %sr+��domain�	site_pathrh�user_id)�
geo_readerz+Failed to bulk insert incidents from %s: %sz(Processed file %s: %d stored, %d dropped)r�getrUrHr5r6r��docrootr�r
r0rKr	rbrcrdr1)rrsrXrhr��incidents_to_insert�
dropped_countr��incidentr?r@�allowed�reason�	site_info�
incident_data�created_incidentsrks                 rr�z)IncidentCollector._process_file_incidents�sZ��!���
��
�
�!	H��%� 
H� 
H��"�,�,�y�)�<�<��&�l�l�=�9�9��X�\�\�!�9�>�>��#'�"3�"D�"D���#�#����
���N�N�=������
"�Q�&�M��#�k�!%�� (�#�x�	��	�!4��i�J�!�!�!�
�$�*�*�=�9�9�9��!�1�1�'�;�G�G�G�G�A 
H�!	H�!	H�!	H�!	H�!	H�!	H�!	H�!	H�!	H�!	H�!	H����!	H�!	H�!	H�!	H�H���
	�	
�$C�'�%�%�!�!���
�
�
����A�&��������������
����	���6���!�"�"��		
�	
�	
�!� s*�C!D�D�D�D!�!
E�+E�Er�c�4�|�dd��}|�d��p|�dd��}|j�||��\}}|st�d||��dS|�||||||��S)Nr?r�r�r@r�)r�rUrHr5r6�_store_incident)	rr�rXrhr�r?r@r�r�s	         r�_process_incidentz#IncidentCollector._process_incident�s����,�,�y�)�4�4���l�l�=�1�1�
�X�\�\��9�6
�6
���+�<�<���
�
����
�	��N�N�5���
�
�
�
�4��#�#�������

�
�	
r r?r@c���	|j|j||jd�}t||��}|j�||��|S#t$r'}t�d||��Yd}~dSd}~wwxYw)Nr�z$Failed to store incident from %s: %s)	r�r�r�rrUrKrbr5rc)	rr�rXrhr?r@r�r�rks	         rr�z!IncidentCollector._store_incident�s���	��+�!�\�$��8�	��I�1�����H�

��-�-�g�{�C�C�C��O���	�	�	��L�L�6�"��
�
�
�
�4�4�4�4�4�����
	���s�AA�
A6�A1�1A6)N)T)rLrMrNrOr
rrrRrrlrq�classmethodrr^�re�compiler�r|rPr`r_�dictr�r�r�r#r rrTrT�s��������+�+�%8�4�%?�+�+�+�+�)-�?#�?#��?#�"&�?#�
�	?#�?#�?#�?#�H)-�'�'��F�|�'�"&�'�
�	'�'�'�'�B�$�4�$�D��J�$�$�$��[�$�N�B�J�@�A�A�M��
=�$�
=�4�
=�
=�
=��[�
=�/��/���*�	/�
"&�/�
�
/�/�/�/�b�v��#��*�����C!���:�C!��C!���*�	C!�
 �C!�
�
C!�C!�C!�C!�J 
�� 
�� 
���*�	 
�
 � 
� 
� 
� 
�D�������*�	�
���
� ������r rT)rO�loggingrvr��statryrr��pathlibr�collectionsr�defence360agent.model.wordpressr�defence360agent.wordpress.clir�)defence360agent.wordpress.incident_parserr�(defence360agent.model.wordpress_incidentrr	r
r�	getLoggerrLr5r
rTr#r r�<module>r�s[��7�7�����	�	�	�	�
�
�
�
���������	�	�	�	�������#�#�#�#�#�#�2�2�2�2�2�2�6�6�6�6�6�6�H�H�H�H�H�H�������������
��	�8�	$�	$��V#�V#�V#�V#�V#�V#�V#�V#�rk�k�k�k�k�k�k�k�k�kr defence360agent/wordpress/__pycache__/incident_parser.cpython-311.opt-1.pyc0000644000000000000000000001313700000000000023625 0ustar  �

%�X7�����|�dZddlZddlZddlZddlZddlmZddlmZej	e
��ZGd�d��ZdS)z+Parser for WordPress plugin incident files.�N)�Path)�
open_nofollowc
��eZdZdZededeefd���Zede	de
dededzfd���Zed	e	de
dededzfd
���ZdS)�IncidentFileParsera'
    Parse incident files written by the WordPress plugin.

    These files have format:
    <?php __halt_compiler();
    #{base64-encoded JSON data for incident}
    #{base64-encoded JSON data for incident}
    ...

    File pattern: wp-content/imunify-security/incidents/yyyy-mm-dd-hh.php
    �	file_path�returnc��g}	tt|����5}tjtj|��dd���5}t|d��D]G\}}|���}|�|||��}|�|�|���H	ddd��n#1swxYwYddd��n#1swxYwYn5#t$r(}t�d||��gcYd}~Sd}~wwxYw|S)aCParse an incident file and return list of incident dictionaries.

        The file format is:
        - First line: <?php __halt_compiler();
        - Following lines: #{base64-encoded JSON}

        Opens with O_NOFOLLOW to prevent reading arbitrary files if
        the incident file was replaced with a symlink.
        �r�utf-8)�encoding�Nz"Error reading incident file %s: %s)r�str�os�fdopen�dup�	enumerate�strip�
_process_line�append�	Exception�logger�error)	�clsr�	incidents�fd�f�line_num�line�incident�es	         �^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/incident_parser.py�
parse_filezIncidentFileParser.parse_files����	�	��s�9�~�~�.�.�
7�"��Y�r�v�b�z�z�3��A�A�A�7�Q�*3�A�q�/�/�7�7���$�#�z�z�|�|��#&�#4�#4�T�8�Y�#O�#O��#�/�%�,�,�X�6�6�6��	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�
7����	�	�	��L�L�4���
�
�
�
�I�I�I�I�I�I�����
	�����se�C�*C�
AB0�$C�0B4	�4C�7B4	�8C�;C�C�C�C�C�
D�D�:D�DrrNc�>�|sdS|�d��r#t�d||j��dS|�d��s,t�d||j|dd���dS|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<?phpz!Skipping PHP header line %d in %s�#z&Line %d in %s doesn't start with #: %s�2r
)�
startswithr�debug�name�_process_encoded_line)rrrr�encoded_datas     r!rz IncidentFileParser._process_line;s��� �	��4��?�?�7�#�#�	��L�L�3����
�
�
�
�4����s�#�#�	��L�L�8�����S�b�S�	�	
�
�
��4��A�B�B�x���(�(��x��K�K�K�r*c��	tj|��}|�d��}tj|��}t|t��r|St�d||j	|dd���dS#ttjf$r-}t�d||j	|��Yd}~dSd}~wwxYw)aM
        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
        rz&Line %d in %s is not a JSON object: %sN�dz,Failed to decode base64 on line %d in %s: %s)
�base64�	b64decode�decode�json�loads�
isinstance�dictr�warningr(r�JSONDecodeErrorr)rr*rr�
decoded_bytes�decoded_strrr s        r!r)z(IncidentFileParser._process_encoded_linees���	�"�,�\�:�:�M�'�.�.�w�7�7�K��z�+�.�.�H��(�D�)�)�
 ����N�N�8�����D�S�D�!�	
�
�
��4���4�/�0�	�	�	��L�L�>�����	
�
�
��4�4�4�4�4�����	���s�AB�*B�C�"C�C)
�__name__�
__module__�__qualname__�__doc__�classmethodr�listr4r"r�intrr)�r+r!rrs�������
�
���4��D��J�����[��>�'L��'L�"%�'L�26�'L�	
���'L�'L�'L��[�'L�R�%��%�*-�%�:>�%�	
���%�%�%��[�%�%�%r+r)
r<r.r1�loggingr�pathlibr�defence360agent.utils.fd_opsr�	getLoggerr9rrr@r+r!�<module>rEs���1�1�
�
�
�
���������	�	�	�	�������6�6�6�6�6�6�	��	�8�	$�	$��}�}�}�}�}�}�}�}�}�}r+defence360agent/wordpress/__pycache__/incident_parser.cpython-311.pyc0000644000000000000000000001313700000000000022666 0ustar  �

%�X7�����|�dZddlZddlZddlZddlZddlmZddlmZej	e
��ZGd�d��ZdS)z+Parser for WordPress plugin incident files.�N)�Path)�
open_nofollowc
��eZdZdZededeefd���Zede	de
dededzfd���Zed	e	de
dededzfd
���ZdS)�IncidentFileParsera'
    Parse incident files written by the WordPress plugin.

    These files have format:
    <?php __halt_compiler();
    #{base64-encoded JSON data for incident}
    #{base64-encoded JSON data for incident}
    ...

    File pattern: wp-content/imunify-security/incidents/yyyy-mm-dd-hh.php
    �	file_path�returnc��g}	tt|����5}tjtj|��dd���5}t|d��D]G\}}|���}|�|||��}|�|�|���H	ddd��n#1swxYwYddd��n#1swxYwYn5#t$r(}t�d||��gcYd}~Sd}~wwxYw|S)aCParse an incident file and return list of incident dictionaries.

        The file format is:
        - First line: <?php __halt_compiler();
        - Following lines: #{base64-encoded JSON}

        Opens with O_NOFOLLOW to prevent reading arbitrary files if
        the incident file was replaced with a symlink.
        �r�utf-8)�encoding�Nz"Error reading incident file %s: %s)r�str�os�fdopen�dup�	enumerate�strip�
_process_line�append�	Exception�logger�error)	�clsr�	incidents�fd�f�line_num�line�incident�es	         �^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/incident_parser.py�
parse_filezIncidentFileParser.parse_files����	�	��s�9�~�~�.�.�
7�"��Y�r�v�b�z�z�3��A�A�A�7�Q�*3�A�q�/�/�7�7���$�#�z�z�|�|��#&�#4�#4�T�8�Y�#O�#O��#�/�%�,�,�X�6�6�6��	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�
7����	�	�	��L�L�4���
�
�
�
�I�I�I�I�I�I�����
	�����se�C�*C�
AB0�$C�0B4	�4C�7B4	�8C�;C�C�C�C�C�
D�D�:D�DrrNc�>�|sdS|�d��r#t�d||j��dS|�d��s,t�d||j|dd���dS|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<?phpz!Skipping PHP header line %d in %s�#z&Line %d in %s doesn't start with #: %s�2r
)�
startswithr�debug�name�_process_encoded_line)rrrr�encoded_datas     r!rz IncidentFileParser._process_line;s��� �	��4��?�?�7�#�#�	��L�L�3����
�
�
�
�4����s�#�#�	��L�L�8�����S�b�S�	�	
�
�
��4��A�B�B�x���(�(��x��K�K�K�r*c��	tj|��}|�d��}tj|��}t|t��r|St�d||j	|dd���dS#ttjf$r-}t�d||j	|��Yd}~dSd}~wwxYw)aM
        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
        rz&Line %d in %s is not a JSON object: %sN�dz,Failed to decode base64 on line %d in %s: %s)
�base64�	b64decode�decode�json�loads�
isinstance�dictr�warningr(r�JSONDecodeErrorr)rr*rr�
decoded_bytes�decoded_strrr s        r!r)z(IncidentFileParser._process_encoded_linees���	�"�,�\�:�:�M�'�.�.�w�7�7�K��z�+�.�.�H��(�D�)�)�
 ����N�N�8�����D�S�D�!�	
�
�
��4���4�/�0�	�	�	��L�L�>�����	
�
�
��4�4�4�4�4�����	���s�AB�*B�C�"C�C)
�__name__�
__module__�__qualname__�__doc__�classmethodr�listr4r"r�intrr)�r+r!rrs�������
�
���4��D��J�����[��>�'L��'L�"%�'L�26�'L�	
���'L�'L�'L��[�'L�R�%��%�*-�%�:>�%�	
���%�%�%��[�%�%�%r+r)
r<r.r1�loggingr�pathlibr�defence360agent.utils.fd_opsr�	getLoggerr9rrr@r+r!�<module>rEs���1�1�
�
�
�
���������	�	�	�	�������6�6�6�6�6�6�	��	�8�	$�	$��}�}�}�}�}�}�}�}�}�}r+defence360agent/wordpress/__pycache__/incident_sender.cpython-311.opt-1.pyc0000644000000000000000000001772600000000000023621 0ustar  �

�%��nJ����dZddlZddlZddlmZddlmZddlmZddlm	Z	ej
e��ZGd�d��Z
dS)	z3Send WordPress incidents to the correlation server.�N)�datetime)�Any)�SensorWordpressIncidentList)�MessageSinkc��eZdZdZdedeeeffd�Zdedeefd�Z	de
dzd	eedefd
�Zde
deefd�Z
dS)
�IncidentSendera
    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
    automatically handled by SendToServer/SendToServerFGW plugins.
    �incident�returnc���t�d|��|�d��pi}t|�d��pd��}t	|��}|r'tj|���d��nd}id|�d|�d|�d	���d
|�d
��pd�d|�d���d
|�d���d|�d���d|�d��pd�d|�d��pd�d|�d��pd�d|�d��pd�d|�d��pd�d|�d��pd�d|�d��r|�d��dknd�d|�d��pd�d |�d!��pd�d"|�|���|�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-%d��dt�	plugin_id�plugin�rule�unknown�name�message�description�severity�attackers_ip�abuser�domain�retries��uri�request_uri�
user_agent�http_user_agent�http_method�request_method�user_logged_in�trueN�	file_path�	site_path�user�username�tag�target�slug�version�mode)r*r+r,r-�details)	�logger�info�get�float�intr�
fromtimestamp�strftime�_build_tags)�selfr	�extra�timestamp_valuer
rs      �^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/incident_sender.py�!_prepare_incident_for_correlationz0IncidentSender._prepare_incident_for_correlations���	���<�h�G�G�G��l�l�<�0�0�6�B��"'�x�|�|�K�'@�'@�'E�A�!F�!F����(�(�	��
�H�"�?�3�3�<�<�Z�H�H�H��	�
���
��"�
�
����h�/�/�
�
�H�L�L��(�(�5�I�	
�

�H�L�L��(�(�
�
�x�|�|�M�2�2�

�
����Z�0�0�
�
�H�L�L��2�2�8�b�
�
�h�l�l�8�,�,�2��
�
�x�|�|�I�.�.�3�!�
�
�5�9�9�]�+�+�1�r�
�
�%�)�)�$5�6�6�<�"�
�
�5�9�9�%5�6�6�<�"�
�
��y�y�)�*�*��e�i�i�(8�9�9�V�C�C��!
�"
����;�/�/�5�2�#
�$
�E�I�I�j�)�)�/�R�%
�&
�4�#�#�E�*�*�'
�*�i�i��)�)�/�R��I�I�f�%�%�+���y�y��+�+�1�r��I�I�f�%�%�+���3
�
�
�	
�r8c�:�ddg}|�d��r|�|d��|�d��r|�d|d����|�d��r|�d|d����|S)N�	wordpress�cver*�target_r-�mode_)r1�append)r7r8�tagss   r:r6zIncidentSender._build_tagsNs����U�#���9�9�U���	&��K�K��e��%�%�%��9�9�X���	5��K�K�3�%��/�3�3�4�4�4��9�9�V���	1��K�K�/��f�
�/�/�0�0�0��r<�sinkN�	incidentsc��d�K�|�t�d��dSt|��dkrt�d��dSt�dt|�����fd�|D��}��||���d{V��t|��S)aC
        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
        Nz+No sink provided, skipping incident sendingrzNo incidents to send, skippingz*Sending %d incidents to correlation serverc�:��g|]}��|����S�)r;)�.0r	r7s  �r:�
<listcomp>z1IncidentSender.send_incidents.<locals>.<listcomp>us7���
�
�
��
�2�2�8�<�<�
�
�
r<)r/�warning�len�debugr0�_send_batch)r7rDrE�correlation_batchs`   r:�send_incidentszIncidentSender.send_incidentsZs�������<��N�N�H�I�I�I��1��y�>�>�Q����L�L�9�:�:�:��1����8�#�i�.�.�	
�	
�	
�
�
�
�
�%�
�
�
��
���t�%6�7�7�7�7�7�7�7�7�7��$�%�%�%r<rOc��K�t�dt|����t�dtj|d�����	|�t
|�����d{V��t�dt|����dS#t$r!}t�d|���d}~wwxYw)a_
        Send a batch of incidents to correlation server.

        Uses SensorIncidentList Reportable message which is automatically
        sent to correlation via SendToServer/SendToServerFGW plugins.

        Args:
            sink: MessageSink to send the batch to
            correlation_batch: Incidents formatted for correlation server
        z3Sending batch of %d incidents to correlation serverzCorrelation batch json: %s�)�indentNz6Queued %d wordpress incident(s) for correlation serverz"Failed to queue incident batch: %s)	r/r0rL�json�dumps�process_messager�	Exception�error)r7rDrO�es    r:rNzIncidentSender._send_batch~s����	���A��!�"�"�	
�	
�	
�	���(��J�(��3�3�3�	
�	
�	
�	��&�&�+�,=�>�>���
�
�
�
�
�
�
�
�K�K�H��%�&�&�
�
�
�
�
��
�	�	�	��L�L�4��
�
�
�
�����	���s�AB-�-
C�7C�C)�__name__�
__module__�__qualname__�__doc__�dict�strrr;�listr6rr3rPrNrHr<r:rrs���������5
��5
�	
�c�3�h��5
�5
�5
�5
�n
��
�$�s�)�
�
�
�
�"&��$�&�"&�37��:�"&�	�"&�"&�"&�"&�H&��&�48��J�&�&�&�&�&�&r<r)r]rT�loggingr�typingr�"defence360agent.contracts.messagesr�!defence360agent.contracts.pluginsr�	getLoggerrZr/rrHr<r:�<module>rfs���9�9���������������������J�J�J�J�J�J�9�9�9�9�9�9�	��	�8�	$�	$��V�V�V�V�V�V�V�V�V�Vr<defence360agent/wordpress/__pycache__/incident_sender.cpython-311.pyc0000644000000000000000000001772600000000000022662 0ustar  �

�%��nJ����dZddlZddlZddlmZddlmZddlmZddlm	Z	ej
e��ZGd�d��Z
dS)	z3Send WordPress incidents to the correlation server.�N)�datetime)�Any)�SensorWordpressIncidentList)�MessageSinkc��eZdZdZdedeeeffd�Zdedeefd�Z	de
dzd	eedefd
�Zde
deefd�Z
dS)
�IncidentSendera
    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
    automatically handled by SendToServer/SendToServerFGW plugins.
    �incident�returnc���t�d|��|�d��pi}t|�d��pd��}t	|��}|r'tj|���d��nd}id|�d|�d|�d	���d
|�d
��pd�d|�d���d
|�d���d|�d���d|�d��pd�d|�d��pd�d|�d��pd�d|�d��pd�d|�d��pd�d|�d��pd�d|�d��r|�d��dknd�d|�d��pd�d |�d!��pd�d"|�|���|�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-%d��dt�	plugin_id�plugin�rule�unknown�name�message�description�severity�attackers_ip�abuser�domain�retries��uri�request_uri�
user_agent�http_user_agent�http_method�request_method�user_logged_in�trueN�	file_path�	site_path�user�username�tag�target�slug�version�mode)r*r+r,r-�details)	�logger�info�get�float�intr�
fromtimestamp�strftime�_build_tags)�selfr	�extra�timestamp_valuer
rs      �^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/incident_sender.py�!_prepare_incident_for_correlationz0IncidentSender._prepare_incident_for_correlations���	���<�h�G�G�G��l�l�<�0�0�6�B��"'�x�|�|�K�'@�'@�'E�A�!F�!F����(�(�	��
�H�"�?�3�3�<�<�Z�H�H�H��	�
���
��"�
�
����h�/�/�
�
�H�L�L��(�(�5�I�	
�

�H�L�L��(�(�
�
�x�|�|�M�2�2�

�
����Z�0�0�
�
�H�L�L��2�2�8�b�
�
�h�l�l�8�,�,�2��
�
�x�|�|�I�.�.�3�!�
�
�5�9�9�]�+�+�1�r�
�
�%�)�)�$5�6�6�<�"�
�
�5�9�9�%5�6�6�<�"�
�
��y�y�)�*�*��e�i�i�(8�9�9�V�C�C��!
�"
����;�/�/�5�2�#
�$
�E�I�I�j�)�)�/�R�%
�&
�4�#�#�E�*�*�'
�*�i�i��)�)�/�R��I�I�f�%�%�+���y�y��+�+�1�r��I�I�f�%�%�+���3
�
�
�	
�r8c�:�ddg}|�d��r|�|d��|�d��r|�d|d����|�d��r|�d|d����|S)N�	wordpress�cver*�target_r-�mode_)r1�append)r7r8�tagss   r:r6zIncidentSender._build_tagsNs����U�#���9�9�U���	&��K�K��e��%�%�%��9�9�X���	5��K�K�3�%��/�3�3�4�4�4��9�9�V���	1��K�K�/��f�
�/�/�0�0�0��r<�sinkN�	incidentsc��d�K�|�t�d��dSt|��dkrt�d��dSt�dt|�����fd�|D��}��||���d{V��t|��S)aC
        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
        Nz+No sink provided, skipping incident sendingrzNo incidents to send, skippingz*Sending %d incidents to correlation serverc�:��g|]}��|����S�)r;)�.0r	r7s  �r:�
<listcomp>z1IncidentSender.send_incidents.<locals>.<listcomp>us7���
�
�
��
�2�2�8�<�<�
�
�
r<)r/�warning�len�debugr0�_send_batch)r7rDrE�correlation_batchs`   r:�send_incidentszIncidentSender.send_incidentsZs�������<��N�N�H�I�I�I��1��y�>�>�Q����L�L�9�:�:�:��1����8�#�i�.�.�	
�	
�	
�
�
�
�
�%�
�
�
��
���t�%6�7�7�7�7�7�7�7�7�7��$�%�%�%r<rOc��K�t�dt|����t�dtj|d�����	|�t
|�����d{V��t�dt|����dS#t$r!}t�d|���d}~wwxYw)a_
        Send a batch of incidents to correlation server.

        Uses SensorIncidentList Reportable message which is automatically
        sent to correlation via SendToServer/SendToServerFGW plugins.

        Args:
            sink: MessageSink to send the batch to
            correlation_batch: Incidents formatted for correlation server
        z3Sending batch of %d incidents to correlation serverzCorrelation batch json: %s�)�indentNz6Queued %d wordpress incident(s) for correlation serverz"Failed to queue incident batch: %s)	r/r0rL�json�dumps�process_messager�	Exception�error)r7rDrO�es    r:rNzIncidentSender._send_batch~s����	���A��!�"�"�	
�	
�	
�	���(��J�(��3�3�3�	
�	
�	
�	��&�&�+�,=�>�>���
�
�
�
�
�
�
�
�K�K�H��%�&�&�
�
�
�
�
��
�	�	�	��L�L�4��
�
�
�
�����	���s�AB-�-
C�7C�C)�__name__�
__module__�__qualname__�__doc__�dict�strrr;�listr6rr3rPrNrHr<r:rrs���������5
��5
�	
�c�3�h��5
�5
�5
�5
�n
��
�$�s�)�
�
�
�
�"&��$�&�"&�37��:�"&�	�"&�"&�"&�"&�H&��&�48��J�&�&�&�&�&�&r<r)r]rT�loggingr�typingr�"defence360agent.contracts.messagesr�!defence360agent.contracts.pluginsr�	getLoggerrZr/rrHr<r:�<module>rfs���9�9���������������������J�J�J�J�J�J�9�9�9�9�9�9�	��	�8�	$�	$��V�V�V�V�V�V�V�V�V�Vr<defence360agent/wordpress/__pycache__/plugin.cpython-311.opt-1.pyc0000644000000000000000000026057200000000000021761 0ustar  �

M��"�
���ddlZddlZddlZddlZddlZddlZddlmZddlm	Z	m
Z
ddlmZddl
mZddlmZddlmZddlmZmZmZmZmZdd	lmZmZdd
lmZddlm 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;m<Z<m=Z=m>Z>m?Z?m@Z@mAZAddlBmCZCmDZDmEZEmFZFmGZGmHZHmIZImJZJmKZKmLZLmMZMmNZNmOZOddlPmQZQejReS��ZTed���ZUdZVejW��ZXdaYdZZdZ[de\fd�Z]de\fd�Z^de\fd�Z_de\fd�Z`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/�Zpd0end1ejqdenfd2�Zrd1ejqd+efd3�Zsd4ejtfd5�Zudvd7evfd8�Zwd9�Zxd:�Zyd;�Zzdeafd<�Z{d=�Z|d>e.fd?�Z}d>e.de~fd@�ZdA�Z�dB�Z�	dwdCe�e.fdD�Z�dEeve.fdF�Z�dddG�d>e.dHeadIend1ejqdzdJedzddfdK�Z�dddG�d>e.dLend1ejqdzdJedzfdM�Z�dddG�d>e.dNend1ejqdzdJedzddf
dO�Z�dEeve.de~fdP�Z�d>e.d1ejqdQeadRe�dSe�ddfdT�Z�		dxdEeve.dUe
e.ejqe�e�ge	dfdVeadWeadXe\ddfdY�Z�dwdQeaddfdZ�Z�d-ed[e\ddfd\�Z�ejW��Z�da�dyd]�Z�dyd^�Z�d_eaddfd`�Z�dEeve.ddfda�Z�d%eaddfdb�Z�d%eaddfdc�Z�dydd�Z�dyde�Z�dfeadge�deafdh�Z�d>e.d1ejqdge�dRe�dSe�ddfdi�Z�		dzdjeveadzddfdk�Z�dwdl�Z�dm�Z�d>e.de\fdn�Z�d>e.doe~de\fdp�Z�dq�Z�Gdr�ds��Z�Gdt�due���Z�dS){�N)�defaultdict)�	Awaitable�Callable)�LooseVersion)�cache)�Path)�
inactivity)�MalwareScanScheduleInterval�SystemConfig�ANTIVIRUS_MODE�UserType�choose_value_from_config)�Index�WP_RULES)�log_message)�importer)�open_dir_no_symlinks�
open_nofollow�	rmtree_fd�safe_dir)�	Wordpress)�
hosting_panel)�get_wp_rules_data�get_wp_ruleset_version)�
WordpressSite�WPSite)�WPDisabledRule)�cli�	telemetry)�PLUGIN_VERSION_FILE)�_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)
�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)�setup_site_authenticationc�0�tjddd���S)Nz(imav.malwarelib.plugins.schedule_watcher�get_user_schedule_config)�module�name�default)r�get���U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/plugin.py�_get_user_schedule_config_imavrCKs$���<�9�
'�����rATF�balanced�returnc�d�	ttj��S#t$r
tcYSwxYw�N)�boolr�WAF_ENABLED�KeyError�_LEGACY_WAF_FALLBACKr@rArB�_get_global_waf_enabledrLc�?��$��I�)�*�*�*���$�$�$�#�#�#�#�$������/�/c�d�	ttj��S#t$r
tcYSwxYwrG)rHr�WAF_DEFAULTrJrKr@rArB�_get_waf_defaultrQjrMrNc�X�	ttj��S#t$rYdSwxYw)NF)rHr�SECURITY_PLUGIN_ENABLEDrJr@rArB�_get_security_plugin_enabledrTqs:��
��I�5�6�6�6�������u�u����s��
)�)c�d�	ttj��S#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.
    )rHr�AI_BOT_PROTECTIONrJ�_AI_BOT_PROTECTION_DEFAULTr@rArB�_get_global_ai_bot_protectionrX|s?��*��I�/�0�0�0���*�*�*�)�)�)�)�*���rNc�j�	tj}n#t$r
tcYSwxYwt	|��S)u�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.
    )r�AI_BOT_PROTECTION_PRESETrJ�!_AI_BOT_PROTECTION_PRESET_DEFAULTr!)�raws rB�$_get_global_ai_bot_protection_presetr]�sF��1��0�����1�1�1�0�0�0�0�1�����C� � � s��#�#r>�overridezglobal kill switchc�T�t��t��t��fS)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@rArB�waf_global_snapshotr`�s)��	%�&�&��!�!�����rA�usernamec��t��s	dtfS	tdd|���\}}n%#t$rt	��t
fcYSwxYw|tjkrt	��t
fSt|��tfS)NF�	WORDPRESS�waf_enabled�ra)
rL�WAF_SOURCE_KILL_SWITCHrrJrQ�WAF_SOURCE_DEFAULTr
�ROOTrH�WAF_SOURCE_OVERRIDE)ra�value�sources   rB�#waf_status_and_source_for_user_syncrl�s���"�$�$�-��,�,�,�6�0����
�
�
�
��v�v���6�6�6��!�!�#5�5�5�5�5�6����
������!�!�#5�5�5���;�;�+�+�+s�/�A�Ac�*�t|��\}}|SrG)rl)ra�enabled�_s   rB�_is_waf_enabled_for_user_syncrp�s��4�X�>�>�J�G�Q��NrAc��pK�tj��}|�dt|���d{V��S)u3Async wrapper — runs config file I/O in executor.N)�asyncio�get_running_loop�run_in_executorrp)ra�loops  rB�is_waf_enabled_for_userrv�sR�����#�%�%�D��%�%��+�X���������rAc�r�	tdd|���\}}n#t$rYdSwxYw|tjkS)NrcrdreF)rrJr
rh)rarorks   rB�$_user_has_explicit_waf_override_syncrx�sY���,����
�
�
�	��6�6�������u�u������X�]�"�"s��
&�&zD/var/lib/cloudlinux-app-version-detector/components_versions.sqlite3�admin_configc��t��}|�*t�d��tjdddfS|||��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�)rC�logger�debug�Interval�NONE)raryr;s   rB�_get_user_schedule_configr��sU�� >�?�?���'����N�	
�	
�	
��}�a��A�%�%�#�#�H�l�;�;�;rA�indexc���t|��}|�dStr|���D]
\}}d|d<�tt	j������r �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.
    N�pass�modec�$��i|]\}}|�v�	||��
Sr@r@)�.0�cve�params�globally_disableds   �rB�
<dictcomp>z-get_updated_wp_rules_data.<locals>.<dictcomp>s5���
�
�
���V��+�+�+�
��+�+�+rA)rr�items�setr�get_global_disabled)r��
rules_datar�r�r�s    @rB�get_updated_wp_rules_datar��s���� #�5�)�)�J����t��$�%�+�+�-�-�	$�	$�K�C��#�F�6�N�N��N�>�@�@�A�A���
�
�
�
�
�)�/�/�1�1�
�
�
�
��rAc�H�t��tj��dS)z#Clear all WordPress-related caches.N)r#r�clear_get_content_dir_cacher@rArB�clear_cachesr�s#��(�*�*�*��#�%�%�%�%�%rAr��	user_infoc�����t|��}d�|D��}|D]D���fd�|D��}|r1t|t���}||�����E|S)Nc��i|]}|g��Sr@r@)r��paths  rBr�zsite_search.<locals>.<dictcomp>s��
.�
.�
.�4�d�B�
.�
.�
.rAc�,��g|]}��|���|��Sr@r@)r�r��item�matchers  ��rB�
<listcomp>zsite_search.<locals>.<listcomp>s*���M�M�M�4����t�9L�9L�M�$�M�M�MrA)�key)r0�max�len�append)r�r�r��
user_sites�result�matching_sites�most_specific_siter�s  `    @rB�site_searchr�s�����#�I�.�.�J�
.�
.�:�
.�
.�
.�F��4�4��M�M�M�M�M�:�M�M�M���	4�!$�^��!=�!=�!=���%�&�-�-�d�3�3�3���MrAc��:K�t||j���d{V��}|�dd��}t|j|��\}}}}d}	|tjkrt
||||��}	t|j��}
t|
|d���}||	|fS)N�	scan_datec�P�|ddko|d�|��S)N�
resource_type�file)�
startswith)r�r�s  rB�<lambda>z)_get_scan_data_for_user.<locals>.<lambda>>s-��4��0�F�:�*���L�#�#�D�)�)�rA)	r'�pw_namer?r�r~rr"r(r�)�sinkr�ry�	last_scan�last_scan_time�interval�hour�day_of_month�day_of_week�next_scan_time�malware_history�malware_by_sites            rB�_get_scan_data_for_userr�"s�����$�D�)�*;�<�<�<�<�<�<�<�<�I��]�]�;��5�5�N�1J���<�1�1�-�H�d�L�+��N��8�=� � �6��d�L�+�
�
��
*�)�*;�<�<�O�"���	*�	*���O��>�?�:�:rA�	semaphorec���K�|4�d{V��	|�d{V��n4#t$r'}t�d|����Yd}~nd}~wwxYwddd���d{V��dS#1�d{V��swxYwYdS)NzTelemetry task failed: )�	Exceptionr|�error)�coror��es   rB�_send_telemetry_taskr�Es+�����8�8�8�8�8�8�8�8�	8��J�J�J�J�J�J�J�J���	8�	8�	8��L�L�6�1�6�6�7�7�7�7�7�7�7�7�����	8����8�8�8�8�8�8�8�8�8�8�8�8�8�8�8�8�8�8�8�8�8�8�8�8����8�8�8�8�8�8s5�A��A�
A�A�A�A�A�
A'�*A'�
�
coroutinesc����K�|sdStj|����fd�|D��}	tj|��d{V��dS#t$r(}t�d|����Yd}~dSd}~wwxYw)zK
    Process a list of telemetry coroutines with a concurrency limit.s
    Nc�T��g|]$}tjt|�������%Sr@)rr�create_taskr�)r�r�r�s  �rBr�z+process_telemetry_tasks.<locals>.<listcomp>Us?���
�
�
��	��0��y�A�A�B�B�
�
�
rAzSome telemetry tasks failed: )rr�	Semaphore�gatherr�r|r�)r��concurrency�tasksr�r�s    @rB�process_telemetry_tasksr�Ms�����������!�+�.�.�I�
�
�
�
��
�
�
�E�
:��n�e�$�$�$�$�$�$�$�$�$�$���:�:�:����8�Q�8�8�9�9�9�9�9�9�9�9�9�����:���s�A�
A3�A.�.A3c��pK�	ttd���}|����d{V��t|��}n3#t$r&}t
�d|��Yd}~dSd}~wwxYw|st
�d��dSt|��}||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<valid WordPress rules not found, skipping rules installation��version�rules)	rr�updater�r�r|�warningrr%)�wp_rules_index�
wp_rules_datar��wp_rules_version�ruleset_dicts     rB�load_wp_rules_phpr�`s�����	��x��?�?�?���#�#�%�%�%�%�%�%�%�%�%�1�.�A�A�
�
���������L�
�	
�	
�	
��t�t�t�t�t��������������J�	
�	
�	
��t�.�n�=�=��#����L�)��6�6�6s�?A�
A4�A/�/A4c��vK�t��}t||��}|����d{V��S)zLInstall the imunify-security plugin for all sites where it is not installed.N)r2�WordPressSiteInstaller�run)r��sites�	installers   rB�install_everywherer��s@���� �"�"�E�&�t�U�3�3�I������ � � � � � � rAc��vK�t��}t||��}|����d{V��S)a�
    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
    N)r1�WordPressSiteAdopterr�)r�r��	processors   rB�adopt_found_sitesr��sB����
� � �E�$�T�5�1�1�I������ � � � � � � rAc��	tj��s"t�dt��dStj�����S#t$r&}t�d|��Yd}~dSd}~wwxYw)zLGet the latest version of the imunify-security plugin from the version file.z&Plugin version file does not exist: %sNz&Failed to read plugin version file: %s)r �existsr|r��	read_text�stripr�)r�s rB�get_latest_plugin_versionr��s���	�"�)�+�+�	��L�L�8�:M�
�
�
��4�"�,�.�.�4�4�6�6�6���������=�q�A�A�A��t�t�t�t�t��������s�3A�$A�
B�&B�Bc��K�t��}|st�d��dSt�d|��t	��}g}t
j�d��5	t|��}t�dt|���d���|s#	t|���d{V��ddd��dSt��}t���d{V��}tt��}|D]"}||j�|���#|���D�],\}	}
	t%j|	��}|j}n3#t*$r&}
t�d|	|
��Yd}
~
�Nd}
~
wwxYwt-|||���d{V��\}}}t/|��}|
D�]�}t1||���d{V��r�	t3j|���d{V��st�d|���Qt7||||||�	��}t9||���d{V��}t;||||�
���d{V��t=||||�
���d{V��t3j|���d{V��|� |��t3j!|���d{V��}|r{|j"}tG||��|�$|��}tK|��tK|��k}|�tMj'||rdnd||�
������v#t*$r'}
t�d||
��Yd}
~
���d}
~
wwxYw��.t�dt|����nf#tPj)$r+t�dt|����Yn-t*$r!}
t�d|
���d}
~
wwxYwt|���d{V��n#t|���d{V��wxYw	ddd��dS#1swxYwYdS)zFUpdate the imunify-security plugin on all sites where it is installed.z)Could not determine latest plugin versionNz<Updating imunify-security wp plugin to the latest version %szwp-plugin-updatezFound z outdated sites�+Failed to get username for uid=%d. error=%sz#WordPress site no longer exists: %s��versions�r��data_dir�downgraded_by_imunify�updated_by_imunify�r��event�siter�z+Failed to update plugin on site=%s error=%sz.Updated imunify-security wp plugin on %d siteszRUpdate of imunify-security wp plugin was cancelled. Plugin was updated on %d sitesz-Error occurred during plugin update. error=%s)*r�r|r��infor�r	�track�taskr/r�r�rr&r�list�uidr�r��pwd�getpwuidr�r�r�r)�remove_site_if_missingr�is_wordpress_installedr*r$�update_scan_data_file�update_plugin_config_file�
plugin_update�add�get_plugin_versionr�r8�build_with_versionrr�
send_eventrr�CancelledError)r��latest_version�updated�telemetry_coros�outdated_sitesryr��
sites_by_userr�r�r�r�rar�r�r�r��
plugin_config�	scan_datar�r��original_version�is_downgrades                       rB�update_everywherer�s�����.�0�0�N������@�A�A�A���
�K�K�F�����
�e�e�G��O�	�	�	�	�1�	2�	2�R;�R;�Q	;�/��?�?�N��K�K�E��^�!4�!4�E�E�E�F�F�F�!�
��V*�/�:�:�:�:�:�:�:�:�:�eR;�R;�R;�R;�R;�R;�R;�R;�(�>�>�L�9�;�;�;�;�;�;�;�;�H�(��-�-�M�&�
5�
5���d�h�'�.�.�t�4�4�4�4�,�1�1�3�3�k
�k
�
��U�	� #��S� 1� 1�I�(�0�H�H�� �����L�L�E������
�H�H�H�H�����
����2��)�\���������	�"�"�#�!6�h� ?� ?�
�!�U�U�D�3�D�$�?�?�?�?�?�?�?�?�!� �R�%(�%?��%E�%E�E�E�E�E�E�E�%�"�K�K� E�t����%�%6�*�*�$� �+�%-�
%�%�%�	�*D� �)�*�*�$�$�$�$�$�$��
4� �%�&/�%-�	����������8� �)�&/�%-�	����������"�/��5�5�5�5�5�5�5�5�5����D�)�)�)�),�(>�t�(D�(D�"D�"D�"D�"D�"D�"D��"��/3�|�,�0��g�>�>�>�$(�#:�#:�7�#C�#C�D�,8� '�,�,� ,�-=� >� >�,?�L�
,�2�2� )� 4�)-�,8�)B�(?�(?�-A�)-�,3�	!"�	!"�	!"������%�������I� �!�����������������aU�n
�K�K�@��G���
�
�
�
���%�	�	�	��K�K�+��G���
�
�
�
�
�
�	�	�	��L�L�?��
�
�
�
�����		����*�/�:�:�:�:�:�:�:�:�:�:��)�/�:�:�:�:�:�:�:�:�:�:����:�eR;�R;�R;�R;�R;�R;�R;�R;�R;�R;�R;�R;����R;�R;�R;�R;�R;�R;s��9Q�;<N �8Q�A5N �E,�+N �,
F�6F�N �F�A	N �&5M�N �D#M�?N �
M2	�M-	�'N �-M2	�2-N �P� 7P�P�	P�"O>�>P�P�Q�P3�3Q�Q�Qr�c���K�tj|���d{V��}	t|��}nn#t$ra}|jtjkrYd}~dS|jtjtjfvr!t�	d|��Yd}~dS�d}~wwxYw	t|j��5}	tj
t|���d{V��tj|��d}n#tj|��d}wxYwtj|j|���ddd��dS#1swxYwYdS#t&$r|dkrtj|���wxYw)Nz/Skipping rmtree: data directory %s is a symlink�����dir_fdr)r�get_data_dirr�OSError�errno�ENOENT�ELOOP�ENOTDIRr|r�r�parentrr�	to_threadr�os�close�rmdirr=�
BaseException)r�r�r�exc�	parent_fds     rB�delete_plugin_filesrIs������%�d�+�+�+�+�+�+�+�+�H�
�%�h�/�/���������9���$�$��F�F�F�F�F��9���e�m�4�4�4��N�N�A�8�
�
�
�
�F�F�F�F�F�
����������
�h�o�
&�
&�	6�)�
��'�	�6�:�:�:�:�:�:�:�:�:���� � � �������� � � ��������H�X�]�9�5�5�5�5�	6�	6�	6�	6�	6�	6�	6�	6�	6�	6�	6�	6����	6�	6�	6�	6�	6�	6�������Q�;�;��H�V����
����sh�.�
B�B�:B�B�B�D:�1D-�3 C*�D-�*D�D-� D:�-D1�1D:�4D1�5D:�:&E c��K�	tj|���d{V��}|s+t|tj��||���d{V��dStj|���d{V��}tj|���d{V��t
|���d{V��t|��}|�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.
    Nr�uninstalled_by_imunifyr�z"Failed to remove plugin from %s %s)r�is_plugin_installed�process_manually_deleted_plugin�timer��plugin_uninstallrr-r�rr�r�r|r�)r�r�r�is_installedr��affectedr�s       rB�remove_from_single_siter$hsz����#� �4�T�:�:�:�:�:�:�:�:���	�1��d�i�k�k�4����
�
�
�
�
�
�
��1��.�t�4�4�4�4�4�4�4�4���"�4�(�(�(�(�(�(�(�(�(�"�$�'�'�'�'�'�'�'�'�'��t�$�$��	���� ��.���	
�
�
�	
�	
�	
�����������9�4��G�G�G��q�q�q�q�q��������s�AC�BC�
D�C<�<Dc
��K�t�d��g}d}tj�d��5	t��t
��}|D]m}	|tjt|||�����d{V��z
}�0#tj
$r,t�d|t|����Y�jwxYwn.#t$r!}t�
d|���d}~wwxYw	t�d|��t|���d{V��n5#t�d|��t|���d{V��wxYw	ddd��dS#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|r�r	r�r�r�r4rr�shieldr$r�r�r�r�r�)r�rr#�	to_remover�r�s      rB�remove_all_installedr(�s5����
�K�K�5�6�6�6��O��H�	�	�	�	�2�	3�	3�;�;�	;��N�N�N�+�-�-�I�!�
�
��
��g�n�/��d�O�L�L�'�'�!�!�!�!�!�!��H�H���-�����K�K�H� ��I���	���������
���	�	�	��L�L�D�e�L�L�L������	����
� 
�K�K�B��
�
�
�*�/�:�:�:�:�:�:�:�:�:�:��

�K�K�B��
�
�
�*�/�:�:�:�:�:�:�:�:�:�:����:�9;�;�;�;�;�;�;�;�;�;�;�;����;�;�;�;�;�;sl�E2� C�#,B�C�8C�C�
C�C�D/�
C:�C5�5C:�:D/�>1E2�/2E!�!E2�2E6�9E6c��K�	t||��t|���d{V��|�tj|d||j�����dS#t$r'}t�d||��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
    N�removed_by_userr�z>Failed to process manually deleted plugin for site=%s error=%s)	r6rr�rr�r�r�r|r�)r��nowr�rr�s     rBrr�s�����
�%�d�C�0�0�0�"�$�'�'�'�'�'�'�'�'�'�	���� ��'����	
�
�
�	
�	
�	
�	
�	
���
�
�
����L���	
�	
�	
�	
�	
�	
�	
�	
�	
�����
���s�AA�
B�%B�B�freshly_installed_sitesc��dK�g}	t|��}|r0tj��}|D]}t||||���d{V���n2#t$r%}t�d|��Yd}~nd}~wwxYw|rt
|���d{V��dSdS#|rt
|���d{V��wwxYw)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 rr�r|r�r�)r�r,r�to_mark_as_manually_removedr+r�r�s       rB�tidy_up_manually_deletedr/�s>�����O�;�&K�#�'
�'
�#�'�	��)�+�+�C�3�
�
��5��#�t�_��������������F�F�F����=�u�E�E�E�E�E�E�E�E�����F�����	;�)�/�:�:�:�:�:�:�:�:�:�:�:�	;�	;��?�	;�)�/�:�:�:�:�:�:�:�:�:�:�	;���s0�AA�B�
A7�A2�-B�2A7�7B�B/r�c
��NK�|sdSt��}t���d{V��}tt��}|D]"}||j�|���#|���D�]-\}}	tj|��}|j	}n3#t$r&}	t�d||	��Yd}	~	�Nd}	~	wwxYwt|||���d{V��\}
}}t|��}
|D]�}t||���d{V��r�	t!|
|||||���}t#||���d{V��}t%||||����d{V��t'||
||����d{V���x#t$r&}	t�d||	��Yd}	~	��d}	~	wwxYw��/dS)Nr�r�r�z.Failed to update site data on site=%s error=%s)rr&rr�r�r�r�r�r�r�r�r|r�r�r)r�r*r$r�r�)r�r�ryr�rr�r�r�rar�r�r�r�rrr�s                rB�update_data_on_sitesr1�s��������� �>�>�L�1�3�3�3�3�3�3�3�3�H� ��%�%�M��-�-���d�h��&�&�t�,�,�,�,�$�)�)�+�+�2�2�
��U�		���S�)�)�I� �(�H�H���	�	�	��L�L�=���
�
�
�

�H�H�H�H�����
	����*�$�	�<�H�H�H�H�H�H�H�H�		
����-�h�7�7�
��	�	�D�+�D�$�7�7�7�7�7�7�7�7�
��
�-�"�"���#�%�
���	�"<�D�)�!L�!L�L�L�L�L�L�L��,��)�y�8�����������
0��-�9�x��������������
�
�
����D���������������
����5	�'2�2s1�=B�
C	�#C�C	�AE0�0
F �:F�F r��filename�datar�c���K�|�tj|j��}|�t||���d{V��}t	|��}t||z||j|j���dS)aWrite ``data`` as embedded JSON to ``<site data dir>/<filename>``.

    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.
    N�r��gid)r�r�r�r$r%r+�pw_gid)r�r2r3r�r��php_contents      rB�_write_json_php_data_filer9Bs��������L���*�*�	���3�D�)�D�D�D�D�D�D�D�D��/��5�5�K�%��8��[�d�h�I�<L������rArc��>K�t|d|||����d{V��dS)N�
scan_data.phpr��r9)r�rr�r�s    rBr�r�ZsV����$�����������������rArc��>K�t|d|||����d{V��dS)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.phpr�Nr<)r�rr�r�s    rBr�r�jsV����$�����������������rAc��&K�|sdSd}tt��}|D]"}||j�|���#|���D]�\}}	tj|��}|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.
    rr�N)r�r{z6Failed to update plugin_config.php on site=%s error=%s)
rr�r�r�r�r�r�r�r�r|r�r)r�)
r�rrr�r�r�r�rar�rs
          rB�update_plugin_config_on_sitesr@�s����� ���q��G�
.9��->�->�M��-�-���d�h��&�&�t�,�,�,�,�(�.�.�0�0�����Z�		���S�)�)�I� �(�H�H���	�	�	��L�L�=���
�
�
�

�H�H�H�H�����
	����.�h�7�7�
��	�	�D�

�/��-�9������������1������
�
�
����L���������������
����
	��Ns0�A8�8
B(�B#�#B(�?C�
D
�'D�D
�wp_rules_phpr�failedc��K�|j}	t||���d{V��}|dz}t|||j|���|�|��t
�d|j��dS#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+r�r�r|r��docrootr�r�)	r�r�rArrBr6r��
rules_pathr�s	         rB�update_wp_rules_for_siterG�s����"�
�C�
�3�D�)�D�D�D�D�D�D�D�D����+�
�)���$�(��	
�	
�	
�	
�	���D�������2�D�L�A�A�A�A�A���
�
�
��
�
�4�������7��L��	
�	
�	
�	
�	
�	
�	
�	
�	
�����
���s�A(A5�5
C�?6B;�;C�	make_task�	task_name�fingerprint�skip_waf_disabledc���K�t��}t��}tj�|��5	t	j��}tt��}	|D]"}
|	|
j�|
���#g}|	�	��D�]+\}}
	tj|��}|j}nW#t$rJ}td|t|
��||d�dd|���|
D]}
|�|
���Yd}~�rd}~wwxYw|rr	t#|���d{V��}n/#t$r"t$�d|d�	��d}YnwxYw|s*t$�d
|t|
������|
D]:}
t+||
���d{V��r�|�||
|||�����;��-d}t-dt|��|��D]&}||||z�}t/j|d
di��d{V���'t	j��|z
}t$�d|t|��t|��|��nh#t.j$r,t$�d|t|����Yn.t$r"}t$�d||���d}~wwxYwddd��dS#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})r��count�user�reasonr��	wordpress��format_args�level�	componentrJN�BCould not check WAF status for user %s, proceeding with deploymentT��exc_infoz-WAF disabled for user %s, skipping %d site(s)r�r�return_exceptionsz@%s deployment complete. Updated: %d, Failed: %d, Duration: %.2fsz-%s deployment was cancelled. Updated %d sitesz-Error occurred during %s deployment. error=%s)r�r	r�r�r rr�r�r�r�r�r�r�r�rr�r�rvr|r�r�r��rangerrr�r�r�)r�rHrIrJrKr�rrB�
start_timerr�r�r�r�r�rar�rd�max_concurrent�i�batch�elapseds                      rB�_deploy_to_sitesr_�s����0�e�e�G�
�U�U�F�	�	�	�	�y�	)�	)�S�S�R	�����J�'��-�-�M��
5�
5���d�h�'�.�.�t�4�4�4�4��E�#0�#6�#6�#8�#8�-
N�-
N���Z�� #��S� 1� 1�I�(�0�H�H�� �����>�%.�%(��_�_�$'�&+�	%�%�(�"-�$/�
�
�
�
�!+�)�)���
�
�4�(�(�(�(��H�H�H�H�����#����&%�!�	+�,C�H�,M�,M�&M�&M�&M�&M�&M�&M����$�+�+�+����:�$�%)�	'����'+����+����'�!����K�$��
�O�O����
!�&�N�N�D�3�D�$�?�?�?�?�?�?�?�?�!� ��L�L���4��G�V�!L�!L�M�M�M�M�N�
 �N��1�c�%�j�j�.�9�9�
E�
E���a�!�n�"4�4�5���n�e�D�t�D�D�D�D�D�D�D�D�D�D��i�k�k�J�.�G��K�K�#���G����F����

�
�
�
���%�	�	�	��K�K�?���G���
�
�
�
�
�
�	�	�	��L�L�?���
�
�
�

�����
	����[S�S�S�S�S�S�S�S�S�S�S�S����S�S�S�S�S�Ss��K�A(I%�)C�I%�
D�AD�I%�D�I%�D5�4I%�5)E!�I%� E!�!DI%�$K�%8K
�K�	K
�(K�K
�
K�K�!Kc���K�t��t��}|st�d��dS�fd�}t	||ddd|����d{V��dS)zHDeploy pre-formatted wp-rules PHP content to all active WordPress sites.zNo active WordPress sites foundNc�*��t||�||��SrG)rG)r�r�rrBrAs    �rBrHz'_deploy_wp_rules_php.<locals>.make_taskYs ���'��)�\�7�F�
�
�	
rAzwp-ruleszwp-rules-update-skip-userT�rIrJrKr�)r�r4r|r}r_)rAr��installed_sitesrHs`   rB�_deploy_wp_rules_phprdPs�������N�N�N�)�+�+�O������6�7�7�7���
�
�
�
�
�
����/��
�
�����������rA�
is_updatedc��K�tjst�d��dS|st�d��dSt�d��t	|��}|st�d��dSt
|��}||d�}t|��}t|���d{V��dS)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|r�r�r�rr%rd)r�rer�r�r�rAs      rB�update_wp_rules_on_sitesrghs������,�����
�	
�	
�	
�	�������?�@�@�@���
�K�K�A�B�B�B�-�e�4�4�M������C�D�D�D���.�e�4�4��#����L�1��>�>�L�
�|�
,�
,�,�,�,�,�,�,�,�,�,rAc��`K�tjst�d��dSt���rdat�d��dSt4�d{V��	dat�d��t���d{V��}|s.t�d��	ddd���d{V��dSt|���d{V��tsnt�d����	ddd���d{V��dS#1�d{V��swxYwYdS)	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_lock�locked�_redeploy_rules_php_pendingr�r�rd)rAs rB�redeploy_rules_phprl�sS�����,�����
�	
�	
�	
�	���&�&�(�(��&*�#����K�L�L�L���'�P�P�P�P�P�P�P�P�	P�*/�'��K�K�H�
�
�
�"3�!4�!4�4�4�4�4�4�4�L��
����I�J�J�J��P�P�P�P�P�P�P�P�P�P�P�P�P�P�'�|�4�4�4�4�4�4�4�4�4�.�
���K�K�N�O�O�O�!	P�	P�P�P�P�P�P�P�P�P�P�P�P�P�P�P�P�P�P�P�P�P�P�P�P�P����P�P�P�P�P�Ps�/AD�8D�
D'�*D'c��ZK�t���d{V��t���d{V��dS)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)rl�update_disabled_rules_on_sitesr@rArB�redeploy_waf_for_all_sitesro�sJ�����
�
��������
(�
*�
*�*�*�*�*�*�*�*�*�*rArEc�t�	t|��}n�#t$rYdSt$rg}|jtjtjfvr!t�d|��Yd}~dSt�d||��Yd}~dSd}~wwxYw	dD]t}	tj
||���t�d||���6#t$rY�Bt$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)rD�disabled-rules.phprz!Removed %s from %s (WAF disabled)zFailed to remove %s from %s: %s)
r�FileNotFoundErrorrrrrr|r�r�r�remover�r)r�rErrr2r�s      rB�_remove_waf_files_for_dirrt�s����%�h�/�/����������������9���e�m�4�4�4��N�N�E��
�
�
�
�F�F�F�F�F����9�7�C�H�H�H����������������;�	�	�H�

��	�(�6�2�2�2�2����7��7������%�
�
�
����
�
�
����5�x��!�������������
����	�	������������������sa��
B�	B�:B
�(B
�
B�D!�2C�
D!�
D�D!�	D� D�=D!�D�D!�!D7c��K�|D]|}	tj|���d{V��}n8#t$r+}t�d|j|��Yd}~�Nd}~wwxYwt
jt||j���d{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
r�r|r�rErrrrt)r�r�r�r�s    rB�_remove_waf_files_from_sitesrv�s������

�

��	� �-�d�3�3�3�3�3�3�3�3�H�H���	�	�	��L�L�7���q�
�
�
�
�H�H�H�H�����		����
��%�x���
�
�	
�	
�	
�	
�	
�	
�	
�	
�

�

s�#�
A�!A�Ac
��t�K�tjsdStj��}	|�dt
j|���d{V���n,#t$rt�	d|��YdSwxYw|�dt���d{V��}�fd�|D��}|sdSt��}t��}t���d{V��}|r|D]}t|�|||���d{V���nt�	d��tj��}t��}	t��}
|D]}t|�||	|
���d{V���t�d|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 redeployc�4��g|]}|j�jk�|��Sr@�r��pw_uid�r��sr�s  �rBr�z)redeploy_waf_for_user.<locals>.<listcomp>�(���@�@�@��a�e�y�/?�&?�&?�!�&?�&?�&?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)rrSrrrsrtr��getpwnamrJr|r�r4r�r�rGr �update_disabled_rules_for_siter�r�)rarur�r�rrBrAr��disabled_rules_ts�
dr_updated�	dr_failedr�s           @rB�redeploy_waf_for_userr�sE������,�����#�%�%�D���.�.�t�S�\�8�L�L�L�L�L�L�L�L�	�	���������<�h�	
�	
�	
�	���	�����&�&�t�-@�A�A�A�A�A�A�A�A�E�@�@�@�@�U�@�@�@�J������e�e�G�
�U�U�F�*�,�,�,�,�,�,�,�,�L��D��	�	�D�*��i��w����
�
�
�
�
�
�
�
�	�
	���B�C�C�C��	�������J����I��
�
��,��)�.�
�I�
�
�	
�	
�	
�	
�	
�	
�	
�	
��K�K�	*���G����F����J����I�������s�'A�%A7�6A7c��d�K�tj��}	|�dtj|���d{V���n,#t
$rt�d|��YdSwxYw|�dt���d{V��}�fd�|D��}t|���d{V��dS)z4Remove WAF files from all sites belonging to a user.Nz-User %s not found, skipping WAF rules removalc�4��g|]}|j�jk�|��Sr@ryr{s  �rBr�z-remove_waf_rules_for_user.<locals>.<listcomp>Dr}rA)
rrrsrtr�r~rJr|r�r4rv)rarur�r�r�s    @rB�remove_waf_rules_for_userr�8s�������#�%�%�D���.�.�t�S�\�8�L�L�L�L�L�L�L�L�	�	���������;�X�	
�	
�	
�	���	�����&�&�t�-@�A�A�A�A�A�A�A�A�E�@�@�@�@�U�@�@�@�J�
&�z�
2�
2�2�2�2�2�2�2�2�2�2s�'A�%A)�(A)c���K�tj��}|�dt���d{V��}t�dt
|����t|���d{V��dS)z?Remove WAF files from all installed sites (global WAF disable).Nz7Global WAF disabled, removing WAF files from %d site(s))rrrsrtr4r|r�r�rv)rur�s  rB�remove_waf_rules_for_all_sitesr�Hs������#�%�%�D��&�&�t�-@�A�A�A�A�A�A�A�A�E�
�K�K�A��E�
�
����'�u�
-�
-�-�-�-�-�-�-�-�-�-rAc��6K�tjsdSt���rdat
�d��dSt4�d{V��	dat��s	ddd���d{V��dSt��}tj
������d{V��}tj
��}|D]�}	|�dt|���d{V��r�&|rt!|���d{V��nt#|���d{V���T#t$$r&}t
�d||��Yd}~�d}~wwxYwtsnt
�d����	ddd���d{V��dS#1�d{V��swxYwYdS)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|r�rLrQr�HostingPanel�	get_usersrrrsrtrxr�r�r�r�)�new_default�	usernamesrurar�s     rB�apply_waf_default_changer�Ss�����,�����%�%�'�'��%)�"����H�I�I�I���&� 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�:�:�D�D�F�F�F�F�F�F�F�F�I��+�-�-�D�%�
�
���!�1�1��<� ���������!�
!�"�B�3�H�=�=�=�=�=�=�=�=�=�=�7��A�A�A�A�A�A�A�A�A��� �����N�N�L� ������������������.�
���K�K�K�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� MsO�F�;AF�"D�.F�/-D�F�
E
�'E�F�E
�
'F�
F�F�domain�	timestampc�p�tj|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�)r�get_domain_disabled�sortedr%)r�r��disabled_rule_idsr3s    rB�generate_disabled_rules_phpr��sN��'�:��u�������)�*�*���D�)��.�.�.rAc��JK�|j}	t||���d{V��}|dz}t|j|��}t	|||j|���t
j|����tj	|j	k���
��|�|��t�
d|j	��dS#t$rA}	|�|��t�d|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
    Nrqr5��disabled_rules_sync_tsz"Updated disabled-rules for site %sz/Failed to update disabled-rules for site %s: %s)r7r$r�r�r+r�rr��whererE�executer�r|r�r�r�)
r�r�r�rrBr6r��disabled_rules_pathr8r�s
          rBrr�sO����"�
�C�
�3�D�)�D�D�D�D�D�D�D�D��&�)=�=��1�$�+�y�I�I��)���$�(��	
�	
�	
�	
�	��I�>�>�>�D�D��!�T�\�1�	
�	
�
�'�)�)�)����D�������8�$�,�G�G�G�G�G���
�
�
��
�
�4�������=��L��	
�	
�	
�	
�	
�	
�	
�	
�	
�����
���s�C
C�
D"�!6D�D"�domainsc��bK�tjst�d��dSt�d��t	��|rt|��}nt
��}|st�d��dSd�}t||ddd|�	���d{V��dS)
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 deploymentc�J�t||tj��||��SrG)rr )r�r�rrBs    rBrHz1update_disabled_rules_on_sites.<locals>.make_task�s%��-��)�T�Y�[�[�'�6�
�
�	
rAzdisabled-ruleszdisabled-rules-update-skip-userTrb)rrSr|r�r�r.r4r_)r�r�r�rHs    rBrnrn�s������,�����
�	
�	
�	
�	��
�K�K�G�H�H�H��N�N�N��&�.�w�7�7���#�%�%�������L�M�M�M���
�
�
�
�
��"�5��
�
�����������rAc
��K�t�d��t��}t��}tj�d��5	t
��t��}|s(t�d��	ddd��dStt��}|D]"}||j
�|���#g}|���D]�\}}	tj|��}	n<#t$r/}
t!dt#|��||
d�ddd	�
��Yd}
~
�Od}
~
wwxYw|D]@}t%||���d{V��r�t'||	||��}|�|���A��d}t)dt#|��|��D]&}
||
|
|z�}t+j|d
di��d{V���'t�dt#|��t#|����nf#t*j$r+t�dt#|����Yn-t$r!}
t�d|
���d}
~
wwxYwddd��dS#1swxYwYdS)z7Update auth.php files for all existing WordPress sites.z4Updating auth.php files for existing WordPress siteszwp-auth-updatez"No installed WordPress sites foundNz�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}�rMrNrOr�rPzwp-plugin-auth-update-skip-userrQr�rrXTz8Updated 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|r�r�r	r�r�r�r4rr�r�r�r�r�r�r�rr�r��update_site_authrYrrr�r�r�)r�rrBrcrr�r�r�r�r�r�r�r[r\r]s               rB�update_auth_everywherer��s�����
�K�K�F�G�G�G��e�e�G�
�U�U�F�	�	�	�	�/�	0�	0�@�@�?	��N�N�N�2�3�3�O�"�
����@�A�A�A��@�@�@�@�@�@�@�@�(��-�-�M�'�
5�
5���d�h�'�.�.�t�4�4�4�4��E�+�1�1�3�3�
'�
'�
��U�� #��S� 1� 1�I�I�� �����D�
&)��Z�Z�$'�&+�%�%�
(�"-�$E�
�
�
�
��H�H�H�H���������""�'�'�D�3�D�$�?�?�?�?�?�?�?�?�!� �+�D�)�W�f�M�M�D��L�L��&�&�&�&�	'� �N��1�c�%�j�j�.�9�9�
E�
E���a�!�n�"4�4�5���n�e�D�t�D�D�D�D�D�D�D�D�D�D��K�K�J��G����F���
�
�
�
���%�	�	�	��K�K�(��G���
�
�
�
�
�
�	�	�	��L�L�F��N�N�N������	����}@�@�@�@�@�@�@�@�@�@�@�@����@�@�@�@�@�@st�I;�8H� AH�5D
�	H�

E�%D>�9H�>E�CH�I;�7I+�?I;�	I+�
I&�&I+�+I;�;I?�I?c���K�	t||���d{V��|�|��dS#t$r<}|�|��t�d||��Yd}~dSd}~wwxYw)z/Process authentication setup for a single site.Nz*Failed to update auth for site=%s error=%s)r9r�r�r|r�)r�r�rrBr�s     rBr�r�>s�����	
�'��i�8�8�8�8�8�8�8�8�8����D��������
�
�
��
�
�4�������8���	
�	
�	
�	
�	
�	
�	
�	
�	
�����
���s�+1�
A7�1A2�2A7c��.K�tj�|j��rdSt	|��}|dkr&|�#tj|d||j����d{V��n1t�	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_removedr�z@Failed to delete missing site %s from database, no rows affectedz2Failed to delete missing site {site} from databaser�r�rPzwp-plugin-site-delete-failedrQT)rr��isdirrEr-rr�r�r|r�r)r�r��rows_deleteds   rBr�r�Ls�����
�w�}�}�T�\�"�"���u��t�$�$�L��a������&��$����	���
�
�
�
�
�
�
��	���N��	
�	
�	
�
	�@�����!�6�	
�	
�	
�	
��4rA�file_permissionsc��K�	tj|���d{V��}	t|��}nC#t$r6}|jtjtjtjfvrYd}~dS�d}~wwxYw	tj	|��j
dz}|dkrtj|d��dD]�}	t||���5}tj
|��}|j
dz|krtj||��ddd��n#1swxYwY�d#t$rY�pt$r<}|jtjkr!t�d||��Yd}~���d}~wwxYw	tj|��n#tj|��wxYwdS#t$$r'}	t�d	||	��Yd}	~	dSd}	~	wwxYw)
z6Fix data file permissions for a single WordPress site.NFi�i�)r;r>zauth.phprDrqrz"Skipping chmod: %s/%s is a symlinkTz.Failed to fix permissions for site=%s error=%s)rr
rrrrrrr�stat�st_mode�chmodr�fstatrrr|r�rr�r�)
r�r�r�rr�current_dir_mode�	file_name�file_fd�str�s
          rB�fix_site_data_file_permissionsr�us{����1��)�$�/�/�/�/�/�/�/�/��	�)�(�3�3�F�F���	�	�	��y�U�\�5�;��
�F�F�F��u�u�u�u�u������	����
	�!�w�v���6��>���5�(�(�����'�'�'��
�
�	��&�y��@�@�@�@�G��X�g�.�.���:��-�1A�A�A��H�W�.>�?�?�?�@�@�@�@�@�@�@�@�@�@�@����@�@�@�@���)�����H������y�E�K�/�/����@�$�%����
!��������������
�0
�H�V������B�H�V���������t���������<���	
�	
�	
�
�u�u�u�u�u�����
���s��F�/�F�
A/�*A*�#F�)A*�*A/�/F�3;E:�/D�8D�8D�D	�D�D	�D�E:�
E!�E:�	E!�%1E�E:�E�E!�!E:�%F�:F�F�
G�G�Gc��\K�t��}t��}tj�d��5	t	��t��}|s	ddd��dSddlm}ddlm	}|��j
|j
krdnd}|D]\}t||���d{V��r�t||���d{V��}|r|�
|���G|�
|���]t�dt!|��t!|����nj#t"j$r+t�d	t!|����Yn1t&$r%}	t�d
|	��Yd}	~	nd}	~	wwxYwddd��dS#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)r�r	r�r�r�r4�+defence360agent.subsys.panels.hosting_panelr��#defence360agent.subsys.panels.pleskr��NAMEr�r�r�r|r�r�rrr�r�r�)
r��fixedrBrcr�r�r�r��successr�s
          rB�$fix_data_file_permissions_everywherer��s�����
�E�E�E�
�U�U�F�	�	�	�	�:�	;�	;�0�0�/	��N�N�N�2�3�3�O�"�
��0�0�0�0�0�0�0�0�
�
�
�
�
�
�
B�A�A�A�A�A�&����,��
�:�:����
�
(�

%�

%��/��d�;�;�;�;�;�;�;�;��� >��*�!�!���������%��I�I�d�O�O�O�O��J�J�t�$�$�$�$��K�K���E�
�
��F���	
�
�
�
���%�	�	�	��K�K�&��E�
�
�
�
�
�
�
�
�	�	�	��L�L�C�U�
�
�
�
�
�
�
�
�����	����[0�0�0�0�0�0�0�0�0�0�0�0����0�0�0�0�0�0sN�F!�D*�,B=D*�)F!�*7F�!F!�#	F�,F�F!�F�F!�!F%�(F%c�d�eZdZdZdZdZdZdZdddd	d
ddd
�Zd�Z	d�Z
d�Zd�Zde
ddfd�Zd�ZdS)r�z�
    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.
    T�installed_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}z�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}��start�complete�foundr��	cancelled�	exception�	skip_userc�H�||_||_t��|_t��|_t��|_t��|_t��|_t��|_d|_	t��|_
d|_dSrG)r�r�r��	processed�
authenticated�rules_installed�failed_rules_updates�disabled_rules_installed�failed_disabled_rules_updatesr��failed_auth�
_current_site)�selfr�r�s   rB�__init__zWordPressSiteInstaller.__init__
sy����	���
������ �U�U���"�u�u���$'�E�E��!�(+����%�-0�U�U��*�/3����5�5���,0����rAc��K�tj|���d{V��}|s3t�d|��t	dd|iddd���d	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}r�r�rPzwp-plugin-cli-not-accessiblerQFT)rr�r|r�r)r�r�r�s   rB�
is_site_readyz$WordPressSiteInstaller.is_site_readys�����(+�'A�$�'G�'G�!G�!G�!G�!G�!G�!G��%�	��N�N�H��
�
�
�
�L�#�T�N��%�:�
�
�
�
��5��trAc��|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)r�r�r5�_stamp_disabled_rules_sync_ts�r�r�r�s   rB�_record_processed_sitez-WordPressSiteInstaller._record_processed_site3sD��	
����4� � � ���v�&�&�&��*�*�4�0�0�0�0�0rAc��K�|j}d|_	t|���d{V��n3#t$r&}t�d||��Yd}~nd}~wwxYw|jrt
j|���d{V��dSdS)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)r�rr�r|r��install_pluginr�try_plugin_uninstall)r�r�r�s   rB�_revert_in_flight_sitez-WordPressSiteInstaller._revert_in_flight_siteEs������!��!���	�%�d�+�+�+�+�+�+�+�+�+�+���	�	�	��N�N�G���
�
�
�
�
�
�
�
�����	������	1��*�4�0�0�0�0�0�0�0�0�0�0�0�	1�	1s�(�
A�A�Ar�rENc���||jvrdS|j�dStj|j����tj|jk�����dS)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�)r�r�rr�r�rEr�)r�r�s  rBr�z4WordPressSiteInstaller._stamp_disabled_rules_sync_tsYse���t�4�4�4��F��!�)��F���#'�#9�	
�	
�	
�
�%�
�%���5�
6�
6�w�w�y�y�y�y�yrAc
��K�t�|jd��g}tj�|j��5	t��|js{t�d��|j	|rLtj|ddi��d{V��}|D]2}t|t��rt�d|���3cddd��St�|jd�t!|j�������t#��}t%���d{V��}t'j��|_t+���d{V��}t-t.��}|jD]"}||j�|���#|���D�]B\}	}
	t7j|	��}|j}nL#t$r?}
t=|jd	t!|
��|	|
d
�dd|j�
��Yd}
~
�gd}
~
wwxYw	tA|���d{V��}n/#t$r"t�d|d���d}YnwxYw|s)t�d|t!|
����tC|j"||���d{V��\}}}tG|��}|
D�]/}tI|j"|���d{V��r�	|�%|���d{V��s�<||_&tO||||||���}tQ||���d{V��}tS||||����d{V��tU||||����d{V��tW|||j,|j-���d{V��|r%|r#t]||||j/|j0���d{V��|r(tc|||j|j2|j3���d{V��|j4rtkj6|���d{V��tkj7|���d{V��}|rtqj9||��}|�:||��d|_&|�tj;tyj=|j"|j>||����������#t$rY}
d|_&t�?|jd�|t�|
�������Yd}
~
��)d}
~
wwxYw��Dt�|jd�t!|j	�������|j-r-t�dt!|j-����|j0r-t�dt!|j0����|j3r-t�dt!|j3����n�#tjA$ro|j&r|�B���d{V��t�|jd�t!|j	�������YnXt$rL}
t�?|jd�t�|
��������d}
~
wwxYw|rLtj|ddi��d{V��}|D]2}t|t��rt�d|���3nT#|rLtj|ddi��d{V��}|D]3}t|t��rt�d|���3wwxYwddd��n#1swxYwY|j	S)z�
        Process WordPress sites for imunify-security plugin operations.

        Returns:
            set: The set of successfully processed sites.
        r�z'No WordPress sites found, nothing to dorXTNzFailed to send telemetry: %sr�)rMr�r�r�rPrQrUrVzFWAF disabled for user %s, skipping WAF rules deployment for %d site(s)r�r�r�r�)r�r�r�zFailed to authenticate %d sitesz&Failed to install wp-rules on %d sitesz,Failed to install disabled-rules on %d sitesr�r�)r�)Cr|r��messagesr	r�r�rIr�r�r�rrr��
isinstancer�r��formatr�rr�r r�r&rr�r�r�r�r�r�r�r�log_fingerprint_skip_userrvr�r�r)r�r�r�r*r$r�r�r�r�r�rGr�r�rr�r�r�r�plugin_installr�rr�r�r�rr��telemetry_eventr��reprr�r�)r��telemetry_tasks�resultsr�ryrAr�rr�r�r�r�rar�rdr�r�r�rrr�r�s                      rBr�zWordPressSiteInstaller.runhs�
����	���D�M�'�*�+�+�+���
�
�
"�
"�4�>�
2�
2�W	�W	�V
������z�*��K�K� I�J�J�J��>�R#��$+�N�(�%�<@�%�%�������G�#*����%�f�i�8�8��"�N�N� >������kW	�W	�W	�W	�W	�W	�W	�W	�����M�'�*�1�1��D�J���1�H�H����
 ,�~�~��&7�%8�%8�8�8�8�8�8�8��*.�����&�!=�!?�!?�?�?�?�?�?�?��!,�D� 1� 1�
� �J�9�9�D�!�$�(�+�2�2�4�8�8�8�8�#0�"5�"5�"7�"7�O�O�J�C��!�$'�L��$5�$5�	�#,�#4����$�!�!�!�#� �M�+�6�),�U���(+�*/�)�)�
#,�&1�(,�(F�
�
�
�
�!���������!����	+�,C�H�,M�,M�&M�&M�&M�&M�&M�&M����$�+�+�+����:�$�%)�	'����'+����+����'�����?�$���J�J�	���6��	�9�l���������	�&�&�'�%:�(�$C�$C�M� %�b�b��!7��	�4�!H�!H�H�H�H�H�H�H�%�$�^�)-�);�);�D�)A�)A�#A�#A�#A�#A�#A�#A�)� (�15�D�.�):� .� .� (� $� /�)1�
)�)�)�I�.H� $�i�.�.�(�(�(�(�(�(�H�
#8� $� )�*3�)1�	#�#�#��������#<� $� -�*3�)1�	#�#�#��������#3� $� )� $� 2� $� 0�	#�#�������� ,�"��"�&>�$(�$-�$0�$(�$8�$(�$=�'"�'"�!"�!"�!"�!"�!"�!"�!"� +�"�&D�$(�$-�$(�$:�$(�$A�$(�$F�'"�'"�!"�!"�!"�!"�!"�!"�!"� $�2�?�&)�&8��&>�&>� >� >� >� >� >� >� >�-0�,B�4�,H�,H�&H�&H�&H�&H�&H�&H�G�&�P�'-�'@��w�'O�'O��!�7�7��g�F�F�F�15�D�.�+�2�2� '� 3�$-�$8�-1�Y�.2�.B�-1�07�	%&�%&�%&�!"�!"�	�	�	�	�� )����15�D�.�"�L�L� $�
�g� 6� =� =�)-�T�%�[�[�!>�!"�!"�����������������yb�F����M�*�-�4�4�3�t�~�;N�;N�4�O�O�����#���N�N�9��D�,�-�-�����,���N�N�@��D�5�6�6�����5���N�N�F��D�>�?�?������
�)�
�
�
��%�8��5�5�7�7�7�7�7�7�7�7�7�����M�+�.�5�5�!�$�.�1�1�6��������
�
�
�
�����M�+�.�5�5�D��K�K�5�H�H���������	
����#��$+�N�(�%�<@�%�%�������G�#*����%�f�i�8�8��"�N�N� >��������
#��$+�N�(�%�<@�%�%�������G�#*����%�f�i�8�8��"�N�N� >������
�����gW	�W	�W	�W	�W	�W	�W	�W	�W	�W	�W	����W	�W	�W	�W	�r�~�s��];�5W7�A];� C1W7�G.�-W7�.
H7�85H2�-W7�2H7�7W7�;I�W7�)I=�:W7�<I=�=A>W7�<R'�W7�F
R'�%W7�'
T
	�1AT	�?W7�T
	�
C,W7�6\�7A;[	�2\�4	[	�=A[�[	�	\�A];�A],�,];�;]?�]?)�__name__�
__module__�__qualname__�__doc__r�r�rIr�r�r�r�r�r�rr�r�r@rArBr�r��s����������N�,�O�(�I� =��8�K�9�H�
5�
G�
7���H�(1�1�1����81�1�1�$1�1�1�(
A�&�
A�T�
A�
A�
A�
A�c�c�c�c�crAr�c�V��eZdZdZdZdZdZdZdddd	d
ddd
�Z�fd�Z	d�Z
�fd�Z�xZS)r�z�
    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}z�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}r�c���t���||��d�tjtj��D��|_dS)Nc��h|]	}|j��
Sr@)rE)r��rs  rB�	<setcomp>z0WordPressSiteAdopter.__init__.<locals>.<setcomp>os'��"
�"
�"
��A�I�"
�"
�"
rA)�superr�r�selectrE�existing_docroots)r�r�r��	__class__s   �rBr�zWordPressSiteAdopter.__init__lsR���
������u�%�%�%�"
�"
�,�3�M�4I�J�J�"
�"
�"
����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)	r�r�rEr�r,r7r8r5r�r�s   rBr�z+WordPressSiteAdopter._record_processed_sitess���	
����4� � � ��<�4�1�1�1�'��-�-�-� ��&�&�&��
3�#�D�'�2�2�2��"�D�6�*�*�*��*�*�4�0�0�0�0�0rAc����K�t���|���d{V��sdStj|���d{V��}|st�d|��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)r�r�rrr|r�)r�r�r"r�s   �rBr�z"WordPressSiteAdopter.is_site_ready�s�������W�W�*�*�4�0�0�0�0�0�0�0�0�	��5�!�4�T�:�:�:�:�:�:�:�:���	��N�N�D��
�
�
��5��trA)
r�r�r�r�r�r�rIr�r�r�r�r��
__classcell__)r�s@rBr�r�Ns�����������N�"�O�$�I� ;��6�I�5�F�
3�L�
7���H�$
�
�
�
�
�1�1�1�.��������rAr�)r�rG)FN)rEN)NN)�rrr�loggingrr�r �collectionsr�collections.abcrr�distutils.versionr�	functoolsr�pathlibr�defence360agent.apir	� defence360agent.contracts.configr
r~rrr
r�defence360agent.filesrr�defence360agent.sentryr�defence360agent.utilsr�defence360agent.utils.fd_opsrrrrr�defence360agent.subsys.panelsr�"defence360agent.wordpress.wp_rulesrr�defence360agent.model.wordpressrr�&defence360agent.model.wp_disabled_ruler�defence360agent.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�	getLoggerr�r|rCrK�Lockr�r�rWr[rHrLrQrTrX�strr]rgrirf�tupler`rlrprvrx�COMPONENTS_DB_PATHr��dictr�r��
struct_passwdr�r�r�r�r�r�r�r�r�r�rr�intr$r(rr�r/r1r9r�r�r@rGr_rdrgrirkrlrortrvr�r�r�r��floatr�rrnr�r�r�r�r�r�r�r@rArB�<module>rs��������������	�	�	�	�
�
�
�
�����#�#�#�#�#�#�/�/�/�/�/�/�/�/�*�*�*�*�*�*�������������*�*�*�*�*�*���������������2�1�1�1�1�1�1�1�.�.�.�.�.�.�*�*�*�*�*�*�������������7�6�6�6�6�6�7�7�7�7�7�7���������B�A�A�A�A�A�A�A�A�A�A�A�A�A�4�4�4�4�4�4�4�4�C�C�C�C�C�C��������������������������������������������������������� K�J�J�J�J�J�	��	�8�	$�	$����������&�'�,�.�.��"��#��$.�!�$��$�$�$�$�$�$�$�$�$�$��d�����*�t�*�*�*�*�!�c�!�!�!�!�"�� ��-���U�4��t�#3�4�����,�#�,�%��c�	�:J�,�,�,�,��C��D�����
�C��D�����#�3�#�4�#�#�#�#��T�J����
<��<�<�<�<�<�<�"�U�"�t�d�{�"�"�"�"�J&�&�&�
�t�
��(9�
�t�
�
�
�
�  ;��&� ;�6B� ;� ;� ;� ;�F8��0A�8�8�8�8�:�:�d�:�:�:�:�&7�7�7�B!�!�!�
!�
!�
!� �3�����`;�`;�`;�F�F�����>)��)�#�)�)�)�)�X";�";�";�J$
�$
�$
�P26�;�;�#&�v�;�;�;�;�;�<A�D��L�A�A�A�A�R+/� �
���
������
� �4�'���T�k�
�
�����8+/� �
�
�
�
�
��
�� �4�'�	
�
�T�k�
�
�
�
�(+/� ����
����� �4�'�	�
�T�k��
�
����.7�t�F�|�7��7�7�7�7�t!
�
�!
�� �!
��!
��	!
�

�!
�
�
!
�!
�!
�!
�V$�	
�n�n���<�n��	��"�C��-�y���>��n�
�n��
n��n�
�n�n�n�n�b��S�������0#-�%�#-�T�#-�d�#-�#-�#-�#-�L(�7�<�>�>��$��*P�*P�*P�*P�Z	+�	+�	+�	+���������@
�d�6�l�
�t�
�
�
�
�(4�#�4�$�4�4�4�4�n
3�c�
3�d�
3�
3�
3�
3� .�.�.�.�,M�,M�,M�,M�^/��/��/�#�/�/�/�/�0%
�
�%
�� �%
��%
��	%
�

�%
�
�
%
�%
�%
�%
�R!%�	
�/�/�
�#�Y��
�/�
�/�/�/�/�dG�G�G�G�T
�
�
�&�V�&��&�&�&�&�R5�
�5�$'�5�	�5�5�5�5�p:�:�:�za�a�a�a�a�a�a�a�HR�R�R�R�R�1�R�R�R�R�RrAdefence360agent/wordpress/__pycache__/plugin.cpython-311.pyc0000644000000000000000000026057200000000000021022 0ustar  �

M��"�
���ddlZddlZddlZddlZddlZddlZddlmZddlm	Z	m
Z
ddlmZddl
mZddlmZddlmZddlmZmZmZmZmZdd	lmZmZdd
lmZddlm 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;m<Z<m=Z=m>Z>m?Z?m@Z@mAZAddlBmCZCmDZDmEZEmFZFmGZGmHZHmIZImJZJmKZKmLZLmMZMmNZNmOZOddlPmQZQejReS��ZTed���ZUdZVejW��ZXdaYdZZdZ[de\fd�Z]de\fd�Z^de\fd�Z_de\fd�Z`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/�Zpd0end1ejqdenfd2�Zrd1ejqd+efd3�Zsd4ejtfd5�Zudvd7evfd8�Zwd9�Zxd:�Zyd;�Zzdeafd<�Z{d=�Z|d>e.fd?�Z}d>e.de~fd@�ZdA�Z�dB�Z�	dwdCe�e.fdD�Z�dEeve.fdF�Z�dddG�d>e.dHeadIend1ejqdzdJedzddfdK�Z�dddG�d>e.dLend1ejqdzdJedzfdM�Z�dddG�d>e.dNend1ejqdzdJedzddf
dO�Z�dEeve.de~fdP�Z�d>e.d1ejqdQeadRe�dSe�ddfdT�Z�		dxdEeve.dUe
e.ejqe�e�ge	dfdVeadWeadXe\ddfdY�Z�dwdQeaddfdZ�Z�d-ed[e\ddfd\�Z�ejW��Z�da�dyd]�Z�dyd^�Z�d_eaddfd`�Z�dEeve.ddfda�Z�d%eaddfdb�Z�d%eaddfdc�Z�dydd�Z�dyde�Z�dfeadge�deafdh�Z�d>e.d1ejqdge�dRe�dSe�ddfdi�Z�		dzdjeveadzddfdk�Z�dwdl�Z�dm�Z�d>e.de\fdn�Z�d>e.doe~de\fdp�Z�dq�Z�Gdr�ds��Z�Gdt�due���Z�dS){�N)�defaultdict)�	Awaitable�Callable)�LooseVersion)�cache)�Path)�
inactivity)�MalwareScanScheduleInterval�SystemConfig�ANTIVIRUS_MODE�UserType�choose_value_from_config)�Index�WP_RULES)�log_message)�importer)�open_dir_no_symlinks�
open_nofollow�	rmtree_fd�safe_dir)�	Wordpress)�
hosting_panel)�get_wp_rules_data�get_wp_ruleset_version)�
WordpressSite�WPSite)�WPDisabledRule)�cli�	telemetry)�PLUGIN_VERSION_FILE)�_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)
�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)�setup_site_authenticationc�0�tjddd���S)Nz(imav.malwarelib.plugins.schedule_watcher�get_user_schedule_config)�module�name�default)r�get���U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/plugin.py�_get_user_schedule_config_imavrCKs$���<�9�
'�����rATF�balanced�returnc�d�	ttj��S#t$r
tcYSwxYw�N)�boolr�WAF_ENABLED�KeyError�_LEGACY_WAF_FALLBACKr@rArB�_get_global_waf_enabledrLc�?��$��I�)�*�*�*���$�$�$�#�#�#�#�$������/�/c�d�	ttj��S#t$r
tcYSwxYwrG)rHr�WAF_DEFAULTrJrKr@rArB�_get_waf_defaultrQjrMrNc�X�	ttj��S#t$rYdSwxYw)NF)rHr�SECURITY_PLUGIN_ENABLEDrJr@rArB�_get_security_plugin_enabledrTqs:��
��I�5�6�6�6�������u�u����s��
)�)c�d�	ttj��S#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.
    )rHr�AI_BOT_PROTECTIONrJ�_AI_BOT_PROTECTION_DEFAULTr@rArB�_get_global_ai_bot_protectionrX|s?��*��I�/�0�0�0���*�*�*�)�)�)�)�*���rNc�j�	tj}n#t$r
tcYSwxYwt	|��S)u�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.
    )r�AI_BOT_PROTECTION_PRESETrJ�!_AI_BOT_PROTECTION_PRESET_DEFAULTr!)�raws rB�$_get_global_ai_bot_protection_presetr]�sF��1��0�����1�1�1�0�0�0�0�1�����C� � � s��#�#r>�overridezglobal kill switchc�T�t��t��t��fS)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@rArB�waf_global_snapshotr`�s)��	%�&�&��!�!�����rA�usernamec��t��s	dtfS	tdd|���\}}n%#t$rt	��t
fcYSwxYw|tjkrt	��t
fSt|��tfS)NF�	WORDPRESS�waf_enabled�ra)
rL�WAF_SOURCE_KILL_SWITCHrrJrQ�WAF_SOURCE_DEFAULTr
�ROOTrH�WAF_SOURCE_OVERRIDE)ra�value�sources   rB�#waf_status_and_source_for_user_syncrl�s���"�$�$�-��,�,�,�6�0����
�
�
�
��v�v���6�6�6��!�!�#5�5�5�5�5�6����
������!�!�#5�5�5���;�;�+�+�+s�/�A�Ac�*�t|��\}}|SrG)rl)ra�enabled�_s   rB�_is_waf_enabled_for_user_syncrp�s��4�X�>�>�J�G�Q��NrAc��pK�tj��}|�dt|���d{V��S)u3Async wrapper — runs config file I/O in executor.N)�asyncio�get_running_loop�run_in_executorrp)ra�loops  rB�is_waf_enabled_for_userrv�sR�����#�%�%�D��%�%��+�X���������rAc�r�	tdd|���\}}n#t$rYdSwxYw|tjkS)NrcrdreF)rrJr
rh)rarorks   rB�$_user_has_explicit_waf_override_syncrx�sY���,����
�
�
�	��6�6�������u�u������X�]�"�"s��
&�&zD/var/lib/cloudlinux-app-version-detector/components_versions.sqlite3�admin_configc��t��}|�*t�d��tjdddfS|||��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�)rC�logger�debug�Interval�NONE)raryr;s   rB�_get_user_schedule_configr��sU�� >�?�?���'����N�	
�	
�	
��}�a��A�%�%�#�#�H�l�;�;�;rA�indexc���t|��}|�dStr|���D]
\}}d|d<�tt	j������r �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.
    N�pass�modec�$��i|]\}}|�v�	||��
Sr@r@)�.0�cve�params�globally_disableds   �rB�
<dictcomp>z-get_updated_wp_rules_data.<locals>.<dictcomp>s5���
�
�
���V��+�+�+�
��+�+�+rA)rr�items�setr�get_global_disabled)r��
rules_datar�r�r�s    @rB�get_updated_wp_rules_datar��s���� #�5�)�)�J����t��$�%�+�+�-�-�	$�	$�K�C��#�F�6�N�N��N�>�@�@�A�A���
�
�
�
�
�)�/�/�1�1�
�
�
�
��rAc�H�t��tj��dS)z#Clear all WordPress-related caches.N)r#r�clear_get_content_dir_cacher@rArB�clear_cachesr�s#��(�*�*�*��#�%�%�%�%�%rAr��	user_infoc�����t|��}d�|D��}|D]D���fd�|D��}|r1t|t���}||�����E|S)Nc��i|]}|g��Sr@r@)r��paths  rBr�zsite_search.<locals>.<dictcomp>s��
.�
.�
.�4�d�B�
.�
.�
.rAc�,��g|]}��|���|��Sr@r@)r�r��item�matchers  ��rB�
<listcomp>zsite_search.<locals>.<listcomp>s*���M�M�M�4����t�9L�9L�M�$�M�M�MrA)�key)r0�max�len�append)r�r�r��
user_sites�result�matching_sites�most_specific_siter�s  `    @rB�site_searchr�s�����#�I�.�.�J�
.�
.�:�
.�
.�
.�F��4�4��M�M�M�M�M�:�M�M�M���	4�!$�^��!=�!=�!=���%�&�-�-�d�3�3�3���MrAc��:K�t||j���d{V��}|�dd��}t|j|��\}}}}d}	|tjkrt
||||��}	t|j��}
t|
|d���}||	|fS)N�	scan_datec�P�|ddko|d�|��S)N�
resource_type�file)�
startswith)r�r�s  rB�<lambda>z)_get_scan_data_for_user.<locals>.<lambda>>s-��4��0�F�:�*���L�#�#�D�)�)�rA)	r'�pw_namer?r�r~rr"r(r�)�sinkr�ry�	last_scan�last_scan_time�interval�hour�day_of_month�day_of_week�next_scan_time�malware_history�malware_by_sites            rB�_get_scan_data_for_userr�"s�����$�D�)�*;�<�<�<�<�<�<�<�<�I��]�]�;��5�5�N�1J���<�1�1�-�H�d�L�+��N��8�=� � �6��d�L�+�
�
��
*�)�*;�<�<�O�"���	*�	*���O��>�?�:�:rA�	semaphorec���K�|4�d{V��	|�d{V��n4#t$r'}t�d|����Yd}~nd}~wwxYwddd���d{V��dS#1�d{V��swxYwYdS)NzTelemetry task failed: )�	Exceptionr|�error)�coror��es   rB�_send_telemetry_taskr�Es+�����8�8�8�8�8�8�8�8�	8��J�J�J�J�J�J�J�J���	8�	8�	8��L�L�6�1�6�6�7�7�7�7�7�7�7�7�����	8����8�8�8�8�8�8�8�8�8�8�8�8�8�8�8�8�8�8�8�8�8�8�8�8����8�8�8�8�8�8s5�A��A�
A�A�A�A�A�
A'�*A'�
�
coroutinesc����K�|sdStj|����fd�|D��}	tj|��d{V��dS#t$r(}t�d|����Yd}~dSd}~wwxYw)zK
    Process a list of telemetry coroutines with a concurrency limit.s
    Nc�T��g|]$}tjt|�������%Sr@)rr�create_taskr�)r�r�r�s  �rBr�z+process_telemetry_tasks.<locals>.<listcomp>Us?���
�
�
��	��0��y�A�A�B�B�
�
�
rAzSome telemetry tasks failed: )rr�	Semaphore�gatherr�r|r�)r��concurrency�tasksr�r�s    @rB�process_telemetry_tasksr�Ms�����������!�+�.�.�I�
�
�
�
��
�
�
�E�
:��n�e�$�$�$�$�$�$�$�$�$�$���:�:�:����8�Q�8�8�9�9�9�9�9�9�9�9�9�����:���s�A�
A3�A.�.A3c��pK�	ttd���}|����d{V��t|��}n3#t$r&}t
�d|��Yd}~dSd}~wwxYw|st
�d��dSt|��}||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<valid WordPress rules not found, skipping rules installation��version�rules)	rr�updater�r�r|�warningrr%)�wp_rules_index�
wp_rules_datar��wp_rules_version�ruleset_dicts     rB�load_wp_rules_phpr�`s�����	��x��?�?�?���#�#�%�%�%�%�%�%�%�%�%�1�.�A�A�
�
���������L�
�	
�	
�	
��t�t�t�t�t��������������J�	
�	
�	
��t�.�n�=�=��#����L�)��6�6�6s�?A�
A4�A/�/A4c��vK�t��}t||��}|����d{V��S)zLInstall the imunify-security plugin for all sites where it is not installed.N)r2�WordPressSiteInstaller�run)r��sites�	installers   rB�install_everywherer��s@���� �"�"�E�&�t�U�3�3�I������ � � � � � � rAc��vK�t��}t||��}|����d{V��S)a�
    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
    N)r1�WordPressSiteAdopterr�)r�r��	processors   rB�adopt_found_sitesr��sB����
� � �E�$�T�5�1�1�I������ � � � � � � rAc��	tj��s"t�dt��dStj�����S#t$r&}t�d|��Yd}~dSd}~wwxYw)zLGet the latest version of the imunify-security plugin from the version file.z&Plugin version file does not exist: %sNz&Failed to read plugin version file: %s)r �existsr|r��	read_text�stripr�)r�s rB�get_latest_plugin_versionr��s���	�"�)�+�+�	��L�L�8�:M�
�
�
��4�"�,�.�.�4�4�6�6�6���������=�q�A�A�A��t�t�t�t�t��������s�3A�$A�
B�&B�Bc��K�t��}|st�d��dSt�d|��t	��}g}t
j�d��5	t|��}t�dt|���d���|s#	t|���d{V��ddd��dSt��}t���d{V��}tt��}|D]"}||j�|���#|���D�],\}	}
	t%j|	��}|j}n3#t*$r&}
t�d|	|
��Yd}
~
�Nd}
~
wwxYwt-|||���d{V��\}}}t/|��}|
D�]�}t1||���d{V��r�	t3j|���d{V��st�d|���Qt7||||||�	��}t9||���d{V��}t;||||�
���d{V��t=||||�
���d{V��t3j|���d{V��|� |��t3j!|���d{V��}|r{|j"}tG||��|�$|��}tK|��tK|��k}|�tMj'||rdnd||�
������v#t*$r'}
t�d||
��Yd}
~
���d}
~
wwxYw��.t�dt|����nf#tPj)$r+t�dt|����Yn-t*$r!}
t�d|
���d}
~
wwxYwt|���d{V��n#t|���d{V��wxYw	ddd��dS#1swxYwYdS)zFUpdate the imunify-security plugin on all sites where it is installed.z)Could not determine latest plugin versionNz<Updating imunify-security wp plugin to the latest version %szwp-plugin-updatezFound z outdated sites�+Failed to get username for uid=%d. error=%sz#WordPress site no longer exists: %s��versions�r��data_dir�downgraded_by_imunify�updated_by_imunify�r��event�siter�z+Failed to update plugin on site=%s error=%sz.Updated imunify-security wp plugin on %d siteszRUpdate of imunify-security wp plugin was cancelled. Plugin was updated on %d sitesz-Error occurred during plugin update. error=%s)*r�r|r��infor�r	�track�taskr/r�r�rr&r�list�uidr�r��pwd�getpwuidr�r�r�r)�remove_site_if_missingr�is_wordpress_installedr*r$�update_scan_data_file�update_plugin_config_file�
plugin_update�add�get_plugin_versionr�r8�build_with_versionrr�
send_eventrr�CancelledError)r��latest_version�updated�telemetry_coros�outdated_sitesryr��
sites_by_userr�r�r�r�rar�r�r�r��
plugin_config�	scan_datar�r��original_version�is_downgrades                       rB�update_everywherer�s�����.�0�0�N������@�A�A�A���
�K�K�F�����
�e�e�G��O�	�	�	�	�1�	2�	2�R;�R;�Q	;�/��?�?�N��K�K�E��^�!4�!4�E�E�E�F�F�F�!�
��V*�/�:�:�:�:�:�:�:�:�:�eR;�R;�R;�R;�R;�R;�R;�R;�(�>�>�L�9�;�;�;�;�;�;�;�;�H�(��-�-�M�&�
5�
5���d�h�'�.�.�t�4�4�4�4�,�1�1�3�3�k
�k
�
��U�	� #��S� 1� 1�I�(�0�H�H�� �����L�L�E������
�H�H�H�H�����
����2��)�\���������	�"�"�#�!6�h� ?� ?�
�!�U�U�D�3�D�$�?�?�?�?�?�?�?�?�!� �R�%(�%?��%E�%E�E�E�E�E�E�E�%�"�K�K� E�t����%�%6�*�*�$� �+�%-�
%�%�%�	�*D� �)�*�*�$�$�$�$�$�$��
4� �%�&/�%-�	����������8� �)�&/�%-�	����������"�/��5�5�5�5�5�5�5�5�5����D�)�)�)�),�(>�t�(D�(D�"D�"D�"D�"D�"D�"D��"��/3�|�,�0��g�>�>�>�$(�#:�#:�7�#C�#C�D�,8� '�,�,� ,�-=� >� >�,?�L�
,�2�2� )� 4�)-�,8�)B�(?�(?�-A�)-�,3�	!"�	!"�	!"������%�������I� �!�����������������aU�n
�K�K�@��G���
�
�
�
���%�	�	�	��K�K�+��G���
�
�
�
�
�
�	�	�	��L�L�?��
�
�
�
�����		����*�/�:�:�:�:�:�:�:�:�:�:��)�/�:�:�:�:�:�:�:�:�:�:����:�eR;�R;�R;�R;�R;�R;�R;�R;�R;�R;�R;�R;����R;�R;�R;�R;�R;�R;s��9Q�;<N �8Q�A5N �E,�+N �,
F�6F�N �F�A	N �&5M�N �D#M�?N �
M2	�M-	�'N �-M2	�2-N �P� 7P�P�	P�"O>�>P�P�Q�P3�3Q�Q�Qr�c���K�tj|���d{V��}	t|��}nn#t$ra}|jtjkrYd}~dS|jtjtjfvr!t�	d|��Yd}~dS�d}~wwxYw	t|j��5}	tj
t|���d{V��tj|��d}n#tj|��d}wxYwtj|j|���ddd��dS#1swxYwYdS#t&$r|dkrtj|���wxYw)Nz/Skipping rmtree: data directory %s is a symlink�����dir_fdr)r�get_data_dirr�OSError�errno�ENOENT�ELOOP�ENOTDIRr|r�r�parentrr�	to_threadr�os�close�rmdirr=�
BaseException)r�r�r�exc�	parent_fds     rB�delete_plugin_filesrIs������%�d�+�+�+�+�+�+�+�+�H�
�%�h�/�/���������9���$�$��F�F�F�F�F��9���e�m�4�4�4��N�N�A�8�
�
�
�
�F�F�F�F�F�
����������
�h�o�
&�
&�	6�)�
��'�	�6�:�:�:�:�:�:�:�:�:���� � � �������� � � ��������H�X�]�9�5�5�5�5�	6�	6�	6�	6�	6�	6�	6�	6�	6�	6�	6�	6����	6�	6�	6�	6�	6�	6�������Q�;�;��H�V����
����sh�.�
B�B�:B�B�B�D:�1D-�3 C*�D-�*D�D-� D:�-D1�1D:�4D1�5D:�:&E c��K�	tj|���d{V��}|s+t|tj��||���d{V��dStj|���d{V��}tj|���d{V��t
|���d{V��t|��}|�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.
    Nr�uninstalled_by_imunifyr�z"Failed to remove plugin from %s %s)r�is_plugin_installed�process_manually_deleted_plugin�timer��plugin_uninstallrr-r�rr�r�r|r�)r�r�r�is_installedr��affectedr�s       rB�remove_from_single_siter$hsz����#� �4�T�:�:�:�:�:�:�:�:���	�1��d�i�k�k�4����
�
�
�
�
�
�
��1��.�t�4�4�4�4�4�4�4�4���"�4�(�(�(�(�(�(�(�(�(�"�$�'�'�'�'�'�'�'�'�'��t�$�$��	���� ��.���	
�
�
�	
�	
�	
�����������9�4��G�G�G��q�q�q�q�q��������s�AC�BC�
D�C<�<Dc
��K�t�d��g}d}tj�d��5	t��t
��}|D]m}	|tjt|||�����d{V��z
}�0#tj
$r,t�d|t|����Y�jwxYwn.#t$r!}t�
d|���d}~wwxYw	t�d|��t|���d{V��n5#t�d|��t|���d{V��wxYw	ddd��dS#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|r�r	r�r�r�r4rr�shieldr$r�r�r�r�r�)r�rr#�	to_remover�r�s      rB�remove_all_installedr(�s5����
�K�K�5�6�6�6��O��H�	�	�	�	�2�	3�	3�;�;�	;��N�N�N�+�-�-�I�!�
�
��
��g�n�/��d�O�L�L�'�'�!�!�!�!�!�!��H�H���-�����K�K�H� ��I���	���������
���	�	�	��L�L�D�e�L�L�L������	����
� 
�K�K�B��
�
�
�*�/�:�:�:�:�:�:�:�:�:�:��

�K�K�B��
�
�
�*�/�:�:�:�:�:�:�:�:�:�:����:�9;�;�;�;�;�;�;�;�;�;�;�;����;�;�;�;�;�;sl�E2� C�#,B�C�8C�C�
C�C�D/�
C:�C5�5C:�:D/�>1E2�/2E!�!E2�2E6�9E6c��K�	t||��t|���d{V��|�tj|d||j�����dS#t$r'}t�d||��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
    N�removed_by_userr�z>Failed to process manually deleted plugin for site=%s error=%s)	r6rr�rr�r�r�r|r�)r��nowr�rr�s     rBrr�s�����
�%�d�C�0�0�0�"�$�'�'�'�'�'�'�'�'�'�	���� ��'����	
�
�
�	
�	
�	
�	
�	
���
�
�
����L���	
�	
�	
�	
�	
�	
�	
�	
�	
�����
���s�AA�
B�%B�B�freshly_installed_sitesc��dK�g}	t|��}|r0tj��}|D]}t||||���d{V���n2#t$r%}t�d|��Yd}~nd}~wwxYw|rt
|���d{V��dSdS#|rt
|���d{V��wwxYw)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 rr�r|r�r�)r�r,r�to_mark_as_manually_removedr+r�r�s       rB�tidy_up_manually_deletedr/�s>�����O�;�&K�#�'
�'
�#�'�	��)�+�+�C�3�
�
��5��#�t�_��������������F�F�F����=�u�E�E�E�E�E�E�E�E�����F�����	;�)�/�:�:�:�:�:�:�:�:�:�:�:�	;�	;��?�	;�)�/�:�:�:�:�:�:�:�:�:�:�	;���s0�AA�B�
A7�A2�-B�2A7�7B�B/r�c
��NK�|sdSt��}t���d{V��}tt��}|D]"}||j�|���#|���D�]-\}}	tj|��}|j	}n3#t$r&}	t�d||	��Yd}	~	�Nd}	~	wwxYwt|||���d{V��\}
}}t|��}
|D]�}t||���d{V��r�	t!|
|||||���}t#||���d{V��}t%||||����d{V��t'||
||����d{V���x#t$r&}	t�d||	��Yd}	~	��d}	~	wwxYw��/dS)Nr�r�r�z.Failed to update site data on site=%s error=%s)rr&rr�r�r�r�r�r�r�r�r|r�r�r)r�r*r$r�r�)r�r�ryr�rr�r�r�rar�r�r�r�rrr�s                rB�update_data_on_sitesr1�s��������� �>�>�L�1�3�3�3�3�3�3�3�3�H� ��%�%�M��-�-���d�h��&�&�t�,�,�,�,�$�)�)�+�+�2�2�
��U�		���S�)�)�I� �(�H�H���	�	�	��L�L�=���
�
�
�

�H�H�H�H�����
	����*�$�	�<�H�H�H�H�H�H�H�H�		
����-�h�7�7�
��	�	�D�+�D�$�7�7�7�7�7�7�7�7�
��
�-�"�"���#�%�
���	�"<�D�)�!L�!L�L�L�L�L�L�L��,��)�y�8�����������
0��-�9�x��������������
�
�
����D���������������
����5	�'2�2s1�=B�
C	�#C�C	�AE0�0
F �:F�F r��filename�datar�c���K�|�tj|j��}|�t||���d{V��}t	|��}t||z||j|j���dS)aWrite ``data`` as embedded JSON to ``<site data dir>/<filename>``.

    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.
    N�r��gid)r�r�r�r$r%r+�pw_gid)r�r2r3r�r��php_contents      rB�_write_json_php_data_filer9Bs��������L���*�*�	���3�D�)�D�D�D�D�D�D�D�D��/��5�5�K�%��8��[�d�h�I�<L������rArc��>K�t|d|||����d{V��dS)N�
scan_data.phpr��r9)r�rr�r�s    rBr�r�ZsV����$�����������������rArc��>K�t|d|||����d{V��dS)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.phpr�Nr<)r�rr�r�s    rBr�r�jsV����$�����������������rAc��&K�|sdSd}tt��}|D]"}||j�|���#|���D]�\}}	tj|��}|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.
    rr�N)r�r{z6Failed to update plugin_config.php on site=%s error=%s)
rr�r�r�r�r�r�r�r�r|r�r)r�)
r�rrr�r�r�r�rar�rs
          rB�update_plugin_config_on_sitesr@�s����� ���q��G�
.9��->�->�M��-�-���d�h��&�&�t�,�,�,�,�(�.�.�0�0�����Z�		���S�)�)�I� �(�H�H���	�	�	��L�L�=���
�
�
�

�H�H�H�H�����
	����.�h�7�7�
��	�	�D�

�/��-�9������������1������
�
�
����L���������������
����
	��Ns0�A8�8
B(�B#�#B(�?C�
D
�'D�D
�wp_rules_phpr�failedc��K�|j}	t||���d{V��}|dz}t|||j|���|�|��t
�d|j��dS#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+r�r�r|r��docrootr�r�)	r�r�rArrBr6r��
rules_pathr�s	         rB�update_wp_rules_for_siterG�s����"�
�C�
�3�D�)�D�D�D�D�D�D�D�D����+�
�)���$�(��	
�	
�	
�	
�	���D�������2�D�L�A�A�A�A�A���
�
�
��
�
�4�������7��L��	
�	
�	
�	
�	
�	
�	
�	
�	
�����
���s�A(A5�5
C�?6B;�;C�	make_task�	task_name�fingerprint�skip_waf_disabledc���K�t��}t��}tj�|��5	t	j��}tt��}	|D]"}
|	|
j�|
���#g}|	�	��D�]+\}}
	tj|��}|j}nW#t$rJ}td|t|
��||d�dd|���|
D]}
|�|
���Yd}~�rd}~wwxYw|rr	t#|���d{V��}n/#t$r"t$�d|d�	��d}YnwxYw|s*t$�d
|t|
������|
D]:}
t+||
���d{V��r�|�||
|||�����;��-d}t-dt|��|��D]&}||||z�}t/j|d
di��d{V���'t	j��|z
}t$�d|t|��t|��|��nh#t.j$r,t$�d|t|����Yn.t$r"}t$�d||���d}~wwxYwddd��dS#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})r��count�user�reasonr��	wordpress��format_args�level�	componentrJN�BCould not check WAF status for user %s, proceeding with deploymentT��exc_infoz-WAF disabled for user %s, skipping %d site(s)r�r�return_exceptionsz@%s deployment complete. Updated: %d, Failed: %d, Duration: %.2fsz-%s deployment was cancelled. Updated %d sitesz-Error occurred during %s deployment. error=%s)r�r	r�r�r rr�r�r�r�r�r�r�r�rr�r�rvr|r�r�r��rangerrr�r�r�)r�rHrIrJrKr�rrB�
start_timerr�r�r�r�r�rar�rd�max_concurrent�i�batch�elapseds                      rB�_deploy_to_sitesr_�s����0�e�e�G�
�U�U�F�	�	�	�	�y�	)�	)�S�S�R	�����J�'��-�-�M��
5�
5���d�h�'�.�.�t�4�4�4�4��E�#0�#6�#6�#8�#8�-
N�-
N���Z�� #��S� 1� 1�I�(�0�H�H�� �����>�%.�%(��_�_�$'�&+�	%�%�(�"-�$/�
�
�
�
�!+�)�)���
�
�4�(�(�(�(��H�H�H�H�����#����&%�!�	+�,C�H�,M�,M�&M�&M�&M�&M�&M�&M����$�+�+�+����:�$�%)�	'����'+����+����'�!����K�$��
�O�O����
!�&�N�N�D�3�D�$�?�?�?�?�?�?�?�?�!� ��L�L���4��G�V�!L�!L�M�M�M�M�N�
 �N��1�c�%�j�j�.�9�9�
E�
E���a�!�n�"4�4�5���n�e�D�t�D�D�D�D�D�D�D�D�D�D��i�k�k�J�.�G��K�K�#���G����F����

�
�
�
���%�	�	�	��K�K�?���G���
�
�
�
�
�
�	�	�	��L�L�?���
�
�
�

�����
	����[S�S�S�S�S�S�S�S�S�S�S�S����S�S�S�S�S�Ss��K�A(I%�)C�I%�
D�AD�I%�D�I%�D5�4I%�5)E!�I%� E!�!DI%�$K�%8K
�K�	K
�(K�K
�
K�K�!Kc���K�t��t��}|st�d��dS�fd�}t	||ddd|����d{V��dS)zHDeploy pre-formatted wp-rules PHP content to all active WordPress sites.zNo active WordPress sites foundNc�*��t||�||��SrG)rG)r�r�rrBrAs    �rBrHz'_deploy_wp_rules_php.<locals>.make_taskYs ���'��)�\�7�F�
�
�	
rAzwp-ruleszwp-rules-update-skip-userT�rIrJrKr�)r�r4r|r}r_)rAr��installed_sitesrHs`   rB�_deploy_wp_rules_phprdPs�������N�N�N�)�+�+�O������6�7�7�7���
�
�
�
�
�
����/��
�
�����������rA�
is_updatedc��K�tjst�d��dS|st�d��dSt�d��t	|��}|st�d��dSt
|��}||d�}t|��}t|���d{V��dS)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|r�r�r�rr%rd)r�rer�r�r�rAs      rB�update_wp_rules_on_sitesrghs������,�����
�	
�	
�	
�	�������?�@�@�@���
�K�K�A�B�B�B�-�e�4�4�M������C�D�D�D���.�e�4�4��#����L�1��>�>�L�
�|�
,�
,�,�,�,�,�,�,�,�,�,rAc��`K�tjst�d��dSt���rdat�d��dSt4�d{V��	dat�d��t���d{V��}|s.t�d��	ddd���d{V��dSt|���d{V��tsnt�d����	ddd���d{V��dS#1�d{V��swxYwYdS)	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_lock�locked�_redeploy_rules_php_pendingr�r�rd)rAs rB�redeploy_rules_phprl�sS�����,�����
�	
�	
�	
�	���&�&�(�(��&*�#����K�L�L�L���'�P�P�P�P�P�P�P�P�	P�*/�'��K�K�H�
�
�
�"3�!4�!4�4�4�4�4�4�4�L��
����I�J�J�J��P�P�P�P�P�P�P�P�P�P�P�P�P�P�'�|�4�4�4�4�4�4�4�4�4�.�
���K�K�N�O�O�O�!	P�	P�P�P�P�P�P�P�P�P�P�P�P�P�P�P�P�P�P�P�P�P�P�P�P�P����P�P�P�P�P�Ps�/AD�8D�
D'�*D'c��ZK�t���d{V��t���d{V��dS)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)rl�update_disabled_rules_on_sitesr@rArB�redeploy_waf_for_all_sitesro�sJ�����
�
��������
(�
*�
*�*�*�*�*�*�*�*�*�*rArEc�t�	t|��}n�#t$rYdSt$rg}|jtjtjfvr!t�d|��Yd}~dSt�d||��Yd}~dSd}~wwxYw	dD]t}	tj
||���t�d||���6#t$rY�Bt$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)rD�disabled-rules.phprz!Removed %s from %s (WAF disabled)zFailed to remove %s from %s: %s)
r�FileNotFoundErrorrrrrr|r�r�r�remover�r)r�rErrr2r�s      rB�_remove_waf_files_for_dirrt�s����%�h�/�/����������������9���e�m�4�4�4��N�N�E��
�
�
�
�F�F�F�F�F����9�7�C�H�H�H����������������;�	�	�H�

��	�(�6�2�2�2�2����7��7������%�
�
�
����
�
�
����5�x��!�������������
����	�	������������������sa��
B�	B�:B
�(B
�
B�D!�2C�
D!�
D�D!�	D� D�=D!�D�D!�!D7c��K�|D]|}	tj|���d{V��}n8#t$r+}t�d|j|��Yd}~�Nd}~wwxYwt
jt||j���d{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
r�r|r�rErrrrt)r�r�r�r�s    rB�_remove_waf_files_from_sitesrv�s������

�

��	� �-�d�3�3�3�3�3�3�3�3�H�H���	�	�	��L�L�7���q�
�
�
�
�H�H�H�H�����		����
��%�x���
�
�	
�	
�	
�	
�	
�	
�	
�	
�

�

s�#�
A�!A�Ac
��t�K�tjsdStj��}	|�dt
j|���d{V���n,#t$rt�	d|��YdSwxYw|�dt���d{V��}�fd�|D��}|sdSt��}t��}t���d{V��}|r|D]}t|�|||���d{V���nt�	d��tj��}t��}	t��}
|D]}t|�||	|
���d{V���t�d|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 redeployc�4��g|]}|j�jk�|��Sr@�r��pw_uid�r��sr�s  �rBr�z)redeploy_waf_for_user.<locals>.<listcomp>�(���@�@�@��a�e�y�/?�&?�&?�!�&?�&?�&?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)rrSrrrsrtr��getpwnamrJr|r�r4r�r�rGr �update_disabled_rules_for_siter�r�)rarur�r�rrBrAr��disabled_rules_ts�
dr_updated�	dr_failedr�s           @rB�redeploy_waf_for_userr�sE������,�����#�%�%�D���.�.�t�S�\�8�L�L�L�L�L�L�L�L�	�	���������<�h�	
�	
�	
�	���	�����&�&�t�-@�A�A�A�A�A�A�A�A�E�@�@�@�@�U�@�@�@�J������e�e�G�
�U�U�F�*�,�,�,�,�,�,�,�,�L��D��	�	�D�*��i��w����
�
�
�
�
�
�
�
�	�
	���B�C�C�C��	�������J����I��
�
��,��)�.�
�I�
�
�	
�	
�	
�	
�	
�	
�	
�	
��K�K�	*���G����F����J����I�������s�'A�%A7�6A7c��d�K�tj��}	|�dtj|���d{V���n,#t
$rt�d|��YdSwxYw|�dt���d{V��}�fd�|D��}t|���d{V��dS)z4Remove WAF files from all sites belonging to a user.Nz-User %s not found, skipping WAF rules removalc�4��g|]}|j�jk�|��Sr@ryr{s  �rBr�z-remove_waf_rules_for_user.<locals>.<listcomp>Dr}rA)
rrrsrtr�r~rJr|r�r4rv)rarur�r�r�s    @rB�remove_waf_rules_for_userr�8s�������#�%�%�D���.�.�t�S�\�8�L�L�L�L�L�L�L�L�	�	���������;�X�	
�	
�	
�	���	�����&�&�t�-@�A�A�A�A�A�A�A�A�E�@�@�@�@�U�@�@�@�J�
&�z�
2�
2�2�2�2�2�2�2�2�2�2s�'A�%A)�(A)c���K�tj��}|�dt���d{V��}t�dt
|����t|���d{V��dS)z?Remove WAF files from all installed sites (global WAF disable).Nz7Global WAF disabled, removing WAF files from %d site(s))rrrsrtr4r|r�r�rv)rur�s  rB�remove_waf_rules_for_all_sitesr�Hs������#�%�%�D��&�&�t�-@�A�A�A�A�A�A�A�A�E�
�K�K�A��E�
�
����'�u�
-�
-�-�-�-�-�-�-�-�-�-rAc��6K�tjsdSt���rdat
�d��dSt4�d{V��	dat��s	ddd���d{V��dSt��}tj
������d{V��}tj
��}|D]�}	|�dt|���d{V��r�&|rt!|���d{V��nt#|���d{V���T#t$$r&}t
�d||��Yd}~�d}~wwxYwtsnt
�d����	ddd���d{V��dS#1�d{V��swxYwYdS)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|r�rLrQr�HostingPanel�	get_usersrrrsrtrxr�r�r�r�)�new_default�	usernamesrurar�s     rB�apply_waf_default_changer�Ss�����,�����%�%�'�'��%)�"����H�I�I�I���&� 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�:�:�D�D�F�F�F�F�F�F�F�F�I��+�-�-�D�%�
�
���!�1�1��<� ���������!�
!�"�B�3�H�=�=�=�=�=�=�=�=�=�=�7��A�A�A�A�A�A�A�A�A��� �����N�N�L� ������������������.�
���K�K�K�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� MsO�F�;AF�"D�.F�/-D�F�
E
�'E�F�E
�
'F�
F�F�domain�	timestampc�p�tj|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�)r�get_domain_disabled�sortedr%)r�r��disabled_rule_idsr3s    rB�generate_disabled_rules_phpr��sN��'�:��u�������)�*�*���D�)��.�.�.rAc��JK�|j}	t||���d{V��}|dz}t|j|��}t	|||j|���t
j|����tj	|j	k���
��|�|��t�
d|j	��dS#t$rA}	|�|��t�d|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
    Nrqr5��disabled_rules_sync_tsz"Updated disabled-rules for site %sz/Failed to update disabled-rules for site %s: %s)r7r$r�r�r+r�rr��whererE�executer�r|r�r�r�)
r�r�r�rrBr6r��disabled_rules_pathr8r�s
          rBrr�sO����"�
�C�
�3�D�)�D�D�D�D�D�D�D�D��&�)=�=��1�$�+�y�I�I��)���$�(��	
�	
�	
�	
�	��I�>�>�>�D�D��!�T�\�1�	
�	
�
�'�)�)�)����D�������8�$�,�G�G�G�G�G���
�
�
��
�
�4�������=��L��	
�	
�	
�	
�	
�	
�	
�	
�	
�����
���s�C
C�
D"�!6D�D"�domainsc��bK�tjst�d��dSt�d��t	��|rt|��}nt
��}|st�d��dSd�}t||ddd|�	���d{V��dS)
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 deploymentc�J�t||tj��||��SrG)rr )r�r�rrBs    rBrHz1update_disabled_rules_on_sites.<locals>.make_task�s%��-��)�T�Y�[�[�'�6�
�
�	
rAzdisabled-ruleszdisabled-rules-update-skip-userTrb)rrSr|r�r�r.r4r_)r�r�r�rHs    rBrnrn�s������,�����
�	
�	
�	
�	��
�K�K�G�H�H�H��N�N�N��&�.�w�7�7���#�%�%�������L�M�M�M���
�
�
�
�
��"�5��
�
�����������rAc
��K�t�d��t��}t��}tj�d��5	t
��t��}|s(t�d��	ddd��dStt��}|D]"}||j
�|���#g}|���D]�\}}	tj|��}	n<#t$r/}
t!dt#|��||
d�ddd	�
��Yd}
~
�Od}
~
wwxYw|D]@}t%||���d{V��r�t'||	||��}|�|���A��d}t)dt#|��|��D]&}
||
|
|z�}t+j|d
di��d{V���'t�dt#|��t#|����nf#t*j$r+t�dt#|����Yn-t$r!}
t�d|
���d}
~
wwxYwddd��dS#1swxYwYdS)z7Update auth.php files for all existing WordPress sites.z4Updating auth.php files for existing WordPress siteszwp-auth-updatez"No installed WordPress sites foundNz�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}�rMrNrOr�rPzwp-plugin-auth-update-skip-userrQr�rrXTz8Updated 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|r�r�r	r�r�r�r4rr�r�r�r�r�r�r�rr�r��update_site_authrYrrr�r�r�)r�rrBrcrr�r�r�r�r�r�r�r[r\r]s               rB�update_auth_everywherer��s�����
�K�K�F�G�G�G��e�e�G�
�U�U�F�	�	�	�	�/�	0�	0�@�@�?	��N�N�N�2�3�3�O�"�
����@�A�A�A��@�@�@�@�@�@�@�@�(��-�-�M�'�
5�
5���d�h�'�.�.�t�4�4�4�4��E�+�1�1�3�3�
'�
'�
��U�� #��S� 1� 1�I�I�� �����D�
&)��Z�Z�$'�&+�%�%�
(�"-�$E�
�
�
�
��H�H�H�H���������""�'�'�D�3�D�$�?�?�?�?�?�?�?�?�!� �+�D�)�W�f�M�M�D��L�L��&�&�&�&�	'� �N��1�c�%�j�j�.�9�9�
E�
E���a�!�n�"4�4�5���n�e�D�t�D�D�D�D�D�D�D�D�D�D��K�K�J��G����F���
�
�
�
���%�	�	�	��K�K�(��G���
�
�
�
�
�
�	�	�	��L�L�F��N�N�N������	����}@�@�@�@�@�@�@�@�@�@�@�@����@�@�@�@�@�@st�I;�8H� AH�5D
�	H�

E�%D>�9H�>E�CH�I;�7I+�?I;�	I+�
I&�&I+�+I;�;I?�I?c���K�	t||���d{V��|�|��dS#t$r<}|�|��t�d||��Yd}~dSd}~wwxYw)z/Process authentication setup for a single site.Nz*Failed to update auth for site=%s error=%s)r9r�r�r|r�)r�r�rrBr�s     rBr�r�>s�����	
�'��i�8�8�8�8�8�8�8�8�8����D��������
�
�
��
�
�4�������8���	
�	
�	
�	
�	
�	
�	
�	
�	
�����
���s�+1�
A7�1A2�2A7c��.K�tj�|j��rdSt	|��}|dkr&|�#tj|d||j����d{V��n1t�	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_removedr�z@Failed to delete missing site %s from database, no rows affectedz2Failed to delete missing site {site} from databaser�r�rPzwp-plugin-site-delete-failedrQT)rr��isdirrEr-rr�r�r|r�r)r�r��rows_deleteds   rBr�r�Ls�����
�w�}�}�T�\�"�"���u��t�$�$�L��a������&��$����	���
�
�
�
�
�
�
��	���N��	
�	
�	
�
	�@�����!�6�	
�	
�	
�	
��4rA�file_permissionsc��K�	tj|���d{V��}	t|��}nC#t$r6}|jtjtjtjfvrYd}~dS�d}~wwxYw	tj	|��j
dz}|dkrtj|d��dD]�}	t||���5}tj
|��}|j
dz|krtj||��ddd��n#1swxYwY�d#t$rY�pt$r<}|jtjkr!t�d||��Yd}~���d}~wwxYw	tj|��n#tj|��wxYwdS#t$$r'}	t�d	||	��Yd}	~	dSd}	~	wwxYw)
z6Fix data file permissions for a single WordPress site.NFi�i�)r;r>zauth.phprDrqrz"Skipping chmod: %s/%s is a symlinkTz.Failed to fix permissions for site=%s error=%s)rr
rrrrrrr�stat�st_mode�chmodr�fstatrrr|r�rr�r�)
r�r�r�rr�current_dir_mode�	file_name�file_fd�str�s
          rB�fix_site_data_file_permissionsr�us{����1��)�$�/�/�/�/�/�/�/�/��	�)�(�3�3�F�F���	�	�	��y�U�\�5�;��
�F�F�F��u�u�u�u�u������	����
	�!�w�v���6��>���5�(�(�����'�'�'��
�
�	��&�y��@�@�@�@�G��X�g�.�.���:��-�1A�A�A��H�W�.>�?�?�?�@�@�@�@�@�@�@�@�@�@�@����@�@�@�@���)�����H������y�E�K�/�/����@�$�%����
!��������������
�0
�H�V������B�H�V���������t���������<���	
�	
�	
�
�u�u�u�u�u�����
���s��F�/�F�
A/�*A*�#F�)A*�*A/�/F�3;E:�/D�8D�8D�D	�D�D	�D�E:�
E!�E:�	E!�%1E�E:�E�E!�!E:�%F�:F�F�
G�G�Gc��\K�t��}t��}tj�d��5	t	��t��}|s	ddd��dSddlm}ddlm	}|��j
|j
krdnd}|D]\}t||���d{V��r�t||���d{V��}|r|�
|���G|�
|���]t�dt!|��t!|����nj#t"j$r+t�d	t!|����Yn1t&$r%}	t�d
|	��Yd}	~	nd}	~	wwxYwddd��dS#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)r�r	r�r�r�r4�+defence360agent.subsys.panels.hosting_panelr��#defence360agent.subsys.panels.pleskr��NAMEr�r�r�r|r�r�rrr�r�r�)
r��fixedrBrcr�r�r�r��successr�s
          rB�$fix_data_file_permissions_everywherer��s�����
�E�E�E�
�U�U�F�	�	�	�	�:�	;�	;�0�0�/	��N�N�N�2�3�3�O�"�
��0�0�0�0�0�0�0�0�
�
�
�
�
�
�
B�A�A�A�A�A�&����,��
�:�:����
�
(�

%�

%��/��d�;�;�;�;�;�;�;�;��� >��*�!�!���������%��I�I�d�O�O�O�O��J�J�t�$�$�$�$��K�K���E�
�
��F���	
�
�
�
���%�	�	�	��K�K�&��E�
�
�
�
�
�
�
�
�	�	�	��L�L�C�U�
�
�
�
�
�
�
�
�����	����[0�0�0�0�0�0�0�0�0�0�0�0����0�0�0�0�0�0sN�F!�D*�,B=D*�)F!�*7F�!F!�#	F�,F�F!�F�F!�!F%�(F%c�d�eZdZdZdZdZdZdZdddd	d
ddd
�Zd�Z	d�Z
d�Zd�Zde
ddfd�Zd�ZdS)r�z�
    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.
    T�installed_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}z�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}��start�complete�foundr��	cancelled�	exception�	skip_userc�H�||_||_t��|_t��|_t��|_t��|_t��|_t��|_d|_	t��|_
d|_dSrG)r�r�r��	processed�
authenticated�rules_installed�failed_rules_updates�disabled_rules_installed�failed_disabled_rules_updatesr��failed_auth�
_current_site)�selfr�r�s   rB�__init__zWordPressSiteInstaller.__init__
sy����	���
������ �U�U���"�u�u���$'�E�E��!�(+����%�-0�U�U��*�/3����5�5���,0����rAc��K�tj|���d{V��}|s3t�d|��t	dd|iddd���d	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}r�r�rPzwp-plugin-cli-not-accessiblerQFT)rr�r|r�r)r�r�r�s   rB�
is_site_readyz$WordPressSiteInstaller.is_site_readys�����(+�'A�$�'G�'G�!G�!G�!G�!G�!G�!G��%�	��N�N�H��
�
�
�
�L�#�T�N��%�:�
�
�
�
��5��trAc��|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)r�r�r5�_stamp_disabled_rules_sync_ts�r�r�r�s   rB�_record_processed_sitez-WordPressSiteInstaller._record_processed_site3sD��	
����4� � � ���v�&�&�&��*�*�4�0�0�0�0�0rAc��K�|j}d|_	t|���d{V��n3#t$r&}t�d||��Yd}~nd}~wwxYw|jrt
j|���d{V��dSdS)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)r�rr�r|r��install_pluginr�try_plugin_uninstall)r�r�r�s   rB�_revert_in_flight_sitez-WordPressSiteInstaller._revert_in_flight_siteEs������!��!���	�%�d�+�+�+�+�+�+�+�+�+�+���	�	�	��N�N�G���
�
�
�
�
�
�
�
�����	������	1��*�4�0�0�0�0�0�0�0�0�0�0�0�	1�	1s�(�
A�A�Ar�rENc���||jvrdS|j�dStj|j����tj|jk�����dS)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�)r�r�rr�r�rEr�)r�r�s  rBr�z4WordPressSiteInstaller._stamp_disabled_rules_sync_tsYse���t�4�4�4��F��!�)��F���#'�#9�	
�	
�	
�
�%�
�%���5�
6�
6�w�w�y�y�y�y�yrAc
��K�t�|jd��g}tj�|j��5	t��|js{t�d��|j	|rLtj|ddi��d{V��}|D]2}t|t��rt�d|���3cddd��St�|jd�t!|j�������t#��}t%���d{V��}t'j��|_t+���d{V��}t-t.��}|jD]"}||j�|���#|���D�]B\}	}
	t7j|	��}|j}nL#t$r?}
t=|jd	t!|
��|	|
d
�dd|j�
��Yd}
~
�gd}
~
wwxYw	tA|���d{V��}n/#t$r"t�d|d���d}YnwxYw|s)t�d|t!|
����tC|j"||���d{V��\}}}tG|��}|
D�]/}tI|j"|���d{V��r�	|�%|���d{V��s�<||_&tO||||||���}tQ||���d{V��}tS||||����d{V��tU||||����d{V��tW|||j,|j-���d{V��|r%|r#t]||||j/|j0���d{V��|r(tc|||j|j2|j3���d{V��|j4rtkj6|���d{V��tkj7|���d{V��}|rtqj9||��}|�:||��d|_&|�tj;tyj=|j"|j>||����������#t$rY}
d|_&t�?|jd�|t�|
�������Yd}
~
��)d}
~
wwxYw��Dt�|jd�t!|j	�������|j-r-t�dt!|j-����|j0r-t�dt!|j0����|j3r-t�dt!|j3����n�#tjA$ro|j&r|�B���d{V��t�|jd�t!|j	�������YnXt$rL}
t�?|jd�t�|
��������d}
~
wwxYw|rLtj|ddi��d{V��}|D]2}t|t��rt�d|���3nT#|rLtj|ddi��d{V��}|D]3}t|t��rt�d|���3wwxYwddd��n#1swxYwY|j	S)z�
        Process WordPress sites for imunify-security plugin operations.

        Returns:
            set: The set of successfully processed sites.
        r�z'No WordPress sites found, nothing to dorXTNzFailed to send telemetry: %sr�)rMr�r�r�rPrQrUrVzFWAF disabled for user %s, skipping WAF rules deployment for %d site(s)r�r�r�r�)r�r�r�zFailed to authenticate %d sitesz&Failed to install wp-rules on %d sitesz,Failed to install disabled-rules on %d sitesr�r�)r�)Cr|r��messagesr	r�r�rIr�r�r�rrr��
isinstancer�r��formatr�rr�r r�r&rr�r�r�r�r�r�r�r�log_fingerprint_skip_userrvr�r�r)r�r�r�r*r$r�r�r�r�r�rGr�r�rr�r�r�r�plugin_installr�rr�r�r�rr��telemetry_eventr��reprr�r�)r��telemetry_tasks�resultsr�ryrAr�rr�r�r�r�rar�rdr�r�r�rrr�r�s                      rBr�zWordPressSiteInstaller.runhs�
����	���D�M�'�*�+�+�+���
�
�
"�
"�4�>�
2�
2�W	�W	�V
������z�*��K�K� I�J�J�J��>�R#��$+�N�(�%�<@�%�%�������G�#*����%�f�i�8�8��"�N�N� >������kW	�W	�W	�W	�W	�W	�W	�W	�����M�'�*�1�1��D�J���1�H�H����
 ,�~�~��&7�%8�%8�8�8�8�8�8�8��*.�����&�!=�!?�!?�?�?�?�?�?�?��!,�D� 1� 1�
� �J�9�9�D�!�$�(�+�2�2�4�8�8�8�8�#0�"5�"5�"7�"7�O�O�J�C��!�$'�L��$5�$5�	�#,�#4����$�!�!�!�#� �M�+�6�),�U���(+�*/�)�)�
#,�&1�(,�(F�
�
�
�
�!���������!����	+�,C�H�,M�,M�&M�&M�&M�&M�&M�&M����$�+�+�+����:�$�%)�	'����'+����+����'�����?�$���J�J�	���6��	�9�l���������	�&�&�'�%:�(�$C�$C�M� %�b�b��!7��	�4�!H�!H�H�H�H�H�H�H�%�$�^�)-�);�);�D�)A�)A�#A�#A�#A�#A�#A�#A�)� (�15�D�.�):� .� .� (� $� /�)1�
)�)�)�I�.H� $�i�.�.�(�(�(�(�(�(�H�
#8� $� )�*3�)1�	#�#�#��������#<� $� -�*3�)1�	#�#�#��������#3� $� )� $� 2� $� 0�	#�#�������� ,�"��"�&>�$(�$-�$0�$(�$8�$(�$=�'"�'"�!"�!"�!"�!"�!"�!"�!"� +�"�&D�$(�$-�$(�$:�$(�$A�$(�$F�'"�'"�!"�!"�!"�!"�!"�!"�!"� $�2�?�&)�&8��&>�&>� >� >� >� >� >� >� >�-0�,B�4�,H�,H�&H�&H�&H�&H�&H�&H�G�&�P�'-�'@��w�'O�'O��!�7�7��g�F�F�F�15�D�.�+�2�2� '� 3�$-�$8�-1�Y�.2�.B�-1�07�	%&�%&�%&�!"�!"�	�	�	�	�� )����15�D�.�"�L�L� $�
�g� 6� =� =�)-�T�%�[�[�!>�!"�!"�����������������yb�F����M�*�-�4�4�3�t�~�;N�;N�4�O�O�����#���N�N�9��D�,�-�-�����,���N�N�@��D�5�6�6�����5���N�N�F��D�>�?�?������
�)�
�
�
��%�8��5�5�7�7�7�7�7�7�7�7�7�����M�+�.�5�5�!�$�.�1�1�6��������
�
�
�
�����M�+�.�5�5�D��K�K�5�H�H���������	
����#��$+�N�(�%�<@�%�%�������G�#*����%�f�i�8�8��"�N�N� >��������
#��$+�N�(�%�<@�%�%�������G�#*����%�f�i�8�8��"�N�N� >������
�����gW	�W	�W	�W	�W	�W	�W	�W	�W	�W	�W	����W	�W	�W	�W	�r�~�s��];�5W7�A];� C1W7�G.�-W7�.
H7�85H2�-W7�2H7�7W7�;I�W7�)I=�:W7�<I=�=A>W7�<R'�W7�F
R'�%W7�'
T
	�1AT	�?W7�T
	�
C,W7�6\�7A;[	�2\�4	[	�=A[�[	�	\�A];�A],�,];�;]?�]?)�__name__�
__module__�__qualname__�__doc__r�r�rIr�r�r�r�r�r�rr�r�r@rArBr�r��s����������N�,�O�(�I� =��8�K�9�H�
5�
G�
7���H�(1�1�1����81�1�1�$1�1�1�(
A�&�
A�T�
A�
A�
A�
A�c�c�c�c�crAr�c�V��eZdZdZdZdZdZdZdddd	d
ddd
�Z�fd�Z	d�Z
�fd�Z�xZS)r�z�
    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}z�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}r�c���t���||��d�tjtj��D��|_dS)Nc��h|]	}|j��
Sr@)rE)r��rs  rB�	<setcomp>z0WordPressSiteAdopter.__init__.<locals>.<setcomp>os'��"
�"
�"
��A�I�"
�"
�"
rA)�superr�r�selectrE�existing_docroots)r�r�r��	__class__s   �rBr�zWordPressSiteAdopter.__init__lsR���
������u�%�%�%�"
�"
�,�3�M�4I�J�J�"
�"
�"
����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)	r�r�rEr�r,r7r8r5r�r�s   rBr�z+WordPressSiteAdopter._record_processed_sitess���	
����4� � � ��<�4�1�1�1�'��-�-�-� ��&�&�&��
3�#�D�'�2�2�2��"�D�6�*�*�*��*�*�4�0�0�0�0�0rAc����K�t���|���d{V��sdStj|���d{V��}|st�d|��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)r�r�rrr|r�)r�r�r"r�s   �rBr�z"WordPressSiteAdopter.is_site_ready�s�������W�W�*�*�4�0�0�0�0�0�0�0�0�	��5�!�4�T�:�:�:�:�:�:�:�:���	��N�N�D��
�
�
��5��trA)
r�r�r�r�r�r�rIr�r�r�r�r��
__classcell__)r�s@rBr�r�Ns�����������N�"�O�$�I� ;��6�I�5�F�
3�L�
7���H�$
�
�
�
�
�1�1�1�.��������rAr�)r�rG)FN)rEN)NN)�rrr�loggingrr�r �collectionsr�collections.abcrr�distutils.versionr�	functoolsr�pathlibr�defence360agent.apir	� defence360agent.contracts.configr
r~rrr
r�defence360agent.filesrr�defence360agent.sentryr�defence360agent.utilsr�defence360agent.utils.fd_opsrrrrr�defence360agent.subsys.panelsr�"defence360agent.wordpress.wp_rulesrr�defence360agent.model.wordpressrr�&defence360agent.model.wp_disabled_ruler�defence360agent.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�	getLoggerr�r|rCrK�Lockr�r�rWr[rHrLrQrTrX�strr]rgrirf�tupler`rlrprvrx�COMPONENTS_DB_PATHr��dictr�r��
struct_passwdr�r�r�r�r�r�r�r�r�r�rr�intr$r(rr�r/r1r9r�r�r@rGr_rdrgrirkrlrortrvr�r�r�r��floatr�rrnr�r�r�r�r�r�r�r@rArB�<module>rs��������������	�	�	�	�
�
�
�
�����#�#�#�#�#�#�/�/�/�/�/�/�/�/�*�*�*�*�*�*�������������*�*�*�*�*�*���������������2�1�1�1�1�1�1�1�.�.�.�.�.�.�*�*�*�*�*�*�������������7�6�6�6�6�6�7�7�7�7�7�7���������B�A�A�A�A�A�A�A�A�A�A�A�A�A�4�4�4�4�4�4�4�4�C�C�C�C�C�C��������������������������������������������������������� K�J�J�J�J�J�	��	�8�	$�	$����������&�'�,�.�.��"��#��$.�!�$��$�$�$�$�$�$�$�$�$�$��d�����*�t�*�*�*�*�!�c�!�!�!�!�"�� ��-���U�4��t�#3�4�����,�#�,�%��c�	�:J�,�,�,�,��C��D�����
�C��D�����#�3�#�4�#�#�#�#��T�J����
<��<�<�<�<�<�<�"�U�"�t�d�{�"�"�"�"�J&�&�&�
�t�
��(9�
�t�
�
�
�
�  ;��&� ;�6B� ;� ;� ;� ;�F8��0A�8�8�8�8�:�:�d�:�:�:�:�&7�7�7�B!�!�!�
!�
!�
!� �3�����`;�`;�`;�F�F�����>)��)�#�)�)�)�)�X";�";�";�J$
�$
�$
�P26�;�;�#&�v�;�;�;�;�;�<A�D��L�A�A�A�A�R+/� �
���
������
� �4�'���T�k�
�
�����8+/� �
�
�
�
�
��
�� �4�'�	
�
�T�k�
�
�
�
�(+/� ����
����� �4�'�	�
�T�k��
�
����.7�t�F�|�7��7�7�7�7�t!
�
�!
�� �!
��!
��	!
�

�!
�
�
!
�!
�!
�!
�V$�	
�n�n���<�n��	��"�C��-�y���>��n�
�n��
n��n�
�n�n�n�n�b��S�������0#-�%�#-�T�#-�d�#-�#-�#-�#-�L(�7�<�>�>��$��*P�*P�*P�*P�Z	+�	+�	+�	+���������@
�d�6�l�
�t�
�
�
�
�(4�#�4�$�4�4�4�4�n
3�c�
3�d�
3�
3�
3�
3� .�.�.�.�,M�,M�,M�,M�^/��/��/�#�/�/�/�/�0%
�
�%
�� �%
��%
��	%
�

�%
�
�
%
�%
�%
�%
�R!%�	
�/�/�
�#�Y��
�/�
�/�/�/�/�dG�G�G�G�T
�
�
�&�V�&��&�&�&�&�R5�
�5�$'�5�	�5�5�5�5�p:�:�:�za�a�a�a�a�a�a�a�HR�R�R�R�R�1�R�R�R�R�RrAdefence360agent/wordpress/__pycache__/proxy_auth.cpython-311.opt-1.pyc0000644000000000000000000001662300000000000022661 0ustar  �

*��[�(���N�ddlZddlZddlZddlZddlZddlmZmZddlmZddl	m
Z
ddlmZddl
mZmZmZeje��Zed���Zd	Zd
ZdZed�
��Zd�Zd�Zed��defd���Zdededefd�Zdededdfd�Z dej!ddfd�Z"dS)�N)�datetime�	timedelta)�	lru_cache)�Path)�atomic_rewrite)�ensure_site_data_directory�format_php_with_embedded_json�!write_plugin_data_file_atomically�H)�hoursz#/etc/imunify-agent-proxy/jwt-secretz'/etc/imunify-agent-proxy/jwt-secret.oldzimunify-agent-proxy�)�daysc���	tjt��}|j}n#t$rd}YnwxYwtj�����|z
t�	��kS)Ng)
�os�stat�JWT_SECRET_PATH�st_mtime�FileNotFoundErrorr�now�	timestamp�SECRET_EXPIRATION_TTL�
total_seconds)rrs  �Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/proxy_auth.py�is_secret_expiredrs|��!��w��'�'���=����������������	���� � �"�"�X�-�
�
-�
-�
/�
/�	0�s�#�2�2c���K�tt��}	t�d��t	jd��}|j�ddd���|�d���t||dtt��d�	��t�
��dS#t$r(}t�d
|d���Yd}~dSd}~wwxYw)
z�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.
    zRotating proxy auth secret� i�T)�mode�parents�exist_oki�)r���)�uid�backup�permissionsz'Got error while rotating the secret: %s)�exc_infoN)rr�logger�info�secrets�token_bytes�parent�mkdir�touchr�str�JWT_SECRET_PATH_OLD�load_secret_from_file�cache_clear�	Exception�error)�secret_path�stub_secret�es   r�
rotate_secretr5*s����
��'�'�K�
����0�1�1�1��)�"�-�-���� � �e�T�D� �I�I�I����u��%�%�%������*�+�+��	
�	
�	
�	
�	�)�)�+�+�+�+�+���
�
�
����5�q�4�	�	
�	
�	
�	
�	
�	
�	
�	
�	
�����
���s�B B:�:
C,�C'�'C,��returnc�^�	ttd��5}|������cddd��S#1swxYwYdS#t$r"t
�dt���t$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)�openr�read�striprr%r1r0)�fr4s  rr.r.Cs����
�/�4�
(�
(�	$�A��6�6�8�8�>�>�#�#�	$�	$�	$�	$�	$�	$�	$�	$�	$�	$�	$�	$����	$�	$�	$�	$�	$�	$���������6��H�H�H�
��������4�a�8�8�8�
��������s9�A�&A
�A�
A�A�A�A�4B,�B'�'B,�username�docrootc��tj��tz}|||d�}	ddl}|�|t��d���}|S#t$r!}t�d|���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_pathrN�HS256)�	algorithmz Failed to generate JWT token: %s)	r�utcnow�DEFAULT_TOKEN_EXPIRATION�jwt�encoder.r0r%r1)r>r?�exp_time�claimsrG�tokenr4s       r�generate_tokenrLQs����� � �#;�;�H��8�'�
J�
J�F�
�	�
�
�
��
�
�6�#8�#:�#:�g�
�N�N������������7��;�;�;�
��������s�)A
�
A8�A3�3A8rK�gidc��lK�	tj|��}t||���d{V��}|dz}d|i}t|��}t	jt||||���d{V��t�d||��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)�pwd�getpwuidrr	�asyncio�	to_threadr
r%r&r0r1)
�siterKr!rM�	user_info�data_dir�auth_file_path�	auth_data�php_contentr4s
          r�create_auth_php_filerYms������L��%�%�	�4�D�)�D�D�D�D�D�D�D�D��!�J�.���e�$�	�3�I�>�>����-�����
�
�	
�	
�	
�	
�	
�	
�	
�	���5�t�^�	
�	
�	
�	
�	
���������E�t�Q�O�O�O�
��������s�BB�
B3�B.�.B3rTc��2K�	t|jt|j����}t	|||j|j���d{V��t�d|��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)rL�pw_namer,r?rY�pw_uid�pw_gidr%r&r0r1)rSrTrKr4s    r�setup_site_authenticationr^�s�������y�0�#�d�l�2C�2C�D�D��"��%��)�9�+;�
�
�	
�	
�	
�	
�	
�	
�	
�	���D�d�K�K�K�K�K���������=�t�Q�	
�	
�	
�	�����	���s�A$A*�*
B�4B�B)#rQ�loggingrrOr'rr�	functoolsr�pathlibr�defence360agent.utilsr�defence360agent.wordpress.utilsrr	r
�	getLogger�__name__r%rFrr-�PROXY_SERVICE_NAMErrr5�bytesr.r,rL�intrY�
struct_passwdr^��r�<module>rls�����������	�	�	�	�
�
�
�
�����(�(�(�(�(�(�(�(�������������0�0�0�0�0�0�����������
��	�8�	$�	$��$�9�2�.�.�.��7��?��*��!�	�q�)�)�)��
�
�
� 
�
�
�2��1���
�u�
�
�
���
��S��3��3�����8%�C�%�3�%�4�%�%�%�%�P��&��	������rkdefence360agent/wordpress/__pycache__/proxy_auth.cpython-311.pyc0000644000000000000000000001662300000000000021722 0ustar  �

*��[�(���N�ddlZddlZddlZddlZddlZddlmZmZddlmZddl	m
Z
ddlmZddl
mZmZmZeje��Zed���Zd	Zd
ZdZed�
��Zd�Zd�Zed��defd���Zdededefd�Zdededdfd�Z dej!ddfd�Z"dS)�N)�datetime�	timedelta)�	lru_cache)�Path)�atomic_rewrite)�ensure_site_data_directory�format_php_with_embedded_json�!write_plugin_data_file_atomically�H)�hoursz#/etc/imunify-agent-proxy/jwt-secretz'/etc/imunify-agent-proxy/jwt-secret.oldzimunify-agent-proxy�)�daysc���	tjt��}|j}n#t$rd}YnwxYwtj�����|z
t�	��kS)Ng)
�os�stat�JWT_SECRET_PATH�st_mtime�FileNotFoundErrorr�now�	timestamp�SECRET_EXPIRATION_TTL�
total_seconds)rrs  �Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/proxy_auth.py�is_secret_expiredrs|��!��w��'�'���=����������������	���� � �"�"�X�-�
�
-�
-�
/�
/�	0�s�#�2�2c���K�tt��}	t�d��t	jd��}|j�ddd���|�d���t||dtt��d�	��t�
��dS#t$r(}t�d
|d���Yd}~dSd}~wwxYw)
z�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.
    zRotating proxy auth secret� i�T)�mode�parents�exist_oki�)r���)�uid�backup�permissionsz'Got error while rotating the secret: %s)�exc_infoN)rr�logger�info�secrets�token_bytes�parent�mkdir�touchr�str�JWT_SECRET_PATH_OLD�load_secret_from_file�cache_clear�	Exception�error)�secret_path�stub_secret�es   r�
rotate_secretr5*s����
��'�'�K�
����0�1�1�1��)�"�-�-���� � �e�T�D� �I�I�I����u��%�%�%������*�+�+��	
�	
�	
�	
�	�)�)�+�+�+�+�+���
�
�
����5�q�4�	�	
�	
�	
�	
�	
�	
�	
�	
�	
�����
���s�B B:�:
C,�C'�'C,��returnc�^�	ttd��5}|������cddd��S#1swxYwYdS#t$r"t
�dt���t$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)�openr�read�striprr%r1r0)�fr4s  rr.r.Cs����
�/�4�
(�
(�	$�A��6�6�8�8�>�>�#�#�	$�	$�	$�	$�	$�	$�	$�	$�	$�	$�	$�	$����	$�	$�	$�	$�	$�	$���������6��H�H�H�
��������4�a�8�8�8�
��������s9�A�&A
�A�
A�A�A�A�4B,�B'�'B,�username�docrootc��tj��tz}|||d�}	ddl}|�|t��d���}|S#t$r!}t�d|���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_pathrN�HS256)�	algorithmz Failed to generate JWT token: %s)	r�utcnow�DEFAULT_TOKEN_EXPIRATION�jwt�encoder.r0r%r1)r>r?�exp_time�claimsrG�tokenr4s       r�generate_tokenrLQs����� � �#;�;�H��8�'�
J�
J�F�
�	�
�
�
��
�
�6�#8�#:�#:�g�
�N�N������������7��;�;�;�
��������s�)A
�
A8�A3�3A8rK�gidc��lK�	tj|��}t||���d{V��}|dz}d|i}t|��}t	jt||||���d{V��t�d||��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)�pwd�getpwuidrr	�asyncio�	to_threadr
r%r&r0r1)
�siterKr!rM�	user_info�data_dir�auth_file_path�	auth_data�php_contentr4s
          r�create_auth_php_filerYms������L��%�%�	�4�D�)�D�D�D�D�D�D�D�D��!�J�.���e�$�	�3�I�>�>����-�����
�
�	
�	
�	
�	
�	
�	
�	
�	���5�t�^�	
�	
�	
�	
�	
���������E�t�Q�O�O�O�
��������s�BB�
B3�B.�.B3rTc��2K�	t|jt|j����}t	|||j|j���d{V��t�d|��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)rL�pw_namer,r?rY�pw_uid�pw_gidr%r&r0r1)rSrTrKr4s    r�setup_site_authenticationr^�s�������y�0�#�d�l�2C�2C�D�D��"��%��)�9�+;�
�
�	
�	
�	
�	
�	
�	
�	
�	���D�d�K�K�K�K�K���������=�t�Q�	
�	
�	
�	�����	���s�A$A*�*
B�4B�B)#rQ�loggingrrOr'rr�	functoolsr�pathlibr�defence360agent.utilsr�defence360agent.wordpress.utilsrr	r
�	getLogger�__name__r%rFrr-�PROXY_SERVICE_NAMErrr5�bytesr.r,rL�intrY�
struct_passwdr^��r�<module>rls�����������	�	�	�	�
�
�
�
�����(�(�(�(�(�(�(�(�������������0�0�0�0�0�0�����������
��	�8�	$�	$��$�9�2�.�.�.��7��?��*��!�	�q�)�)�)��
�
�
� 
�
�
�2��1���
�u�
�
�
���
��S��3��3�����8%�C�%�3�%�4�%�%�%�%�P��&��	������rkdefence360agent/wordpress/__pycache__/site_repository.cpython-311.opt-1.pyc0000644000000000000000000006355400000000000023727 0ustar  �

X'�q���9����ddlZddlZddlZddlmZddlmZmZmZddl	m
Z
ddlmZm
Z
ddlmZeje��Zed��Zded	eefd
�Zdejd	eefd�Zd	eefd
�Zd	eefd�Zdeed	dfd�Zded	eefd�Zdeded	dfd�Z	d,deed	eefd�Z deded	dfd�Z!ded	dfd�Z"			d-de#dzde#dzde#d	e$e#eeffd�Z%d	e&e#e#ffd�Z'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)�SqliteDatabase�OperationalError�fn)�retry_on)�WPSite�
WordpressSite)�PLUGIN_SLUGzD/var/lib/cloudlinux-app-version-detector/components_versions.sqlite3�path�returnc�<�t���s;t�dt	t����t��St
t���d|�d���}d�|���D��S)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
�p�g|]3}t|d|dt|d�������4S�r��)�docroot�domain�uid�r�int��.0�rows  �^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/site_repository.py�
<listcomp>z%get_sites_by_path.<locals>.<listcomp>I�I������	�s�1�v�c�!�f�#�c�!�f�+�+�>�>�>����)	�COMPONENTS_DB_PATH�exists�logger�error�str�listr�execute_sql�fetchall)r�cursors  r�get_sites_by_pathr(s����$�$�&�&�����;��"�#�#�	
�	
�	
��v�v�
�
�.�
/�
/�
;�
;�	�()-�)	�	�	�!�!�F�D���?�?�$�$����r�	user_infoc��t���r|�;t�dt	t����t��S|�(t�d��t��St
t���d|j�d���}d�|�	��D��S)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
        c��g|]
}|d��S)r�rs  rrz&get_sites_for_user.<locals>.<listcomp>zs��0�0�0�s�C��F�0�0�0r)
rr r!r"r#r$rr%�pw_uidr&)r)r's  r�get_sites_for_userr.Os����$�$�&�&��)�*;����;��"�#�#�	
�	
�	
��v�v�
������5�	
�	
�	
��v�v�
�
�.�
/�
/�
;�
;�	�'�-�		�	�	���F�&1�0�f�o�o�/�/�0�0�0�0rc�b�t���s;t�dt	t����t��St
t���dtj	dd���d���}d�|�
��D��S)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
�p�h|]3}t|d|dt|d�������4Srrrs  r�	<setcomp>z+get_sites_without_plugin.<locals>.<setcomp>�rr�rr r!r"r#�setrr%r
�replacer&�r's r�get_sites_without_pluginr9}����$�$�&�&�����;��"�#�#�	
�	
�	
��u�u��
�.�
/�
/�
;�
;�+	�R0;�/B�3��/L�/L�S+	�+	�+	�-�-�F�\���?�?�$�$����rc���t��}d�tjtj��D����fd�|D��S)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.
    c��h|]	}|j��
Sr,�r�r�rs  rr4z'get_sites_to_install.<locals>.<setcomp>�s'�������	���rc�&��h|]
}|j�v�|��Sr,r=)r�s�existing_docrootss  �rr4z'get_sites_to_install.<locals>.<setcomp>�s-�������1�9�<M�+M�+M��+M�+M�+Mr)r9r	�selectr)�sites_without_pluginrBs @r�get_sites_to_installrE�si���4�5�5����(�/�
�0E�F�F���������'����r�sitesc�n�|sdStjd�|D�������dS)z�
    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.
    Nc�F�g|]}|j|j|j|jdd���S)N)rrr�version�manually_deleted_at)rrrrI�r�sites  rrz*insert_installed_sites.<locals>.<listcomp>�sH��		
�		
�		
��
�+��<��x��<�'+�
�
�		
�		
�		
r)r	�insert_many�execute)rFs r�insert_installed_sitesrO�sP��������		
�		
��		
�		
�		
����g�i�i�i�i�ir�latest_versionc���|st�d��gSd�tj���tj���tj|k��D��S)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 versionc�6�g|]}tj|����Sr,�r�from_wordpress_siter>s  rrz&get_outdated_sites.<locals>.<listcomp>�3�����
�	�"�1�%�%���r)r!r"r	rC�whererJ�is_nullrI)rPs r�get_outdated_sitesrX�s��������F�	
�	
�	
��	����%�'�'�-�-��-�5�5�7�7��!�^�3�
�
����rrL�	timestampc���t�d||��tj|����tj|jk�����dS)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)�rJN�r!�infor	�updaterVrrN)rLrYs  r�mark_site_as_manually_deletedr_	s\���K�K�D������	���;�;�;�	��}�$���4�	5�	5�	������r�freshly_installed_sitesc�2��t��}d�|D��}d�tj���tj�����D���|t
���z}|r|d�|D��z}�fd�|D��S)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
    c��h|]	}|j��
Sr,r=�rrAs  rr4z8get_sites_to_mark_as_manually_deleted.<locals>.<setcomp>-s��G�G�G�Q�q�y�G�G�Grc�B�i|]}|jtj|����Sr,)rrrTr>s  r�
<dictcomp>z9get_sites_to_mark_as_manually_deleted.<locals>.<dictcomp>0s7�����
�	
�	�6�-�a�0�0���rc��h|]	}|j��
Sr,r=rcs  rr4z8get_sites_to_mark_as_manually_deleted.<locals>.<setcomp><s��H�H�H�1�Q�Y�H�H�Hrc� ��h|]
}�|��Sr,r,)r�d�active_db_sitess  �rr4z8get_sites_to_mark_as_manually_deleted.<locals>.<setcomp>>s���9�9�9�1�O�A��9�9�9r)r9r	rCrVrJrWr6)r`rD�docroots_without_plugin�docroots_to_markris    @r�%get_sites_to_mark_as_manually_deletedrls����4�5�5��G�G�2F�G�G�G�����%�'�'�-�-��-�5�5�7�7�
�
����O�/��_�1E�1E�E���I��H�H�0G�H�H�H�H��9�9�9�9�(8�9�9�9�9rrIc��tj|����tj|jk�����dS)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  r�update_site_versionrnAsA�����)�)�)�/�/�����-���
�g�i�i�i�i�irc��tj|j|j����tj|jk�����dS)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^rrrVrrN�rLs r�update_site_identityrqNsG��������:�:�:�@�@�����-���
�g�i�i�i�i�irr�limit�offsetc�~�tj���tj�d����}|�#|�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)
    TNrc�6�g|]}tj|����Sr,rSrKs  rrz1get_installed_sites_paginated.<locals>.<listcomp>xs#��@�@�@�$�V�
'��
-�
-�@�@�@r)	r	rCrVrJrWr�countrrrs)rrrrs�query�total_countrFs      r�get_installed_sites_paginatedry[s��� 
� �"�"�(�(��)�1�1�$�7�7�
�
�E������M�-��4�5�5���+�+�-�-�K������E�"�"��
��z�z����V�$�$��@�@�%�@�@�@�E����rc�t�tjtjtjtj���d�����tj�	d�����
tj�����}d�|D��S)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|d��S)rrvr,rs  rrez0count_installed_sites_by_uid.<locals>.<dictcomp>�s"��6�6�6��C��J��G��6�6�6r)r	rCrr�COUNTr�aliasrVrJrW�group_by�dicts)rws r�count_installed_sites_by_uidr�|s���	�����H�]�*�+�+�1�1�'�:�:�	
�	
�
��}�0�8�8��>�>�	?�	?�	��-�#�	$�	$�	����
�7�6��6�6�6�6rc�(�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  r�get_installed_sitesr��s��-�.�.�H�A�u��Lr�domainsc���|sgSd�tj���tj�d��tj�|����D��S)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
    c�6�g|]}tj|����Sr,rSr>s  rrz2get_installed_sites_by_domains.<locals>.<listcomp>�rUrT)r	rCrVrJrWr�in_)r�s r�get_installed_sites_by_domainsr��ss�����	����%�'�'�-�-��-�5�5�d�;�;�� �$�$�W�-�-�
�
����rc��>K�tjd���d{V��dS)Ng�?)�asyncio�sleep)�	exception�attempts  r�sleep_on_errorr��s.����
�-��
�
���������r�TF)�	max_tries�silent�log�on_errorc��tj���tj|jk�����S)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_siter��s5��&	����	��}�$���4�	5�	5�	����rc�b�t���s;t�dt	t����t��St
t���dtj	dd���d���}d�|�
��D��S)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
�p�h|]3}t|d|dt|d�������4Srrrs  rr4z(get_sites_with_plugin.<locals>.<setcomp>rrr5r8s r�get_sites_with_pluginr��r:rc����t��}d�tjtj���tj�d����D����fd�|D��S)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.
    c��h|]	}|j��
Sr,r=r>s  rr4z%get_sites_to_adopt.<locals>.<setcomp>'s*�����
�	
�	���rTc�&��h|]
}|j�v�|��Sr,r=)rrA�tracked_docrootss  �rr4z%get_sites_to_adopt.<locals>.<setcomp>.s&���N�N�N�!�A�I�=M�,M�,M�A�,M�,M�,Mr)r�r	rCrrVrJrW)�sites_with_pluginr�s @r�get_sites_to_adoptr�s����.�/�/�����%�m�&;�<�<�B�B��-�5�5�d�;�;�
�
�����O�N�N�N�(�N�N�N�Nrc���t�d|��tjd����tj|jk�����dS)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
    z<Clearing manually_deleted_at flag for site %s (plugin found)Nr[r\rps r�clear_manually_deleted_flagr�1sY���K�K�F�����
	���6�6�6�	��}�$���4�	5�	5�	������r)N)NNr)/r��logging�pwd�pathlibr�peeweerrr�defence360agent.utilsr�defence360agent.model.wordpressrr	�#defence360agent.wordpress.constantsr
�	getLogger�__name__r!rr#r$r(�
struct_passwdr.r6r9rErOrX�floatr_rlrnrqr�tuplery�dictr�r�r�r�r�r�r�r�r,rr�<module>r�s�����������
�
�
�
�������7�7�7�7�7�7�7�7�7�7�*�*�*�*�*�*�A�A�A�A�A�A�A�A�;�;�;�;�;�;�	��	�8�	$�	$���T�J����
:�C�:�D��L�:�:�:�:�z+1�#�"3�+1��S�	�+1�+1�+1�+1�\E�#�f�+�E�E�E�E�P�c�&�k�����$�#�f�+��$�����2�s��t�F�|�����2���5��T�����*,0�!:�!:� ��[�!:���[�!:�!:�!:�!:�H
�f�
�s�
�t�
�
�
�
�
�v�
�$�
�
�
�
������	�t�����:��
���3��V����	����B7�d�3��8�n�7�7�7�7�.	�T�&�\�	�	�	�	��D��I��$�v�,�����,���
�����
�
�����f���������$E�s�6�{�E�E�E�E�PO�C��K�O�O�O�O�.�f��������rdefence360agent/wordpress/__pycache__/site_repository.cpython-311.pyc0000644000000000000000000006355400000000000022770 0ustar  �

X'�q���9����ddlZddlZddlZddlmZddlmZmZmZddl	m
Z
ddlmZm
Z
ddlmZeje��Zed��Zded	eefd
�Zdejd	eefd�Zd	eefd
�Zd	eefd�Zdeed	dfd�Zded	eefd�Zdeded	dfd�Z	d,deed	eefd�Z deded	dfd�Z!ded	dfd�Z"			d-de#dzde#dzde#d	e$e#eeffd�Z%d	e&e#e#ffd�Z'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)�SqliteDatabase�OperationalError�fn)�retry_on)�WPSite�
WordpressSite)�PLUGIN_SLUGzD/var/lib/cloudlinux-app-version-detector/components_versions.sqlite3�path�returnc�<�t���s;t�dt	t����t��St
t���d|�d���}d�|���D��S)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
�p�g|]3}t|d|dt|d�������4S�r��)�docroot�domain�uid�r�int��.0�rows  �^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/site_repository.py�
<listcomp>z%get_sites_by_path.<locals>.<listcomp>I�I������	�s�1�v�c�!�f�#�c�!�f�+�+�>�>�>����)	�COMPONENTS_DB_PATH�exists�logger�error�str�listr�execute_sql�fetchall)r�cursors  r�get_sites_by_pathr(s����$�$�&�&�����;��"�#�#�	
�	
�	
��v�v�
�
�.�
/�
/�
;�
;�	�()-�)	�	�	�!�!�F�D���?�?�$�$����r�	user_infoc��t���r|�;t�dt	t����t��S|�(t�d��t��St
t���d|j�d���}d�|�	��D��S)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
        c��g|]
}|d��S)r�rs  rrz&get_sites_for_user.<locals>.<listcomp>zs��0�0�0�s�C��F�0�0�0r)
rr r!r"r#r$rr%�pw_uidr&)r)r's  r�get_sites_for_userr.Os����$�$�&�&��)�*;����;��"�#�#�	
�	
�	
��v�v�
������5�	
�	
�	
��v�v�
�
�.�
/�
/�
;�
;�	�'�-�		�	�	���F�&1�0�f�o�o�/�/�0�0�0�0rc�b�t���s;t�dt	t����t��St
t���dtj	dd���d���}d�|�
��D��S)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
�p�h|]3}t|d|dt|d�������4Srrrs  r�	<setcomp>z+get_sites_without_plugin.<locals>.<setcomp>�rr�rr r!r"r#�setrr%r
�replacer&�r's r�get_sites_without_pluginr9}����$�$�&�&�����;��"�#�#�	
�	
�	
��u�u��
�.�
/�
/�
;�
;�+	�R0;�/B�3��/L�/L�S+	�+	�+	�-�-�F�\���?�?�$�$����rc���t��}d�tjtj��D����fd�|D��S)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.
    c��h|]	}|j��
Sr,�r�r�rs  rr4z'get_sites_to_install.<locals>.<setcomp>�s'�������	���rc�&��h|]
}|j�v�|��Sr,r=)r�s�existing_docrootss  �rr4z'get_sites_to_install.<locals>.<setcomp>�s-�������1�9�<M�+M�+M��+M�+M�+Mr)r9r	�selectr)�sites_without_pluginrBs @r�get_sites_to_installrE�si���4�5�5����(�/�
�0E�F�F���������'����r�sitesc�n�|sdStjd�|D�������dS)z�
    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.
    Nc�F�g|]}|j|j|j|jdd���S)N)rrr�version�manually_deleted_at)rrrrI�r�sites  rrz*insert_installed_sites.<locals>.<listcomp>�sH��		
�		
�		
��
�+��<��x��<�'+�
�
�		
�		
�		
r)r	�insert_many�execute)rFs r�insert_installed_sitesrO�sP��������		
�		
��		
�		
�		
����g�i�i�i�i�ir�latest_versionc���|st�d��gSd�tj���tj���tj|k��D��S)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 versionc�6�g|]}tj|����Sr,�r�from_wordpress_siter>s  rrz&get_outdated_sites.<locals>.<listcomp>�3�����
�	�"�1�%�%���r)r!r"r	rC�whererJ�is_nullrI)rPs r�get_outdated_sitesrX�s��������F�	
�	
�	
��	����%�'�'�-�-��-�5�5�7�7��!�^�3�
�
����rrL�	timestampc���t�d||��tj|����tj|jk�����dS)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)�rJN�r!�infor	�updaterVrrN)rLrYs  r�mark_site_as_manually_deletedr_	s\���K�K�D������	���;�;�;�	��}�$���4�	5�	5�	������r�freshly_installed_sitesc�2��t��}d�|D��}d�tj���tj�����D���|t
���z}|r|d�|D��z}�fd�|D��S)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
    c��h|]	}|j��
Sr,r=�rrAs  rr4z8get_sites_to_mark_as_manually_deleted.<locals>.<setcomp>-s��G�G�G�Q�q�y�G�G�Grc�B�i|]}|jtj|����Sr,)rrrTr>s  r�
<dictcomp>z9get_sites_to_mark_as_manually_deleted.<locals>.<dictcomp>0s7�����
�	
�	�6�-�a�0�0���rc��h|]	}|j��
Sr,r=rcs  rr4z8get_sites_to_mark_as_manually_deleted.<locals>.<setcomp><s��H�H�H�1�Q�Y�H�H�Hrc� ��h|]
}�|��Sr,r,)r�d�active_db_sitess  �rr4z8get_sites_to_mark_as_manually_deleted.<locals>.<setcomp>>s���9�9�9�1�O�A��9�9�9r)r9r	rCrVrJrWr6)r`rD�docroots_without_plugin�docroots_to_markris    @r�%get_sites_to_mark_as_manually_deletedrls����4�5�5��G�G�2F�G�G�G�����%�'�'�-�-��-�5�5�7�7�
�
����O�/��_�1E�1E�E���I��H�H�0G�H�H�H�H��9�9�9�9�(8�9�9�9�9rrIc��tj|����tj|jk�����dS)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  r�update_site_versionrnAsA�����)�)�)�/�/�����-���
�g�i�i�i�i�irc��tj|j|j����tj|jk�����dS)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^rrrVrrN�rLs r�update_site_identityrqNsG��������:�:�:�@�@�����-���
�g�i�i�i�i�irr�limit�offsetc�~�tj���tj�d����}|�#|�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)
    TNrc�6�g|]}tj|����Sr,rSrKs  rrz1get_installed_sites_paginated.<locals>.<listcomp>xs#��@�@�@�$�V�
'��
-�
-�@�@�@r)	r	rCrVrJrWr�countrrrs)rrrrs�query�total_countrFs      r�get_installed_sites_paginatedry[s��� 
� �"�"�(�(��)�1�1�$�7�7�
�
�E������M�-��4�5�5���+�+�-�-�K������E�"�"��
��z�z����V�$�$��@�@�%�@�@�@�E����rc�t�tjtjtjtj���d�����tj�	d�����
tj�����}d�|D��S)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|d��S)rrvr,rs  rrez0count_installed_sites_by_uid.<locals>.<dictcomp>�s"��6�6�6��C��J��G��6�6�6r)r	rCrr�COUNTr�aliasrVrJrW�group_by�dicts)rws r�count_installed_sites_by_uidr�|s���	�����H�]�*�+�+�1�1�'�:�:�	
�	
�
��}�0�8�8��>�>�	?�	?�	��-�#�	$�	$�	����
�7�6��6�6�6�6rc�(�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  r�get_installed_sitesr��s��-�.�.�H�A�u��Lr�domainsc���|sgSd�tj���tj�d��tj�|����D��S)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
    c�6�g|]}tj|����Sr,rSr>s  rrz2get_installed_sites_by_domains.<locals>.<listcomp>�rUrT)r	rCrVrJrWr�in_)r�s r�get_installed_sites_by_domainsr��ss�����	����%�'�'�-�-��-�5�5�d�;�;�� �$�$�W�-�-�
�
����rc��>K�tjd���d{V��dS)Ng�?)�asyncio�sleep)�	exception�attempts  r�sleep_on_errorr��s.����
�-��
�
���������r�TF)�	max_tries�silent�log�on_errorc��tj���tj|jk�����S)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_siter��s5��&	����	��}�$���4�	5�	5�	����rc�b�t���s;t�dt	t����t��St
t���dtj	dd���d���}d�|�
��D��S)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
�p�h|]3}t|d|dt|d�������4Srrrs  rr4z(get_sites_with_plugin.<locals>.<setcomp>rrr5r8s r�get_sites_with_pluginr��r:rc����t��}d�tjtj���tj�d����D����fd�|D��S)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.
    c��h|]	}|j��
Sr,r=r>s  rr4z%get_sites_to_adopt.<locals>.<setcomp>'s*�����
�	
�	���rTc�&��h|]
}|j�v�|��Sr,r=)rrA�tracked_docrootss  �rr4z%get_sites_to_adopt.<locals>.<setcomp>.s&���N�N�N�!�A�I�=M�,M�,M�A�,M�,M�,Mr)r�r	rCrrVrJrW)�sites_with_pluginr�s @r�get_sites_to_adoptr�s����.�/�/�����%�m�&;�<�<�B�B��-�5�5�d�;�;�
�
�����O�N�N�N�(�N�N�N�Nrc���t�d|��tjd����tj|jk�����dS)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
    z<Clearing manually_deleted_at flag for site %s (plugin found)Nr[r\rps r�clear_manually_deleted_flagr�1sY���K�K�F�����
	���6�6�6�	��}�$���4�	5�	5�	������r)N)NNr)/r��logging�pwd�pathlibr�peeweerrr�defence360agent.utilsr�defence360agent.model.wordpressrr	�#defence360agent.wordpress.constantsr
�	getLogger�__name__r!rr#r$r(�
struct_passwdr.r6r9rErOrX�floatr_rlrnrqr�tuplery�dictr�r�r�r�r�r�r�r�r,rr�<module>r�s�����������
�
�
�
�������7�7�7�7�7�7�7�7�7�7�*�*�*�*�*�*�A�A�A�A�A�A�A�A�;�;�;�;�;�;�	��	�8�	$�	$���T�J����
:�C�:�D��L�:�:�:�:�z+1�#�"3�+1��S�	�+1�+1�+1�+1�\E�#�f�+�E�E�E�E�P�c�&�k�����$�#�f�+��$�����2�s��t�F�|�����2���5��T�����*,0�!:�!:� ��[�!:���[�!:�!:�!:�!:�H
�f�
�s�
�t�
�
�
�
�
�v�
�$�
�
�
�
������	�t�����:��
���3��V����	����B7�d�3��8�n�7�7�7�7�.	�T�&�\�	�	�	�	��D��I��$�v�,�����,���
�����
�
�����f���������$E�s�6�{�E�E�E�E�PO�C��K�O�O�O�O�.�f��������rdefence360agent/wordpress/__pycache__/telemetry.cpython-311.opt-1.pyc0000644000000000000000000000204600000000000022463 0ustar  �

!�E�G�ld��\�ddlZddlmZddlmZeje��Zddededefd�Z	dS)	�N)�MessageType)�WPSite�event�site�versionc	��K�|�d}|�tj||j|j|j|������d{V��dS)Nz1.0.0)r�domain�	site_path�user�plugin_version)�process_messager�WordpressPluginTelemetryr	�docroot�uid)�sinkrrrs    �X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/telemetry.py�
send_eventrsz��������
�
�
��,���;��l���"�	
�	
�	
������������)N)
�logging�"defence360agent.contracts.messagesr�defence360agent.model.wordpressr�	getLogger�__name__�logger�strr�rr�<module>rs|������:�:�:�:�:�:�2�2�2�2�2�2�	��	�8�	$�	$����#��V��c������rdefence360agent/wordpress/__pycache__/telemetry.cpython-311.pyc0000644000000000000000000000204600000000000021524 0ustar  �

!�E�G�ld��\�ddlZddlmZddlmZeje��Zddededefd�Z	dS)	�N)�MessageType)�WPSite�event�site�versionc	��K�|�d}|�tj||j|j|j|������d{V��dS)Nz1.0.0)r�domain�	site_path�user�plugin_version)�process_messager�WordpressPluginTelemetryr	�docroot�uid)�sinkrrrs    �X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/telemetry.py�
send_eventrsz��������
�
�
��,���;��l���"�	
�	
�	
������������)N)
�logging�"defence360agent.contracts.messagesr�defence360agent.model.wordpressr�	getLogger�__name__�logger�strr�rr�<module>rs|������:�:�:�:�:�:�2�2�2�2�2�2�	��	�8�	$�	$����#��V��c������rdefence360agent/wordpress/__pycache__/utils.cpython-311.opt-1.pyc0000644000000000000000000006602200000000000021615 0ustar  �

qd4�$����ddlZddlZddlZddlZddlZddlZddlZddlmZddl	m	Z	m
Z
ddlmZm
Z
ddlmZddlmZddlmZmZddlmZdd	lmZdd
lmZddlmZmZmZm 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.ddd���Z/ed���Z0ed���Z1ed���Z2e3d��Z4ej5e6��Z7de8de9fd�Z:ed���de;e9e<e9ffd���Z=de9d e9de<fd!�Z>e
d"�#��de?fd$���Z@d%�ZAd&e9d'e<de<fd(�ZBd e9d)e9de<e9fd*�ZCd+e'd&e9dee9fd,�ZDd&e9de<fd-�ZEd&e9de;fd.�ZFd/�ZGde;e9e9dzffd0�ZH	dId1eId2eId&e9d+e'd3e;d4e;e9e9dzfdzde;fd5�ZJd&e9de;fd6�ZKd&e9de;fd7�ZLdd8�d9e9d:eMd;eMd<eMdzddf
d=�ZNd>e9de9fd?�ZOd@e9de9fdA�ZPdBe;de9fdC�ZQd9e9de;fdD�ZRdEed:eMd;eMd<eMddf
dF�ZSd+e'dGejTdefdH�ZUdS)J�N)�defaultdict)�datetime�	timedelta)�cache�	lru_cache)�Path)�Optional)�choose_value_from_config�MalwareScanScheduleInterval)�
LicenseCLN)�HostingPanel)�Plesk)�IMUNIFY_PACKAGE_NAMES�async_lru_cache�atomic_rewrite�	check_run�importer�system_packages_info)�open_dir_no_symlinks�safe_dir)�WPSite)�WP_CLI_WRAPPER_PATH)�PHPErrorz/usr/sbin/cagefs_enter_userz/usr/sbin/cagefsctlzimav.malwarelib.model�
MalwareHit��module�name�defaultc�0�tjddd���S)Nz*imav.malwarelib.scan.queue_supervisor_sync�QueueSupervisorSyncr�r�get���T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/utils.py�_queue_supervisor_clsr&,s$���<�;�
"�����r$c�0�tjddd���S)N�imav.malwarelib.utils.user_list�fetch_user_listrr!r#r$r%�_fetch_user_list_fnr*5s$���<�0�
�����r$c�0�tjddd���S)Nr(�sortrr!r#r$r%�_sort_user_list_fnr->s$���<�0�
�����r$)�balanced�strict�monitor�value�returnc�F�t|t��r|tvr|SdS)u�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.
    r.)�
isinstance�str�_VALID_PRESETS)r1s r%�_validate_presetr7Ls*���%�����%�>�"9�"9����:r$�<)�ttlc��K�t��}|����d{V��}tt��}|���D]%\}}|D]}||�|����&|S)zN
    Get a mapping of docroots to their associated domains, with caching.
    N)r
�get_domain_pathsr�list�items�append)�
hosting_panel�panel_paths�docroot_map�domain�docroots�docroots      r%r;r;[s�����
!�N�N�M�%�6�6�8�8�8�8�8�8�8�8�K��d�#�#�K�'�-�-�/�/�0�0�����	0�	0�G��� �'�'��/�/�/�/�	0��r$�php_pathrDc�0�tt��||gS)zGet wp cli common command list)r5r)rErDs  r%�
wp_wrapperrGis���#�$�$�h��8�8r$�)�maxsizec��tj�t��r$tjttj��st
��Stjtdgdd���}|j	dkrt
��S|j
����d��}t
|dd���S)z)Get the list of users enabled for CageFS.z--list-enabledT)�capture_output�textr�
rHN)
�os�path�isfile�CAGEFS_CTL_PATH�access�X_OK�set�
subprocess�run�
returncode�stdout�strip�split)�result�liness  r%�get_cagefs_enabled_usersr]ns����7�>�>�/�*�*��"�)����3�3���u�u��
�^�	�*�+�D�t����F���A����u�u���M���!�!�'�'��-�-�E��u�Q�R�R�y�>�>�r$c�8�t���dS)z-Clear the cache for get_cagefs_enabled_users.N)r]�cache_clearr#r$r%�$clear_get_cagefs_enabled_users_cacher`�s���(�(�*�*�*�*�*r$�username�argsc��|t��vrTtj�t��r0tjttj��rtd|g|�Sddd|dtj|��gS)zNBuild the necessary command to run the given cmdline args with specified user.z--no-io-and-memory-limit�suz-sz	/bin/bashz-c)	r]rNrOrP�CAGEFS_ENTER_PATHrRrS�shlex�join)rarbs  r%�build_command_for_userrh�s����+�-�-�-�-�
�7�>�>�+�,�,�	����r�w�2
�2
�	�"�*����	�
�	
�����
�
�4���
�r$�domain_to_excludec��x�K�t���d{V��}|�|g��}�fd�|D��S)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%�
<listcomp>z+get_domains_for_docroot.<locals>.<listcomp>�s$���L�L�L�v��:K�0K�0K�F�0K�0K�0Kr$)r;r")rDrirA�all_domainss `  r%�get_domains_for_docrootro�sS�����)�*�*�*�*�*�*�*�*�K��/�/�'�2�.�.�K�L�L�L�L��L�L�L�Lr$�sitec��P���	K�ddlm}m}|���|���	dtdttf��	�fd�}||j��}|r|St
|j|j����d{V��}|D]}||��}|r|cS�td|j�d	������)
z/Determine PHP binary path for the given WPSite.r)�get_domains_php_info�get_installed_php_versionsrBr2c�����|��}|r|�d���krdS|�d��}|sdS�D]2}|�d��|kr|�d��cS�3dS)Nra�display_version�
identifier�bin)r")rB�domain_info�php_display_version�php_version�domains_php_info�installed_php_versionsras    ���r%�find_php_binary_for_domainz7get_php_binary_path.<locals>.find_php_binary_for_domain�s����&�*�*�6�2�2���	�k�o�o�j�9�9�X�E�E��4�)�o�o�.?�@�@��"�	��4�1�	.�	.�K����|�,�,�0C�C�C�"���u�-�-�-�-�-�D��tr$)riNz+PHP binary was not identified for docroot: z, username: )	�clcommon.cpapirrrsr5r	rBrorDr)
rprarrrsr}�php_binary_path�domainsrBr{r|s
 `      @@r%�get_php_binary_pathr��sX���������������
,�+�-�-��7�7�9�9���3��8�C�=���������1�0���=�=�O�����,��������������G��#�#��4�4�V�<�<���	#�"�"�"�"�	#��	�d�l�	�	��	�	���r$c��t�t�d��gSt�|���\}}|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)r�logger�debug�malicious_list)ra�	max_count�hitss   r%�get_malware_historyr��sJ�������L�	
�	
�	
��	�"�1�1�x�1�@�@��Y���Kr$c�� K�t��}t��}t��}|�|�|�t�d��iS||��}||j|h����d{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-r�r��get_scans_from_paths)�sinkra�queue_supervisor_clsr)�sort_user_list�queue�_�userss        r%�
get_last_scanr��s�����1�2�2��)�+�+�O�'�)�)�N��$��"��!����F�	
�	
�	
��	� � ��&�&�E�$�_�
�"�8�*����������H�A�u����	��N�5�+�D�9�9�9�E���8�Or$c	�h��tj��}|tjkrF|�|ddd���}||kr|td���z
}|���S|tjkrt||���dzdzz
dzdz}|dkr
|j	|krd}|t|���z}|�|ddd������S|tj
kr�ddlm��fd�}|j
|kp5|j
|ko
|j	|kp|�|j|j��dk}	|	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)�hour�minute�second�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#)�year�monthr��current_year�
current_month�
days_in_monthr�s      �r%�find_next_suitable_monthz?calculate_next_scan_timestamp.<locals>.find_next_suitable_month/s����*.��-�L�
�Q��M��r�!�!� !�
���!��
&� *�
�<�� G� G�� J�
��=�(�(�'��6�6���"�
� �2�%�%�$%�M� �A�%�L�
&r$)�dayr�r�r�r�r�r�)r�r�r�r�r�N)r�utcnow�Interval�DAY�replacer�	timestamp�WEEK�weekdayr��MONTH�calendarr�r�r�r�)
�intervalr��day_of_month�day_of_week�today�	next_scan�
days_ahead�next_scan_dater��should_advance_month�	next_year�
next_monthr�s
            @r%�calculate_next_scan_timestampr�s9���
�O���E��8�<����M�M�����	"�
�
�	��I������*�*�*�*�I��"�"�$�$�$��8�=� � �"�U�]�]�_�_�q�%8�A�$=�=��A�Q�F�
���?�?�u�z�T�1�1��J���
�!;�!;�!;�;���%�%��a��q�&�
�
�
�)�+�+�	��8�>�!�!�'�'�'�'�'�'�	&�	&�	&�	&�	&�0
�I��$�
E��	�\�)�@�e�j�D�.@�
E��j�j���U�[�A�A�!�D�D�
	� �	�$<�$<��
�E�K��%�%�!�I�z�#�]�]� � ������+���N�N�#�]�]� �����+���N��'�'�)�)�)�w"�!r$c��:K�tt���d{V��S)z�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).
    N)rrr#r$r%�get_imunify_package_versionsr�js)����&�&;�<�<�<�<�<�<�<�<�<r$�last_scan_time�next_scan_time�malware_by_site�versionsc��gd�}i}|D]B\}}	||vri||<	t||	|���\}
}n#t$rd}
YnwxYw|
|||	<�C||||�|jg��|t	j��d�}|�||d<|S)aE
    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
    ))�MALWARE_SCANNING�enable_scan_cpanel)r��default_action)�PROACTIVE_DEFENCE�blamer�raN)�lastScanTimestamp�nextScanTimestampra�malware�config�licenser�)r
�KeyErrorr"rDr�license_info)
r�r�rarpr�r��config_sections�config_items�section�optionr1r�r[s
             r%�prepare_scan_datar�ts���@���O��L�*�.�.�����,�&�&�$&�L��!�	�/���!����H�E�1�1��
�	�	�	��E�E�E�	����(-��W��f�%�%�,�+��"�&�&�t�|�R�8�8���*�,�,�
��F���%��z���Ms�.�=�=c���	tdd|���\}}n#t$rd}YnwxYw	tdd|���\}}n#t$rd}YnwxYwt|��t|��d�S)z�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.
    �	WORDPRESS�ai_bot_protectionr�F�ai_bot_protection_presetr.)r��preset)r
r��boolr7)rar�r�r�s    r%�_prepare_ai_bot_settingsr��s���"�7���� 
� 
� 
���1�1��
�"�"�"�!����"�����,��&��
�
�
�	�����
������������"�"3�4�4�"�6�*�*���s��'�'�A�A�Ac�P�t|��}tj��|d<|S)u�
    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".
    �license_type)r�r�get_license_type)ra�settingss  r%�prepare_plugin_configr��s*��B(��1�1�H�)�:�<�<�H�^���Or$)�dir_fd�content�uid�gidr�c
�
�t��jtjkrdnd}|�t||d||||���dSt	|j��5}t||d||||���ddd��dS#1swxYwYdS)z�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.
    i �NF)�backupr�r��permissionsr�)r
�NAMErrr�parent)�	file_pathr�r�r�r�r��owned_dir_fds       r%�!write_plugin_data_file_atomicallyr��s���(�>�>�.�%�*�<�<�%�%�%�K�
��������#��	
�	
�	
�	
�	��	�)�"�	#�	#�	
�|�������#��	
�	
�	
�	
�	
�	
�	
�	
�	
�	
�	
�	
�	
�	
�	
�	
����	
�	
�	
�	
�	
�	
s�A8�8A<�?A<�json_strc�V�|�dd���dd��S)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�)r�s r%�)_escape_json_for_php_single_quoted_stringr�s*�����D�&�)�)�1�1�#�u�=�=�=r$�escapedc�V�|�dd���dd��S)z\
    Reverse the escaping applied by :func:`_escape_json_for_php_single_quoted_string`.
    r�r�r�r�r�)r�s r%� _unescape_php_single_quoted_jsonr�,s(���?�?�5�#�&�&�.�.�v�t�<�<�<r$�datac�P�dttj|����zdzS)al
    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
    zB<?php
if ( ! defined( 'WPINC' ) ) {
	exit;
}
return json_decode( 'z
', true );)r��json�dumps)r�s r%�format_php_with_embedded_jsonr�3s3��	 �
4�D�J�t�4D�4D�
E�
E�	F��
	�r$c�*�d}|�|��}|dkrtd���|t|��z
}|�d|��}|dkrtd���t|||���}t	j|��S)a)
    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
    zjson_decode( '���z%No embedded JSON found in PHP contentz	', true )z&Malformed embedded JSON in PHP content)�find�
ValueError�lenr�r��loads)r��marker�start�endr�s     r%�parse_php_with_embedded_jsonrLs����F��L�L�� � �E���{�{��@�A�A�A�	�S��[�[��E�
�,�,�{�E�
*�
*�C�
�b�y�y��A�B�B�B�/���c�	�0B�C�C�H��:�h���r$�data_dirc�x�dddd�}|���D]\}}||z}t|||||����dS)a}
    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.
    z2DirectoryIndex index.php index.html
deny from all
z+<?php
// This file is intentionally blank.
z+<!-- This file is intentionally blank. -->
)z	.htaccessz	index.phpz
index.html�r�r�r�N)r=r�)rr�r�r��protection_files�filenamer�r�s        r%�#ensure_directory_listing_protectionrisx��L�D�D����.�3�3�5�5�
�
���'��x�'�	�)��w�C�S��	
�	
�	
�	
�	
�
�
r$�	user_infoc	��*K�ddlm}|�|���d{V��}d}	t|��}n�#t$r�t|jddt|��g��}t|���d{V��	t|��}n[#t$rN}|j
tjtjfvrtd|�d���|�td	|�d
|����|�d}~wwxYwd}YnEt$r9}|j
tjtjfvrtd|�d���|��d}~wwxYw	|rtj|d��t!||j|j|�
��tj|��n#tj|��wxYw|S)a�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
    r)�cliNF�mkdirz-pzData directory z is a symlink, skipping.zFailed to open data directory z: Ti�r	)�defence360agent.wordpressr�get_data_dirr�FileNotFoundErrorrh�pw_namer5r�OSError�errno�ELOOP�ENOTDIR�	ExceptionrN�chmodrr��pw_gid�close)rpr
rr�
newly_createdr��command�excs        r%�ensure_site_data_directoryr �s%����&.�-�-�-�-�-��%�%�d�+�+�+�+�+�+�+�+�H��M��%�h�/�/��������)���
�d�C��M�M�*�
�
���� � � � � � � � � �		�)�(�3�3�F�F���	�	�	��y�U�[�%�-�8�8�8��H�h�H�H�H������B��B�B�S�B�B����
�����	�����
�
�
������9���e�m�4�4�4��D�(�D�D�D����
�	�����������	$��H�V�U�#�#�#�+��$�(�	�(8��	
�	
�	
�	
�	�������������������OsM�7�AD-�<B�D-�
C$�A	C�C$�$D-�+	D-�44D(�(D-�14E:�:F)N)Vrr��loggingrN�pwdrfrU�collectionsrrr�	functoolsrr�pathlibr�typingr	� defence360agent.contracts.configr
rr��!defence360agent.contracts.licenser�+defence360agent.subsys.panels.hosting_panelr
�#defence360agent.subsys.panels.pleskr�defence360agent.utilsrrrrrr�defence360agent.utils.fd_opsrr�defence360agent.model.wordpressr�#defence360agent.wordpress.constantsr�#defence360agent.wordpress.exceptionrrerQr"rr&r*r-�	frozensetr6�	getLogger�__name__r��objectr5r7�dictr<r;rGrTr]r`rhror�r�r�r�r��floatr�r�r��intr�r�r�r�rr�
struct_passwdr r#r$r%�<module>r8s?��������������	�	�	�	�
�
�
�
���������#�#�#�#�#�#�(�(�(�(�(�(�(�(�&�&�&�&�&�&�&�&���������������������9�8�8�8�8�8�D�D�D�D�D�D�5�5�5�5�5�5�����������������H�G�G�G�G�G�G�G�2�2�2�2�2�2�C�C�C�C�C�C�8�8�8�8�8�8�1��'��
�X�\�"��t����
�
��������������������<�=�=��	��	�8�	$�	$���F��s�������R����
��S�$�s�)�^� 4�
�
�
���
�9��9�s�9�t�9�9�9�9�
��1�����#�������"+�+�+�
�S���������.	M�
�	M�%(�	M�	�#�Y�	M�	M�	M�	M�)�F�)�c�)�h�s�m�)�)�)�)�X�#��$�����"��������Ba*�a*�a*�H=�D��c�D�j��,A�=�=�=�=� .2�
@�@��@��@��@��	@�
�@��3��d�
�?�#�d�*�
@�
�@�@�@�@�F�s��t�����<#�C�#�D�#�#�#�#�NJN� 
� 
� 
�� 
�"%� 
�,/� 
�<?�$�J� 
�	� 
� 
� 
� 
�F>��>��>�>�>�>�=�c�=�c�=�=�=�=���������2 �#� �$� � � � �:
��
��
�#&�
�36�
�	�
�
�
�
�0>�
�>� �.�>�	�>�>�>�>�>�>r$defence360agent/wordpress/__pycache__/utils.cpython-311.pyc0000644000000000000000000006602200000000000020656 0ustar  �

qd4�$����ddlZddlZddlZddlZddlZddlZddlZddlmZddl	m	Z	m
Z
ddlmZm
Z
ddlmZddlmZddlmZmZddlmZdd	lmZdd
lmZddlmZmZmZm 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.ddd���Z/ed���Z0ed���Z1ed���Z2e3d��Z4ej5e6��Z7de8de9fd�Z:ed���de;e9e<e9ffd���Z=de9d e9de<fd!�Z>e
d"�#��de?fd$���Z@d%�ZAd&e9d'e<de<fd(�ZBd e9d)e9de<e9fd*�ZCd+e'd&e9dee9fd,�ZDd&e9de<fd-�ZEd&e9de;fd.�ZFd/�ZGde;e9e9dzffd0�ZH	dId1eId2eId&e9d+e'd3e;d4e;e9e9dzfdzde;fd5�ZJd&e9de;fd6�ZKd&e9de;fd7�ZLdd8�d9e9d:eMd;eMd<eMdzddf
d=�ZNd>e9de9fd?�ZOd@e9de9fdA�ZPdBe;de9fdC�ZQd9e9de;fdD�ZRdEed:eMd;eMd<eMddf
dF�ZSd+e'dGejTdefdH�ZUdS)J�N)�defaultdict)�datetime�	timedelta)�cache�	lru_cache)�Path)�Optional)�choose_value_from_config�MalwareScanScheduleInterval)�
LicenseCLN)�HostingPanel)�Plesk)�IMUNIFY_PACKAGE_NAMES�async_lru_cache�atomic_rewrite�	check_run�importer�system_packages_info)�open_dir_no_symlinks�safe_dir)�WPSite)�WP_CLI_WRAPPER_PATH)�PHPErrorz/usr/sbin/cagefs_enter_userz/usr/sbin/cagefsctlzimav.malwarelib.model�
MalwareHit��module�name�defaultc�0�tjddd���S)Nz*imav.malwarelib.scan.queue_supervisor_sync�QueueSupervisorSyncr�r�get���T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/utils.py�_queue_supervisor_clsr&,s$���<�;�
"�����r$c�0�tjddd���S)N�imav.malwarelib.utils.user_list�fetch_user_listrr!r#r$r%�_fetch_user_list_fnr*5s$���<�0�
�����r$c�0�tjddd���S)Nr(�sortrr!r#r$r%�_sort_user_list_fnr->s$���<�0�
�����r$)�balanced�strict�monitor�value�returnc�F�t|t��r|tvr|SdS)u�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.
    r.)�
isinstance�str�_VALID_PRESETS)r1s r%�_validate_presetr7Ls*���%�����%�>�"9�"9����:r$�<)�ttlc��K�t��}|����d{V��}tt��}|���D]%\}}|D]}||�|����&|S)zN
    Get a mapping of docroots to their associated domains, with caching.
    N)r
�get_domain_pathsr�list�items�append)�
hosting_panel�panel_paths�docroot_map�domain�docroots�docroots      r%r;r;[s�����
!�N�N�M�%�6�6�8�8�8�8�8�8�8�8�K��d�#�#�K�'�-�-�/�/�0�0�����	0�	0�G��� �'�'��/�/�/�/�	0��r$�php_pathrDc�0�tt��||gS)zGet wp cli common command list)r5r)rErDs  r%�
wp_wrapperrGis���#�$�$�h��8�8r$�)�maxsizec��tj�t��r$tjttj��st
��Stjtdgdd���}|j	dkrt
��S|j
����d��}t
|dd���S)z)Get the list of users enabled for CageFS.z--list-enabledT)�capture_output�textr�
rHN)
�os�path�isfile�CAGEFS_CTL_PATH�access�X_OK�set�
subprocess�run�
returncode�stdout�strip�split)�result�liness  r%�get_cagefs_enabled_usersr]ns����7�>�>�/�*�*��"�)����3�3���u�u��
�^�	�*�+�D�t����F���A����u�u���M���!�!�'�'��-�-�E��u�Q�R�R�y�>�>�r$c�8�t���dS)z-Clear the cache for get_cagefs_enabled_users.N)r]�cache_clearr#r$r%�$clear_get_cagefs_enabled_users_cacher`�s���(�(�*�*�*�*�*r$�username�argsc��|t��vrTtj�t��r0tjttj��rtd|g|�Sddd|dtj|��gS)zNBuild the necessary command to run the given cmdline args with specified user.z--no-io-and-memory-limit�suz-sz	/bin/bashz-c)	r]rNrOrP�CAGEFS_ENTER_PATHrRrS�shlex�join)rarbs  r%�build_command_for_userrh�s����+�-�-�-�-�
�7�>�>�+�,�,�	����r�w�2
�2
�	�"�*����	�
�	
�����
�
�4���
�r$�domain_to_excludec��x�K�t���d{V��}|�|g��}�fd�|D��S)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%�
<listcomp>z+get_domains_for_docroot.<locals>.<listcomp>�s$���L�L�L�v��:K�0K�0K�F�0K�0K�0Kr$)r;r")rDrirA�all_domainss `  r%�get_domains_for_docrootro�sS�����)�*�*�*�*�*�*�*�*�K��/�/�'�2�.�.�K�L�L�L�L��L�L�L�Lr$�sitec��P���	K�ddlm}m}|���|���	dtdttf��	�fd�}||j��}|r|St
|j|j����d{V��}|D]}||��}|r|cS�td|j�d	������)
z/Determine PHP binary path for the given WPSite.r)�get_domains_php_info�get_installed_php_versionsrBr2c�����|��}|r|�d���krdS|�d��}|sdS�D]2}|�d��|kr|�d��cS�3dS)Nra�display_version�
identifier�bin)r")rB�domain_info�php_display_version�php_version�domains_php_info�installed_php_versionsras    ���r%�find_php_binary_for_domainz7get_php_binary_path.<locals>.find_php_binary_for_domain�s����&�*�*�6�2�2���	�k�o�o�j�9�9�X�E�E��4�)�o�o�.?�@�@��"�	��4�1�	.�	.�K����|�,�,�0C�C�C�"���u�-�-�-�-�-�D��tr$)riNz+PHP binary was not identified for docroot: z, username: )	�clcommon.cpapirrrsr5r	rBrorDr)
rprarrrsr}�php_binary_path�domainsrBr{r|s
 `      @@r%�get_php_binary_pathr��sX���������������
,�+�-�-��7�7�9�9���3��8�C�=���������1�0���=�=�O�����,��������������G��#�#��4�4�V�<�<���	#�"�"�"�"�	#��	�d�l�	�	��	�	���r$c��t�t�d��gSt�|���\}}|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)r�logger�debug�malicious_list)ra�	max_count�hitss   r%�get_malware_historyr��sJ�������L�	
�	
�	
��	�"�1�1�x�1�@�@��Y���Kr$c�� K�t��}t��}t��}|�|�|�t�d��iS||��}||j|h����d{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-r�r��get_scans_from_paths)�sinkra�queue_supervisor_clsr)�sort_user_list�queue�_�userss        r%�
get_last_scanr��s�����1�2�2��)�+�+�O�'�)�)�N��$��"��!����F�	
�	
�	
��	� � ��&�&�E�$�_�
�"�8�*����������H�A�u����	��N�5�+�D�9�9�9�E���8�Or$c	�h��tj��}|tjkrF|�|ddd���}||kr|td���z
}|���S|tjkrt||���dzdzz
dzdz}|dkr
|j	|krd}|t|���z}|�|ddd������S|tj
kr�ddlm��fd�}|j
|kp5|j
|ko
|j	|kp|�|j|j��dk}	|	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)�hour�minute�second�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#)�year�monthr��current_year�
current_month�
days_in_monthr�s      �r%�find_next_suitable_monthz?calculate_next_scan_timestamp.<locals>.find_next_suitable_month/s����*.��-�L�
�Q��M��r�!�!� !�
���!��
&� *�
�<�� G� G�� J�
��=�(�(�'��6�6���"�
� �2�%�%�$%�M� �A�%�L�
&r$)�dayr�r�r�r�r�r�)r�r�r�r�r�N)r�utcnow�Interval�DAY�replacer�	timestamp�WEEK�weekdayr��MONTH�calendarr�r�r�r�)
�intervalr��day_of_month�day_of_week�today�	next_scan�
days_ahead�next_scan_dater��should_advance_month�	next_year�
next_monthr�s
            @r%�calculate_next_scan_timestampr�s9���
�O���E��8�<����M�M�����	"�
�
�	��I������*�*�*�*�I��"�"�$�$�$��8�=� � �"�U�]�]�_�_�q�%8�A�$=�=��A�Q�F�
���?�?�u�z�T�1�1��J���
�!;�!;�!;�;���%�%��a��q�&�
�
�
�)�+�+�	��8�>�!�!�'�'�'�'�'�'�	&�	&�	&�	&�	&�0
�I��$�
E��	�\�)�@�e�j�D�.@�
E��j�j���U�[�A�A�!�D�D�
	� �	�$<�$<��
�E�K��%�%�!�I�z�#�]�]� � ������+���N�N�#�]�]� �����+���N��'�'�)�)�)�w"�!r$c��:K�tt���d{V��S)z�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).
    N)rrr#r$r%�get_imunify_package_versionsr�js)����&�&;�<�<�<�<�<�<�<�<�<r$�last_scan_time�next_scan_time�malware_by_site�versionsc��gd�}i}|D]B\}}	||vri||<	t||	|���\}
}n#t$rd}
YnwxYw|
|||	<�C||||�|jg��|t	j��d�}|�||d<|S)aE
    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
    ))�MALWARE_SCANNING�enable_scan_cpanel)r��default_action)�PROACTIVE_DEFENCE�blamer�raN)�lastScanTimestamp�nextScanTimestampra�malware�config�licenser�)r
�KeyErrorr"rDr�license_info)
r�r�rarpr�r��config_sections�config_items�section�optionr1r�r[s
             r%�prepare_scan_datar�ts���@���O��L�*�.�.�����,�&�&�$&�L��!�	�/���!����H�E�1�1��
�	�	�	��E�E�E�	����(-��W��f�%�%�,�+��"�&�&�t�|�R�8�8���*�,�,�
��F���%��z���Ms�.�=�=c���	tdd|���\}}n#t$rd}YnwxYw	tdd|���\}}n#t$rd}YnwxYwt|��t|��d�S)z�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.
    �	WORDPRESS�ai_bot_protectionr�F�ai_bot_protection_presetr.)r��preset)r
r��boolr7)rar�r�r�s    r%�_prepare_ai_bot_settingsr��s���"�7���� 
� 
� 
���1�1��
�"�"�"�!����"�����,��&��
�
�
�	�����
������������"�"3�4�4�"�6�*�*���s��'�'�A�A�Ac�P�t|��}tj��|d<|S)u�
    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".
    �license_type)r�r�get_license_type)ra�settingss  r%�prepare_plugin_configr��s*��B(��1�1�H�)�:�<�<�H�^���Or$)�dir_fd�content�uid�gidr�c
�
�t��jtjkrdnd}|�t||d||||���dSt	|j��5}t||d||||���ddd��dS#1swxYwYdS)z�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.
    i �NF)�backupr�r��permissionsr�)r
�NAMErrr�parent)�	file_pathr�r�r�r�r��owned_dir_fds       r%�!write_plugin_data_file_atomicallyr��s���(�>�>�.�%�*�<�<�%�%�%�K�
��������#��	
�	
�	
�	
�	��	�)�"�	#�	#�	
�|�������#��	
�	
�	
�	
�	
�	
�	
�	
�	
�	
�	
�	
�	
�	
�	
�	
����	
�	
�	
�	
�	
�	
s�A8�8A<�?A<�json_strc�V�|�dd���dd��S)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�)r�s r%�)_escape_json_for_php_single_quoted_stringr�s*�����D�&�)�)�1�1�#�u�=�=�=r$�escapedc�V�|�dd���dd��S)z\
    Reverse the escaping applied by :func:`_escape_json_for_php_single_quoted_string`.
    r�r�r�r�r�)r�s r%� _unescape_php_single_quoted_jsonr�,s(���?�?�5�#�&�&�.�.�v�t�<�<�<r$�datac�P�dttj|����zdzS)al
    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
    zB<?php
if ( ! defined( 'WPINC' ) ) {
	exit;
}
return json_decode( 'z
', true );)r��json�dumps)r�s r%�format_php_with_embedded_jsonr�3s3��	 �
4�D�J�t�4D�4D�
E�
E�	F��
	�r$c�*�d}|�|��}|dkrtd���|t|��z
}|�d|��}|dkrtd���t|||���}t	j|��S)a)
    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
    zjson_decode( '���z%No embedded JSON found in PHP contentz	', true )z&Malformed embedded JSON in PHP content)�find�
ValueError�lenr�r��loads)r��marker�start�endr�s     r%�parse_php_with_embedded_jsonrLs����F��L�L�� � �E���{�{��@�A�A�A�	�S��[�[��E�
�,�,�{�E�
*�
*�C�
�b�y�y��A�B�B�B�/���c�	�0B�C�C�H��:�h���r$�data_dirc�x�dddd�}|���D]\}}||z}t|||||����dS)a}
    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.
    z2DirectoryIndex index.php index.html
deny from all
z+<?php
// This file is intentionally blank.
z+<!-- This file is intentionally blank. -->
)z	.htaccessz	index.phpz
index.html�r�r�r�N)r=r�)rr�r�r��protection_files�filenamer�r�s        r%�#ensure_directory_listing_protectionrisx��L�D�D����.�3�3�5�5�
�
���'��x�'�	�)��w�C�S��	
�	
�	
�	
�	
�
�
r$�	user_infoc	��*K�ddlm}|�|���d{V��}d}	t|��}n�#t$r�t|jddt|��g��}t|���d{V��	t|��}n[#t$rN}|j
tjtjfvrtd|�d���|�td	|�d
|����|�d}~wwxYwd}YnEt$r9}|j
tjtjfvrtd|�d���|��d}~wwxYw	|rtj|d��t!||j|j|�
��tj|��n#tj|��wxYw|S)a�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
    r)�cliNF�mkdirz-pzData directory z is a symlink, skipping.zFailed to open data directory z: Ti�r	)�defence360agent.wordpressr�get_data_dirr�FileNotFoundErrorrh�pw_namer5r�OSError�errno�ELOOP�ENOTDIR�	ExceptionrN�chmodrr��pw_gid�close)rpr
rr�
newly_createdr��command�excs        r%�ensure_site_data_directoryr �s%����&.�-�-�-�-�-��%�%�d�+�+�+�+�+�+�+�+�H��M��%�h�/�/��������)���
�d�C��M�M�*�
�
���� � � � � � � � � �		�)�(�3�3�F�F���	�	�	��y�U�[�%�-�8�8�8��H�h�H�H�H������B��B�B�S�B�B����
�����	�����
�
�
������9���e�m�4�4�4��D�(�D�D�D����
�	�����������	$��H�V�U�#�#�#�+��$�(�	�(8��	
�	
�	
�	
�	�������������������OsM�7�AD-�<B�D-�
C$�A	C�C$�$D-�+	D-�44D(�(D-�14E:�:F)N)Vrr��loggingrN�pwdrfrU�collectionsrrr�	functoolsrr�pathlibr�typingr	� defence360agent.contracts.configr
rr��!defence360agent.contracts.licenser�+defence360agent.subsys.panels.hosting_panelr
�#defence360agent.subsys.panels.pleskr�defence360agent.utilsrrrrrr�defence360agent.utils.fd_opsrr�defence360agent.model.wordpressr�#defence360agent.wordpress.constantsr�#defence360agent.wordpress.exceptionrrerQr"rr&r*r-�	frozensetr6�	getLogger�__name__r��objectr5r7�dictr<r;rGrTr]r`rhror�r�r�r�r��floatr�r�r��intr�r�r�r�rr�
struct_passwdr r#r$r%�<module>r8s?��������������	�	�	�	�
�
�
�
���������#�#�#�#�#�#�(�(�(�(�(�(�(�(�&�&�&�&�&�&�&�&���������������������9�8�8�8�8�8�D�D�D�D�D�D�5�5�5�5�5�5�����������������H�G�G�G�G�G�G�G�2�2�2�2�2�2�C�C�C�C�C�C�8�8�8�8�8�8�1��'��
�X�\�"��t����
�
��������������������<�=�=��	��	�8�	$�	$���F��s�������R����
��S�$�s�)�^� 4�
�
�
���
�9��9�s�9�t�9�9�9�9�
��1�����#�������"+�+�+�
�S���������.	M�
�	M�%(�	M�	�#�Y�	M�	M�	M�	M�)�F�)�c�)�h�s�m�)�)�)�)�X�#��$�����"��������Ba*�a*�a*�H=�D��c�D�j��,A�=�=�=�=� .2�
@�@��@��@��@��	@�
�@��3��d�
�?�#�d�*�
@�
�@�@�@�@�F�s��t�����<#�C�#�D�#�#�#�#�NJN� 
� 
� 
�� 
�"%� 
�,/� 
�<?�$�J� 
�	� 
� 
� 
� 
�F>��>��>�>�>�>�=�c�=�c�=�=�=�=���������2 �#� �$� � � � �:
��
��
�#&�
�36�
�	�
�
�
�
�0>�
�>� �.�>�	�>�>�>�>�>�>r$defence360agent/wordpress/__pycache__/wp_rules.cpython-311.opt-1.pyc0000644000000000000000000001237500000000000022317 0ustar  �

����n($���dZddlZddlZddlZddlmZddlmZeje	��Z
dZdZdede
d	edzfd
�Zded	edzfd�Zded	edzfd
�Zded	e
fd�ZdS)z�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.
�N)�Path)�Indexzwp-rules.zip�VERSION�index�filename�returnc�4�|���D]N}|d|kr@t|�|d����}|���r|cS�Ot�d||�|j����dS)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
    �name�urlz%s not found in %sN)�itemsr�
localfilepath�exists�logger�error�
files_path�type)rr�item�	file_paths    �W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/wp_rules.py�find_file_in_indexrs������
�
�!�!����<�8�#�#��U�0�0��e��=�=�>�>�I����!�!�
!� � � � ��
�L�L�%�x��1A�1A�%�*�1M�1M�N�N�N��4��zip_pathc���	tj|d��5}|�d��5}tj|��}ddd��n#1swxYwYddd��n#1swxYwYnJ#tjttjf$r&}t�	d|��Yd}~dSd}~wwxYwt|t��st�	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)�zipfile�ZipFile�open�yaml�	safe_load�
BadZipFile�KeyError�	YAMLErrorrr�
isinstance�dict)r�zip_file�	yaml_file�
rules_data�es     r�extract_wp_rules_yamlr),sb���
�_�X�s�
+�
+�	7�x�����/�/�
7�9�!�^�I�6�6�
�
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�������C�Q�G�G�G��t�t�t�t�t����������j�$�'�'�����7��D�D�D��t��s]�A1�A%�A�A%�A	�A%�A	�A%�A1�%A)�)A1�,A)�-A1�1!B8�B3�3B8c��t|t��}|sdSt|��}|sdSt�d��|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)r�WP_RULES_ZIP_FILENAMEr)r�info)rrr's   r�get_wp_rules_datar-DsV��""�%�)>�?�?�H����t�'�x�0�0�J����t�
�K�K�3�4�4�4��rc�"�t|t��}|sdS	|������}t�d|��|S#t$r&}t�d|��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)r�WP_RULES_VERSION_FILENAME�	read_text�striprr,�	Exceptionr)r�version_path�version_stringr(s    r�get_wp_ruleset_versionr6bs���&�e�-F�G�G�L����t��%�/�/�1�1�7�7�9�9�����<�n�M�M�M�����������6��:�:�:��t�t�t�t�t��������s�AA�
B�(B	�	B)�__doc__�loggingrr�pathlibr�defence360agent.filesr�	getLogger�__name__rr+r0�strrr$r)r-r6�rr�<module>r?s����������������������'�'�'�'�'�'�	��	�8�	$�	$��'��%���e��s��t�d�{�����*�D��T�D�[�����0�U��t�d�{�����<�%��C������rdefence360agent/wordpress/__pycache__/wp_rules.cpython-311.pyc0000644000000000000000000001237500000000000021360 0ustar  �

����n($���dZddlZddlZddlZddlmZddlmZeje	��Z
dZdZdede
d	edzfd
�Zded	edzfd�Zded	edzfd
�Zded	e
fd�ZdS)z�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.
�N)�Path)�Indexzwp-rules.zip�VERSION�index�filename�returnc�4�|���D]N}|d|kr@t|�|d����}|���r|cS�Ot�d||�|j����dS)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
    �name�urlz%s not found in %sN)�itemsr�
localfilepath�exists�logger�error�
files_path�type)rr�item�	file_paths    �W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/wp_rules.py�find_file_in_indexrs������
�
�!�!����<�8�#�#��U�0�0��e��=�=�>�>�I����!�!�
!� � � � ��
�L�L�%�x��1A�1A�%�*�1M�1M�N�N�N��4��zip_pathc���	tj|d��5}|�d��5}tj|��}ddd��n#1swxYwYddd��n#1swxYwYnJ#tjttjf$r&}t�	d|��Yd}~dSd}~wwxYwt|t��st�	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)�zipfile�ZipFile�open�yaml�	safe_load�
BadZipFile�KeyError�	YAMLErrorrr�
isinstance�dict)r�zip_file�	yaml_file�
rules_data�es     r�extract_wp_rules_yamlr),sb���
�_�X�s�
+�
+�	7�x�����/�/�
7�9�!�^�I�6�6�
�
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�������C�Q�G�G�G��t�t�t�t�t����������j�$�'�'�����7��D�D�D��t��s]�A1�A%�A�A%�A	�A%�A	�A%�A1�%A)�)A1�,A)�-A1�1!B8�B3�3B8c��t|t��}|sdSt|��}|sdSt�d��|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)r�WP_RULES_ZIP_FILENAMEr)r�info)rrr's   r�get_wp_rules_datar-DsV��""�%�)>�?�?�H����t�'�x�0�0�J����t�
�K�K�3�4�4�4��rc�"�t|t��}|sdS	|������}t�d|��|S#t$r&}t�d|��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)r�WP_RULES_VERSION_FILENAME�	read_text�striprr,�	Exceptionr)r�version_path�version_stringr(s    r�get_wp_ruleset_versionr6bs���&�e�-F�G�G�L����t��%�/�/�1�1�7�7�9�9�����<�n�M�M�M�����������6��:�:�:��t�t�t�t�t��������s�AA�
B�(B	�	B)�__doc__�loggingrr�pathlibr�defence360agent.filesr�	getLogger�__name__rr+r0�strrr$r)r-r6�rr�<module>r?s����������������������'�'�'�'�'�'�	��	�8�	$�	$��'��%���e��s��t�d�{�����*�D��T�D�[�����0�U��t�d�{�����<�%��C������rdefence360agent/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:
    <?php __halt_compiler();
    #{base64-encoded JSON for action 1}
    #{base64-encoded JSON for action 2}

Each JSON action has the form:
    {"action": "disable"|"enable", "rule_id": "xyz", "ts": ...}

The user_id stored with each action is the system UID of the WordPress site
owner (site.uid).
"""

import errno
import logging
import os
from pathlib import Path

from defence360agent.contracts.messages import MessageType
from defence360agent.contracts.permissions import (
    WP_WAF_RULES_EDIT,
    has_permission,
)
from defence360agent.contracts.plugins import MessageSink
from defence360agent.model.wordpress import WPSite, WordpressSite
from defence360agent.model.wp_disabled_rule import WPDisabledRule
from defence360agent.utils.fd_ops import open_nofollow
from defence360agent.wordpress.cli import get_data_dir
from defence360agent.wordpress.incident_parser import IncidentFileParser
from defence360agent.wordpress.utils import parse_php_with_embedded_json

logger = logging.getLogger(__name__)

CHANGELOG_FILENAME = "changelog.php"
DISABLED_RULES_FILENAME = "disabled-rules.php"

ACTION_DISABLE = "disable"
ACTION_ENABLE = "enable"


class ChangelogProcessor:
    """Process WordPress rule disable/enable changelog files.

    Reads changelog.php from each site's data directory, applies
    disable/enable actions to the WPDisabledRule database, reports events
    to the correlation server, and deletes the file after processing.

    If no changelog exists (or no new entries), checks whether
    disabled-rules.php has been modified externally (e.g. backup restore)
    and flags the domain for regeneration.
    """

    def __init__(self) -> 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.py0000644000000000000000000004072700000000000017025 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 (
    upsert_wordpress_incident,
    bulk_create_wordpress_incidents,
    build_incident_dict,
    country_reader,
)

logger = logging.getLogger(__name__)


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
    ) -> tuple[bool, str]:
        """
        Check if adding an incident would exceed rate limits.

        Args:
            rule_id: Rule identifier
            attacker_ip: IP address of the attacker

        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
        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()

    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.

        Only returns files older than one hour to give the WordPress plugin
        time to process and finalize the incident data before collection.

        Args:
            data_dir: Path to the imunify-security data directory

        Returns:
            List of incident file paths, sorted by modification time
        """
        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
    _FILE_PATTERN = re.compile(r"^\d{4}-\d{2}-\d{2}-\d{2}\.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
        """
        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:
            incidents = self.parser.parse_file(incident_file)

            if not incidents:
                logger.warning(
                    "No valid incidents in file %s",
                    incident_file.name,
                )
                if delete_after_processing:
                    incident_file.unlink(missing_ok=True)

                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:
                incident_file.unlink(missing_ok=True)
                logger.debug("Deleted processed file %s", incident_file.name)

            return collected_incidents

        except Exception as e:
            logger.error(
                "Error processing incident file %s for site %s: %s",
                incident_file.name,
                site,
                e,
            )
            return []

    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 = []
        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,
                )

                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)
                self.rate_limiter.record_incident(rule_id, attacker_ip)

        # Bulk insert all incidents in a single transaction
        created_incidents = []
        if incidents_to_insert:
            try:
                created_incidents = bulk_create_wordpress_incidents(
                    incidents_to_insert
                )
            except Exception as e:
                logger.error(
                    "Failed to bulk insert incidents from %s: %s",
                    incident_file_name,
                    e,
                )

        logger.info(
            "Processed file %s: %d stored, %d dropped",
            incident_file_name,
            len(created_incidents),
            dropped_count,
        )

        return created_incidents

    def _process_incident(
        self,
        incident: dict,
        site: WPSite,
        username: str | None,
        incident_file_name: str,
    ):
        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,
        )

        if not allowed:
            logger.warning(
                "Rate limit exceeded for site %s: %s",
                site,
                reason,
            )
            return None

        return self._store_incident(
            incident,
            site,
            username,
            rule_id,
            attacker_ip,
            incident_file_name,
        )

    def _store_incident(
        self,
        incident: dict,
        site: WPSite,
        username: str | None,
        rule_id: str,
        attacker_ip: str,
        incident_file_name: str,
    ):
        try:
            site_info = {
                "domain": site.domain,
                "site_path": site.docroot,
                "username": username,
                "user_id": site.uid,
            }
            incident = upsert_wordpress_incident(
                incident,
                site_info,
            )

            self.rate_limiter.record_incident(rule_id, attacker_ip)
            return incident

        except Exception as e:
            logger.error(
                "Failed to store incident from %s: %s",
                incident_file_name,
                e,
            )
            return None
defence360agent/wordpress/incident_parser.py0000644000000000000000000001003300000000000016316 0ustar  """Parser for WordPress plugin incident files."""

import base64
import json
import logging
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:
    <?php __halt_compiler();
    #{base64-encoded JSON data for incident}
    #{base64-encoded JSON data for incident}
    ...

    File pattern: wp-content/imunify-security/incidents/yyyy-mm-dd-hh.php
    """

    @classmethod
    def parse_file(cls, file_path: Path) -> list[dict]:
        """Parse an incident file and return list of incident dictionaries.

        The file format is:
        - First line: <?php __halt_compiler();
        - Following lines: #{base64-encoded JSON}

        Opens with O_NOFOLLOW to prevent reading arbitrary files if
        the incident file was replaced with a symlink.
        """
        incidents = []

        try:
            with open_nofollow(str(file_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:
                    for line_num, line in enumerate(f, 1):
                        line = line.strip()
                        incident = cls._process_line(line, line_num, file_path)
                        if incident is not None:
                            incidents.append(incident)
        except Exception as e:
            logger.error(
                "Error reading incident file %s: %s",
                file_path,
                e,
            )
            return []

        return incidents

    @classmethod
    def _process_line(
        cls, line: str, line_num: int, file_path: Path
    ) -> 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("<?php"):
            logger.debug(
                "Skipping PHP header line %d in %s",
                line_num,
                file_path.name,
            )
            return None

        # Lines should start with # followed by base64-encoded JSON
        if not line.startswith("#"):
            logger.debug(
                "Line %d in %s doesn't start with #: %s",
                line_num,
                file_path.name,
                line[:50],
            )
            return None

        # Remove the # prefix
        encoded_data = line[1:]

        return cls._process_encoded_line(encoded_data, line_num, file_path)

    @classmethod
    def _process_encoded_line(
        cls, encoded_data: str, line_num: int, file_path: Path
    ) -> 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 isinstance(incident, dict):
                return incident

            logger.warning(
                "Line %d in %s is not a JSON object: %s",
                line_num,
                file_path.name,
                decoded_str[:100],
            )
            return None

        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
defence360agent/wordpress/incident_sender.py0000644000000000000000000001254400000000000016313 0ustar  """Send WordPress incidents to the correlation server."""

import json
import logging
from datetime import datetime
from typing import Any

from defence360agent.contracts.messages import SensorWordpressIncidentList
from defence360agent.contracts.plugins import MessageSink

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
    automatically handled by SendToServer/SendToServerFGW plugins.
    """

    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 "",
            "retries": incident.get("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_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
        ]

        await self._send_batch(sink, correlation_batch)

        return len(correlation_batch)

    async def _send_batch(
        self, sink: MessageSink, correlation_batch: list[dict]
    ):
        """
        Send a batch of incidents to correlation server.

        Uses SensorIncidentList Reportable message which is automatically
        sent to correlation via SendToServer/SendToServerFGW plugins.

        Args:
            sink: MessageSink to send the batch to
            correlation_batch: Incidents formatted for correlation server
        """
        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),
        )
        try:
            # Send as a Reportable message
            # The SendToServer/SendToServerFGW plugin will handle actual delivery
            await sink.process_message(
                SensorWordpressIncidentList(correlation_batch)
            )

            logger.info(
                "Queued %d wordpress incident(s) for correlation server",
                len(correlation_batch),
            )

        except Exception as e:
            logger.error(
                "Failed to queue incident batch: %s",
                e,
            )
            raise
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 ``<site data dir>/<filename>``.

    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 (
        "<?php\n"
        "if ( ! defined( 'WPINC' ) ) {\n"
        "\texit;\n"
        "}\n"
        "return json_decode( '"
        + _escape_json_for_php_single_quoted_string(json.dumps(data))
        + "', true );"
    )


def parse_php_with_embedded_json(content: str) -> 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": "<?php\n// This file is intentionally blank.\n",
        "index.html": "<!-- This file is intentionally blank. -->\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.0-py3.11.egg-info/0000755000000000000000000000000000000000000013544 5ustar  imunify_core-8.12.0-py3.11.egg-info/PKG-INFO0000644000000000000000000000027100000000000014641 0ustar  Metadata-Version: 2.1
Name: imunify-core
Version: 8.12.0
Summary: ImunifyCore
Home-page: http://www.imunify360.com
Author: Cloud Linux Zug GmbH
Author-email: imunify-dev@imunify360.com
imunify_core-8.12.0-py3.11.egg-info/SOURCES.txt0000644000000000000000000006374300000000000015445 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/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/__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.0-py3.11.egg-info/dependency_links.txt0000644000000000000000000000000100000000000017612 0ustar  
imunify_core-8.12.0-py3.11.egg-info/top_level.txt0000644000000000000000000000002000000000000016266 0ustar  defence360agent