PluginProbe
Security Plugin, Firewall & Malware Scanner with Auto Removal / 2.187
Security Plugin, Firewall & Malware Scanner with Auto Removal v2.187
2.187 2.186 2.185 2.184 2.183 2.182.1 2.182 2.181 2.180 2.179 2.178 2.115 2.116 2.117 2.118 2.119 2.12 2.120 2.121 2.122 2.123 2.124 2.125 2.126 2.126.1 All 308 releases
security-malware-firewall / security-malware-firewall.php

security-malware-firewall.php in Security Plugin, Firewall & Malware Scanner with Auto Removal 2.187, at security-malware-firewall.php

2,125 lines 72.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /*
4 Plugin Name: Security by CleanTalk
5 Plugin URI: https://wordpress.org/plugins/security-malware-firewall/
6 Description: Security & Malware scan by CleanTalk to protect your website from online threats and viruses. IP/Country FireWall, Web application FireWall. Detailed stats and logs to have full control.
7 Author: CleanTalk Security
8 Version: 2.187
9 Author URI: https://cleantalk.org
10 Text Domain: security-malware-firewall
11 Domain Path: /i18n
12 */
13
14 use CleantalkSP\Security\LoginCollectingProtector;
15 use CleantalkSP\SpbctWP\Activator;
16 use CleantalkSP\SpbctWP\DTO\SecurityLogsDataRowDTO;
17 use CleantalkSP\SpbctWP\DTO\SecurityLogsDTO;
18 use CleantalkSP\SpbctWP\FSWatcher\Controller as FSWatcherController;
19 use CleantalkSP\SpbctWP\DB;
20 use CleantalkSP\SpbctWP\Firewall\BFP;
21 use CleantalkSP\SpbctWP\Firewall\FW;
22 use CleantalkSP\SpbctWP\Cron as SpbcCron;
23 use CleantalkSP\SpbctWP\HTTP\CDNHeadersChecker;
24 use CleantalkSP\SpbctWP\RemoteCalls as SpbcRemoteCalls;
25 use CleantalkSP\SpbctWP\RenameLoginPage;
26 use CleantalkSP\SpbctWP\Sanitize;
27 use CleantalkSP\SpbctWP\SpbcDevLogger;
28 use CleantalkSP\SpbctWP\Scanner\Services\SendFileToCloudService;
29 use CleantalkSP\SpbctWP\Scanner\Stages\SignatureAnalysis\SignatureAnalysisFacade;
30 use CleantalkSP\SpbctWP\Settings\FilesScanPathExclusion;
31 use CleantalkSP\SpbctWP\Settings\FrontendScanDomainExclusion;
32 use CleantalkSP\SpbctWP\State;
33 use CleantalkSP\SpbctWP\Sync as SpbcSync;
34 use CleantalkSP\SpbctWP\SpbcEarlyTranslation as SpbcET;
35 use CleantalkSP\SpbctWP\Transaction;
36 use CleantalkSP\SpbctWP\Variables\Cookie;
37 use CleantalkSP\SpbctWP\VulnerabilityAlarm\VulnerabilityAlarmService;
38 use CleantalkSP\Updater\Updater;
39 use CleantalkSP\Updater\UpdaterScripts;
40 use CleantalkSP\Variables\Get;
41 use CleantalkSP\Variables\Post;
42 use CleantalkSP\Variables\Server;
43 use CleantalkSP\SpbctWP\Helpers\IP;
44 use CleantalkSP\SpbctWP\Helpers\HTTP;
45 use CleantalkSP\SpbctWP\API as SpbcAPI;
46 use CleantalkSP\SpbctWP\Scanner\ScanRepository;
47 use CleantalkSP\SpbctWP\Scanner\ScanStorage;
48 use CleantalkSP\SpbctWP\VulnerabilityAlarm\VulnerabilityAlarm;
49 use CleantalkSP\SpbctWP\UsersPassCheckModule\UsersPassCheckCron;
50 use CleantalkSP\SpbctWP\DoingItWrongHandler;
51 use CleantalkSP\SpbctWP\Scanner\ScannerAjaxEndpoints;
52 use CleantalkSP\SpbctWP\UploadDirPreventPhpExecutionModule\UploadDirPreventPhpExecution;
53
54 // Prevent direct call
55 if ( ! defined('WPINC') ) {
56 die('Not allowed!');
57 }
58
59 // Getting version form main file (look above)
60 $plugin_info = get_file_data(__FILE__, array('Version' => 'Version', 'Name' => 'Plugin Name', 'Description' => 'Description'));
61 $plugin_version__agent = $plugin_info['Version'];
62 // Converts xxx.xxx.xx-dev to xxx.xxx.2xx
63 // And xxx.xxx.xx-fix to xxx.xxx.1xx
64 if ( preg_match('@^(\d+)\.(\d+)\.(\d{1,2})-(dev|fix)$@', $plugin_version__agent, $m) ) {
65 $plugin_version__agent = $m[1] . '.' . $m[2] . '.' . ($m[4] === 'dev' ? '2' : '1') . str_pad($m[3], 2, '0', STR_PAD_LEFT);
66 }
67
68 // Common params
69 define('SPBC_NAME', $plugin_info['Name']);
70 define('SPBC_VERSION', $plugin_info['Version']);
71 define('SPBC_AGENT', 'wordpress-security-' . $plugin_version__agent);
72 define('SPBC_USER_AGENT', 'Cleantalk-Security-Wordpress-Plugin/' . $plugin_info['Version']);
73 define('SPBC_API_URL', 'https://api.cleantalk.org'); //Api URL
74 define('SPBC_PLUGIN_DIR', dirname(__FILE__) . DIRECTORY_SEPARATOR); //System path. Plugin root folder with '/'.
75 define('SPBC_PLUGIN_BASE_NAME', plugin_basename(__FILE__)); //Plugin base name.
76 define(
77 'SPBC_PATH',
78 is_ssl()
79 ? preg_replace('/^http(s)?/', 'https', plugins_url('', __FILE__))
80 : plugins_url('', __FILE__)
81 ); //HTTP(S)? path. Plugin root folder without '/'.
82
83 // SSL Serttificate path
84 if ( ! defined('CLEANTALK_CASERT_PATH') ) {
85 define('CLEANTALK_CASERT_PATH', file_exists(ABSPATH . WPINC . '/certificates/ca-bundle.crt') ? ABSPATH . WPINC . '/certificates/ca-bundle.crt' : '');
86 }
87
88 // Options names
89 define('SPBC_DATA', 'spbc_data'); //Option name with different plugin data.
90 define('SPBC_SETTINGS', 'spbc_settings'); //Option name with plugin settings.
91 define('SPBC_NETWORK_SETTINGS', 'spbc_network_settings'); //Option name with plugin network settings.
92 define('SPBC_CRON', 'spbc_cron'); //Option name with scheduled tasks.
93 define('SPBC_ERRORS', 'spbc_errors'); //Option name with errors.
94 define('SPBC_DEBUG', 'spbc_debug'); //Option name with a debug data. Empty by default.
95 define('SPBC_PLUGINS', 'spbc_plugins'); //Option name with a debug data. Empty by default.
96 define('SPBC_THEMES', 'spbc_themes'); //Option name with a debug data. Empty by default.
97
98 // Different params
99 define('SPBC_REMOTE_CALL_SLEEP', 10); //Minimum time between remote call
100 define('SPBC_LAST_ACTIONS_TO_VIEW', 20); //Nubmer of last actions to show in plugin settings page.
101
102 // Auth params
103 define('SPBC_2FA_KEY_TTL', 600); // 2fa key lifetime in seconds
104
105 /**
106 * Set SPBC_BFP_DISABLE to true to disable Brute Force Protection module.
107 * Kept for backward compatibility; prefer settings option bfp__enabled.
108 */
109 if (!defined('SPBC_BFP_DISABLE')) {
110 define('SPBC_BFP_DISABLE', false);
111 }
112
113 // DataBase params
114 global $wpdb;
115
116 define('SPBC_TBL_FIREWALL_DATA', $wpdb->base_prefix . 'spbc_firewall_data');
117 define('SPBC_TBL_FIREWALL_DATA_V4', SPBC_TBL_FIREWALL_DATA . '_v4');
118 define('SPBC_TBL_FIREWALL_DATA_V6', SPBC_TBL_FIREWALL_DATA . '_v6');
119 define('SPBC_TBL_FIREWALL_DATA__IPS', $wpdb->prefix . 'spbc_firewall__personal_ips');
120 define('SPBC_TBL_FIREWALL_DATA__IPS_V4', SPBC_TBL_FIREWALL_DATA__IPS . '_v4'); // Table with firewall IPS v4
121 define('SPBC_TBL_FIREWALL_DATA__IPS_V6', SPBC_TBL_FIREWALL_DATA__IPS . '_v6'); // Table with firewall IPS v6
122 define('SPBC_TBL_FIREWALL_DATA__COUNTRIES', $wpdb->prefix . 'spbc_firewall__personal_countries'); // Table with firewall countries.
123 define('SPBC_TBL_FIREWALL_LOG', $wpdb->prefix . 'spbc_firewall_logs'); // Table with firewall logs.
124 define('SPBC_TBL_SESSIONS', $wpdb->prefix . 'spbc_sessions'); // Alternative sessions table
125
126 define('SPBC_TBL_MONITORING_USERS', $wpdb->prefix . 'spbc_monitoring_users'); // Table with users monitoring data
127 define('SPBC_TBL_SECURITY_LOG', $wpdb->prefix . 'spbc_auth_logs'); // Table with security logs.
128 define('SPBC_TBL_SECURITY_LOG_HOSTNAMES', $wpdb->base_prefix . 'spbc_security_log_hostnames'); // Table with security logs hostnames.
129 define('SPBC_TBL_TC_LOG', $wpdb->prefix . 'spbc_traffic_control_logs'); // Table with traffic control logs.
130 define('SPBC_TBL_BFP_BLOCKED', $wpdb->prefix . 'spbc_bfp_blocked'); // Table with traffic control logs.
131 define('SPBC_TBL_SCAN_FILES', $wpdb->base_prefix . 'spbc_scan_results'); // Table with scan results.
132 define('SPBC_TBL_SCAN_RESULTS_LOG', $wpdb->base_prefix . 'spbc_scan_results_log'); // Table with log of scan results.
133 define('SPBC_TBL_SCAN_LINKS', $wpdb->prefix . 'spbc_scan_links_logs'); // For links scanner. Results of scan.
134 define('SPBC_TBL_SCAN_FRONTEND', $wpdb->base_prefix . 'spbc_scan_frontend'); // For frontend scanner. Results of scan.
135 define('SPBC_TBL_SCAN_SIGNATURES', $wpdb->base_prefix . 'spbc_scan_signatures'); // For malware signatures.
136 define('SPBC_TBL_BACKUPED_FILES', $wpdb->prefix . 'spbc_backuped_files'); // Contains backuped files
137 define('SPBC_TBL_BACKUPS', $wpdb->prefix . 'spbc_backups'); // Contains backup info.
138 define('SPBC_TBL_CURE_LOG', $wpdb->base_prefix . 'spbc_cure_log'); // Table with scan results.
139 define('SPBC_TBL_RATE_LIMITS', $wpdb->base_prefix . 'spbc_rate_limits'); // Table with scan results.
140 define('SPBC_SURFACE_COMPLETED_DIRS', $wpdb->base_prefix . 'spbc_surface_completed_dirs'); // Table with scan results.
141 define('SPBC_TBL_PSC_PLUGINS_CACHE', $wpdb->prefix . 'spbc_psc_plugins_cache'); // PSC certified plugins cache.
142 define('SPBC_SELECT_LIMIT', 1500); // Select limit for logs.
143 define('SPBC_WRITE_LIMIT', 5000); // Write limit for firewall data.
144
145 // Multisite
146 define('SPBC_WPMS', (is_multisite() ? true : false)); // WMPS is enabled
147
148 // Scanner params for background scanning
149 define('SPBC_SCAN_SURFACE_AMOUNT', 1000); // Surface scan amount for 1 iteration
150 define('SPBC_SCAN_SURFACE_PERIOD', 30); // Surface scan call period
151 define('SPBC_SCAN_MODIFIED_AMOUNT', 5); // Deep scan amount for 1 iteration
152 define('SPBC_SCAN_SIGNATURE_AMOUNT', 20); // Deep scan amount for 1 iteration
153 define('SPBC_SCAN_MODIFIED_PERIOD', 30); // Deep scan call period
154 define('SPBC_SCAN_LINKS_AMOUNT', 10); // Links scan amount for 1 iteration
155 define('SPBC_SCAN_FRONTEND_AMOUNT', 10); // Links scan amount for 1 iteration
156 define('SPBC_SCAN_LINKS_PERIOD', 30); // Links scan call period
157 define('SPBC_PSCAN_UPDATE_FILES_STATUS_PERIOD', 60); // Check cloud analysis files status period
158 define('SPBC_PSCAN_RESEND_FILES_STATUS_PERIOD', 300); // Resend files
159
160 // brief data limits
161 define('SPBC_BRIEF_DATA_DAYS_LIMIT', 7); // how many days will be logs looked for
162 define('SPBC_BRIEF_DATA_ACTIONS_LIMIT', 10); // how many actions will be logs looked for
163
164
165 require_once SPBC_PLUGIN_DIR . 'lib/spbc-php-patch.php'; // PHP functions patches
166 require_once SPBC_PLUGIN_DIR . 'lib/autoloader.php'; // Autoloader
167
168 /**
169 * Early translation call.
170 * @param string $text text to translate
171 * @return mixed|string|null
172 */
173 function __spbc($text)
174 {
175 static $et;
176 if (!isset($et) || !$et instanceof SpbcET) {
177 $et = new SpbcET(
178 SPBC_PLUGIN_DIR,
179 WP_CONTENT_DIR
180 );
181 if (did_action('init')) {
182 $et->checkWPTranslationReady();
183 } else {
184 add_action('init', [$et, 'checkWPTranslationReady']);
185 }
186 }
187 return $et->translate($text);
188 }
189
190 require_once SPBC_PLUGIN_DIR . 'inc/fw-update.php';
191
192 // Misc libs
193 require_once SPBC_PLUGIN_DIR . 'inc/spbc-tools.php'; // Different helper functions
194 require_once SPBC_PLUGIN_DIR . 'inc/spbc-pluggable.php'; // WordPress functions
195 require_once SPBC_PLUGIN_DIR . 'inc/spbc-scanner.php';
196 require_once(SPBC_PLUGIN_DIR . 'inc/spbc-wpcli.php');
197
198 // ArrayObject with settings and other global variables
199 global $spbc;
200 $spbc = new State(
201 'spbc',
202 array(
203 'settings',
204 'data',
205 'remote_calls',
206 'debug',
207 'installing',
208 'errors',
209 'fw_stats',
210 'scan_plugins_info',
211 'scan_themes_info'
212 ),
213 is_multisite(),
214 is_main_site()
215 );
216
217 require_once SPBC_PLUGIN_DIR . 'inc/spbc-auth.php';
218
219 // Update plugin's data to current version
220 spbc_update_actions();
221
222 // Collect and partially suppress doing_it_wrong_errors
223 new DoingItWrongHandler($spbc);
224
225 add_action('init', function () {
226 // Cron
227 global $spbc_cron, $spbc; // Letting know functions that they are running under spbc_cron
228 $spbc_cron = new SpbcCron();
229 ! SpbcRemoteCalls::check() && $spbc_cron->execute();
230 unset($spbc_cron);
231
232 // Remote calls
233 if ( SpbcRemoteCalls::check() ) {
234 try {
235 if ( Get::getString('spbc_remote_call_action') === 'run_service_template_get' ) {
236 require_once(SPBC_PLUGIN_DIR . 'inc/spbc-settings.php');
237 }
238 $rc = new SpbcRemoteCalls($spbc);
239 $rc->process();
240 } catch ( Exception $e ) {
241 die(json_encode(array('ERROR:' => $e->getMessage())));
242 }
243 }
244 });
245
246 //First start
247 // Do recheck the settings key
248 if ( !$spbc->key_is_ok || !empty($spbc->errors['apikey']) ) {
249 !empty($spbc->settings['api_key']) && spbc_check_account_status($spbc->settings['api_key']);
250 }
251
252 if ( $spbc->settings && $spbc->key_is_ok) {
253 require_once SPBC_PLUGIN_DIR . 'inc/spbc-firewall.php';
254 add_action('init', function () use ($spbc) {
255 // protect author login enumeration and password reset confirmation text
256 $login_protector = new LoginCollectingProtector($spbc);
257 $login_protector->init();
258 if ( is_admin() && spbc_is_user_logged_in() ) {
259 //do this if in admin area and user is logged in - check only admin area (WAF run)
260 if ( ! spbc_firewall_skip_check()) {
261 spbc_firewall_check_admin_area();
262 }
263
264 if ( ! spbc_firewall_skip_check_uploadchecker()) {
265 spbc_upload_checker__check();
266 }
267 } else {
268 //if not in admin area and user is not logged in - check with all modules
269 if ( ! spbc_firewall_skip_check()) {
270 spbc_firewall__check();
271 }
272 }
273 });
274 }
275
276 // Disable XMLRPC if setting is enabled
277 if ( $spbc->settings['wp__disable_xmlrpc'] ) {
278 add_filter('xmlrpc_enabled', '__return_false');
279 }
280
281 // Disable WordPress REST API for non-authenticated
282 if ( $spbc->settings['wp__disable_rest_api'] == '2' ) {
283 add_filter(
284 'rest_authentication_errors',
285 function ($result) {
286 if ( empty($result) && ! is_user_logged_in() ) {
287 return new WP_Error(
288 'rest_not_logged_in',
289 'You are not currently logged in. (Security by CleanTalk)',
290 array('status' => 401)
291 );
292 }
293 return $result;
294 }
295 );
296 }
297
298 // Disable the WordPress endpoint "users" REST API
299 if ($spbc->settings['wp__disable_rest_api'] == '1') {
300 add_filter(
301 'rest_authentication_errors',
302 function ($result) {
303 if (
304 Server::inUriCanonical('/wp/') &&
305 Server::inUriCanonical('users') &&
306 !is_user_logged_in() // do not check if user logged in
307 ) {
308 return new WP_Error(
309 'access_denied',
310 __('Current REST route access denied. (Security by CleanTalk)'),
311 array( 'status' => 401 )
312 );
313 }
314 return $result;
315 }
316 );
317 }
318
319 /**
320 * This is the Cron handler for the `spbc_security_check_vulnerabilities` task
321 *
322 * @return array|void
323 */
324 function spbc_security_check_vulnerabilities()
325 {
326 global $spbc;
327 try {
328 VulnerabilityAlarm::updateWPModulesVulnerabilities();
329 $spbc->data['spbc_security_check_vulnerabilities_last_call'] = time();
330 $spbc->save('data');
331 // Send found vulnerabilities to the cloud
332 VulnerabilityAlarmService::sendReport();
333 } catch ( \Exception $exception ) {
334 return ['error' => $exception->getMessage()];
335 }
336 }
337
338 /**
339 * This is the Cron handler for the `spbc_upload_dir_prevent_php_execution` task
340 *
341 * @return array|bool
342 */
343 function spbc_upload_dir_prevent_php_execution()
344 {
345 global $spbc;
346
347 if ($spbc->settings['wp__upload_dir_prevent_php_execution']) {
348 return UploadDirPreventPhpExecution::handle();
349 }
350 }
351
352 /**
353 * This is the Cron handler for the `spbc_cron__users_pass_check_routine` task
354 *
355 * @return void
356 */
357 function spbc_cron__users_pass_check_routine()
358 {
359 global $spbc;
360
361 if ($spbc->settings['check_pass__enable'] == '0') {
362 return;
363 }
364
365 UsersPassCheckCron::handle();
366 }
367
368 /**
369 * This is the Cron handler for the `spbc_cron__users_pass_check_worker` task
370 *
371 * @return void
372 */
373 function spbc_cron__users_pass_check_worker()
374 {
375 global $spbc;
376
377 if ($spbc->settings['check_pass__enable'] == '0') {
378 return;
379 }
380
381 UsersPassCheckCron::worker();
382 }
383
384 /**
385 * Update scanner exclusions from external files.
386 * @return array|true array on error, true otherwise
387 */
388 function spbc_update_scan_settings_exclusions()
389 {
390 global $spbc;
391
392 $path_exclusions_view = $spbc->settings['scanner__path_exclusions_view'];
393 $domains_exclusions_view = $spbc->settings['scanner__frontend_analysis__domains_exclusions_view'];
394
395 $settings = clone $spbc->settings;
396 $settings['scanner__path_exclusions_view'] = $path_exclusions_view;
397 $settings['scanner__frontend_analysis__domains_exclusions_view'] = $domains_exclusions_view;
398
399 try {
400 // update files exclusion from external
401 $pathExclusion = new FilesScanPathExclusion();
402 $path_exclusions_view = $pathExclusion->pathExclusionsView($path_exclusions_view);
403 $settings['scanner__path_exclusions_view'] = $path_exclusions_view;
404 $path_exclusions = $pathExclusion->pathExclusions($path_exclusions_view);
405 $settings['scanner__path_exclusions'] = $path_exclusions;
406
407 // update domains exclusion from external
408 $domainExclusion = new FrontendScanDomainExclusion();
409 $domains_exclusions_view = $domainExclusion->frontendScanDomainExclusionsView($domains_exclusions_view);
410 $settings['scanner__frontend_analysis__domains_exclusions_view'] = $domains_exclusions_view;
411 $domains_exclusions = $domainExclusion->domainExclusions($domains_exclusions_view);
412 $settings['scanner__frontend_analysis__domains_exclusions'] = $domains_exclusions;
413
414 // reset frontend results
415 $domainExclusion->resetScannerFrontendResult($settings);
416
417 $spbc->settings = $settings;
418 $spbc->save('settings');
419 } catch ( \Exception $exception ) {
420 return ['error' => $exception->getMessage()];
421 }
422
423 return true;
424 }
425
426 function spbc_change_author_name($link, $_author_id, $_author_nicename)
427 {
428 $link = preg_replace('@(.*?)([\w-]+\/)$@', '$1honeypot_login_' . microtime(true), $link);
429 wp_redirect($link);
430 die();
431 }
432
433 if ( $spbc->settings['monitoring__users'] ) {
434 add_action('admin_head', array( '\CleantalkSP\Monitoring\User', 'record' ));
435 add_action('wp_head', array( '\CleantalkSP\Monitoring\User', 'record' ));
436 }
437
438 //Password-protected pages also uses wp-login page, we should not break it
439 if ( $spbc->settings['login_page_rename__enabled'] ) {
440 new RenameLoginPage(
441 $spbc->settings['login_page_rename__name'],
442 $spbc->settings['login_page_rename__redirect']
443 );
444 }
445
446 // Logged hooks
447 register_activation_hook(__FILE__, 'spbc_activation');
448 register_deactivation_hook(__FILE__, 'spbc_deactivation');
449 register_uninstall_hook(__FILE__, 'spbc_uninstall');
450
451 // Hook for newly added blog
452 Activator::addActionForNetworkBlogLegacy(get_bloginfo('version'));
453
454 add_action('plugins_loaded', 'spbc_plugin_loaded', 1); // Main hook
455
456 // Posts hooks
457 add_action('wp_insert_post', 'spbc_update_postmeta_links', 10, 3);
458 add_action('wp_insert_comment', 'spbc_update_postmeta_links__by_comment', 10, 2);
459
460 // Set headers
461 add_action('init', 'spbc_set_headers');
462 add_action('login_enqueue_scripts', 'spbc_attach_public_css');
463
464 if ( $spbc->settings['spbc_trusted_and_affiliate__footer'] === '1' ) {
465 add_action('wp_enqueue_scripts', 'spbc_attach_public_css');
466 add_action('wp_footer', 'spbc_hook__wp_footer_trusted_text', 998);
467 }
468
469 if ( is_admin() || is_network_admin() ) {
470 include_once SPBC_PLUGIN_DIR . 'inc/spbc-admin.php';
471 add_action('init', function () {
472 include_once SPBC_PLUGIN_DIR . 'templates/spbc_settings_main.php'; // Templates for settings pgae
473 });
474 include_once SPBC_PLUGIN_DIR . 'inc/spbct-sync-react.php';
475
476 // Async loading for JavaScript
477 add_filter('script_loader_tag', array('CleantalkSP\SpbctWP\SpbcEnqueue', 'addScriptAttributes'), 10, 3);
478 add_action('admin_init', array('CleantalkSP\SpbctWP\Activator', 'redirectAfterActivation'), 1); // Redirect after activation
479 add_action('admin_init', 'spbc_admin_init', 1, 1); // Main admin hook
480 add_action('admin_menu', 'spbc_admin_add_page'); // Admin pages
481 add_action('network_admin_menu', 'spbc_admin_add_page'); // Network admin pages
482 add_action('admin_enqueue_scripts', array('CleantalkSP\SpbctWP\SpbcEnqueue', 'handleEnqueueHook')); // Scripts
483
484 if ( Post::getInt('spbc_brief_refresh') === 1 ) {
485 add_action('admin_init', 'spbc_set_brief_data', 1);
486 }
487
488 if ( $spbc->settings['wp__dashboard_widget__show'] ) {
489 add_action('wp_dashboard_setup', 'spbc_widget_scripts_init');
490 add_action('wp_dashboard_setup', 'spbc_dashboard_statistics_widget');
491 }
492
493
494 add_action('admin_init', function () {
495 global $spbc;
496 $admin_banners_handler = new \CleantalkSP\SpbctWP\AdminBannersModule\AdminBannersHandler($spbc);
497 $admin_banners_handler->handle();
498 });
499
500 // Customize row with the plugin on plugins list page.
501 if ( ( isset($pagenow) && $pagenow === 'plugins.php' ) || ( isset($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], 'plugins.php') !== false ) ) {
502 add_filter('plugin_action_links_' . SPBC_PLUGIN_BASE_NAME, 'spbc_plugin_action_links', 10, 2);
503 add_filter('network_admin_plugin_action_links_' . SPBC_PLUGIN_BASE_NAME, 'spbc_plugin_action_links', 10, 2);
504 add_filter('all_plugins', 'spbc_admin__change_plugin_description');
505 add_filter('plugin_row_meta', 'spbc_plugin_links_meta', 10, 2);
506 }
507 }
508
509 add_action('init', function () use ($spbc) {
510 if ( $spbc->feature_restrictions->getState($spbc, 'fswatcher')->is_active && $spbc->settings['scanner__fs_watcher'] ) {
511 FSWatcherController::getInstance();
512 }
513 });
514
515 function spbc_set_headers()
516 {
517 global $spbc;
518 if ( ! headers_sent() ) {
519 // Additional headers
520 if ( $spbc->settings['data__additional_headers'] ) {
521 header('X-XSS-Protection: 1; mode=block');
522 header('X-Content-Type-Options: nosniff');
523 header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
524 header('Referrer-Policy: strict-origin-when-cross-origin');
525 }
526
527 // Forbid to show in iframes
528 if ( $spbc->settings['misc__forbid_to_show_in_iframes'] ) {
529 header('X-Frame-Options: sameorigin', false);
530 }
531
532 // Set cookie to detect any logged in user
533 if (
534 spbc_is_user_logged_in() &&
535 ! empty($spbc->settings['data__set_cookies']) &&
536 (
537 ! Cookie::getString('spbc_is_logged_in') ||
538 Cookie::getString('spbc_is_logged_in') !== md5($spbc->data['salt'] . get_option('home'))
539 )
540 ) {
541 // skip rewriting spbc_is_logged_in cookie on favicon request for WPMS - its always run on main site url and returns it`s home url
542 if (
543 is_multisite() &&
544 strpos(Server::get('REQUEST_URI', null, 'url'), 'favicon.ico') !== false
545 ) {
546 return;
547 }
548 //rewrite spbc_is_logged_in cookie
549 Cookie::set('spbc_is_logged_in', md5($spbc->data['salt'] . get_option('home')), time() + 86400 * 365, '/');
550 }
551 }
552 }
553
554 function spbc_update_actions()
555 {
556 global $spbc;
557
558 //Update logic
559 $current_version = $spbc->data['plugin_version'];
560
561 if ( $current_version != SPBC_VERSION ) {
562 //Migrate DB data on updating to 2.128.1
563 add_action('ColumnCreator_before_drop_column_analysis_status', [UpdaterScripts::class, 'migrateDbData_2_128_1']);
564 add_action('ColumnCreator_before_change_column_event', [UpdaterScripts::class, 'migrateDbData_2_141_0']);
565
566 // Perform a transaction and exit transaction ID isn't match
567 if ( ! Transaction::get('updater', 5)->perform() ) {
568 return;
569 }
570
571 Updater::runUpdateScripts($current_version, SPBC_VERSION);
572
573 $spbc->data['plugin_version'] = SPBC_VERSION;
574 $spbc->save('data');
575
576 Transaction::get('updater')->clearTransactionTimer();
577 }
578 }
579
580 /**
581 * Plugin activation
582 *
583 * @param $network
584 * @param $redirect
585 *
586 * @return void
587 * @throws Exception
588 */
589 function spbc_activation($network, $redirect = true)
590 {
591 Activator::activation($network, $redirect);
592 }
593
594
595 /**
596 * A code during plugin deactivation.
597 *
598 * @param $network
599 *
600 * @return void
601 */
602 function spbc_deactivation($network)
603 {
604 \CleantalkSP\SpbctWP\Deactivator::deactivation($network);
605 }
606
607 /**
608 * Run deactivation process (complete deactivation forced) for hook register_uninstall_hook.
609 * Notice: this hook returns no callback arg!
610 * @return void
611 */
612 function spbc_uninstall()
613 {
614 \CleantalkSP\SpbctWP\Deactivator::deactivation(true, false, true);
615 }
616
617 /**
618 * @deprecated 2.125 use Deactivator::deleteBlogTables()
619 * @return void
620 */
621 function spbc_deactivation__delete_blog_tables() //deprecated
622 {
623 \CleantalkSP\SpbctWP\Deactivator::deleteBlogTables();
624 }
625
626 /**
627 * @deprecated 2.125 use Deactivator::deleteCommonTables()
628 * @return void
629 */
630 function spbc_deactivation__delete_common_tables() //deprecated
631 {
632 \CleantalkSP\SpbctWP\Deactivator::deleteCommonTables();
633 }
634
635 // Misc functions to test the plugin.
636 function spbc_plugin_loaded()
637 {
638 global $spbc;
639
640 if ( is_admin() || is_network_admin() ) {
641 $dir = plugin_basename(dirname(__FILE__)) . '/i18n';
642 load_plugin_textdomain('security-malware-firewall', false, $dir);
643 }
644
645 if ( $spbc->settings['spbc_trusted_and_affiliate__shortcode'] === '1' ) {
646 add_action('wp_enqueue_scripts', 'spbc_attach_public_css');
647 add_shortcode('cleantalk_security_affiliate_link', 'spbc_trusted_text_shortcode_handler');
648 }
649 }
650
651 /**
652 * Check brute force attack
653 *
654 * @return void
655 */
656 function spbc_authenticate__check_brute_force()
657 {
658 global $spbc;
659
660 $bfp = new BFP(
661 array(
662 'api_key' => $spbc->api_key,
663 'state' => $spbc,
664 'is_login_page' => spbc_is_login_page_request(),
665 'is_logged_in' => Cookie::getString('spbc_is_logged_in') === md5($spbc->data['salt'] . get_option('home')),
666 'bf_limit' => $spbc->settings['bfp__allowed_wrong_auths'],
667 'block_period' => $spbc->settings['bfp__block_period__5_fails'],
668 'count_period' => $spbc->settings['bfp__count_interval'],
669 )
670 );
671
672 $bfp->setDb(new DB());
673 $bfp->setIpArray([IP::get()]);
674 $bfp_result = $bfp->check();
675 $bfp->middleAction();
676
677 if (!empty($bfp_result)) {
678 $bfp->_die($bfp_result[0]);
679 }
680 }
681
682 //
683 // Sorts some data.
684 //
685 function spbc_usort_desc($a, $b)
686 {
687 return $b->datetime_ts - $a->datetime_ts;
688 }
689
690 /**
691 * Function to get the countries by IPs list.
692 *
693 * @param $ips_data
694 *
695 * @return array
696 */
697 function spbc_get_countries_by_ips($ips_data = '')
698 {
699 $ips_c = array();
700
701 if ( $ips_data === '' ) {
702 return $ips_c;
703 }
704
705 $result = SpbcAPI::method__ip_info($ips_data);
706
707 if ( empty($result['error']) ) {
708 foreach ( $result as $ip_dec => $v2 ) {
709 if ( isset($v2['country_code']) ) {
710 $ips_c[ $ip_dec ]['country_code'] = $v2['country_code'];
711 }
712 if ( isset($v2['country_name']) ) {
713 $ips_c[ $ip_dec ]['country_name'] = $v2['country_name'];
714 }
715 if ( isset($v2['subdivision']) && is_string($v2['subdivision']) && $v2['subdivision'] !== '' ) {
716 $ips_c[ $ip_dec ]['subdivision'] = $v2['subdivision'];
717 }
718 if ( isset($v2['city']) && is_string($v2['city']) && $v2['city'] !== '' ) {
719 $ips_c[ $ip_dec ]['city'] = $v2['city'];
720 }
721 }
722 }
723
724 return $ips_c;
725 }
726
727 /**
728 * Gets and write new signatures in local database
729 * @param bool $force_update if true, ignores last update and overwrite current signatures
730 * @return bool|array
731 * @global State $spbc
732 * @global WPDB $wpdb
733 */
734 function spbc_scanner__signatures_update($force_update = false)
735 {
736 global $spbc;
737
738 /**
739 * @psalm-suppress InvalidScalarArgument
740 */
741 $spbc->error_delete('scanner_update_signatures_bad_signatures', 'save');
742
743 $latest_signature_submitted_time = $force_update ? 0 : SignatureAnalysisFacade::getLatestSignatureSubmittedTime();
744
745 $signatures_from_cloud = SignatureAnalysisFacade::getSignaturesFromCloud($latest_signature_submitted_time);
746
747 // Signatures updated
748 if (!$force_update && isset($signatures_from_cloud['error']) && $signatures_from_cloud['error'] === 'UP_TO_DATE') {
749 return array('success' => 'UP_TO_DATE');
750 }
751
752 // There is errors
753 if (isset($signatures_from_cloud['error'])) {
754 return $signatures_from_cloud;
755 }
756
757 $signatures = $signatures_from_cloud['values'];
758 $map = $signatures_from_cloud['map'];
759
760 SignatureAnalysisFacade::clearSignaturesTable();
761
762 $signatures_added = SignatureAnalysisFacade::addSignaturesToDb($map, $signatures);
763
764 if (!$signatures_added) {
765 // Attempt to record one at a time
766 $signatures_added = SignatureAnalysisFacade::addSignaturesToDbOneByOne($map, $signatures);
767
768 if (isset($signatures_added['bad_signatures'])) {
769 $spbc->error_add('scanner_update_signatures_bad_signatures', $signatures_added['bad_signatures']);
770 }
771 }
772
773 $spbc->data['scanner']['last_signature_update'] = current_time('timestamp');
774 $spbc->data['scanner']['signature_count'] = count($signatures);
775 $spbc->save('data');
776
777 return true;
778 }
779
780 /**
781 * Ajax handler for sending Security FireWall logs
782 */
783 function spbc_send_firewall_logs_ajax_handler()
784 {
785 spbc_check_ajax_referer('spbc_secret_nonce', 'security');
786 $result = spbc_send_firewall_logs();
787 wp_send_json($result);
788 }
789
790 /**
791 * Sending Security FireWall logs
792 *
793 * @param $api_key
794 *
795 * @return array|int
796 */
797 function spbc_send_firewall_logs($api_key = false)
798 {
799 global $spbc;
800
801 $api_key = ! empty($api_key) ? $api_key : $spbc->api_key;
802
803 if ( ! empty($api_key) ) {
804 $result = FW::sendLog(
805 DB::getInstance(),
806 SPBC_TBL_FIREWALL_LOG,
807 $api_key
808 );
809
810 if ( empty($result['error']) ) {
811 $spbc->fw_stats['last_send'] = current_time('timestamp');
812 $spbc->fw_stats['last_send_count'] = $result;
813 $spbc->save('fw_stats', true, false);
814
815 return $result;
816 }
817
818 return $result;
819 }
820
821 return array(
822 'error' => 'KEY_EMPTY'
823 );
824 }
825
826 /**
827 * Drop Security FireWall data
828 *
829 * @return bool|string[]
830 */
831 function spbc_security_firewall_drop()
832 {
833 global $wpdb;
834
835 // @psalm-suppress WpdbUnsafeMethodsIssue
836 $result = $wpdb->query('DELETE FROM `' . SPBC_TBL_FIREWALL_DATA . '`;');
837
838 if ( $result !== false ) {
839 return true;
840 }
841
842 return array( 'error' => 'DELETE_ERROR' );
843 }
844
845 /**
846 * Handle firewall private_records remote call.
847 * @param $action string 'add','delete'
848 * @param $test_data string JSON string used in test cases
849 * @return string JSON string of results
850 * @throws Exception
851 */
852 function spbct_sfw_private_records_handler($action, $test_data = null)
853 {
854
855 $error = 'secfw_private_records_handler: ';
856
857 if ( !empty($action) && (in_array($action, array('add', 'delete'))) ) {
858 $metadata = !empty($test_data) ? $test_data : Post::getString('metadata'); // validation is done next line
859
860 /**
861 * Validate JSON
862 */
863 if ( !empty($metadata) ) {
864 $metadata = json_decode(stripslashes($metadata), true);
865 if ( $metadata === 'NULL' || $metadata === null ) {
866 throw new InvalidArgumentException($error . 'metadata JSON decoding failed');
867 }
868 } else {
869 throw new InvalidArgumentException($error . 'metadata is empty');
870 }
871
872 foreach ( $metadata as $_key => &$row ) {
873 $row = explode(',', $row);
874
875 /**
876 * Validation of JSON decoded array data
877 */
878 $ip_validated = false;
879 $validation_error = '';
880
881 //validate IP
882 if ( IP::validate($row[0]) === 'v6' ) {
883 $ip_validated = $row[0];
884 } elseif (
885 IP::validate(long2ip((int)$row[0])) === 'v4'
886 && (int)($row[0]) === ip2long(long2ip((int)$row[0]))
887 ) {
888 $ip_validated = (int)$row[0];
889 } else {
890 $validation_error = 'network value does not look like IP address ';
891 }
892
893 //do this to get info more obvious
894 $metadata_assoc_array = array(
895 'network' => $ip_validated ?: null,
896 'mask' => (int)$row[1],
897 'status' => isset($row[2]) && $row[2] !== '' ? (int)$row[2] : null,
898 );
899
900 //validate mask and status
901 if ( $metadata_assoc_array['mask'] === 0
902 || $metadata_assoc_array['mask'] > 4294967295
903 ) {
904 $validation_error = 'metadata validate failed on "mask" value';
905 }
906
907 //only for adding
908 if ( $action === 'add' ) {
909 if ( !in_array($metadata_assoc_array['status'], array(-4, -3, -2, -1, 0, 1, 2, 99)) ) {
910 $validation_error = 'metadata validate failed on "status" value';
911 }
912 }
913
914 if ( !empty($validation_error) ) {
915 throw new InvalidArgumentException($error . $validation_error);
916 }
917
918 /**
919 * Ip version logic
920 */
921 if ( is_string($metadata_assoc_array['network']) ) {
922 $metadata_assoc_array['network'] = IP::convertIPv6ToFourIPv4(IP::extendIPv6(IP::normalizeIPv6($metadata_assoc_array['network'])));
923
924
925 if ($metadata_assoc_array['mask'] > 128) {
926 $validation_error = 'metadata validate failed on "mask" value';
927 break;
928 }
929
930 /**
931 * Versatility for mask for v6 and v4
932 * @psalm-suppress LoopInvalidation
933 */
934 for ( $masks = array(), $mask = $metadata_assoc_array['mask'], $k = 4; $k >= 1; $k-- ) {
935 $masks[$k] = (2 ** 32) - (2 ** (32 - ($mask > 32 ? 32 : $mask)));
936 $mask -= 32;
937 $mask = $mask > 0 ? $mask : 0;
938 }
939 $metadata_assoc_array['mask'] = $masks;
940 }
941
942 //all checks done, change on link
943 $row = $metadata_assoc_array;
944 }
945 unset($row);
946
947 if ( !empty($validation_error) ) {
948 throw new InvalidArgumentException($error . $validation_error);
949 }
950
951 //method selection
952 if ( $action === 'add' ) {
953 $handler_output = FW::privateRecordsAdd(
954 DB::getInstance(),
955 $metadata
956 );
957 } elseif ( $action === 'delete' ) {
958 $handler_output = FW::privateRecordsDelete(
959 DB::getInstance(),
960 $metadata
961 );
962 } else {
963 $error .= 'unknown action name: ' . $action;
964 throw new InvalidArgumentException($error);
965 }
966 } else {
967 throw new InvalidArgumentException($error . 'empty action name');
968 }
969
970 return json_encode(array('OK' => $handler_output));
971 }
972
973 function spbc_update_postmeta_links($post_ID)
974 {
975 delete_post_meta($post_ID, '_spbc_links_checked');
976 delete_post_meta($post_ID, 'spbc_links_checked');
977 }
978
979 function spbc_update_postmeta_links__by_comment($id)
980 {
981 $comment = get_comment($id);
982 spbc_update_postmeta_links($comment->comment_post_ID);
983 }
984
985 // Install MU-plugin
986 function spbc_mu_plugin__install()
987 {
988
989 // If WPMU_PLUGIN_DIR is not exists -> create it
990 if ( ! is_dir(WPMU_PLUGIN_DIR) && ! mkdir(WPMU_PLUGIN_DIR) && ! is_dir(WPMU_PLUGIN_DIR) ) {
991 throw new \RuntimeException(sprintf('Directory "%s" was not created', WPMU_PLUGIN_DIR));
992 }
993
994 // Get data from info file and write it to new plugin file
995 $file = '<?php' . PHP_EOL . file_get_contents(SPBC_PLUGIN_DIR . '/install/security-malware-firewall-mu.php');
996
997 return @file_put_contents(WPMU_PLUGIN_DIR . '/0security-malware-firewall-mu.php', $file) ? true : false;
998 }
999
1000 /**
1001 * Uninstall MU-plugin
1002 * @deprecated 2.125 Use Deactivator::muPluginUninstall
1003 * @return bool
1004 */
1005 function spbc_mu_plugin__uninstall()
1006 {
1007 return \CleantalkSP\SpbctWP\Deactivator::muPluginUninstall();
1008 }
1009
1010 function spbc_user_is_admin()
1011 {
1012 global $spbc;
1013
1014 if (!empty($spbc->settings['data__set_cookies'])) {
1015 return
1016 Cookie::getString('spbc_is_logged_in') === md5($spbc->data['salt'] . get_option('home')) &&
1017 Cookie::getString('spbc_admin_logged_in') === md5($spbc->data['salt'] . 'admin' . get_option('home'));
1018 }
1019
1020 return is_admin();
1021 }
1022
1023 /**
1024 * Ajax handler for sending logs.
1025 */
1026 function spbc_send_logs_ajax_handler()
1027 {
1028 spbc_check_ajax_referer('spbc_secret_nonce', 'security');
1029 $result = spbc_send_logs();
1030 wp_send_json($result);
1031 }
1032
1033 //Function to send logs
1034 function spbc_send_logs($api_key = null)
1035 {
1036 global $spbc, $wpdb;
1037
1038 if ( $api_key == null ) {
1039 if ( ! $spbc->is_mainsite && $spbc->ms__work_mode == 2 ) {
1040 $api_key = $spbc->network_settings['spbc_key'];
1041 } else {
1042 $api_key = $spbc->settings['spbc_key'];
1043 }
1044 }
1045
1046 if ( ! $api_key ) {
1047 return array(
1048 'error' => 'KEY_EMPTY'
1049 );
1050 }
1051
1052 $wpms_snippet = SPBC_WPMS
1053 ? $wpdb->prepare(" WHERE blog_id = %d AND ", get_current_blog_id())
1054 : " WHERE ";
1055
1056 // @psalm-suppress WpdbUnsafeMethodsIssue
1057 $rows = $wpdb->get_results(
1058 "SELECT id, datetime, timestamp_gmt, user_login, page, page_time, event, auth_ip, role, user_agent, browser_sign
1059 FROM " . SPBC_TBL_SECURITY_LOG
1060 . $wpms_snippet
1061 . " sent <> 1"
1062 . " ORDER BY datetime DESC"
1063 . " LIMIT " . SPBC_SELECT_LIMIT . ";"
1064 );
1065
1066 $rows_count = count($rows);
1067
1068 if ( $rows_count ) {
1069 $data = array();
1070
1071 foreach ( $rows as $record ) {
1072 $page_time = (string) $record->page_time;
1073 if ((int)$page_time <= 0) {
1074 $page_time = '1';
1075 }
1076
1077 $user_agent = null;
1078 $browser_signature = null;
1079 if ( in_array(strval($record->event), array( 'login', 'login_2fa', 'login_new_device', 'login_token', 'logout', )) ) {
1080 $user_agent = $record->user_agent;
1081 $browser_signature = $record->browser_sign;
1082 }
1083
1084 $security_logs_row_dto = new SecurityLogsDataRowDTO(array(
1085 'log_id' => (string) $record->id,
1086 'datetime' => (string) $record->datetime,
1087 'datetime_gmt' => $record->timestamp_gmt,
1088 'user_log' => (string) $record->user_login,
1089 'event' => (string) $record->event,
1090 'auth_ip' => strpos($record->auth_ip, ':') === false
1091 ? (int) sprintf('%u', ip2long($record->auth_ip))
1092 : (string) $record->auth_ip,
1093 'page_url' => (string) $record->page,
1094 'event_runtime' => $page_time,
1095 'role' => (string) $record->role,
1096 'user_agent' => (string) $user_agent,
1097 'browser_signature' => (string) $browser_signature,
1098 ));
1099 $data[] = $security_logs_row_dto->getArray();
1100 }
1101
1102 $security_logs_method_dto = new SecurityLogsDTO(
1103 array(
1104 'auth_key' => $api_key,
1105 'method_name' => 'security_logs',
1106 'timestamp' => current_time('timestamp'),
1107 'data' => json_encode($data),
1108 'rows' => count($data),
1109 )
1110 );
1111
1112 $result = SpbcAPI::method__security_logs($security_logs_method_dto);
1113
1114 if ( empty($result['error']) ) {
1115 // Clear local table if it's ok.
1116 if ( $result['rows'] == $rows_count ) {
1117 $updated_ids = array();
1118 foreach ($data as $item) {
1119 $updated_ids[] = $item['log_id'];
1120 }
1121
1122 $placeholders = rtrim(str_repeat('%s,', count($updated_ids)), ',');
1123 if ( SPBC_WPMS ) {
1124 $sql = "UPDATE " . SPBC_TBL_SECURITY_LOG . " SET sent = 1 WHERE id IN ($placeholders)"
1125 . ( $spbc->ms__work_mode == 2 ? '' : ' AND blog_id = ' . get_current_blog_id() )
1126 . ";";
1127 } else {
1128 $sql = "UPDATE " . SPBC_TBL_SECURITY_LOG . " SET sent = 1 WHERE id IN ($placeholders);";
1129 }
1130 $wpdb->query($wpdb->prepare($sql, $updated_ids));
1131
1132 $result = $rows_count;
1133 } else {
1134 $result = array(
1135 'error' => sprintf(__('Sent: %d. Confirmed receiving of %d rows.', 'security-malware-firewall'), $rows_count, intval($result['rows']))
1136 );
1137 }
1138 }
1139 } else {
1140 $result = array(
1141 'error' => 'NO_LOGS_TO_SEND'
1142 );
1143 }
1144
1145 global $spbc_cron;
1146 if ( ! empty($spbc_cron) && empty($result['error']) ) {
1147 $spbc->data['logs_last_sent'] = current_time('timestamp');
1148 $spbc->data['last_sent_events_count'] = $result;
1149 }
1150
1151 $has_unsent_logs = spbc_has_unsent_security_logs();
1152
1153 if ($has_unsent_logs) {
1154 SpbcCron::updateTask('send_logs', 'spbc_send_logs', 60, time() + 60);
1155 } else {
1156 SpbcCron::updateTask('send_logs', 'spbc_send_logs', 3600, time() + 3600);
1157 }
1158
1159 return $result;
1160 }
1161
1162 /**
1163 * Checks if there are unsent logs persists in the SPBC_TBL_SECURITY_LOG and the count is larger than SPBC_SELECT_LIMIT.
1164 * Used in cases if logs were generated faster than sending logs cron handled them.
1165 * @return bool
1166 */
1167 function spbc_has_unsent_security_logs()
1168 {
1169 global $wpdb;
1170
1171 $wpms_snippet = SPBC_WPMS
1172 ? $wpdb->prepare(" WHERE blog_id = %d AND ", get_current_blog_id())
1173 : " WHERE ";
1174
1175 $rows = $wpdb->get_var(
1176 "SELECT count(id)
1177 FROM " . SPBC_TBL_SECURITY_LOG
1178 . $wpms_snippet
1179 . " sent <> 1"
1180 . " ORDER BY datetime DESC;"
1181 );
1182
1183 return (int)$rows > SPBC_SELECT_LIMIT;
1184 }
1185
1186 /**
1187 * @return bool
1188 * @psalm-suppress RedundantCondition
1189 */
1190 function spbc_set_api_key()
1191 {
1192 global $spbc;
1193
1194 $website = parse_url(get_option('home'), PHP_URL_HOST) . parse_url(get_option('home'), PHP_URL_PATH);
1195 $platform = 'wordpress';
1196 $user_ip = IP::get();
1197 $timezone = (string)get_option('gmt_offset');
1198 $language = Server::getString('HTTP_ACCEPT_LANGUAGE');
1199 $is_wpms = is_multisite() && defined('SUBDOMAIN_INSTALL') && ! SUBDOMAIN_INSTALL;
1200 $white_label = false;
1201 $hoster_api_key = $spbc->ms__hoster_api_key;
1202
1203 $result = SpbcAPI::method__get_api_key(
1204 'security',
1205 get_network_option(0, 'admin_email'),
1206 $website,
1207 $platform,
1208 $timezone,
1209 $language,
1210 $user_ip,
1211 $is_wpms,
1212 $white_label,
1213 $hoster_api_key
1214 );
1215
1216 if ( ! empty($result['error']) ) {
1217 $spbc->data['key_is_ok'] = false;
1218 $spbc->error_add('get_key', $result);
1219
1220 return false;
1221 } else {
1222 $api_key = trim($result['auth_key']);
1223 $api_key = preg_match('/^[a-z\d]*$/', $api_key) ? $api_key : $spbc->settings['spbc_key']; // Check key format a-z\d
1224 $api_key = is_main_site() || $spbc->ms__work_mode != 2 ? $api_key : $spbc->network_settings['spbc_key'];
1225 $spbc->settings['spbc_key'] = $api_key;
1226 $spbc->save('settings');
1227
1228 $spbc->data['user_token'] = ( ! empty($result['user_token']) ? $result['user_token'] : '' );
1229 $spbc->data['key_is_ok'] = spbc_api_key__is_correct($api_key);
1230 $spbc->data['key_changed'] = true;
1231 $spbc->save('data');
1232
1233 $spbc->error_delete('get_key api_key');
1234
1235 return true;
1236 }
1237 }
1238
1239 /**
1240 * The functions check to check an account
1241 * Executes only via cron (on the main blog)
1242 *
1243 * @param null $spbc_key
1244 *
1245 * @return array|bool|bool[]|string[]
1246 */
1247 function spbc_access_key_notices($spbc_key = null)
1248 {
1249 global $spbc;
1250
1251 $spbc_key = $spbc_key ?: $spbc->settings['spbc_key'];
1252
1253 if ( empty($spbc_key) ) {
1254 if ( ! $spbc->is_mainsite && $spbc->ms__work_mode != 2 ) {
1255 $spbc_key = ! empty($spbc->network_settings['spbc_key']) ? $spbc->network_settings['spbc_key'] : false;
1256 if ( ! $spbc_key ) {
1257 return array( 'error' => 'KEY_IS_NOT_OK_ON_MAIN_WPMS_SITE' );
1258 }
1259 } else {
1260 $spbc_key = ! empty($spbc->settings['spbc_key']) ? $spbc->settings['spbc_key'] : false;
1261 if ( ! $spbc_key ) {
1262 return array( 'error' => 'KEY_IS_NOT_OK' );
1263 }
1264 }
1265 }
1266
1267 $account_status = spbc_check_account_status($spbc_key);
1268 if (is_string($account_status)) {
1269 return array( 'error' => $account_status);
1270 }
1271
1272 return true;
1273 }
1274
1275 function spbc_PHP_logs__collect($last_log_sent)
1276 {
1277 $logs = array();
1278 $start_timestamp = time();
1279
1280
1281 // Try to get log from wp-content/debug/log if default file is not accessible
1282 $file = ini_get('error_log');
1283 $file = file_exists($file) && is_readable($file)
1284 ? $file
1285 : WP_CONTENT_DIR . '/debug.log';
1286
1287 if ( file_exists($file) ) {
1288 if ( is_readable($file) ) {
1289 // Return if file is empty
1290 if ( ! filesize($file) ) {
1291 return array();
1292 }
1293
1294 $fd = @fopen($file, 'rb');
1295
1296 if ( $fd ) {
1297 $eol = spbc_PHP_logs__detect_EOL_type($file, false);
1298 if (is_null($eol) || is_int($eol)) {
1299 $eol = "\n";
1300 }
1301
1302 for (
1303 // Initialization
1304 $fsize = filesize($file), $offset = 1024 * 5, $position = $fsize - $offset,
1305 $max_log_size = 1024 * 1024 * 1, $max_read_size = 1024 * 1024 * 4,
1306 $log_size = 0, $read = 0,
1307 $log_count = 0;
1308 // Conditions
1309 $log_size < $max_log_size && // Max usefull data
1310 $offset === 1024 * 5 && // End of file
1311 $read < $max_read_size && // Max read depth
1312 $log_count < 3500 &&
1313 time() < $start_timestamp + 25;
1314 // Iteartion adjustments
1315 $position -= $offset
1316 ) {
1317 $offset = $position < 0 ? $offset + $position : $offset;
1318 $position = $position < 0 ? 0 : $position;
1319
1320 // Set pointer to $it * $offset from the EOF. Or 0 if it's negative.
1321 fseek($fd, $position);
1322
1323 // Read $offset bytes
1324 $it_logs = fread($fd, $offset);
1325
1326 // Clean to first EOL, splitting to array by PHP_EOL.
1327 if ( $position != 0 ) {
1328 $position_adjustment = strpos($it_logs, $eol);
1329 $position += $position_adjustment + 1;
1330 $it_logs = substr($it_logs, $position_adjustment);
1331 }
1332
1333 $read += strlen($it_logs);
1334 $it_logs = explode($eol, $it_logs);
1335
1336 // Filtering and parsing
1337 foreach ( $it_logs as $log_line ) {
1338 if ( spbc_PHP_logs__filter($log_line, $last_log_sent) ) {
1339 $log_size += strlen($log_line);
1340 $log_count++;
1341 $parsed_log_line = spbc_PHP_logs__parse_line($log_line);
1342 if ( $parsed_log_line ) {
1343 $logs[] = $parsed_log_line;
1344 }
1345 }
1346 }
1347 }
1348
1349 return $logs;
1350 } else {
1351 return array( 'error' => 'COULDNT_OPEN_LOG_FILE' );
1352 }
1353 } else {
1354 return array( 'error' => 'LOG_FILE_IS_UNACCESSIBLE' );
1355 }
1356 } else {
1357 return array( 'error' => 'LOG_FILE_NOT_EXISTS' );
1358 }
1359 }
1360
1361 function spbc_PHP_logs__filter($line, $php_logs_last_sent)
1362 {
1363 $line = trim($line);
1364
1365 if ( ! empty($line) ) {
1366 preg_match('/^\[(.*?\s\d\d:\d\d:\d\d.*?)]/', $line, $matches);
1367 if ( isset($matches[1]) && strtotime($matches[1]) >= $php_logs_last_sent ) {
1368 if ( preg_match('/^\[(.*?)\]\s+PHP\s(Warning|Fatal|Notice|Parse)/', $line) ) {
1369 } else {
1370 $line = false;
1371 }
1372 } else {
1373 $line = false;
1374 }
1375 } else {
1376 $line = false;
1377 }
1378
1379 return $line;
1380 }
1381
1382 function spbc_PHP_logs__parse_line($line)
1383 {
1384 if ( preg_match('/^\[(.*?)\]\s((.*?):\s+(.+))$/', $line, $matches) ) {
1385 return array(
1386 date('Y-m-d H:i:s', strtotime($matches[1])),
1387 $matches[2],
1388 );
1389 }
1390 }
1391
1392 function spbc_PHP_logs__send()
1393 {
1394 global $spbc;
1395
1396 if ( empty($spbc->settings['misc__backend_logs_enable']) || empty($spbc->settings['spbc_key']) ) {
1397 return true;
1398 }
1399
1400 $logs = spbc_PHP_logs__collect($spbc->data['last_php_log_sent']);
1401
1402 if ( empty($logs['error']) ) {
1403 if ( ! empty($logs) ) {
1404 $result = SpbcAPI::method__security_backend_logs($spbc->settings['spbc_key'], $logs);
1405
1406 if ( empty($result['error']) ) {
1407 if ( isset($result['total_logs_found']) ) {
1408 if ( $result['total_logs_found'] == count($logs) ) {
1409 $spbc->data['last_php_log_sent'] = time();
1410 $spbc->data['last_php_log_amount'] = $result['total_logs_found'];
1411 $spbc->save('data');
1412
1413 return true;
1414 } else {
1415 return array( 'error' => 'LOGS_COUNT_DOES_NOT_MATCH' );
1416 }
1417 } else {
1418 return array( 'error' => 'LOGS_COUNT_IS_EMPTY' );
1419 }
1420 } else {
1421 return $result;
1422 }
1423 } else {
1424 return true;
1425 }
1426 } else {
1427 return $logs;
1428 }
1429 }
1430
1431 /**
1432 * Extended check_ajax_referer function. Includes admin role check.
1433 * @param string|int $action ajax action
1434 * @param string|false $query_arg query arg where to search a nonce
1435 * @param bool $die is need to stop the flow with 403 wp_die()
1436 * @param bool $strict_admins is need to check current user capabilities comply to admin's
1437 * @return bool
1438 */
1439 function spbc_check_ajax_referer($action = -1, $query_arg = false, $die = true, $strict_admins = true)
1440 {
1441 $result = true;
1442 if (function_exists('check_ajax_referer')) {
1443 /** @psalm-suppress ForbiddenCode */
1444 $result = check_ajax_referer($action, $query_arg, $die);
1445 if (!$result && $die) {
1446 wp_die('-1', 403);
1447 }
1448 }
1449 // if native check passed, check if user is admin - only if param provided
1450 if ($result && $strict_admins) {
1451 $result = current_user_can('manage_options');
1452 }
1453 if (!$result && $die) {
1454 wp_die('-1', 403);
1455 }
1456 return (bool)$result;
1457 }
1458
1459 /**
1460 * Check connection to the API servers
1461 *
1462 * @param array $urls_to_test
1463 *
1464 * @return array
1465 */
1466 function spbc_test_connection($urls_to_test = array())
1467 {
1468
1469 $out = array();
1470 $urls_to_test = $urls_to_test ?: array_keys(HTTP::getCleantalksAPIServersFromDNS());
1471
1472 foreach ( $urls_to_test as $url ) {
1473 $start = microtime(true);
1474 $result = HTTP::getContentFromURL($url, false);
1475
1476 $out[ $url ] = array(
1477 'result' => ! empty($result['error']) ? $result['error'] : 'OK',
1478 'exec_time' => microtime(true) - $start,
1479 );
1480 }
1481
1482 return $out;
1483 }
1484
1485 /**
1486 * Run full sync with CleanTalk cloud.
1487 *
1488 * @return array{success: bool, reload: bool}
1489 */
1490 function spbc_sync()
1491 {
1492 return SpbcSync::run();
1493 }
1494
1495 function spbct_perform_service_get()
1496 {
1497 global $spbc;
1498
1499 $result_service_get = SpbcAPI::method__service_get(
1500 $spbc->api_key,
1501 $spbc->data['user_token']
1502 );
1503
1504 if ( empty($result_service_get['error']) ) {
1505 $spbc->settings['fw__custom_message'] = isset($result_service_get['server_response'])
1506 ? $result_service_get['server_response']
1507 : '';
1508 $spbc->save('settings');
1509 }
1510
1511 return $result_service_get;
1512 }
1513
1514 // The functions sends daily reports about attempts to login.
1515 function spbc_send_daily_report($skip_data_rotation = false)
1516 {
1517
1518 if ( ! function_exists('wp_mail') ) {
1519 add_action('plugins_loaded', 'spbc_send_daily_report');
1520
1521 return;
1522 }
1523
1524 global $spbc, $wpdb, $spbc_tpl;
1525
1526 //If key is not ok, send daily report!
1527 if ( ! $spbc->key_is_ok ) {
1528 include_once SPBC_PLUGIN_DIR . 'templates/spbc_send_daily_report.php';
1529
1530 // Hours
1531 $report_interval = 24 * 7;
1532
1533 $admin_email = spbc_get_admin_email();
1534 if ( ! $admin_email ) {
1535 SpbcDevLogger::write(
1536 sprintf(
1537 '%s: can\'t send the Daily report because of empty Admin email. File: %s, line %d.',
1538 $spbc->data["wl_brandname"],
1539 __FILE__,
1540 __LINE__
1541 )
1542 );
1543
1544 return false;
1545 }
1546
1547 $sql = $wpdb->prepare(
1548 'SELECT id,datetime,user_login,event,auth_ip,page,page_time
1549 FROM ' . SPBC_TBL_SECURITY_LOG . ' WHERE datetime between now() - interval %d hour and now();',
1550 $report_interval
1551 );
1552 $rows = $wpdb->get_results($sql);
1553 foreach ( $rows as $k => $v ) {
1554 if ( isset($v->datetime) ) {
1555 $v->datetime_ts = strtotime($v->datetime);
1556 }
1557 $rows[$k] = $v;
1558 }
1559 usort($rows, "spbc_usort_desc");
1560
1561 $record_datetime = time();
1562 $events = array();
1563 $auth_failed_events = array();
1564 $invalid_username_events = array();
1565 $auth_failed_count = 0;
1566 $invalid_username_count = 0;
1567 $ips_data = '';
1568 foreach ( $rows as $record ) {
1569 if ( strtotime($record->datetime) > $record_datetime ) {
1570 $record_datetime = strtotime($record->datetime);
1571 }
1572 $events[ $record->event ][ $record->user_login ][] = array(
1573 'datetime' => $record->datetime,
1574 'auth_ip' => $record->auth_ip,
1575 'user_login' => $record->user_login,
1576 'page' => $record->page ?: '-',
1577 'page_time' => $record->page_time ?: 'Unknown'
1578 );
1579
1580 switch ( $record->event ) {
1581 case 'auth_failed':
1582 $auth_failed_events[ $record->user_login ][ $record->auth_ip ] = array(
1583 'attempts' => isset($auth_failed_events[ $record->user_login ][ $record->auth_ip ]['attempts'])
1584 ? $auth_failed_events[ $record->user_login ][ $record->auth_ip ]['attempts'] + 1
1585 : 1,
1586 'auth_ip' => $record->auth_ip,
1587 'user_login' => $record->user_login
1588 );
1589 $auth_failed_count++;
1590 break;
1591 case 'invalid_username':
1592 $invalid_username_events[ $record->user_login ][ $record->auth_ip ] = array(
1593 'attempts' => isset($invalid_username_events[ $record->user_login ][ $record->auth_ip ]['attempts'])
1594 ? $invalid_username_events[ $record->user_login ][ $record->auth_ip ]['attempts'] + 1
1595 : 1,
1596 'auth_ip' => $record->auth_ip,
1597 'user_login' => $record->user_login
1598 );
1599 $invalid_username_count++;
1600 break;
1601 }
1602 if ( $ips_data != '' ) {
1603 $ips_data .= ',';
1604 }
1605 $ips_data .= $record->auth_ip;
1606 }
1607
1608 $ips_c = spbc_get_countries_by_ips($ips_data);
1609
1610 $event_part = '';
1611 $auth_failed_part = sprintf(
1612 "<p style=\"color: #666;\">%s</p>",
1613 _("0 brute force attacks have been made for past day.")
1614 );
1615 if ( $auth_failed_count ) {
1616 foreach ( $auth_failed_events as $e ) {
1617 $ip_part = '';
1618 foreach ( $e as $ip ) {
1619 $country_part = spbc_report_country_part($ips_c, $ip['auth_ip']);
1620 $ip_part .= sprintf(
1621 "<a href=\"https://cleantalk.org/blacklists/%s\">%s</a>, #%d, %s<br />",
1622 $ip['auth_ip'],
1623 $ip['auth_ip'],
1624 $ip['attempts'],
1625 $country_part
1626 );
1627 }
1628 $event_part .= sprintf($spbc_tpl['event_part_tpl'], $ip['user_login'], $ip_part);
1629 }
1630 $auth_failed_part = sprintf($spbc_tpl['auth_failed_part'], $event_part);
1631 }
1632
1633 $invalid_username_part = sprintf(
1634 "<p style=\"color: #666;\">%s</p>",
1635 _('0 brute force attacks have been made for past day.')
1636 );
1637
1638 if ( $invalid_username_count ) {
1639 foreach ( $invalid_username_events as $e ) {
1640 $ip_part = '';
1641 foreach ( $e as $ip ) {
1642 $country_part = spbc_report_country_part($ips_c, $ip['auth_ip']);
1643 $ip_part .= sprintf(
1644 "<a href=\"https://cleantalk.org/blacklists/%s\">%s</a>, #%d, %s<br />",
1645 $ip['auth_ip'],
1646 $ip['auth_ip'],
1647 $ip['attempts'],
1648 $country_part
1649 );
1650 }
1651 $event_part .= sprintf(
1652 $spbc_tpl['event_part_tpl'],
1653 $ip['user_login'],
1654 $ip_part
1655 );
1656 }
1657 $invalid_username_part = sprintf($spbc_tpl['auth_failed_part'], $event_part);
1658 }
1659
1660 $logins_part = sprintf(
1661 "<p style=\"color: #666;\">%s</p>",
1662 _('0 users have been logged in for past day.')
1663 );
1664 if ( isset($events['login']) && count($events['login']) ) {
1665 $event_part = '';
1666 foreach ( $events['login'] as $user_login => $e ) {
1667 $l_part = '';
1668 foreach ( $e as $e2 ) {
1669 $country_part = spbc_report_country_part($ips_c, $e2['auth_ip']);
1670 $l_part .= sprintf(
1671 "%s, <a href=\"https://cleantalk.org/blacklists/%s\">%s</a>, %s<br />",
1672 date("M d Y H:i:s", strtotime($e2['datetime'])),
1673 $e2['auth_ip'],
1674 $e2['auth_ip'],
1675 $country_part
1676 );
1677 }
1678 $event_part .= sprintf(
1679 $spbc_tpl['event_part_tpl'],
1680 $user_login,
1681 $l_part
1682 );
1683 }
1684 $logins_part = sprintf(
1685 $spbc_tpl['logins_part_tpl'],
1686 $event_part
1687 );
1688 }
1689
1690 $title_main_part = _('Daily security report');
1691 $subject = sprintf(
1692 '%s %s',
1693 parse_url(get_option('home'), PHP_URL_HOST),
1694 $title_main_part
1695 );
1696
1697 $message_anounce = sprintf(
1698 _('%s brute force attacks or failed logins, %d successful logins.'),
1699 number_format($auth_failed_count + $invalid_username_count, 0, ',', ' '),
1700 isset($events['login']) ? count($events['login']) : 0
1701 );
1702
1703
1704 $message = sprintf(
1705 $spbc_tpl['message_tpl'],
1706 $spbc_tpl['message_style'],
1707 $title_main_part,
1708 $message_anounce,
1709 $auth_failed_part,
1710 $invalid_username_part,
1711 $logins_part,
1712 $spbc->data["wl_brandname"]
1713 );
1714
1715
1716 $headers = array('Content-Type: text/html; charset=UTF-8');
1717 wp_mail(
1718 $admin_email,
1719 $subject,
1720 $message,
1721 $headers
1722 );
1723
1724 if ( ! $skip_data_rotation ) {
1725 $sql = $wpdb->prepare(
1726 "DELETE FROM " . SPBC_TBL_SECURITY_LOG . " WHERE datetime <= %s;",
1727 date("Y-m-d H:i:s", $record_datetime)
1728 );
1729 $wpdb->query($sql);
1730 };
1731 }
1732
1733 return null;
1734 }
1735
1736 function spbc_private_list_add()
1737 {
1738 global $spbc, $current_user;
1739
1740 spbc_check_ajax_referer('spbc_secret_nonce', 'security');
1741
1742 $ip = IP::get();
1743
1744 if ( Cookie::getString('spbc_secfw_ip_wl') === md5($ip . $spbc->spbc_key) ) {
1745 return;
1746 }
1747
1748 if ( in_array('administrator', $current_user->roles) ) {
1749 $res = spbc_private_list_add_api_call($ip);
1750 if ( $res ) {
1751 if ( ! headers_sent() ) {
1752 $cookie_val = md5($ip . $spbc->spbc_key);
1753 Cookie::set('spbc_secfw_ip_wl', $cookie_val, time() + 86400 * 25, '/', '', false, true);
1754 }
1755
1756 // Add to the local database
1757 $status_for_db = 1;
1758 $version = IP::validate($ip);
1759 if ( $version === 'v4' ) {
1760 $data[] = ip2long($ip) . ',' . ip2long('255.255.255.255') . ',' . $status_for_db;
1761 } elseif ( $version === 'v6' ) {
1762 $data[] = $ip . ',' . '128' . ',' . $status_for_db;
1763 } else {
1764 wp_send_json_error('Local database: adding IP ' . $ip . ' failed: ip does not look like a valid IP address');
1765 }
1766 try {
1767 $res_local = spbct_sfw_private_records_handler('add', json_encode($data, JSON_FORCE_OBJECT));
1768 wp_send_json_success($res_local);
1769 } catch (\Exception $e) {
1770 wp_send_json_error('Local database: adding IP ' . $ip . ' failed: ' . $e->getMessage());
1771 }
1772 }
1773 wp_send_json_error('API wrong answer.');
1774 }
1775 }
1776
1777 function spbc_private_list_add_api_call($ip)
1778 {
1779 global $spbc;
1780 if ( IP::validate($ip) !== false ) {
1781 $res = SpbcAPI::method__private_list_add__secfw_wl($spbc->user_token, $ip, $spbc->data['service_id']);
1782
1783 return isset($res['records'][0]['operation_status']) && $res['records'][0]['operation_status'] === 'SUCCESS';
1784 }
1785
1786 return false;
1787 }
1788
1789 /**
1790 * Cron. Update statuses of files sent to the cloud sandbox.
1791 */
1792 function spbc_scanner_update_pscan_files_status()
1793 {
1794 global $wpdb;
1795 // Reading DB for NEW files
1796 $undone_files_list = $wpdb->get_results(
1797 'SELECT fast_hash'
1798 . ' FROM ' . SPBC_TBL_SCAN_FILES
1799 . ' WHERE pscan_processing_status <> "DONE" AND pscan_processing_status IS NOT NULL',
1800 ARRAY_A
1801 );
1802
1803 if ( ! empty($undone_files_list) ) {
1804 $files_fast_hashes_to_update = array();
1805 foreach ( $undone_files_list as $file ) {
1806 $files_fast_hashes_to_update[] = $file['fast_hash'];
1807 }
1808 ScannerAjaxEndpoints::checkFilesAnalysisStatus(true, $files_fast_hashes_to_update);
1809 } else {
1810 \CleantalkSP\SpbctWP\Cron::removeTask('scanner_update_pscan_files_status');
1811 }
1812 }
1813
1814 /**
1815 * Cron. Resend files that were not added to the cloud sandbox queue.
1816 */
1817 function spbc_scanner_resend_pscan_files()
1818 {
1819 $pending_queue_files = ScanRepository::getPendingQueueFiles();
1820
1821 if (!empty($pending_queue_files)) {
1822 foreach ($pending_queue_files as $file) {
1823 // fix for files sent to manual analysis
1824 if (!empty($file['status']) && $file['status'] === 'APPROVED_BY_CT') {
1825 ScanStorage::setFileAsNotPendingQueue($file['fast_hash']);
1826 continue;
1827 }
1828
1829 SendFileToCloudService::sendFile($file['fast_hash']);
1830 }
1831 } else {
1832 \CleantalkSP\SpbctWP\Cron::removeTask('scanner_resend_pscan_files');
1833 }
1834 }
1835
1836 /**
1837 * Checking account status.
1838 * @param $api_key
1839 * @return true|string - true if account is ok, string with first error message otherwise
1840 */
1841 function spbc_check_account_status($api_key)
1842 {
1843 global $spbc, $plugin_info;
1844
1845 $validation_result = spbc_validate_access_key($api_key);
1846 $result = $validation_result['api_response'];
1847 $validation_errors = $validation_result['errors'];
1848 if ( !empty($validation_errors) ) {
1849 foreach ($validation_errors as $error) {
1850 $error = is_string($error) ? $error : json_encode($error);
1851 $error = __spbc(SpbcET::__KEY_VALIDATION__FAILED_COMMON) . ' ' . $error;
1852 $spbc->error_add('apikey', $error);
1853 }
1854 $spbc->data['key_is_ok'] = false;
1855 $spbc->save('data');
1856 $first_error = reset($validation_errors);
1857 return is_string($first_error) ? $first_error : 'UNKNOWN_ACCOUNT_STATUS_ERROR';
1858 }
1859 if (!empty($result)) {
1860 $spbc->data['key_is_ok'] = true;
1861 $spbc->error_delete('apikey', true);
1862 }
1863
1864 if ( isset($result['user_token']) ) {
1865 $spbc->data['user_token'] = $result['user_token'];
1866 }
1867 $spbc->data['notice_show'] = isset($result['show_notice']) ? $result['show_notice'] : 0;
1868 $spbc->data['notice_renew'] = isset($result['renew']) ? $result['renew'] : 0;
1869 $spbc->data['notice_trial'] = isset($result['trial']) ? $result['trial'] : 0;
1870 $spbc->data['notice_review'] = isset($result['show_review']) ? (int)$result['show_review'] : 0;
1871 $spbc->data['service_id'] = isset($result['service_id']) ? $result['service_id'] : 0;
1872 $spbc->data['user_id'] = isset($result['user_id']) ? $result['user_id'] : 0;
1873 $spbc->data['moderate'] = isset($result['moderate']) ? $result['moderate'] : 0;
1874 $spbc->data['license_trial'] = isset($result['license_trial']) ? $result['license_trial'] : 0;
1875 $spbc->data['account_name_ob'] = isset($result['account_name_ob']) ? $result['account_name_ob'] : '';
1876 $spbc->data['extra_package']['backend_logs'] = isset($result['extra_package']) && is_array($result['extra_package']) && in_array('backend_logs', $result['extra_package'], true)
1877 ? 1
1878 : 0;
1879
1880 //todo:temporary solution for description, until we found the way to transfer this from cloud
1881 if (defined('SPBC_WHITELABEL_PLUGIN_DESCRIPTION')) {
1882 $result['wl_plugin_description'] = SPBC_WHITELABEL_PLUGIN_DESCRIPTION;
1883 }
1884
1885 //todo:temporary solution for FAQ
1886 if (defined('SPBC_WHITELABEL_FAQ_LINK')) {
1887 $result['wl_faq_url'] = SPBC_WHITELABEL_FAQ_LINK;
1888 }
1889
1890 if ( $spbc->is_network && $spbc->is_mainsite && $spbc->ms__work_mode == 1 ) {
1891 $spbc->data['services_count '] = isset($result['services_count']) ? $result['services_count'] : '';
1892 $spbc->data['services_max'] = isset($result['services_max']) ? $result['services_max'] : '';
1893 $spbc->data['services_utilization'] = isset($result['services_utilization']) ? $result['services_utilization'] : '';
1894 }
1895
1896 if ( isset($result['wl_status']) && $result['wl_status'] === 'ON' ) {
1897 $spbc->data['wl_mode_enabled'] = true;
1898 $spbc->data['wl_brandname'] = isset($result['wl_brandname'])
1899 ? Sanitize::cleanTextField($result['wl_brandname'])
1900 : $spbc->default_data['wl_brandname'];
1901 $spbc->data['wl_url'] = isset($result['wl_url'])
1902 ? Sanitize::cleanUrl($result['wl_url'])
1903 : $spbc->default_data['wl_url'];
1904
1905 if (isset($result['wl_faq_url'])) {
1906 $spbc->data['wl_support_faq'] = Sanitize::cleanUrl($result['wl_faq_url']);
1907 } elseif (isset($result['wl_support_url'])) {
1908 $spbc->data['wl_support_faq'] = Sanitize::cleanUrl($result['wl_support_url']);
1909 } else {
1910 $spbc->data['wl_support_faq'] = $spbc->default_data['wl_support_url'];
1911 }
1912
1913 $spbc->data['wl_support_url'] = isset($result['wl_support_url'])
1914 ? Sanitize::cleanUrl($result['wl_support_url'])
1915 : $spbc->default_data['wl_support_url'];
1916 $spbc->data['wl_support_email'] = isset($result['wl_support_email'])
1917 ? Sanitize::cleanEmail($result['wl_support_email'])
1918 : $spbc->default_data['wl_support_email'];
1919 $spbc->data['wl_plugin_description'] = isset($result['wl_plugin_description'])
1920 ? Sanitize::cleanTextField($result['wl_plugin_description'])
1921 : $plugin_info['Description'];
1922 } else {
1923 $spbc->data['wl_mode_enabled'] = false;
1924 $spbc->data['wl_brandname'] = $spbc->default_data['wl_brandname'];
1925 $spbc->data['wl_url'] = $spbc->default_data['wl_url'];
1926 $spbc->data['wl_support_faq'] = $spbc->default_data['wl_support_url'];
1927 $spbc->data['wl_support_url'] = $spbc->default_data['wl_support_url'];
1928 $spbc->data['wl_support_email'] = $spbc->default_data['wl_support_email'];
1929 }
1930
1931 // Disable/enable the collecting backend PHP log depends on the extra package data
1932 $spbc->settings['misc__backend_logs_enable'] = (int)(
1933 $spbc->data['extra_package']['backend_logs'] == 1 &&
1934 $spbc->settings['misc__backend_logs_enable'] == 1
1935 );
1936 $spbc->save('settings');
1937 $spbc->save('data');
1938
1939 if ( SPBC_WPMS ) {
1940 $spbc->network_settings['moderate'] = $spbc->data['moderate'];
1941 $spbc->network_settings['key_is_ok'] = $spbc->data['key_is_ok'];
1942 $spbc->save('network_settings');
1943 $spbc->network_data = array(
1944 'key_is_ok' => $spbc->data['key_is_ok'],
1945 'user_token' => isset($spbc->data['user_token']) ? $spbc->data['user_token'] : '',
1946 'service_id' => isset($spbc->data['service_id']) ? $spbc->data['service_id'] : '',
1947 'moderate' => $spbc->data['moderate'],
1948 );
1949 $spbc->save('network_data');
1950 }
1951
1952 return true;
1953 }
1954
1955 /**
1956 * Revalidate access key from settings using settings api key.
1957 * @param string $api_key
1958 * @return array{api_response: ArrayAccess|array<array-key, mixed>|bool|mixed|null, errors: list<string>}
1959 */
1960 function spbc_validate_access_key($api_key)
1961 {
1962 $recheck_errors = array();
1963 $recheck_result = null;
1964 $key_is_correct = !empty($api_key) && spbc_api_key__is_correct($api_key);
1965 if (!$key_is_correct) {
1966 $recheck_errors[] = __spbc(SpbcET::__KEY_VALIDATION__KEY_FORMAT_IS_INVALID);
1967 }
1968 if ( $key_is_correct ) {
1969 $recheck_result = SpbcAPI::method__notice_paid_till(
1970 $api_key,
1971 preg_replace('/http[s]?:\/\//', '', get_option('home'), 1),
1972 'security'
1973 );
1974 if (!empty($recheck_result['error'])) {
1975 $api_error = is_string($recheck_result['error']) ? $recheck_result['error'] : json_encode($recheck_result['error']);
1976 $recheck_errors[] = __spbc(SpbcET::__KEY_VALIDATION__API_RESPONSE__ERROR_OCCURRED) . ' - ' . $api_error;
1977 }
1978 if (isset($recheck_result['valid']) && $recheck_result['valid'] == 0) {
1979 $recheck_errors[] = __spbc(SpbcET::__KEY_VALIDATION__API_RESPONSE__KEY_IS_INVALID);
1980 }
1981 }
1982 return array('api_response' => $recheck_result, 'errors' => $recheck_errors);
1983 }
1984
1985 /**
1986 * Clears the table with security logs. Leaves only 50 entries.
1987 */
1988 function spbc_security_log_clear()
1989 {
1990 global $spbc, $wpdb;
1991
1992 $remain_ids = array();
1993
1994 // Getting ids of last 50 rows
1995 try {
1996 $blog_id = SPBC_WPMS
1997 ? $wpdb->prepare(" AND blog_id = %d", get_current_blog_id())
1998 : '';
1999
2000 $ids = $wpdb->get_results(
2001 "SELECT id
2002 FROM " . SPBC_TBL_SECURITY_LOG
2003 . " WHERE sent=1"
2004 . $blog_id
2005 . " ORDER BY datetime DESC"
2006 . " LIMIT 50;",
2007 'ARRAY_N'
2008 );
2009
2010 if ($ids) {
2011 foreach ($ids as $id) {
2012 $remain_ids[] = $id[0];
2013 }
2014 }
2015 } catch (\Exception $e) {
2016 return false;
2017 }
2018
2019 if (empty($remain_ids)) {
2020 return false;
2021 }
2022
2023 $wpms_query_part = '';
2024 if ( SPBC_WPMS && $spbc->ms__work_mode == 2 ) {
2025 $wpms_query_part = ' AND blog_id = ' . get_current_blog_id();
2026 }
2027
2028 $placeholders = rtrim(str_repeat('%s,', count($remain_ids)), ',');
2029 $query = "DELETE FROM " . SPBC_TBL_SECURITY_LOG . " WHERE sent = 1 AND id NOT IN ($placeholders) " . $wpms_query_part . ";";
2030
2031 $wpdb->query($wpdb->prepare($query, $remain_ids));
2032
2033 // @psalm-suppress WpdbUnsafeMethodsIssue
2034 $wpdb->query("DELETE FROM " . SPBC_TBL_SECURITY_LOG_HOSTNAMES . ";");
2035
2036 return true;
2037 }
2038
2039 /**
2040 * Check whether the request is AMP
2041 *
2042 * @return bool
2043 */
2044 function spbc_is_amp_request()
2045 {
2046 if (function_exists('amp_is_request')) {
2047 return amp_is_request();
2048 }
2049
2050 return false;
2051 }
2052
2053 /**
2054 * Parse CDN checker self-request to find CDN headers.
2055 * @return array|null[]|string[]
2056 */
2057 function spbc_cdn_checker__parse_request()
2058 {
2059 global $spbc;
2060 if ($spbc->settings['secfw__get_ip__enable_cdn_auto_self_check']) {
2061 return CDNHeadersChecker::check();
2062 }
2063 return array('error' => 'CDN checker disabled');
2064 }
2065
2066 /**
2067 * Send test request to host. Then it should be parsed to find CDN headers.
2068 * @return void
2069 * @psalm-suppress
2070 */
2071 function spbc_cdn_checker__send_request()
2072 {
2073 global $spbc;
2074 if ($spbc->settings['secfw__get_ip__enable_cdn_auto_self_check']) {
2075 CDNHeadersChecker::sendCDNCheckerRequest();
2076 }
2077 }
2078
2079 /**
2080 * Current site admin e-mail
2081 * @return string Admin e-mail
2082 */
2083 function spbc_get_admin_email()
2084 {
2085 global $spbc;
2086
2087 if ( ! is_multisite() ) {
2088 $admin_email = get_option('admin_email');
2089 } else {
2090 $admin_email = get_blog_option(get_current_blog_id(), 'admin_email');
2091 }
2092
2093 if ( $spbc->data['account_email'] ) {
2094 add_filter('spbc_get_api_key_email', function () {
2095 global $spbc;
2096 return $spbc->data['account_email'];
2097 });
2098 }
2099
2100 return $admin_email;
2101 }
2102
2103 /**
2104 * Cron wrapper. Remove support user.
2105 * @return void
2106 */
2107 function spbc_cron_remove_support_user()
2108 {
2109 $temp_user_service = new \CleantalkSP\SpbctWP\SupportUser();
2110 $temp_user_service->performCronDeleteUser();
2111 }
2112
2113 function spbc_cron__fs_watcher_do_work()
2114 {
2115 global $spbc;
2116 if ( $spbc->feature_restrictions->getState($spbc, 'fswatcher')->is_active && $spbc->settings['scanner__fs_watcher'] ) {
2117 FSWatcherController::getInstance()->createSnapshot();
2118 }
2119 }
2120
2121 function spbc_cron__rate_limiter_cleanup()
2122 {
2123 \CleantalkSP\SpbctWP\SpbcRateLimit\SpbcRateLimiter::cleanUpOnCron();
2124 }
2125