d( jddlZddlZddlZddlZddlZddlZddlmZddlm Z m Z ddl m Z ddl mZddlmZddlmZmZmZmZddlmZmZdd lmZdd lmZmZmZm Z dd lm!Z!dd l"m#Z#m$Z$dd l%m&Z&m'Z'ddl(m)Z)ddl*m+Z+m,Z,ddl-m.Z.ddl/m0Z0m1Z1m2Z2m3Z3m4Z4m5Z5m6Z6m7Z7m8Z8m9Z9m:Z:ddl;mZ>m?Z?m@Z@mAZAmBZBmCZCmDZDmEZEmFZFmGZGmHZHddlImJZJejKeLZMdZNdZOdZPdeQfdZRdeQfdZSdeTfdZUdeTdeQfdZVdeTdeQfdZWedZXdeTdefd ZYd!edeZdzfd"Z[d#Z\d$eZd%ej]deZfd&Z^d%ej]defd'Z_d(ej`fd)Zaddd+ebfd,Zcd-Zdd.Zed/ZfdeTfd0Zgd1Zhd2e'fd3Zid2e'dejfd4Zkd5Zld6Zm ded7ene'fd8Zod9ebe'fd:Zpd2e'd;eTdeZfd?Zrd2e'd@eZddfdAZsd9ebe'dejfdBZtd2e'd%ej]dCeTdDendEenddf dFZu dfd9ebe'dGe e'ej]enenge dfdHeTdIeTdJeQddf dKZvdedCeTddfdLZwd!edMeQddfdNZxejyZzda{dgdOZ|d9ebe'ddfdPZ}deTddfdQZ~deTddfdRZdgdSZdTeTdUedeTfdVZd2e'd%ej]dUedDendEenddf dWZ dhdXebeTdzddfdYZdedZZd[Zd2e'deQfd\Zd2e'd]ejdeQfd^Zd_ZGd`daZGdbdceZdS)iN) defaultdict) AwaitableCallable) LooseVersion)Path) inactivity)MalwareScanScheduleInterval SystemConfigANTIVIRUS_MODEchoose_value_from_config)IndexWP_RULES) log_message)open_dir_no_symlinks open_nofollow rmtree_fdsafe_dir) Wordpress)get_wp_rules_dataget_wp_ruleset_version) WordpressSiteWPSite)WPDisabledRule)cli telemetry)PLUGIN_VERSION_FILE) _validate_presetcalculate_next_scan_timestamp$clear_get_cagefs_enabled_users_cacheensure_site_data_directoryformat_php_with_embedded_jsonget_imunify_package_versions get_last_scanget_malware_historyprepare_plugin_configprepare_scan_data!write_plugin_data_file_atomically) clear_manually_deleted_flag delete_siteget_installed_sites_by_domainsget_outdated_sitesget_sites_for_userget_sites_to_adoptget_sites_to_install%get_sites_to_mark_as_manually_deletedget_installed_sitesinsert_installed_sitesmark_site_as_manually_deletedupdate_site_identityupdate_site_version)setup_site_authenticationTFbalancedreturncd ttjS#t$r tcYSwxYw)uRead WORDPRESS.waf_enabled from config, defaulting to True. Returns _WAF_ENABLED_DEFAULT when the config key is missing (old schema without waf_enabled), so WAF stays enabled — no behavior change on upgrade. )boolr WAF_ENABLEDKeyError_WAF_ENABLED_DEFAULTU/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/plugin.py_get_global_waf_enabledr@Ss?$I)*** $$$####$ //cd ttjS#t$r tcYSwxYw)uSRead WORDPRESS.ai_bot_protection from config, defaulting to False. Returns _AI_BOT_PROTECTION_DEFAULT when the config key is missing — e.g. the ai_bot_protection field hasn't rolled out to this install's imunify360 yet, or a sibling package is still on an older schema. Keeps the feature off in all ambiguous cases. )r9rAI_BOT_PROTECTIONr;_AI_BOT_PROTECTION_DEFAULTr=r>r?_get_global_ai_bot_protectionrE`s?*I/000 ***))))*rAcj tj}n#t$r tcYSwxYwt |S)uRead WORDPRESS.ai_bot_protection_preset from config, defaulting to "balanced". Two layers of safety: KeyError on a missing key (older schema, agent upgrade in progress) and _validate_preset() on the value itself (hand-edited override file, future preset rolled in via a sibling package this version doesn't recognise). Both fall back to the same canonical default so all layers — schema, agent, plugin — agree. )rAI_BOT_PROTECTION_PRESETr;!_AI_BOT_PROTECTION_PRESET_DEFAULTr)raws r?$_get_global_ai_bot_protection_presetrJnsF10 11100001 C  s ##usernamectsdS tdd|\}}n#t$r tcYSwxYw|tnt |S)zECheck if WAF is enabled for a user, respecting global-off precedence.F WORDPRESS waf_enabled)rK)r@r r;r<r9)rKvalue_s r?_is_waf_enabled_for_user_syncrQsz " $ $u$+    qq $$$####$#(=  d5kkAs (<<cpKtj}|dt|d{VS)u3Async wrapper — runs config file I/O in executor.N)asyncioget_event_looprun_in_executorrQ)rKloops r?is_waf_enabled_for_userrWsR  ! # #D%% +X      r>zD/var/lib/cloudlinux-app-version-detector/components_versions.sqlite3 admin_configc ddlm}|||S#t$r-tdt jdddfcYSwxYw)z Get user-specific schedule configuration with lazy import fallback. Returns default values if imav.malwarelib is not available. r)get_user_schedule_configz@imav.malwarelib not available, returning default schedule config)(imav.malwarelib.plugins.schedule_watcherrZ ImportErrorloggerdebugIntervalNONE)rKrXrZs r?_get_user_schedule_configrbs &      (',??? &&& N   }aA%%%% &s4A  A indexct|}|dStr|D] \}}d|d< tt jr fd|D}|S)uI Retrieve WordPress rules with ANTIVIRUS_MODE handling and global disable filtering. In ANTIVIRUS_MODE, all rules are set to monitoring mode ("pass"). Globally disabled rules are filtered out entirely — they should not appear in rules.php. Domain-specific disables are handled separately via disabled-rules.php. Args: index: The Index object used to locate the wp-rules.zip file. Returns: The parsed wp-rules data with mode adjusted for ANTIVIRUS_MODE and globally disabled rules removed, or None if rules cannot be loaded. Npassmodec$i|] \}}|v || Sr=r=).0cveparamsglobally_disableds r? z-get_updated_wp_rules_data..s5   V+++ +++r>)rr itemssetrget_global_disabled)rc rules_datarirjrks @r?get_updated_wp_rules_datarqs #5))Jt$%++-- $ $KC#F6NNN>@@AA     )//11   r>cHttjdS)z#Clear all WordPress-related caches.N)rrclear_get_content_dir_cacher=r>r? clear_cachesrts#(***#%%%%%r>rm user_infoct|}d|D}|D]Dfd|D}|r1t|t}||E|S)Nci|]}|gSr=r=)rhpaths r?rlzsite_search..s . . .4dB . . .r>c,g|]}||Sr=r=)rhrxitemmatchers r? zsite_search..s*MMM4t9L9LM$MMMr>)key)r,maxlenappend)rmrur{ user_sitesresultmatching_sitesmost_specific_siterzs ` @r? site_searchrs#I..J . .: . . .F44MMMMM:MMM  4!$^!=!=!=  % & - -d 3 3 3 Mr>c:Kt||jd{V}|dd}t|j|\}}}}d} |tjkrt ||||} t|j} t| |d} || | fS)N scan_datecP|ddko|d|S)N resource_typefile) startswith)rzrxs r?z)_get_scan_data_for_user..s-40F:* L # #D ) )r>) r#pw_namegetrbr`rarr$r) sinkrurX last_scanlast_scan_timeintervalhour day_of_month day_of_weeknext_scan_timemalware_historymalware_by_sites r?_get_scan_data_for_userrs$D)*;<<<<<<<? ::r> semaphorecK|4d{V |d{Vn4#t$r'}td|Yd}~nd}~wwxYwdddd{VdS#1d{VswxYwYdS)NzTelemetry task failed: ) Exceptionr^error)corores r?_send_telemetry_taskr s+88888888 8JJJJJJJJ 8 8 8 LL6166 7 7 7 7 7 7 7 7 8888888888888888888888888888888s5AA AAAAA A'*A' coroutinescK|sdStj|fd|D} tj|d{VdS#t$r(}td|Yd}~dSd}~wwxYw)zK Process a list of telemetry coroutines with a concurrency limit.s NcTg|]$}tjt|%Sr=)rS create_taskr)rhrrs r?r|z+process_telemetry_tasks..s?     0yAABB   r>zSome telemetry tasks failed: )rS Semaphoregatherrr^r)r concurrencytasksrrs @r?process_telemetry_tasksrs !+..I       E :ne$$$$$$$$$$ ::: 8Q88999999999:sA A3 A..A3cpK ttd}|d{Vt|}n3#t$r&}t d|Yd}~dSd}~wwxYw|st ddSt|}||d}t|S)z Load WordPress rules from the index and format them as PHP. Returns: str or None: PHP-formatted rules data, or None if rules could not be loaded. F)integrity_checkNz>Failed to load wp-rules index: %s, skipping rules installationzcvKt}t||}|d{VS)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)r-WordPressSiteAdopterr)rr processors r?adopt_found_sitesrMsB  E$T511I  r>c tjs"tdtdStjS#t $r&}td|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)rexistsr^r read_textstripr)rs r?get_latest_plugin_versionr]s ")++  LL8:M   4",..44666  =qAAAttttts3A$A B &BB c jKt}|stddStd|t }g}t jd5 t|}tdt|d|s# t|d{VddddSt}td{V}tt}|D]"}||j|#|D]\} } t%j| } | j} n3#t*$r&} td| | Yd} ~ Nd} ~ wwxYwt-|| |d{V\}}}t/| }| D]}t1||d{Vr t3j|d{Vstd|Qt7||| ||| }t9||d{Vt;||d{Vt3j|d{V||t3j |d{V}|r{|j!}tE|||#|}tI|tI|k}|tKj&||rd nd || Z#t*$r'} td || Yd} ~ d} ~ wwxYwtdt|nf#tNj($r+tdt|Yn-t*$r!} td| d} ~ wwxYwt|d{Vn#t|d{VwxYw ddddS#1swxYwYdS)zFUpdate the imunify-security plugin on all sites where it is installed.z)Could not determine latest plugin versionNz>L9;;;;;;;;H(--M& 5 5dh'..t4444,1133\ \  U  # S 1 1I(0HH LLE HHHH 2)\ ""#!6h ? ? !FFD3D$????????! C%(%?%E%EEEEEEE%"KK Et%%6**$ +%- %%% 4D)DDDDDDDDD 8mLLLLLLLLL"/555555555 D)))),(>t(D(D"D"D"D"D"D"D"/3|,0g>>>$(#:#:7#C#CD,8 ',, ,-= > >,?L ,22 ) 4)-,8)B(?(?-A)-,3 !" !" !"   % I !CFP KK@G     %    KK+G           LL?      */:: : : : : : : : :)/:: : : : : : : : : :GC;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;s9P(;1E2/2E!!E22E69E6cK t||t|d{V|tj|d||jdS#t $r'}td||Yd}~dSd}~wwxYw)a Process the manually deleted plugin for a single site. Args: site: The site to process. now: The current time. sink: The telemetry/event sink. telemetry_coros: The list of telemetry coroutines to add the event to. The process includes: - marking the site as manually deleted in the database - removing plugin data files - sending telemetry for manual removal Nremoved_by_userrz>Failed to process manually deleted plugin for site=%s error=%s) r2rrrrrrr^r)rnowrrrs r?rros %dC000"$'''''''''   '              L            sAA B %BB freshly_installed_sitescdKg} t|}|r0tj}|D]}t||||d{Vn2#t$r%}td|Yd}~nd}~wwxYw|rt |d{VdSdS#|rt |d{VwwxYw)a> Tidy up sites that have been manually deleted by the user. Args: sink: The telemetry/event sink. freshly_installed_sites: Optional set of sites that were just installed and should be excluded from being marked as manually deleted to avoid race conditions. Nz&Error occurred during site tidy up. %s)r/rrrr^rr)rrrto_mark_as_manually_removedrrrs r?tidy_up_manually_deletedrs>O;&K #' ' # ' )++C3  5#t_ FFF =uEEEEEEEEF  ;)/:: : : : : : : : : : ; ;? ;)/:: : : : : : : : : ;s0AAB A7A2-B2A77BB/rc K|sdSt}td{V}tt}|D]"}||j|#|D]\}} tj|}|j }n3#t$r&} t d|| Yd} ~ Nd} ~ wwxYwt|||d{V\} } } t|} |D]}t||d{Vr t!| | ||| |}t#||d{Vt%|| d{V\#t$r&} t d|| Yd} ~ d} ~ wwxYwdS)Nrrz.Failed to update site data on site=%s error=%s)r r"rrrrrmrrrrr^rrr%rr&rr)rrrXrrrrrurKrrrrrrs r?update_data_on_sitesrsQ  >>L133333333H %%M--dh&&t,,,,$))++++ U  S))I (HH    LL=    HHHH  *$ <HH H H H H H H    -h77   D+D$77777777  -""#%  ,D)<<<<<<<<<0mDDDDDDDDDD    D ' '++s1=B C #CC AE FE??FfilenamedatacKtj|j}|j}t ||d{V}t |}t ||z ||j|dS)zKEmbed ``data`` as JSON inside a PHP file at ``/``.Nrgid)rrrpw_gidr r!r')rrrrurr php_contents r?_write_json_php_data_filers TX&&I  C/i@@@@@@@@H/55K%8hCr>rc8Kt|d|d{VdS)N scan_data.phpr)rrs r?rrs0 #D/9 E EEEEEEEEEEr>rc8Kt|d|d{VdS)z Write plugin_config.php for a single WordPress site. Separate file from scan_data.php so a config toggle doesn't force rewriting the malware list, and so the mu-plugin hot path loads only what it needs per request. plugin_config.phpNr)rrs r?rrs3 $D*=} M MMMMMMMMMMr>c"K|sdSd}tt}|D]"}||j|#|D]\}} t j|}|j}n3#t$r&}t d||Yd}~Md}~wwxYwt|} |D]Q} t|| d{V|dz }#t$r&}t d||Yd}~Jd}~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. rrNr[z6Failed to update plugin_config.php on site=%s error=%s) rrrrrmrrrrr^rr%r) rrrrrrrurKrrs r?update_plugin_config_on_sitesr# s qG .9->->M--dh&&t,,,,(..00Z  S))I (HH    LL=    HHHH  .h77   D /mDDDDDDDDD1     L   Ns0A88 B(B##B(?C D %DD  wp_rules_phprfailedcK|j} t||d{V}|dz }t|||j|||t d|jdS#t$rA}||t d|j|Yd}~dSd}~wwxYw)a= Deploy wp-rules to a single WordPress site and track the result. Args: site: WordPress site to deploy to user_info: User information from pwd wp_rules_php: Formatted PHP rules content updated: Set to add site to if successful failed: Set to add site to if failed N rules.phprzUpdated wp-rules for site %sz)Failed to update wp-rules for site %s: %s) rr r'rrr^rdocrootrr) rrur$rr%rr rules_pathrs r?update_wp_rules_for_siter*Es"  C 3D)DDDDDDDD + )  $(      D 2DLAAAAA     4 7 L           sA(A55 C?6B;;C make_task task_name fingerprintskip_waf_disabledc Kt}t}tj|5 t j}t t } |D]"} | | j| #g} | D]+\} } tj | }|j }nW#t$rJ}td|t| | |ddd|| D]} || Yd}~rd}~wwxYw|rr t#|d{V}n/#t$r"t$d|d d}YnwxYw|s*t$d |t| | D]:} t+|| d{Vr| || |||;-d }t-d t| |D]&}| |||z}t/j|d did{V't j|z }t$d|t|t||nh#t.j$r,t$d|t|Yn.t$r"}t$d||d}~wwxYwddddS#1swxYwYdS)a6 Run a per-site async deployment over a list of WordPress sites. Groups sites by user, resolves UIDs, then runs tasks concurrently in batches. Args: sites: WordPress sites to deploy to make_task: Callable that creates a coroutine for one site. Signature: (site, user_info, updated_set, failed_set) -> awaitable task_name: Human-readable name for logging and inactivity tracking fingerprint: Sentry fingerprint for user-lookup failures sink: Optional telemetry sink for remove_site_if_missing zwSkipping {task} update for {count} site(s) belonging to user {user} because username retrieval failed. Reason: {reason})rcountuserreasonr wordpress format_argslevel componentr-NBCould not check WAF status for user %s, proceeding with deploymentTexc_infoz-WAF disabled for user %s, skipping %d site(s)rrreturn_exceptionsz@%s deployment complete. Updated: %d, Failed: %d, Duration: %.2fsz-%s deployment was cancelled. Updated %d sitesz-Error occurred during %s deployment. error=%s)rnrrrrrrrrrmrrrrrrrrWr^rrrrangerSrrr)rr+r,r-r.rrr% start_timerrrrrrurKrrNmax_concurrentibatchelapseds r?_deploy_to_sitesrBis0eeG UUF    y ) )SSR J'--M 5 5dh'..t4444E#0#6#6#8#8- N- NZ # S 1 1I(0HH >%.%(__$'&+ %% ("-$/    !+)) 4((((HHHH#&%! +,CH,M,M&M&M&M&M&M&M $+++:$%) ' '+ +'! K$ OO !&NND3D$????????! LL4GV!L!LMMMMN  N1c%jj.99 E Ea!n"445neDtDDDDDDDDDDikkJ.G KK#G F      %    KK?G           LL?      [SSSSSSSSSSSSSSSSSSsKA(I%)CI% DADI%DI%D54I%5)E!I% E!!DI%$K%8K K K (KK  KK!KcKtt}|stddSfd}t ||ddd|d{VdS)zHDeploy pre-formatted wp-rules PHP content to all active WordPress sites.zNo active WordPress sites foundNc*t||||SN)r*)rrurr%r$s r?r+z'_deploy_wp_rules_php..make_tasks ' )\7F   r>zwp-ruleszwp-rules-update-skip-userTr,r-r.r)rtr0r^r_rB)r$rinstalled_sitesr+s` r?_deploy_wp_rules_phprHsNNN)++O  6777     /     r> is_updatedcKtjstddS|stddStdt |}|stddSt |}||d}t|}t|d{VdS)z Hook that runs when wp-rules files are updated. Extracts wp-rules.yaml from wp-rules.zip and deploys to all active WordPress sites. Args: index: Index object for wp-rules is_updated: Whether files were actually updated zCwordpress security plugin not enabled, skipping wp-rules deploymentNz)wp-rules not updated, skipping deploymentz/Starting wp-rules deployment to WordPress sitesz,No valid wp-rules found, skipping deploymentr) rSECURITY_PLUGIN_ENABLEDr^rrqrrr!rH)rcrIrrrr$s r?update_wp_rules_on_sitesrLs  ,        ?@@@ KKABBB-e44M  CDDD.e44#L1>>L | , ,,,,,,,,,,r>c`KtjstddStrdatddSt4d{V datdtd{V}|s.td dddd{VdSt|d{Vt sntd dddd{VdS#1d{VswxYwYdS) aN Re-deploy rules.php to all WordPress sites. Used when globally disabled rules change, requiring rules.php to be regenerated with updated rule filtering. Uses a coalescing lock: if a redeployment is already running, the request is merged into the current run rather than starting a duplicate deployment. zEwordpress security plugin not enabled, skipping wp-rules redeploymentNTz5wp-rules redeployment already in progress, coalescingFz6Starting wp-rules redeployment (global disable change)z(Could not load wp-rules for redeploymentz4Re-running wp-rules redeployment (coalesced request)) rrKr^r_redeploy_wp_rules_locklocked_redeploy_wp_rules_pendingrrrH)r$s r?redeploy_wp_rulesrQsS  ,      %%''%)" KLLL&PPPPPPPP P). & KKH   "3!4!4444444L IJJJPPPPPPPPPPPPPP'|44 4 4 4 4 4 4 4-  KKN O O O! P PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPs/AD8D D'*D'cKtj}|D]} tj|d{V}|dz }|d|jd{VrG|dt j|d{Vt d|j #t$r+}t d|j |Yd}~d}~wwxYwdS)z&Remove rules.php from the given sites.Nr'z(Removed rules.php from %s (WAF disabled)z&Failed to remove rules.php from %s: %s) rSrTrrrUrrremover^rr(rr)rrVrrr)rs r?_remove_rules_php_from_sitesrTKs/  ! # #D    -d33333333H!K/J))$ 0ABBBBBBBB **4JGGGGGGGGG >L    LL8$,           sBB## C-!CCcKtjsdStj} |dt j|d{Vn,#t$rt d|YdSwxYwtd{V}|st ddS|dtd{V}fd|D}|sdSt}t}|D]}t||||d{Vtd|t|t|dS)z?Redeploy rules.php only for sites belonging to a specific user.Nz.User %s not found, skipping WAF rules redeployz)Could not load wp-rules for user redeployc4g|]}|jjk|Sr=rpw_uidrhsrus r?r|z.redeploy_wp_rules_for_user..r(@@@aey/?&?&?!&?&?&?r>z6Redeployed wp-rules for user %s: %d updated, %d failed)rrKrSrTrUrgetpwnamr;r^rrr0rnr*rr) rKrVr$rrrr%rrus @r?redeploy_wp_rules_for_userr]^s  ,  ! # #D..tS\8LLLLLLLL  .r[r>) rSrTrUrr\r;r^rr0rT)rKrVrrrus @r?remove_waf_rules_for_userr`s  ! # #D..tS\8LLLLLLLL  ;X      &&t-@AA A A A A A AE@@@@U@@@J &z 2 2222222222s'A%A)(A)cKtj}|dtd{V}tdt |t|d{VdS)z?Remove rules.php from all installed sites (global WAF disable).Nz7Global WAF disabled, removing rules.php from %d site(s))rSrTrUr0r^rrrT)rVrs r?remove_waf_rules_for_all_sitesrbs  ! # #D&&t-@AA A A A A A AE KKA E  'u - ----------r>domain timestampcptj|d}|t|d}t|S)a| Generate the disabled-rules.php content for a specific domain. Only includes domain-specific disabled rules. Globally disabled rules are handled separately by filtering them out of rules.php. Args: domain: The domain to generate disabled rules for timestamp: Unix timestamp to embed in the file Returns: PHP file content string F)include_global)tsr)rget_domain_disabledsortedr!)rcrddisabled_rule_idsrs r?generate_disabled_rules_phprksN':u)**  D ) . ..r>cJK|j} t||d{V}|dz }t|j|}t |||j|t j|t j |j k | |t d|j dS#t$rA} | |td|j | Yd} ~ dSd} ~ wwxYw)a[ Deploy disabled-rules.php to a single WordPress site and track the result. Args: site: WordPress site to deploy to user_info: User information from pwd timestamp: Unix timestamp for both file content and DB record updated: Set to add site to if successful failed: Set to add site to if failed Ndisabled-rules.phprdisabled_rules_sync_tsz"Updated disabled-rules for site %sz/Failed to update disabled-rules for site %s: %s)rr rkrcr'rrrwherer(executerr^rrr) rrurdrr%rrdisabled_rules_pathrrs r?update_disabled_rules_for_siterssO"  C 3D)DDDDDDDD&)==1$+yII ) $(     I>>>DD  !T\ 1  '))) D 8$,GGGGG     4 = L           sC C D"!6DD"domainscbKtjstddStdt |rt |}nt }|stddSd}t||ddd| d{VdS) ai Deploy disabled-rules.php to WordPress sites. If domains are specified, only updates sites for those domains. If domains is None, updates all installed sites (e.g., after a global disable/enable). Args: domains: List of domains to update, or None for all sites sink: Optional telemetry sink for remove_site_if_missing zIwordpress security plugin not enabled, skipping disabled-rules deploymentNz5Starting disabled-rules deployment to WordPress sitesz6No WordPress sites found for disabled-rules deploymentcJt||tj||SrE)rsr)rrurr%s r?r+z1update_disabled_rules_on_sites..make_tasks%- )TY[['6   r>zdisabled-ruleszdisabled-rules-update-skip-userTrF)rrKr^rrtr*r0rB)rtrrr+s r?update_disabled_rules_on_sitesrws  ,       KKGHHHNNN&.w77#%%  LMMM    "5     r>c Ktdt}t}tjd5 t t}|s(td ddddStt}|D]"}||j  |#g}| D]\}} tj|} n<#t$r/} t!dt#||| dddd Yd} ~ Od} ~ wwxYw|D]@}t%||d{Vrt'|| ||} | | Ad } t)d t#|| D]&} || | | z}t+j|d did{V'tdt#|t#|nf#t*j$r+tdt#|Yn-t$r!} td| d} ~ wwxYwddddS#1swxYwYdS)z7Update auth.php files for all existing WordPress sites.z4Updating auth.php files for existing WordPress siteszwp-auth-updatez"No installed WordPress sites foundNzSkipping auth update for WordPress sites on {count} site(s) because they belong to user {user} and it is not possible to retrieve username for this user. Reason: {reason}r0r1r2rr3zwp-plugin-auth-update-skip-userr4rrr;Tz8Updated 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^rrnrrrrtr0rrrrrmrrrrrrupdate_site_authr<rSrrr)rrr%rGrrrrrrurrr>r?r@s r?update_auth_everywherer{s KKFGGGeeG UUF    / 0 0@@?  NNN233O"  @AAA@@@@@@@@(--M' 5 5dh'..t4444E+1133 ' ' U # S 1 1II D &)ZZ$'&+%% ("-$E    HHHH""''D3D$????????! +D)WfMMDLL&&&& ' N1c%jj.99 E Ea!n"445neDtDDDDDDDDDD KKJG F      %    KK(G           LLF N N N  }@@@@@@@@@@@@@@@@@@stI;8H AH5D  H E%D>9H>ECHI;7I+?I; I+ I&&I++I;;I?I?cK t||d{V||dS#t$r<}||td||Yd}~dSd}~wwxYw)z/Process authentication setup for a single site.Nz*Failed to update auth for site=%s error=%s)r5rrr^r)rrurr%rs r?rzrz\s  'i888888888 D     4 8            s+1 A71A22A7c.Ktj|jrdSt |}|dkr&|#t j|d||jd{Vn1t d|tdd|id d d d S)a Checks if the site directory exists. If not, removes the site from the local database and sends a 'site_removed' telemetry event only if deletion is successful. Returns True if the site was removed (directory missing), False otherwise. Parameters: sink: The telemetry/event sink. site: The WPSite object to check and potentially remove. Side effect: If the site is missing and successfully deleted from database, a telemetry event will be sent. FrN site_removedrz@Failed to delete missing site %s from database, no rows affectedz2Failed to delete missing site {site} from databaserrr3zwp-plugin-site-delete-failedr4T) rrxisdirr(r)rrrr^rr)rr rows_deleteds r?rrjs w}}T\""ut$$La  &$          N     @!6      4r>file_permissionscK tj|d{V} t|}nC#t$r6}|jtjtjtjfvrYd}~dSd}~wwxYw tj |j dz}|dkrtj |ddD]} t||5}tj |}|j dz|krtj ||dddn #1swxYwYd#t$rYpt$r<}|jtjkr!td||Yd}~d}~wwxYw tj|n#tj|wxYwdS#t$$r'} td || Yd} ~ dSd} ~ wwxYw) z6Fix data file permissions for a single WordPress site.NFii)rr!zauth.phpr'rmrz"Skipping chmod: %s/%s is a symlinkTz.Failed to fix permissions for site=%s error=%s)rrrrrrrrrstatst_modechmodrfstatFileNotFoundErrorr^rrrr) rrrrrcurrent_dir_mode file_namefile_fdstrs r?fix_site_data_file_permissionsrs{1)$//////// )(33FF   yU\5; FFFuuuuu    !wv6> 5(('''   &y@@@@GXg..:-1AAAHW.>???@@@@@@@@@@@@@@@)HyEK//@$% ! 0 HV    BHV    t  <     uuuuu sF/F A/*A*#F)A**A//F3;E:/D8D8 DD D D DE: E!E: E!%1EE:EE!!E:%F:FF GGGc\Kt}t}tjd5 t t }|s ddddSddlm}ddlm }|j |j krdnd}|D]\}t||d{Vrt||d{V}|r| |G| |]tdt!|t!|nj#t"j$r+td t!|Yn1t&$r%} td | Yd} ~ nd} ~ wwxYwddddS#1swxYwYdS) z Fix data file permissions for all WordPress sites with imunify-security plugin installed. Args: sink: The telemetry/event sink zwp-plugin-fix-permissionsNr) HostingPanel)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)rnrrrrtr0+defence360agent.subsys.panels.hosting_panelr#defence360agent.subsys.panels.pleskrNAMErrrr^rrrSrrr) rfixedr%rGrrrrsuccessrs r?$fix_data_file_permissions_everywherers EEE UUF    : ; ;00/  NNN233O" 00000000       B A A A A A&, ::  ( % %/d;;;;;;;; >*!!%IIdOOOOJJt$$$$ KKE F     %    KK&E           LLCU         [000000000000000000sNF!D*,B=D*)F!*7F!F!# F,F F! FF!!F%(F%cdeZdZdZdZdZdZdZdddd d d d d ZdZ dZ dZ dZ de ddfdZdZdS)rz Handles installation of imunify-security plugin on WordPress sites. This class processes WordPress sites and installs the imunify-security plugin, including setting up authentication, scan data files, and rules. Tinstalled_by_imunifyzwp-plugin-installationzwp-plugin-install-skip-userz%Installing imunify-security wp pluginz5Installed imunify-security wp plugin on {count} sitesz&Found {count} site(s) for installationz5Failed to install plugin to site={site} error={error}z`Installation of imunify-security wp plugin was cancelled. Plugin was installed for {count} sitesz8Error occurred during plugin installation. error={error}zSkipping installation of WordPress plugin on {count} site(s) because they belong to user {user} and it is not possible to retrieve username for this user. Reason: {reason}startcompletefoundr cancelled exception skip_usercH||_||_t|_t|_t|_t|_t|_t|_d|_ t|_ d|_ dSrE) rrrn processed authenticatedrules_installedfailed_rules_updatesdisabled_rules_installedfailed_disabled_rules_updatesdisabled_rules_ts failed_auth _current_site)selfrrs r?__init__zWordPressSiteInstaller.__init__(sy   UU"uu$'EE!(+%-0UU*/355,0r>cKtj|d{V}|s3td|t dd|idddd Sd S) a Check if site is ready for processing. Override in subclasses to implement different readiness checks. Args: site: The WordPress site to check. Returns: bool: True if the site is ready for processing, False otherwise. Nz6WordPress site is not accessible using WP CLI. site=%sz:WordPress site is not accessible using WP CLI. site={site}rrr3zwp-plugin-cli-not-accessibler4FT)rrr^rr)rrrs r? is_site_readyz$WordPressSiteInstaller.is_site_ready5s(+'A$'G'G!G!G!G!G!G!G%  NNH    L#TN%:     5tr>c|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)rrr1_stamp_disabled_rules_sync_tsrrrs r?_record_processed_sitez-WordPressSiteInstaller._record_processed_siteQsD 4   v&&& **400000r>cK|j}d|_ t|d{Vn3#t$r&}td||Yd}~nd}~wwxYw|jrt j|d{VdSdS)aRevert the site that was mid-processing when cancellation occurred. Deletes data files and, if this processor installs plugins, attempts to uninstall the partially-installed plugin. Each step runs independently so one failure doesn't skip the other. Nz5Failed to delete data files for in-flight site %s: %s)rrrr^rinstall_pluginrtry_plugin_uninstall)rrrs r?_revert_in_flight_sitez-WordPressSiteInstaller._revert_in_flight_sitecs!! %d++ + + + + + + + +    NNG            1*400 0 0 0 0 0 0 0 0 0 1 1s( AAArr7Nc||jvrdS|jdStj|jtj|jkdS)z Stamp disabled_rules_sync_ts for a single site after it has been inserted into the DB. Called from ``_record_processed_site`` so the DB row already exists. Nrn)rrrrrpr(rq)rrs r?rz4WordPressSiteInstaller._stamp_disabled_rules_sync_tswse t4 4 4 F  ! ) F#'#9   % %5 6 6wwyyyyyr>c Kt|jdg}tj|j5 t|js{td|j |rLtj |ddid{V}|D]2}t|trtd|3cdddSt|jdt!|jt#}t%d{V}t'j|_t+d{V}t-t.}|jD]"}||j|#|D]&\} } t7j| } | j} nL#t$r?} t=|jd t!| | | d d d |j Yd} ~ gd} ~ wwxYw tA| d{V}n/#t$r"td| dd}YnwxYw|s)td| t!| tC|j"| |d{V\}}}tG| }| D]}tI|j"|d{Vr |%|d{Vs<||_&tO||| |||}tQ||d{VtS||d{VtU|| |j+|j,d{V|r%|r#t[|| ||j.|j/d{V|r(ta|| |j|j1|j2d{V|j3rtij5|d{Vtij6|d{V}|rtoj8||}|9||d|_&|tj:twj<|j"|j=||#t$rY} d|_&t>|jd|t| Yd} ~  d} ~ wwxYw(t|jdt!|j |j,r-tdt!|j,|j/r-tdt!|j/|j2r-tdt!|j2n#tj@$ro|j&r|Ad{Vt|jdt!|j YnXt$rL} t>|jdt| d} ~ wwxYw|rLtj |ddid{V}|D]2}t|trtd|3nT#|rLtj |ddid{V}|D]3}t|trtd|3wwxYwdddn #1swxYwY|j S)z Process WordPress sites for imunify-security plugin operations. Returns: set: The set of successfully processed sites. rz'No WordPress sites found, nothing to dor;TNzFailed to send telemetry: %sr)r0rryrr3r4r8r9zFWAF disabled for user %s, skipping WAF rules deployment for %d site(s)rrr)rrrzFailed to authenticate %d sitesz&Failed to install wp-rules on %d sitesz,Failed to install disabled-rules on %d sitesrr)r)Br^rmessagesrrrr,rtrrrSr isinstancerrformatrr rrrr"rrrrrmrrrrlog_fingerprint_skip_userrWrrr%rrrr&rrrzrrr*rrrsrrrrplugin_installrrrrrrrtelemetry_eventrreprrr)rtelemetry_tasksresultsrrXr$rrrrrrurKrrNrrrrrrs r?rzWordPressSiteInstaller.runs<   DM'*+++   " "4> 2 2I I H z*KK IJJJ>v#$+N(%<@%%G#*%fi88"NN >OI I I I I I I I  M'*11DJ1HH ,~~ &7%8%8888888 *.&!=!?!???????!,D 1 1  J99D!$(+2248888#0"5"5"7"7AAJC!$'L$5$5 #,#4$ ! ! !# M+6),U(+*/)) #,&1(,(F    ! ! +,CH,M,M&M&M&M&M&M&M $+++:$%) ' '+ +' ?$JJ 6 9l &&'%:($C$CM %TT!7 4!H!HHHHHHH%$P)-););D)A)A#A#A#A#A#A#A) (15D.): . . ( $ /)1 )))I#8i"H"HHHHHHHH"; $m###3 $ ) $ 2 $ 0 ## ," "&>$($-$0$($8$($= '"'"!"!"!"!"!"!"!" +"&D$($-$($:$($A$($F '"'"!"!"!"!"!"!"!" $2?&)&8&>&> > > > > > > >-0,B4,H,H&H&H&H&H&H&HG&P'-'@w'O'O!77gFFF15D.+22 ' 3$-$8-1Y.2.B-107 %&%&%&!"!"     )15D."LL $ g 6 = =)-T%[[!>!"!"]Tj M*-443t~;N;N4OO#NN9D,--,NN@D5665NNFD>?? )   %855777777777 M+.55!$.116     M+.55DKK5HH   #$+N(%<@%%G#*%fi88"NN > #$+N(%<@%%G#*%fi88"NN > KI I I I I I I I I I I I I I I V~s]5WA] C1WG.-W. H785H2-W2H77W;IW)I=:W<I==A>W<R WE1R  W S. AS) #W)S. .C,W[?A;Z-[? Z-!AZ((Z--[?0A]?A]]]#&]#)__name__ __module__ __qualname____doc__rrr,rrrrrrrrrr=r>r?rrsN,O(I =8K9H 5 G 7H( 1 1 18111$111( A& AT A A A AUUUUUr>rcVeZdZdZdZdZdZdZdddd d d d d ZfdZ dZ fdZ xZ S)rz Handles adoption of existing WordPress sites with imunify-security plugin. Adoption is a special case of installation where the site already has the plugin installed but is not tracked in our database. F site_foundzwp-plugin-adoptionzwp-plugin-adopt-skip-userz#Adopting imunify-security wp pluginz3Adopted imunify-security wp plugin on {count} sitesz"Found {count} site(s) for adoptionz3Failed to adopt plugin to site={site} error={error}zZAdoption of imunify-security wp plugin was cancelled. Plugin was adopted for {count} sitesz4Error occurred during plugin adoption. error={error}zSkipping adoption of WordPress plugin on {count} site(s) because they belong to user {user} and it is not possible to retrieve username for this user. Reason: {reason}rct||dtjtjD|_dS)Nch|] }|j Sr=)r()rhrs r? z0WordPressSiteAdopter.__init__..s'" " " AI" " " r>)superrrselectr(existing_docroots)rrr __class__s r?rzWordPressSiteAdopter.__init__|sR u%%%" " ,3M4IJJ" " " r>c|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) rrr(rr(r3r4r1rrs r?rz+WordPressSiteAdopter._record_processed_sites 4   <41 1 1 ' - - -  & & & 3#D'222 "D6 * * * **400000r>cKt|d{VsdStj|d{V}|std|dSdS)z Check if site is ready for adoption. Args: site: The WordPress site to check. Returns: bool: True if the site is ready for adoption, False otherwise. NFz2Plugin not installed on site %s, skipping adoptionT)rrrrr^r)rrrrs r?rz"WordPressSiteAdopter.is_site_readysWW**400000000 5!4T::::::::   NND   5tr>) rrrrrrr,rrrrr __classcell__)rs@r?rr^sN"O$I ;6I5F 3L 7H$     111.r>r)rrE)FN)r7N)NN)rSrloggingrrr collectionsrcollections.abcrrdistutils.versionrpathlibrdefence360agent.apir defence360agent.contracts.configr r`r r r defence360agent.filesr rdefence360agent.sentryrdefence360agent.utils.fd_opsrrrrr"defence360agent.wordpress.wp_rulesrrdefence360agent.model.wordpressrr&defence360agent.model.wp_disabled_rulerdefence360agent.wordpressrr#defence360agent.wordpress.constantsrdefence360agent.wordpress.utilsrrrr r!r"r#r$r%r&r')defence360agent.wordpress.site_repositoryr(r)r*r+r,r-r.r/r0r1r2r3r4$defence360agent.wordpress.proxy_authr5 getLoggerrr^r<rDrHr9r@rEstrrJrQrWCOMPONENTS_DB_PATHrbdictrqrt struct_passwdrrrrrrrrrrrrintrr rrnrrrrrr#r*rBrHrLLockrNrPrQrTr]r`rbfloatrkrsrwr{rzrrrrrr=r>r?rs   ######////////************ 21111111...... 766666BAAAAAAAAAAAAA44444444CCCCCC                           KJJJJJ  8 $ $  #$.! $ $ $ $ $ *t * * * *!c!!!!" BC BD B B B BCDTJ &&<&&&&&"U"td{""""J&&& t (9 t     ;& ;6B ; ; ; ;F80A8888::d::::&777B!!! ! ! ! 3    Q;Q;Q;hF>))#))))X";";";J$ $ $ P26;;#&v;;;;;<:DL::::z    '+      FfFFFFFN&NN$NNNN5tF|55555p! !  ! !  !  !  ! ! ! ! V$ nn <n "C-y>n  n  nn nnnnbS0#-%#-T#-d#-#-#-#-L'',..#*P*P*P*PZd6lt&$s$t$$$$N 3c 3d 3 3 3 3 ....///#////0% %  % %  %  %  % % % % R!% // #Y / ////dGGGGT    &V&&&&&R5 5$'5 5555p:::zSSSSSSSSl RRRRR1RRRRRr>