4ݮ%xddlZddlZddlZddlZddlZddlZddlZddlmZddl m Z m Z ddl m Z ddl mZddlmZddlmZmZddlmZdd lmZdd lmZdd lmZmZmZmZm Z dd l!m"Z"m#Z#dd l$m%Z%ddl&m'Z'ddl(m)Z)dZ*dZ+e,dZ-ej.e/Z0de1de2fdZ3edde4e2e5e2ffdZ6de2de2de5fdZ7e dde8fdZ9dZ:d e2d!e5de5fd"Z;de2d#e2de5e2fd$Zd e2de4fd(Z?d)Z@de4e2e2dzffd*ZA dBd+eBd,eBd e2d%e%d-e4d.e4e2e2dzfdzde4fd/ZCd e2de4fd0ZDdd1d2e2d3eEd4eEd5eEdzddf d6ZFd7e2de2fd8ZGd9e2de2fd:ZHd;e4de2fd<ZId2e2de4fd=ZJd>ed3eEd4eEd5eEddf d?ZKd%e%d@ejLdefdAZMdS)CN) defaultdict)datetime timedelta) lru_cache)Path)Optional)choose_value_from_configMalwareScanScheduleInterval) LicenseCLN) HostingPanel)Plesk)IMUNIFY_PACKAGE_NAMESasync_lru_cacheatomic_rewrite check_runsystem_packages_info)open_dir_no_symlinkssafe_dir)WPSite)WP_CLI_WRAPPER_PATH)PHPErrorz/usr/sbin/cagefs_enter_userz/usr/sbin/cagefsctl)balancedstrictmonitorvaluereturncFt|tr |tvr|SdS)uCoerce a config-read preset value to a canonical preset string. Returns "balanced" for anything outside _VALID_PRESETS — including None, non-strings, and hand-edited values like "extreme" or "BALANCED". The agent always writes lowercase canonical values, so a non-canonical read indicates either a manual edit or a future preset that this version doesn't recognise; "balanced" is the safe fallback in both cases. r) isinstancestr_VALID_PRESETS)rs T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/utils.py_validate_presetr"+s*%%>"9"9 :<)ttlcKt}|d{V}tt}|D]%\}}|D]}|||&|S)zN Get a mapping of docroots to their associated domains, with caching. N)r get_domain_pathsrlistitemsappend) hosting_panel panel_paths docroot_mapdomaindocrootsdocroots r!r'r':s !NNM%6688888888Kd##K'--//00 0 0G  ' ' / / / / 0 r#php_pathr0c0tt||gS)zGet wp cli common command list)rr)r1r0s r! wp_wrapperr3Hs # $ $h 88r#)maxsizectjtr$tjttjst Stjtdgdd}|j dkrt S|j d}t |ddS)z)Get the list of users enabled for CageFS.z--list-enabledT)capture_outputtextr r4N) ospathisfileCAGEFS_CTL_PATHaccessX_OKset subprocessrun returncodestdoutstripsplit)resultliness r!get_cagefs_enabled_usersrIMs 7>>/ * *")33uu ^ *+DtFAuu M   ! ! ' ' - -E uQRRy>>r#c8tdS)z-Clear the cache for get_cagefs_enabled_users.N)rI cache_clearr#r!$clear_get_cagefs_enabled_users_cacherM_s((*****r#usernameargsc|tvrTtjtr0tjttjr td|g|Sddd|dtj|gS)zNBuild the necessary command to run the given cmdline args with specified user.z--no-io-and-memory-limitsuz-sz /bin/bashz-c) rIr:r;r<CAGEFS_ENTER_PATHr>r?shlexjoin)rNrOs r!build_command_for_userrUds+---- 7>>+ , ,  rw2 2  "*      4  r#domain_to_excludecxKtd{V}||g}fd|DS)z Get all domains associated with a given document root, excluding one domain. It's panel-agnostic and uses a cached mapping. Nc g|] }|k| SrLrL).0r.rVs r! z+get_domains_for_docroot..s$ L L Lv:K0K0KF0K0K0Kr#)r'get)r0rVr- all_domainss ` r!get_domains_for_docrootr]{sS)********K//'2..K L L L L L L LLr#sitecP Kddlm}m}|| dtdttf fd }||j}|r|St |j|jd{V}|D]}||}|r|cStd|jd ) z/Determine PHP binary path for the given WPSite.r)get_domains_php_infoget_installed_php_versionsr.rc|}|r|dkrdS|d}|sdSD]2}|d|kr|dcS3dS)NrNdisplay_version identifierbin)r[)r. domain_infophp_display_version php_versiondomains_php_infoinstalled_php_versionsrNs r!find_php_binary_for_domainz7get_php_binary_path..find_php_binary_for_domains&**622  kooj99XEE4)oo.?@@" 41 . .K|,,0CCC"u-----Dtr#)rVNz+PHP binary was not identified for docroot: z , username: ) clcommon.cpapir`rarrr.r]r0r) r^rNr`rarkphp_binary_pathdomainsr.rirjs ` @@r!get_php_binary_pathrosX ,+--7799 3 8C=        10==O,  G##44V<<  #" " " " #  dl       r#c ddlm}||\}}|S#t$rtdgcYSwxYw)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. r) MalwareHit)userz>imav.malwarelib not available, returning empty malware history)imav.malwarelib.modelrqmalicious_list ImportErrorloggerdebug)rNrq max_counthitss r!get_malware_historyrzsx 444444&5585DDD  L    s #&A  A c$K ddlm}ddlm}n,#t$rt dicYSwxYw||}||j|hd{V\}}|siS| |dd }|dS) z Get the last scan for the specified user. This is an equivalent of calling `imunify360-agent malware user list --user {username}`. Returns empty dict if imav malware module is not available. r)QueueSupervisorSync) user_listz8imav.malwarelib not available, returning empty last scan)matchN scan_dateT)desc) *imav.malwarelib.scan.queue_supervisor_syncr|imav.malwarelib.utilsr}rurvrwfetch_user_listget_scans_from_pathssort)sinkrN ScanQueuer}queue_userss r! get_last_scanrs        4333333  F     IdOOE.. "8*/HAu  NN5+DN 9 9E 8Os &::c h tj}|tjkrF||ddd}||kr|t dz }|S|tjkrt||dzdzz dzdz}|dkr |j |krd}|t |z}||dddS|tj krddl m  fd}|j |kp5|j |ko |j |kp| |j|jdk} | r7||j|j|\} } ||| | |ddd}n|||ddd }|Sd S) a Calculate the next scan timestamp based on schedule configuration. Args: interval: Scan interval (DAY, WEEK, MONTH, or NONE) hour: Hour of day to run scan (0-23) day_of_month: Day of month to run scan (1-31) day_of_week: Day of week to run scan (0-6, where 0=Sunday) Returns: Timestamp of next scan, or None if interval is NONE r)hourminutesecond microsecondr4)days) monthrangec||}}|dz }|dkrd}|dz } ||d}||kr||fS|dz }|dkrd}|dz }/)z;Find the next month that has at least given number of days.r4 rL)yearmonthr current_year current_month days_in_monthrs r!find_next_suitable_monthz?calculate_next_scan_timestamp..find_next_suitable_months*.-L Q Mr!! ! !  & * < G G J =(('66"  2%%$%M A%L &r#)dayrrrrrr)rrrrrN)rutcnowIntervalDAYreplacer timestampWEEKweekdayrMONTHcalendarrrrr) intervalr day_of_month day_of_weektoday next_scan days_aheadnext_scan_datershould_advance_month next_year next_monthrs @r!calculate_next_scan_timestamprs9 O  E8<MM "  I   *** *I""$$$8=  "U]]__q%8A$==AQF ??uzT11J !;!;!;;%%aq&  )++ 8>!!'''''' & & & & &0 I $ E \)@ejD.@ EjjU[AA!DD   $<$< EK%% !Iz#]]  +NN#]]  +N'')))w"!r#c:Kttd{VS)zFetch installed versions of Imunify packages. Returns a dict mapping package name to version string, with None for packages that are not installed. Intended to be called once per sync cycle (not per site). N)rrrLr#r!get_imunify_package_versionsrJs)&&;<< < < < < < <>.%*<<%%%K   #      )" # #  |  #                       sA88A<?A<json_strcV|ddddS)a Escape a JSON string for embedding inside a PHP single-quoted string. PHP single-quoted strings only recognise two escape sequences: ``\\`` (literal backslash) and ``\'`` (literal single quote). All other backslash sequences are kept verbatim. That means we must double every ``\`` *before* we escape ``'``, otherwise PHP will consume JSON backslashes (e.g. ``\\s`` in JSON becomes ``\s`` after PHP parsing, which is not a valid JSON escape). \\\'\'r)rs r!)_escape_json_for_php_single_quoted_stringrs*   D& ) ) 1 1#u = ==r#escapedcV|ddddS)z\ Reverse the escaping applied by :func:`_escape_json_for_php_single_quoted_string`. rrrrr)rs r! _unescape_php_single_quoted_jsonrs( ??5# & & . .vt < < )z .htaccessz index.phpz index.htmlrrrN)r)r)rrrrprotection_filesfilenamerrs r!#ensure_directory_listing_protectionr:sxLDD .3355  'x' ) wCS       r# user_infoc *Kddlm}||d{V}d} t|}n#t$rt |jddt|g}t|d{V t|}n[#t$rN}|j tj tj fvrtd|d|td |d ||d}~wwxYwd }YnEt$r9}|j tj tj fvrtd|d|d}~wwxYw |rtj|d t!||j|j| tj|n#tj|wxYw|S)aEnsure the site's data directory exists with correct permissions. The directory is opened with symlink protection after creation (or if it already exists) to obtain a stable file descriptor. All subsequent operations use that descriptor. Args: site: WordPress site user_info: User information from pwd Returns: Path to data directory Raises: Exception: If the data directory is a symlink or cannot be created r)cliNFmkdirz-pzData directory z is a symlink, skipping.zFailed to open data directory z: Tir)defence360agent.wordpressr get_data_dirrFileNotFoundErrorrUpw_namerrOSErrorerrnoELOOPENOTDIR Exceptionr:chmodrrpw_gidclose)r^rrr newly_createdrcommandexcs r!ensure_site_data_directoryrRs%&.-----%%d++++++++HM%h// )   dCMM *            )(33FF   yU[%-888HhHHHBBBSBB     9em4 4 4D(DDD     $ HVU # # #+ $( (8      OsM7AD-<B  D- C$A CC$$D-+ D-44D((D-14E::F)N)Nrrloggingr:pwdrSrA collectionsrrr functoolsrpathlibrtypingr defence360agent.contracts.configr r r!defence360agent.contracts.licenser +defence360agent.subsys.panels.hosting_panelr #defence360agent.subsys.panels.pleskr defence360agent.utilsrrrrrdefence360agent.utils.fd_opsrrdefence360agent.model.wordpressr#defence360agent.wordpress.constantsr#defence360agent.wordpress.exceptionrrRr= frozensetr getLogger__name__rvobjectrr"dictr(r'r3r@rIrMrUr]rorzrrrfloatrrintrrrrrr struct_passwdrrLr#r!r)s  ######((((((((988888DDDDDD555555HGGGGGGG222222CCCCCC8888881'<==  8 $ $ F s    R S$s)^ 4    99s9t9999  1#"+++ S. M  M%( M #Y M M M M)F)c)hsm))))X#$(>a*a*a*H=DcDj,A==== .2 @@@@@  @  @ 3d ?#d* @ @@@@F2C2D2222lJN      "%  ,/   > > > > >=c=c====2 # $    :   #& 36      0> > .> >>>>>>r#