diff --git a/CHANGELOG b/CHANGELOG index 3e6bd6e..35fd99b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +Version 2.31 Mar 04, 2020 +------------------------------ +- Setting "Check registration" added. +- Fix for ut8_decode function. + Version 2.30 Dec 24, 2019 ------------------------------ - Bug fixes and other minor improvements diff --git a/README.md b/README.md index 16d5808..ad7325c 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Build Status](https://travis-ci.org/CleanTalk/smf-antispam.svg)](https://travis-ci.org/CleanTalk/smf-antispam) -* **Version:** 2.30 +* **Version:** 2.31 * **License:** GNU General Public License * **Compatible with:** SMF 2.0 and up * **Languages:** English, Russian diff --git a/RoboFile.php b/RoboFile.php index f31e193..8ca2ef7 100644 --- a/RoboFile.php +++ b/RoboFile.php @@ -8,7 +8,7 @@ class RoboFile extends \Robo\Tasks { const PACKAGE = 'antispam_cleantalk_smf'; - const VERSION = '2.30'; + const VERSION = '2.31'; const SMF_VERSION = '2.0.14'; // for forumPrepare @@ -99,7 +99,7 @@ public function versionCheck() $errors[] = 'Not found version in CHANGELOG'; } $ctVersion = str_replace('.', '', $version); - if (!preg_match("#'smf-" . preg_quote($ctVersion) . "'\);#m", file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'cleantalkMod.php'))) { + if (!preg_match("#'smf-" . preg_quote($ctVersion) . "'\);#m", file_get_contents( __DIR__ . DIRECTORY_SEPARATOR . '/cleantalk/cleantalkMod.php' ))) { $errors[] = 'Not found CT_AGENT_VERSION in cleantalkMod.php'; } if (count($errors)) { diff --git a/attention.png b/cleantalk/attention.png similarity index 100% rename from attention.png rename to cleantalk/attention.png diff --git a/cleantalkMod.php b/cleantalk/cleantalkMod.php similarity index 98% rename from cleantalkMod.php rename to cleantalk/cleantalkMod.php index 523c42e..465900a 100644 --- a/cleantalkMod.php +++ b/cleantalk/cleantalkMod.php @@ -9,22 +9,20 @@ * @license GNU/GPL: http://www.gnu.org/copyleft/gpl.html */ +use CleantalkAP\Variables\Post; + if (!defined('SMF')) { die('Hacking attempt...'); } // Fixes for old PHP versions -require_once(dirname(__FILE__) . '/phpFix.php'); +require_once(dirname(__FILE__) . '/lib/phpFix.php'); -// Base classes -require_once(dirname(__FILE__) . '/Cleantalk.php'); -require_once(dirname(__FILE__) . '/CleantalkRequest.php'); -require_once(dirname(__FILE__) . '/CleantalkResponse.php'); -require_once(dirname(__FILE__) . '/CleantalkHelper.php'); -require_once(dirname(__FILE__) . '/CleantalkSFW.php'); +// Classes autoloader +require_once(dirname(__FILE__) . '/lib/autoloader.php'); // Common CleanTalk options -define('CT_AGENT_VERSION', 'smf-230'); +define('CT_AGENT_VERSION', 'smf-231'); define('CT_SERVER_URL', 'http://moderate.cleantalk.org'); define('CT_DEBUG', false); define('CT_REMOTE_CALL_SLEEP', 10); @@ -422,8 +420,12 @@ function cleantalk_check_register(&$regOptions, $theme_vars){ if (SMF == 'SSI') return; - if ($regOptions['interface'] == 'admin') + if ( + $regOptions['interface'] == 'admin' || // Skip admin + ! $modSettings['cleantalk_check_registrations'] // Skip if registrations check are disabled + ) return; + if ($executed_check_register) { $executed_check_register = false; @@ -982,7 +984,7 @@ function cleantalk_load() if(!empty($user_info['is_admin'])){ // Deleting selected users - if(isset($_POST['ct_del_user'])) + if(Post::get('ct_del_user')) { checkSession('request'); @@ -991,7 +993,7 @@ function cleantalk_load() if (isset($db_connection) && $db_connection != false) { - foreach($_POST['ct_del_user'] as $key=>$value) + foreach(Post::get('ct_del_user') as $key=>$value) { $result = $smcFunc['db_query']('', 'delete from {db_prefix}members where id_member='.intval($key),Array('db_error_skip' => true)); $result = $smcFunc['db_query']('', 'delete from {db_prefix}topics where id_member_started='.intval($key),Array('db_error_skip' => true)); @@ -1001,7 +1003,7 @@ function cleantalk_load() } // Deleting all users - if(isset($_POST['ct_delete_all'])) + if(Post::get('ct_delete_all')) { checkSession('request'); @@ -1222,12 +1224,12 @@ function cleantalk_buffer($buffer) { global $modSettings, $user_info, $smcFunc, $txt, $forum_version, $db_connection; - + if (SMF == 'SSI') return $buffer; - if($user_info['is_admin'] && isset($_GET['action'], $_GET['area']) && $_GET['action'] == 'admin' && $_GET['area'] == 'modsettings'){ - + if(isset($_GET['action'], $_GET['area']) && $_GET['action'] == 'admin' && $_GET['area'] == 'modsettings'){ + if(strpos($forum_version, 'SMF 2.0')===false){ $html=''; diff --git a/cleantalkModAdmin.php b/cleantalk/cleantalkModAdmin.php similarity index 93% rename from cleantalkModAdmin.php rename to cleantalk/cleantalkModAdmin.php index 4ca4ec7..a975e1b 100644 --- a/cleantalkModAdmin.php +++ b/cleantalk/cleantalkModAdmin.php @@ -9,6 +9,8 @@ * @license GNU/GPL: http://www.gnu.org/copyleft/gpl.html */ +use CleantalkAP\Variables\Post; + if (!defined('SMF')) { die('Hacking attempt...'); } @@ -67,6 +69,7 @@ function cleantalk_general_mod_settings($return_config = false) $config_vars = array( array('title', 'cleantalk_settings'), array('text', 'cleantalk_api_key'), + array('check', 'cleantalk_check_registrations', 'subtext' => $txt['cleantalk_check_registrations']), array('check', 'cleantalk_first_post_checking', 'subtext' => $txt['cleantalk_first_post_checking_postinput']), array('check', 'cleantalk_check_personal_messages', 'subtext' => $txt['cleantalk_check_personal_messages_postinput']), array('check', 'cleantalk_automod', 'subtext' => $txt['cleantalk_automod_postinput']), @@ -99,7 +102,7 @@ function cleantalk_general_mod_settings($return_config = false) } } - $save_key = $key_is_valid ? $save_key : $_POST['cleantalk_api_key']; + $save_key = $key_is_valid ? $save_key : Post::get( 'cleantalk_api_key' ); if(!$key_is_valid) { $result = CleantalkHelper::apbct_key_is_correct($save_key); @@ -109,9 +112,10 @@ function cleantalk_general_mod_settings($return_config = false) { $result = CleantalkHelper::api_method__notice_paid_till($save_key, preg_replace('/http[s]?:\/\//', '', $_SERVER['HTTP_HOST'], 1)); - if (empty($result['error'])) - { - if( $result['valid'] ) { + if (empty($result['error'])){ + + if($result['valid']){ + $key_is_ok = true; $settings_array = array( 'cleantalk_api_key' => ($save_key) ? $save_key : '', @@ -128,8 +132,8 @@ function cleantalk_general_mod_settings($return_config = false) 'cleantalk_account_name_ob' => isset($result['account_name_ob']) ? $result['account_name_ob'] : '', 'cleantalk_last_account_check' => time(), ); - if (isset($_POST['cleantalk_sfw']) && $_POST['cleantalk_sfw'] == 1) - { + + if (Post::get( 'cleantalk_sfw' ) == 1){ $settings_array['cleantalk_sfw'] = '1'; $settings_array['cleantalk_sfw_last_update'] = time(); $settings_array['cleantalk_sfw_last_logs_sent'] = time(); @@ -137,16 +141,12 @@ function cleantalk_general_mod_settings($return_config = false) $sfw->sfw_update($save_key); $sfw->send_logs($save_key); } - } - else - { + }else{ // @ToDo have to handle errors! // return array('error' => 'KEY_IS_NOT_VALID'); } - } - else - { + }else{ // @ToDo have to handle errors! // return array('error' => $result); } @@ -154,8 +154,7 @@ function cleantalk_general_mod_settings($return_config = false) $settings_array['cleantalk_api_key_is_ok'] = ($key_is_ok) ? '1' : '0'; updateSettings($settings_array, false); } - - + if (isset($_GET['save'])) { checkSession(); saveDBSettings($config_vars); diff --git a/lib/error.html b/cleantalk/error.html similarity index 100% rename from lib/error.html rename to cleantalk/error.html diff --git a/lib/Cleantalk.php b/cleantalk/lib/Cleantalk.php similarity index 100% rename from lib/Cleantalk.php rename to cleantalk/lib/Cleantalk.php diff --git a/cleantalk/lib/CleantalkAP/Common/API.php b/cleantalk/lib/CleantalkAP/Common/API.php new file mode 100644 index 0000000..6fb98a7 --- /dev/null +++ b/cleantalk/lib/CleantalkAP/Common/API.php @@ -0,0 +1,717 @@ + STRING) + */ + static public function method__get_2s_blacklists_db($api_key, $out = null, $do_check = true) + { + $request = array( + 'method_name' => '2s_blacklists_db', + 'auth_key' => $api_key, + 'out' => $out, + ); + + $result = static::send_request($request); + $result = $do_check ? static::check_response($result, '2s_blacklists_db') : $result; + + return $result; + } + + /** + * Wrapper for get_api_key API method. + * Gets access key automatically. + * + * @param string $product_name Type of product + * @param string $email Website admin email + * @param string $website Website host + * @param string $platform Website platform + * @param string|null $timezone + * @param string|null $language + * @param string|null $user_ip + * @param bool $wpms + * @param bool $white_label + * @param string $hoster_api_key + * @param bool $do_check + * + * @return array|bool|mixed + */ + static public function method__get_api_key($product_name, $email, $website, $platform, $timezone = null, $language = null, $user_ip = null, $wpms = false, $white_label = false, $hoster_api_key = '', $do_check = true) + { + $request = array( + 'method_name' => 'get_api_key', + 'product_name' => $product_name, + 'email' => $email, + 'website' => $website, + 'platform' => $platform, + 'timezone' => $timezone, + 'http_accept_language' => $language, + 'user_ip' => $user_ip, + 'wpms_setup' => $wpms, + 'hoster_whitelabel' => $white_label, + 'hoster_api_key' => $hoster_api_key, + ); + + $result = static::send_request($request); + $result = $do_check ? static::check_response($result, 'get_api_key') : $result; + + return $result; + } + + /** + * Wrapper for get_antispam_report API method. + * Gets spam report. + * + * @param string $host website host + * @param integer $period report days + * @param boolean $do_check + * + * @return array|bool|mixed + */ + static public function method__get_antispam_report($host, $period = 1, $do_check = true) + { + $request = Array( + 'method_name' => 'get_antispam_report', + 'hostname' => $host, + 'period' => $period + ); + + $result = static::send_request($request); + $result = $do_check ? static::check_response($result, 'get_antispam_report') : $result; + + return $result; + } + + /** + * Wrapper for get_antispam_report_breif API method. + * Ggets spam statistics. + * + * @param string $api_key + * @param bool $do_check + * + * @return array|bool|mixed + */ + static public function method__get_antispam_report_breif($api_key, $do_check = true) + { + $request = array( + 'method_name' => 'get_antispam_report_breif', + 'auth_key' => $api_key, + ); + + $result = static::send_request($request); + $result = $do_check ? static::check_response($result, 'get_antispam_report_breif') : $result; + + return $result; + } + + /** + * Wrapper for notice_paid_till API method. + * Gets information about renew notice. + * + * @param string $api_key API key + * @param string $path_to_cms Website URL + * @param string $product_name + * @param bool $do_check + * + * @return array|bool|mixed + */ + static public function method__notice_paid_till($api_key, $path_to_cms, $product_name = 'antispam', $do_check = true) + { + $request = array( + 'method_name' => 'notice_paid_till', + 'path_to_cms' => $path_to_cms, + 'auth_key' => $api_key, + ); + + $product_id = null; + $product_id = $product_name == 'antispam' ? 1 : $product_id; + $product_id = $product_name == 'security' ? 4 : $product_id; + if($product_id) + $request['product_id'] = $product_id; + + $result = static::send_request($request); + $result = $do_check ? static::check_response($result, 'notice_paid_till') : $result; + + return $result; + } + + /** + * Wrapper for ip_info API method. + * Gets IP country. + * + * @param string $data + * @param bool $do_check + * + * @return array|bool|mixed + */ + static public function method__ip_info($data, $do_check = true) + { + $request = array( + 'method_name' => 'ip_info', + 'data' => $data + ); + + $result = static::send_request($request); + $result = $do_check ? static::check_response($result, 'ip_info') : $result; + return $result; + } + + /** + * Wrapper for spam_check_cms API method. + * Checks IP|email via CleanTalk's database. + * + * @param string $api_key + * @param array $data + * @param null|string $date + * @param bool $do_check + * + * @return array|bool|mixed + */ + static public function method__spam_check_cms($api_key, $data, $date = null, $do_check = true) + { + $request = Array( + 'method_name' => 'spam_check_cms', + 'auth_key' => $api_key, + 'data' => is_array($data) ? implode(',', $data) : $data, + ); + + if($date) $request['date'] = $date; + + $result = static::send_request($request); + $result = $do_check ? static::check_response($result, 'spam_check_cms') : $result; + + return $result; + } + + /** + * Wrapper for spam_check API method. + * Checks IP|email via CleanTalk's database. + * + * @param string $api_key + * @param array $data + * @param null|string $date + * @param bool $do_check + * + * @return array|bool|mixed + */ + static public function method__spam_check($api_key, $data, $date = null, $do_check = true) + { + $request = Array( + 'method_name' => 'spam_check', + 'auth_key' => $api_key, + 'data' => is_array($data) ? implode(',', $data) : $data, + ); + + if($date) $request['date'] = $date; + + $result = static::send_request($request); + $result = $do_check ? static::check_response($result, 'spam_check') : $result; + + return $result; + } + + /** + * Wrapper for sfw_logs API method. + * Sends SpamFireWall logs to the cloud. + * + * @param string $api_key + * @param array $data + * @param bool $do_check + * + * @return array|bool|mixed + */ + static public function method__sfw_logs($api_key, $data, $do_check = true) + { + + $request = array( + 'auth_key' => $api_key, + 'method_name' => 'sfw_logs', + 'data' => json_encode($data), + 'rows' => count($data), + 'timestamp' => time() + ); + + $result = static::send_request($request); + $result = $do_check ? static::check_response($result, 'sfw_logs') : $result; + + return $result; + } + + /** + * Wrapper for security_logs API method. + * Sends security logs to the cloud. + * + * @param string $api_key + * @param array $data + * @param bool $do_check + * + * @return array|bool|mixed + */ + static public function method__security_logs($api_key, $data, $do_check = true) + { + $request = array( + 'auth_key' => $api_key, + 'method_name' => 'security_logs', + 'timestamp' => current_time('timestamp'), + 'data' => json_encode($data), + 'rows' => count($data), + ); + + $result = static::send_request($request); + $result = $do_check ? static::check_response($result, 'security_logs') : $result; + + return $result; + } + + /** + * Wrapper for security_logs API method. + * Sends Securitty Firewall logs to the cloud. + * + * @param string $api_key + * @param array $data + * @param bool $do_check + * + * @return array|bool|mixed + */ + static public function method__security_logs__sendFWData($api_key, $data, $do_check = true) + { + + $request = array( + 'auth_key' => $api_key, + 'method_name' => 'security_logs', + 'timestamp' => current_time('timestamp'), + 'data_fw' => json_encode($data), + 'rows_fw' => count($data), + ); + + $result = static::send_request($request); + $result = $do_check ? static::check_response($result, 'security_logs') : $result; + + return $result; + } + + /** + * Wrapper for security_logs API method. + * Sends empty data to the cloud to syncronize version. + * + * @param string $api_key + * @param bool $do_check + * + * @return array|bool|mixed + */ + static public function method__security_logs__feedback($api_key, $do_check = true) + { + $request = array( + 'auth_key' => $api_key, + 'method_name' => 'security_logs', + 'data' => '0', + ); + + $result = static::send_request($request); + $result = $do_check ? static::check_response($result, 'security_logs') : $result; + + return $result; + } + + /** + * Wrapper for security_firewall_data API method. + * Gets Securitty Firewall data to write to the local database. + * + * @param string $api_key + * @param bool $do_check + * + * @return array|bool|mixed + */ + static public function method__security_firewall_data($api_key, $do_check = true) + { + + $request = array( + 'auth_key' => $api_key, + 'method_name' => 'security_firewall_data', + ); + + $result = static::send_request($request); + $result = $do_check ? static::check_response($result, 'security_firewall_data') : $result; + + return $result; + } + + /** + * Wrapper for security_firewall_data_file API method. + * Gets URI with security firewall data in .csv.gz file to write to the local database. + * + * @param string $api_key + * @param bool $do_check + * + * @return array|bool|mixed + */ + static public function method__security_firewall_data_file($api_key, $do_check = true) + { + + $request = array( + 'auth_key' => $api_key, + 'method_name' => 'security_firewall_data_file', + ); + + $result = static::send_request($request); + $result = $do_check ? static::check_response($result, 'security_firewall_data_file') : $result; + + return $result; + } + + /** + * Wrapper for security_linksscan_logs API method. + * Send data to the cloud about scanned links. + * + * @param string $api_key + * @param string $scan_time Datetime of scan + * @param bool $scan_result + * @param int $links_total + * @param array $links_list + * @param bool $do_check + * + * @return array|bool|mixed + */ + static public function method__security_linksscan_logs($api_key, $scan_time, $scan_result, $links_total, $links_list, $do_check = true) + { + $request = array( + 'auth_key' => $api_key, + 'method_name' => 'security_linksscan_logs', + 'started' => $scan_time, + 'result' => $scan_result, + 'total_links_found' => $links_total, + 'links_list' => $links_list, + ); + + $result = static::send_request($request); + $result = $do_check ? static::check_response($result, 'security_linksscan_logs') : $result; + + return $result; + } + + /** + * Wrapper for security_mscan_logs API method. + * Sends result of file scan to the cloud. + * + * @param string $api_key + * @param int $service_id + * @param string $scan_time Datetime of scan + * @param bool $scan_result + * @param int $scanned_total + * @param array $modified List of modified files with details + * @param array $unknown List of modified files with details + * @param bool $do_check + * + * @return array|bool|mixed + */ + static public function method__security_mscan_logs($api_key, $service_id, $scan_time, $scan_result, $scanned_total, $modified, $unknown, $do_check = true) + { + $request = array( + 'method_name' => 'security_mscan_logs', + 'auth_key' => $api_key, + 'service_id' => $service_id, + 'started' => $scan_time, + 'result' => $scan_result, + 'total_core_files' => $scanned_total, + ); + + if(!empty($modified)){ + $request['failed_files'] = json_encode($modified); + $request['failed_files_rows'] = count($modified); + } + if(!empty($unknown)){ + $request['unknown_files'] = json_encode($unknown); + $request['unknown_files_rows'] = count($unknown); + } + + $result = static::send_request($request); + $result = $do_check ? static::check_response($result, 'security_mscan_logs') : $result; + + return $result; + } + + /** + * Wrapper for security_mscan_files API method. + * Sends file to the cloud for analysis. + * + * @param string $api_key + * @param string $file_path Path to the file + * @param array $file File itself + * @param string $file_md5 MD5 hash of file + * @param array $weak_spots List of weak spots found in file + * @param bool $do_check + * + * @return array|bool|mixed + */ + static public function method__security_mscan_files($api_key, $file_path, $file, $file_md5, $weak_spots, $do_check = true) + { + $request = array( + 'method_name' => 'security_mscan_files', + 'auth_key' => $api_key, + 'path_to_sfile' => $file_path, + 'attached_sfile' => $file, + 'md5sum_sfile' => $file_md5, + 'dangerous_code' => $weak_spots, + ); + + $result = static::send_request($request); + $result = $do_check ? static::check_response($result, 'security_mscan_files') : $result; + + return $result; + } + + /** + * Wrapper for get_antispam_report API method. + * Function gets spam domains report. + * + * @param string $api_key + * @param array|string|mixed $data + * @param string $date + * @param bool $do_check + * + * @return array|bool|mixed + */ + static public function method__backlinks_check_cms($api_key, $data, $date = null, $do_check = true) + { + $request = array( + 'method_name' => 'backlinks_check_cms', + 'auth_key' => $api_key, + 'data' => is_array($data) ? implode(',', $data) : $data, + ); + + if($date) $request['date'] = $date; + + $result = static::send_request($request); + $result = $do_check ? static::check_response($result, 'backlinks_check_cms') : $result; + + return $result; + } + + /** + * Wrapper for get_antispam_report API method. + * Function gets spam domains report + * + * @param string $api_key + * @param array $logs + * @param bool $do_check + * + * @return array|bool|mixed + */ + static public function method__security_backend_logs($api_key, $logs, $do_check = true) + { + $request = array( + 'method_name' => 'security_backend_logs', + 'auth_key' => $api_key, + 'logs' => json_encode($logs), + 'total_logs' => count($logs), + ); + + $result = static::send_request($request); + $result = $do_check ? static::check_response($result, 'security_backend_logs') : $result; + + return $result; + } + + /** + * Wrapper for get_antispam_report API method. + * Sends data about auto repairs + * + * @param string $api_key + * @param bool $repair_result + * @param string $repair_comment + * @param $repaired_processed_files + * @param $repaired_total_files_proccessed + * @param $backup_id + * @param bool $do_check + * + * @return array|bool|mixed + */ + static public function method__security_mscan_repairs($api_key, $repair_result, $repair_comment, $repaired_processed_files, $repaired_total_files_proccessed, $backup_id, $do_check = true) + { + $request = array( + 'method_name' => 'security_mscan_repairs', + 'auth_key' => $api_key, + 'repair_result' => $repair_result, + 'repair_comment' => $repair_comment, + 'repair_processed_files' => json_encode($repaired_processed_files), + 'repair_total_files_processed' => $repaired_total_files_proccessed, + 'backup_id' => $backup_id, + 'mscan_log_id' => 1, + ); + + $result = static::send_request($request); + $result = $do_check ? static::check_response($result, 'security_mscan_repairs') : $result; + + return $result; + } + + /** + * Wrapper for get_antispam_report API method. + * Force server to update checksums for specific plugin\theme + * + * @param string $api_key + * @param string $plugins_and_themes_to_refresh + * @param bool $do_check + * + * @return array|bool|mixed + */ + static public function method__request_checksums($api_key, $plugins_and_themes_to_refresh, $do_check = true) + { + $request = array( + 'method_name' => 'request_checksums', + 'auth_key' => $api_key, + 'data' => $plugins_and_themes_to_refresh + ); + + $result = static::send_request($request); + $result = $do_check ? static::check_response($result, 'request_checksums') : $result; + + return $result; + } + + static public function get_agent(){ + return defined( 'CLEANTALK_AGENT' ) ? CLEANTALK_AGENT : static::DEFAULT_AGENT; + } + + /** + * Function sends raw request to API server + * + * @param array $data to send + * @param string $url of API server + * @param integer $timeout timeout in seconds + * @param boolean $ssl use ssl on not + * + * @return array|bool + */ + static public function send_request($data, $url = self::URL, $ssl = false) + { + // Default preset is 'api' + $presets = array( 'api' ); + + $data['agent'] = static::get_agent(); + + // Add ssl to 'presets' if enabled + if( $ssl ) + array_push( $presets, 'ssl' ); + + $result = \CleantalkAP\Common\Helper::http__request( $url, $data, $presets ); + + // Retry with SSL enabled if failed + if( ! empty ( $result['error'] ) && $ssl === false ) + $result = \CleantalkAP\Common\Helper::http__request( $url, $data, 'api ssl' ); + + return $result; + } + + /** + * Function checks server response + * + * @param string $result + * @param string $method_name + * + * @return mixed (array || array('error' => true)) + */ + static public function check_response($result, $method_name = null) + { + // Errors handling + // Bad connection + if(is_array($result) && isset($result['error'])){ + return array( + 'error' => 'CONNECTION_ERROR: ' . (isset($result['error']) ? ' ' . $result['error'] : ''), + ); + } + + // JSON decode errors + $result = json_decode($result, true); + if(empty($result)){ + return array( + 'error' => 'JSON_DECODE_ERROR', + ); + } + + // Server errors + if($result && + (isset($result['error_no']) || isset($result['error_message'])) && + (isset($result['error_no']) && $result['error_no'] != 12) + ){ + return array( + 'error' => "SERVER_ERROR NO: {$result['error_no']} MSG: {$result['error_message']}", + 'error_no' => $result['error_no'], + 'error_message' => $result['error_message'], + ); + } + + // Pathces for different methods + switch($method_name){ + + // notice_paid_till + case 'notice_paid_till': + + $result = isset($result['data']) ? $result['data'] : $result; + + if((isset($result['error_no']) && $result['error_no'] == 12) || + ( + !(isset($result['service_id']) && is_int($result['service_id'])) && + empty($result['moderate_ip']) + ) + ) + $result['valid'] = 0; + else + $result['valid'] = 1; + + return $result; + + break; + + // get_antispam_report_breif + case 'get_antispam_report_breif': + + $out = isset($result['data']) && is_array($result['data']) + ? $result['data'] + : array('error' => 'NO_DATA'); + + for($tmp = array(), $i = 0; $i < 7; $i++){ + $tmp[date('Y-m-d', time() - 86400 * 7 + 86400 * $i)] = 0; + } + $out['spam_stat'] = (array)array_merge($tmp, isset($out['spam_stat']) ? $out['spam_stat'] : array()); + $out['top5_spam_ip'] = isset($out['top5_spam_ip']) ? $out['top5_spam_ip'] : array(); + + return $out; + + break; + + default: + return isset($result['data']) && is_array($result['data']) + ? $result['data'] + : array('error' => 'NO_DATA'); + break; + } + } +} \ No newline at end of file diff --git a/cleantalk/lib/CleantalkAP/Common/Arr.php b/cleantalk/lib/CleantalkAP/Common/Arr.php new file mode 100644 index 0000000..0533485 --- /dev/null +++ b/cleantalk/lib/CleantalkAP/Common/Arr.php @@ -0,0 +1,252 @@ +array = is_array( $array ) + ? $array + : array(); + + return $this; + } + + /** + * Recursive + * Check if Array has keys given keys + * Save found keys in $this->found + * + * @param array|string $keys + * @param bool $regexp + * @param array $array + * + * @return Arr + */ + public function get_keys( $keys = array(), $regexp = false, $array = array() ) + { + $array = $array ? $array : $this->array; + $keys = is_array( $keys ) ? $keys : explode( ',', $keys ); + + if( empty( $array ) || empty( $keys ) ) + return $this; + + $this->found = $keys === array('all') + ? $this->array + : $this->search( + 'key', + $array, + $keys, + $regexp + ); + + return $this; + } + + /** + * Recursive + * Check if Array has valuse given valuse + * Save found keys in $this->found + * + * @param array|string $values + * @param bool $regexp + * @param array $array + * + * @return $this + */ + public function get_values( $values = array(), $regexp = false, $array = array() ) + { + $array = $array ? $array : $this->array; + $keys = is_array( $values ) ? $values : explode( ',', $values ); + + if( empty( $array ) || empty( $values ) ) + return $this; + + $this->found = $values === array('all') + ? $this->array + : $this->search( + 'value', + $array, + $keys, + $regexp + ); + + return $this; + } + + public function get_array( $searched = array(), $regexp = false, $array = array() ){ + + $array = $array ? $array : $this->array; + + + if( empty( $array ) || empty( $searched ) ) + return $this; + + $this->found = $searched === array('all') + ? $this->array + : $this->search( + 'array', + $array, + $searched, + $regexp + ); + + $this->found = $this->found === $searched ? $this->found : array(); + + return $this; + } + + /** + * Recursive + * Check if array contains wanted data type + * + * @param string $type + * @param array $array + * @param array $found + * + * @return bool|void + */ + public function is( $type, $array = array(), $found = array() ) + { + $array = $array ? $array : $this->array; + $found = $found ? $found : $this->found; + + foreach ( $array as $key => $value ){ + + if( array_key_exists( $key, $found ) ){ + if( is_array( $found[ $key ] ) ){ + if( ! $this->is( $type, $value, $found[ $key ] ) ){ + return false; + } + }else{ + switch ( $type ){ + case 'regexp': + $value = preg_match( '/\/.*\//', $value ) === 1 ? $value : '/' . $value . '/'; + if( @preg_match( $value, null ) === false ){ + return false; + } + break; + } + } + } + + } + + return true; + } + + /** + * @param string $type + * @param array $array + * @param array $searched + * @param bool $regexp + * @param array $found + * + * @return array + */ + private function search( $type, $array = array(), $searched = array(), $regexp = false, $found = array() ) + { + foreach ( $array as $key => $value ){ + + // Recursion + if( is_array( $value ) ){ + $result = $this->search( $type, $value, $searched, $regexp, array() ); + if($result) + $found[$key] = $result; + + // Execution + }else{ + foreach ( $searched as $searched_key => $searched_val ){ + switch ($type){ + case 'key': + if( $key === $searched_val || ($regexp && preg_match( '/' . $searched_val . '/', $key) === 1) ) + $found[$key] = true; + break; + case 'value': + if( stripos($value, $searched_val) !== false || ($regexp && preg_match( '/' . $searched_val . '/', $value) === 1) ) + $found[$key] = true; + break; + case 'array': + if( stripos($key, $searched_key) !== false || ($regexp && preg_match( '/' . $searched_key . '/', $key) === 1) ) + if( is_array( $value ) && is_array( $value )){ + $result = $this->search( 'array', $value, $searched_key, $regexp, array() ); + if( $result ){ + $found[ $key ] = $result; + } + }else{ + $found[$key] = $value; + } + break; + } + } + } + } + + return $found; + } + + public function compare( $arr1, $arr2 ){ + // $arr1 = is_array( $arr1 ) ? $arr1 : array(); + // $arr2 = is_array( $arr2 ) ? $arr2 : array(); + foreach ( $arr1 as $key1 => $val1 ){ + if( $arr1 === $arr2 ){ + if(is_array($arr1) && is_array($arr2)){ + $result = $this->compare( $arr1, $arr2 ); + } + } + } + } + + /** + * Recursive + * Delete elements from array with found keys ( $this->found ) + * If $searched param is differ from 'arr_special_param' + * + * @param mixed $searched + * @param array $array + * @param array $found + * + * @return array + */ + public function delete( $searched = 'arr_special_param', $array = array(), $found =array() ) + { + $array = $array ? $array : $this->array; + $found = $found ? $found : $this->found; + + foreach($array as $key => $value){ + + if(array_key_exists($key, $found)){ + if( is_array( $found[ $key ] ) ){ + $array[ $key ] = $this->delete( $searched, $value, $found[ $key ] ); + if( empty( $array[ $key ] ) ) + unset( $array[ $key ] ); + }else{ + if( $searched === 'arr_special_param' || $searched === $value ){ + unset( $array[ $key ] ); + } + } + } + + } + + $this->result = $array; + return $array; + } + + public function result(){ + return (boolean) $this->found; + } +} \ No newline at end of file diff --git a/cleantalk/lib/CleantalkAP/Common/Cron.php b/cleantalk/lib/CleantalkAP/Common/Cron.php new file mode 100644 index 0000000..df2906f --- /dev/null +++ b/cleantalk/lib/CleantalkAP/Common/Cron.php @@ -0,0 +1,269 @@ +errors = static::getErrors(); + $this->tasks = empty($tasks) ? array() : $tasks; + } + + static public function getErrors(){ + return Err::getInstance(); + } + + /** + * Return arrray with tasks + * Should be rewritten for specific platform + * + * @return array + */ + static public function getTasks(){ + return array(); + } + + /** + * Save tasks in some way + * Should be rewritten for specific platform + * + * @param $tasks + * + * @return bool + */ + static public function saveTasks( $tasks ){ + return true; + } + + /** + * Creates new entry in task list + * + * @param string $task + * @param callable $handler + * @param int $period + * @param int|null $first_call + * @param array $params + * + * @return bool + */ + static public function addTask($task, $handler, $period, $first_call = null, $params = array()) + { + // First call time() + preiod + $first_call = !$first_call ? time() + $period : $first_call; + + $tasks = static::getTasks(); + + if(isset($tasks[$task])) + return false; + + // Task entry + $tasks[$task] = array( + 'handler' => $handler, + 'next_call' => $first_call, + 'period' => $period, + 'params' => $params, + ); + + return static::saveTasks( $tasks ); + } + + /** + * Remove task from the list and save it to base + * + * @param string $task + * + * @return bool + */ + static public function removeTask($task) + { + $tasks = static::getTasks(); + + if(!isset($tasks[$task])) + return false; + + unset($tasks[$task]); + + return static::saveTasks($tasks); + } + + /** + * Updates cron task, create task if not exists + * Save it to base + * + * @param string $task + * @param callable $handler + * @param int $period + * @param int|null $first_call + * @param array $params + * + * @return bool + */ + static public function updateTask($task, $handler, $period, $first_call = null, $params = array()){ + return static::removeTask($task) && + static::addTask($task, $handler, $period, $first_call, $params); + } + + /** + * Getting tasks which should be run. + * Putting tasks that should be run to $this->tasks_to_run + * + * @return array|bool + */ + public function checkTasks() + { + if(empty($this->tasks)) + return true; + + foreach($this->tasks as $task => $task_data){ + + if($task_data['next_call'] <= time()) + $this->tasks_to_run[] = $task; + + }unset($task, $task_data); + + return $this->tasks_to_run; + } + + /** + * Runs all tasks from $this->tasks_to_run + * Saves all results to (array) $this->tasks_completed + * + * @return void + */ + public function runTasks() + { + if(empty($this->tasks_to_run)){ + return; + } + + foreach($this->tasks_to_run as $task){ + + $this->selectTask($task); + + if(function_exists($this->handler)){ + + $result = call_user_func_array($this->handler, isset($this->params) ? $this->params : array()); + + if(empty($result['error'])){ + $this->tasks_completed[$task] = true; + $this->errors::delete('cron', $task); + }else{ + $this->tasks_completed[$task] = false; + $this->errors::add('cron', $task, $result); + } + + }else{ + $this->tasks_completed[$task] = false; + $this->errors::add('cron', $task, 'function ' . $this->handler . ' is not exists.'); + } + + $this->saveSelectedTask($task); + + }unset($task); + + // Merging executed tasks with updated during execution + $tasks = $this->getTasks(); + + foreach($tasks as $task => $task_data){ + + // Task where added during execution + if(!isset($this->tasks[$task])){ + $this->tasks[$task] = $task_data; + continue; + } + + // Task where updated during execution + if($task_data !== $this->tasks[$task]){ + $this->tasks[$task] = $task_data; + continue; + } + + // Setting next call depending on results + if(isset($this->tasks[$task], $this->tasks_completed[$task])){ + $this->tasks[$task]['next_call'] = $this->tasks_completed[$task] + ? time() + $this->tasks[$task]['period'] + : time() + round($this->tasks[$task]['period']/4); + } + + if(empty($this->tasks[$task]['next_call']) || $this->tasks[$task]['next_call'] < time()){ + $this->tasks[$task]['next_call'] = time() + $this->tasks[$task]['period']; + } + + } unset($task, $task_data); + + // Task where deleted during execution + $tmp = $this->tasks; + foreach($tmp as $task => $task_data){ + if(!isset($tasks[$task])) + unset($this->tasks[$task]); + } unset($task, $task_data); + + //*/ End of merging + + static::saveTasks( $this->tasks ); + + return; + } + + /** + * Save a task to private properties for comfortable use + * + * @param string $task + */ + protected function selectTask($task) + { + $this->task = $task; + $this->handler = $this->tasks[$task]['handler']; + $this->period = $this->tasks[$task]['period']; + $this->next_call = $this->tasks[$task]['next_call']; + $this->params = isset($this->tasks[$task]['params']) ? $this->tasks[$task]['params'] : array(); + } + + /** + * Save task from private properties to public for comfortable use + * + * @param string $task + */ + protected function saveSelectedTask($task) + { + $task = $this->task; + + $this->tasks[$task]['handler'] = $this->handler; + $this->tasks[$task]['period'] = $this->period; + $this->tasks[$task]['next_call'] = $this->next_call; + $this->tasks[$task]['params'] = $this->params; + } +} diff --git a/cleantalk/lib/CleantalkAP/Common/DB.php b/cleantalk/lib/CleantalkAP/Common/DB.php new file mode 100644 index 0000000..d172524 --- /dev/null +++ b/cleantalk/lib/CleantalkAP/Common/DB.php @@ -0,0 +1,98 @@ +query string for next uses + * + * @param $query + * @return $this + */ + public function set_query($query){ } + + /** + * Safely replace place holders + * + * @param string $query + * @param array $vars + * + * @return $this + */ + public function prepare($query, $vars = array()){ } + + /** + * Run any raw request + * + * @param $query + * + * @return bool|int Raw result + */ + public function execute($query){ } + + /** + * Fetchs first column from query. + * May receive raw or prepared query. + * + * @param bool $query + * @param bool $response_type + * + * @return array|object|void|null + */ + public function fetch($query = false, $response_type = false){ } + + /** + * Fetchs all result from query. + * May receive raw or prepared query. + * + * @param bool $query + * @param bool $response_type + * + * @return array|object|null + */ + public function fetch_all($query = false, $response_type = false){ } +} \ No newline at end of file diff --git a/cleantalk/lib/CleantalkAP/Common/Err.php b/cleantalk/lib/CleantalkAP/Common/Err.php new file mode 100644 index 0000000..03a89cc --- /dev/null +++ b/cleantalk/lib/CleantalkAP/Common/Err.php @@ -0,0 +1,62 @@ +errors[] = implode(': ', func_get_args()); + return static::$instance; + } + + public function prepend( $string ){ + $this->errors[ count( $this->errors ) - 1 ] = $string . ': ' . end( static::getInstance()->errors ); + } + + public function append( $string ){ + $this->string = $string . ': ' . $this->string; + } + + public static function get_last( $output_style = 'bool' ){ + $out = $out = (bool) static::$instance->errors; + if($output_style == 'as_json') + $out = json_encode( array('error' => end( static::$instance->errors ) ), true ); + if($output_style == 'string') + $out = array('error' => end( static::$instance->errors ) ); + return $out; + } + + public function get_all( $output_style = 'string' ){ + $out = static::$instance->errors; + if($output_style == 'as_json') + $out = json_encode( static::$instance->errors, true ); + return $out; + } + + public static function check(){ + return (bool)static::$instance->errors; + } + + public static function check_and_output( $output_style = 'string' ){ + if(static::check()) + return static::$instance->get_last( $output_style ); + else + return false; + } +} \ No newline at end of file diff --git a/cleantalk/lib/CleantalkAP/Common/File.php b/cleantalk/lib/CleantalkAP/Common/File.php new file mode 100644 index 0000000..c9f6cec --- /dev/null +++ b/cleantalk/lib/CleantalkAP/Common/File.php @@ -0,0 +1,179 @@ + array( + '10.0.0.0/8', + '100.64.0.0/10', + '172.16.0.0/12', + '192.168.0.0/16', + '127.0.0.1/32', + ), + 'v6' => array( + '0:0:0:0:0:0:0:1/128', // localhost + '0:0:0:0:0:0:a:1/128', // ::ffff:127.0.0.1 + ), + ); + + /** + * @var array Set of CleanTalk servers + */ + public static $cleantalks_servers = array( + // MODERATE + 'moderate1.cleantalk.org' => '162.243.144.175', + 'moderate2.cleantalk.org' => '159.203.121.181', + 'moderate3.cleantalk.org' => '88.198.153.60', + 'moderate4.cleantalk.org' => '159.69.51.30', + 'moderate5.cleantalk.org' => '95.216.200.119', + 'moderate6.cleantalk.org' => '138.68.234.8', + // APIX + 'apix1.cleantalk.org' => '35.158.52.161', + 'apix2.cleantalk.org' => '18.206.49.217', + 'apix3.cleantalk.org' => '3.18.23.246', + //ns + 'netserv2.cleantalk.org' => '178.63.60.214', + 'netserv3.cleantalk.org' => '188.40.14.173', + ); + + /** + * Getting arrays of IP (REMOTE_ADDR, X-Forwarded-For, X-Real-Ip, Cf_Connecting_Ip) + * + * @param array $ip_types Type of IP you want to receive + * @param bool $v4_only + * + * @return array|mixed|null + */ + static public function ip__get($ip_types = array('real', 'remote_addr', 'x_forwarded_for', 'x_real_ip', 'cloud_flare'), $v4_only = true) + { + $ips = array_flip($ip_types); // Result array with IPs + $headers = apache_request_headers(); + + // REMOTE_ADDR + if(isset($ips['remote_addr'])){ + $ip_type = self::ip__validate(Server::get( 'REMOTE_ADDR' )); + if($ip_type){ + $ips['remote_addr'] = $ip_type == 'v6' ? self::ip__v6_normalize(Server::get( 'REMOTE_ADDR' )) : Server::get( 'REMOTE_ADDR' ); + } + } + + // X-Forwarded-For + if(isset($ips['x_forwarded_for'])){ + if(isset($headers['X-Forwarded-For'])){ + $tmp = explode(",", trim($headers['X-Forwarded-For'])); + $tmp = trim($tmp[0]); + $ip_type = self::ip__validate($tmp); + if($ip_type){ + $ips['x_forwarded_for'] = $ip_type == 'v6' ? self::ip__v6_normalize($tmp) : $tmp; + } + } + } + + // X-Real-Ip + if(isset($ips['x_real_ip'])){ + if(isset($headers['X-Real-Ip'])){ + $tmp = explode(",", trim($headers['X-Real-Ip'])); + $tmp = trim($tmp[0]); + $ip_type = self::ip__validate($tmp); + if($ip_type){ + $ips['x_forwarded_for'] = $ip_type == 'v6' ? self::ip__v6_normalize($tmp) : $tmp; + } + } + } + + // Cloud Flare + if(isset($ips['cloud_flare'])){ + if(isset($headers['CF-Connecting-IP'], $headers['CF-IPCountry'], $headers['CF-RAY']) || isset($headers['Cf-Connecting-Ip'], $headers['Cf-Ipcountry'], $headers['Cf-Ray'])){ + $tmp = isset($headers['CF-Connecting-IP']) ? $headers['CF-Connecting-IP'] : $headers['Cf-Connecting-Ip']; + $tmp = strpos($tmp, ',') !== false ? explode(',', $tmp) : (array)$tmp; + $ip_type = self::ip__validate(trim($tmp[0])); + if($ip_type){ + $ips['real'] = $ip_type == 'v6' ? self::ip__v6_normalize(trim($tmp[0])) : trim($tmp[0]); + } + } + } + + // Getting real IP from REMOTE_ADDR or Cf_Connecting_Ip if set or from (X-Forwarded-For, X-Real-Ip) if REMOTE_ADDR is local. + if(isset($ips['real'])){ + + // Detect IP type + $ip_type = self::ip__validate(Server::get( 'REMOTE_ADDR' ) ); + if($ip_type) + $ips['real'] = $ip_type == 'v6' ? self::ip__v6_normalize(Server::get( 'REMOTE_ADDR' )) : Server::get( 'REMOTE_ADDR' ); + + // Cloud Flare + if(isset($headers['CF-Connecting-IP'], $headers['CF-IPCountry'], $headers['CF-RAY']) || isset($headers['Cf-Connecting-Ip'], $headers['Cf-Ipcountry'], $headers['Cf-Ray'])){ + $tmp = isset($headers['CF-Connecting-IP']) ? $headers['CF-Connecting-IP'] : $headers['Cf-Connecting-Ip']; + $tmp = strpos($tmp, ',') !== false ? explode(',', $tmp) : (array)$tmp; + $ip_type = self::ip__validate(trim($tmp[0])); + if($ip_type) + $ips['real'] = $ip_type == 'v6' ? self::ip__v6_normalize(trim($tmp[0])) : trim($tmp[0]); + + // Sucury + }elseif(isset($headers['X-Sucuri-Clientip'], $headers['X-Sucuri-Country'])){ + $ip_type = self::ip__validate($headers['X-Sucuri-Clientip']); + if($ip_type) + $ips['real'] = $ip_type == 'v6' ? self::ip__v6_normalize($headers['X-Sucuri-Clientip']) : $headers['X-Sucuri-Clientip']; + + // OVH + }elseif(isset($headers['X-Cdn-Any-Ip'], $headers['Remote-Ip'])){ + $ip_type = self::ip__validate($headers['X-Cdn-Any-Ip']); + if($ip_type) + $ips['real'] = $ip_type == 'v6' ? self::ip__v6_normalize($headers['X-Cdn-Any-Ip']) : $headers['X-Cdn-Any-Ip']; + + // Incapsula proxy + }elseif(isset($headers['Incap-Client-Ip'])){ + $ip_type = self::ip__validate($headers['Incap-Client-Ip']); + if($ip_type) + $ips['real'] = $ip_type == 'v6' ? self::ip__v6_normalize($headers['Incap-Client-Ip']) : $headers['Incap-Client-Ip']; + } + + // Is private network + if($ip_type === false || ($ip_type && (self::ip__is_private_network($ips['real'], $ip_type) || self::ip__mask_match($ips['real'], filter_input(INPUT_SERVER, 'SERVER_ADDR') . '/24', $ip_type)))){ + + // X-Forwarded-For + if(isset($headers['X-Forwarded-For'])){ + $tmp = explode(',', trim($headers['X-Forwarded-For'])); + $tmp = trim($tmp[0]); + $ip_type = self::ip__validate($tmp); + if($ip_type) + $ips['real'] = $ip_type == 'v6' ? self::ip__v6_normalize($tmp) : $tmp; + + // X-Real-Ip + }elseif(isset($headers['X-Real-Ip'])){ + $tmp = explode(',', trim($headers['X-Real-Ip'])); + $tmp = trim($tmp[0]); + $ip_type = self::ip__validate($tmp); + if($ip_type) + $ips['real'] = $ip_type == 'v6' ? self::ip__v6_normalize($tmp) : $tmp; + } + } + } + + // Validating IPs + $result = array(); + foreach($ips as $key => $ip){ + $ip_version = self::ip__validate($ip); + if($ip && (($v4_only && $ip_version == 'v4') || !$v4_only)){ + $result[$key] = $ip; + } + } + + $result = array_unique($result); + return count($result) > 1 + ? $result + : (reset($result) !== false + ? reset($result) + : null); + } + + /** + * Checks if the IP is in private range + * + * @param string $ip + * @param string $ip_type + * + * @return bool + */ + static function ip__is_private_network($ip, $ip_type = 'v4') + { + return self::ip__mask_match($ip, self::$private_networks[$ip_type], $ip_type); + } + + /** + * Check if the IP belong to mask. Recursive. + * Octet by octet for IPv4 + * Hextet by hextet for IPv6 + * + * @param string $ip + * @param string $cidr work to compare with + * @param string $ip_type IPv6 or IPv4 + * @param int $xtet_count Recursive counter. Determs current part of address to check. + * + * @return bool + */ + static public function ip__mask_match($ip, $cidr, $ip_type = 'v4', $xtet_count = 0) + { + if(is_array($cidr)){ + foreach($cidr as $curr_mask){ + if(self::ip__mask_match($ip, $curr_mask, $ip_type)){ + return true; + } + } + unset($curr_mask); + return false; + } + + $xtet_base = ($ip_type == 'v4') ? 8 : 16; + + // Calculate mask + $exploded = explode('/', $cidr); + $net_ip = $exploded[0]; + $mask = $exploded[1]; + + // Exit condition + $xtet_end = ceil($mask / $xtet_base); + if($xtet_count == $xtet_end) + return true; + + // Lenght of bits for comparsion + $mask = $mask - $xtet_base * $xtet_count >= $xtet_base ? $xtet_base : $mask - $xtet_base * $xtet_count; + + // Explode by octets/hextets from IP and Net + $net_ip_xtets = explode($ip_type == 'v4' ? '.' : ':', $net_ip); + $ip_xtets = explode($ip_type == 'v4' ? '.' : ':', $ip); + + // Standartizing. Getting current octets/hextets. Adding leading zeros. + $net_xtet = str_pad(decbin($ip_type == 'v4' ? $net_ip_xtets[$xtet_count] : hexdec($net_ip_xtets[$xtet_count])), $xtet_base, 0, STR_PAD_LEFT); + $ip_xtet = str_pad(decbin($ip_type == 'v4' ? $ip_xtets[$xtet_count] : hexdec($ip_xtets[$xtet_count])), $xtet_base, 0, STR_PAD_LEFT); + + // Comparing bit by bit + for($i = 0, $result = true; $mask != 0; $mask--, $i++){ + if($ip_xtet[$i] != $net_xtet[$i]){ + $result = false; + break; + } + } + + // Recursing. Moving to next octet/hextet. + if($result) + $result = self::ip__mask_match($ip, $cidr, $ip_type, $xtet_count + 1); + + return $result; + + } + + /** + * Converts long mask like 4294967295 to number like 32 + * + * @param int $long_mask + * + * @return int + */ + static function ip__mask__long_to_number($long_mask) + { + $num_mask = strpos((string)decbin($long_mask), '0'); + return $num_mask === false ? 32 : $num_mask; + } + + /** + * Validating IPv4, IPv6 + * + * @param string $ip + * + * @return string|bool + */ + static public function ip__validate($ip) + { + if(!$ip) return false; // NULL || FALSE || '' || so on... + if(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) && $ip != '0.0.0.0') return 'v4'; // IPv4 + if(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && self::ip__v6_reduce($ip) != '0::0') return 'v6'; // IPv6 + return false; // Unknown + } + + /** + * Expand IPv6 + * + * @param string $ip + * + * @return string IPv6 + */ + static public function ip__v6_normalize($ip) + { + $ip = trim($ip); + // Searching for ::ffff:xx.xx.xx.xx patterns and turn it to IPv6 + if(preg_match('/^::ffff:([0-9]{1,3}\.?){4}$/', $ip)){ + $ip = dechex(sprintf("%u", ip2long(substr($ip, 7)))); + $ip = '0:0:0:0:0:0:' . (strlen($ip) > 4 ? substr('abcde', 0, -4) : '0') . ':' . substr($ip, -4, 4); + // Normalizing hextets number + }elseif(strpos($ip, '::') !== false){ + $ip = str_replace('::', str_repeat(':0', 8 - substr_count($ip, ':')) . ':', $ip); + $ip = strpos($ip, ':') === 0 ? '0' . $ip : $ip; + $ip = strpos(strrev($ip), ':') === 0 ? $ip . '0' : $ip; + } + // Simplifyng hextets + if(preg_match('/:0(?=[a-z0-9]+)/', $ip)){ + $ip = preg_replace('/:0(?=[a-z0-9]+)/', ':', strtolower($ip)); + $ip = self::ip__v6_normalize($ip); + } + return $ip; + } + + /** + * Reduce IPv6 + * + * @param string $ip + * + * @return string IPv6 + */ + static public function ip__v6_reduce($ip) + { + if(strpos($ip, ':') !== false){ + $ip = preg_replace('/:0{1,4}/', ':', $ip); + $ip = preg_replace('/:{2,}/', '::', $ip); + $ip = strpos($ip, '0') === 0 ? substr($ip, 1) : $ip; + } + return $ip; + } + + /** + * Get URL form IP. Check if it's belong to cleantalk. + * + * @param string $ip + * + * @return false|int|string + */ + static public function ip__is_cleantalks($ip) + { + if(self::ip__validate($ip)){ + $url = array_search($ip, self::$cleantalks_servers); + return $url + ? true + : false; + }else + return false; + } + + /** + * Get URL form IP. Check if it's belong to cleantalk. + * + * @param $ip + * + * @return false|int|string + */ + static public function ip__resolve__cleantalks($ip) + { + if(self::ip__validate($ip)){ + $url = array_search($ip, self::$cleantalks_servers); + return $url + ? $url + : self::ip__resolve($ip); + }else + return $ip; + } + + /** + * Get URL form IP + * + * @param $ip + * + * @return string + */ + static public function ip__resolve($ip) + { + if(self::ip__validate($ip)){ + $url = gethostbyaddr($ip); + if($url) + return $url; + } + return $ip; + } + + /** + * Resolve DNS to IP + * + * @param $host + * @param bool $out + * + * @return bool + */ + static public function dns__resolve($host, $out = false) + { + + // Get DNS records about URL + if(function_exists('dns_get_record')){ + $records = dns_get_record($host, DNS_A); + if($records !== false){ + $out = $records[0]['ip']; + } + } + + // Another try if first failed + if(!$out && function_exists('gethostbynamel')){ + $records = gethostbynamel($host); + if($records !== false){ + $out = $records[0]; + } + } + + return $out; + + } + + static public function http__user_agent(){ + return defined( 'CLEANTALK_USER_AGENT' ) ? CLEANTALK_USER_AGENT : static::DEFAULT_USER_AGENT; + } + + /** + * Function sends raw http request + * + * May use 4 presets(combining possible): + * get_code - getting only HTTP response code + * async - async requests + * get - GET-request + * ssl - use SSL + * + * @param string $url URL + * @param array $data POST|GET indexed array with data to send + * @param string|array $presets String or Array with presets: get_code, async, get, ssl, dont_split_to_array + * @param array $opts Optional option for CURL connection + * + * @return array|bool (array || array('error' => true)) + */ + static public function http__request($url, $data = array(), $presets = null, $opts = array()) + { + // For debug purposes + if( defined( 'CLEANTALK_DEBUG' ) && CLEANTALK_DEBUG ){ + global $apbct_debug; + $apbct_debug['data'] = $data; + } + + // Preparing presets + $presets = is_array($presets) ? $presets : explode(' ', $presets); + $curl_only = in_array( 'async', $presets ) || + in_array( 'dont_follow_redirects', $presets ) || + in_array( 'ssl', $presets ) || + in_array( 'split_to_array', $presets ) + ? true : false; + + if(function_exists('curl_init')){ + + $ch = curl_init(); + + // Set data if it's not empty + if(!empty($data)){ + // If $data scalar converting it to array + $opts[CURLOPT_POSTFIELDS] = is_scalar($data) + ? array($data) + : $data; + } + + // Merging OBLIGATORY options with GIVEN options + // Using POST method by default + $opts = static::array_merge__save_numeric_keys( + array( + CURLOPT_URL => $url, + CURLOPT_TIMEOUT => 5, + CURLOPT_CONNECTTIMEOUT_MS => 3000, + CURLOPT_FORBID_REUSE => true, + CURLOPT_POST => true, + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => 0, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_MAXREDIRS => 5, + CURLOPT_USERAGENT => static::http__user_agent() . '; ' . ( ! empty( Server::get( 'SERVER_NAME' ) ) ? Server::get( 'SERVER_NAME' ) : 'UNKNOWN_HOST' ), + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_0, // see http://stackoverflow.com/a/23322368 + CURLOPT_RETURNTRANSFER => true, // receive server response ... + CURLOPT_HTTPHEADER => array('Expect:'), // Fix for large data and old servers http://php.net/manual/ru/function.curl-setopt.php#82418 + ), + $opts + ); + + foreach($presets as $preset){ + + switch($preset){ + + // Do not follow redirects + case 'dont_follow_redirects': + $opts[CURLOPT_FOLLOWLOCATION] = false; + $opts[CURLOPT_MAXREDIRS] = 0; + break; + + // Get headers only + case 'get_code': + $opts[CURLOPT_HEADER] = true; + $opts[CURLOPT_NOBODY] = true; + break; + + // Make a request, don't wait for an answer + case 'async': + $opts[CURLOPT_CONNECTTIMEOUT_MS] = 1000; + $opts[CURLOPT_TIMEOUT_MS] = 500; + break; + + case 'get': + $opts[CURLOPT_URL] .= $data ? '?' . str_replace( "&", "&", http_build_query( $data ) ) : ''; + $opts[CURLOPT_POST] = false; + unset($opts[CURLOPT_POSTFIELDS]); + break; + + case 'ssl': + $opts[CURLOPT_SSL_VERIFYPEER] = true; + $opts[CURLOPT_SSL_VERIFYHOST] = 2; + if(defined('CLEANTALK_CASERT_PATH') && CLEANTALK_CASERT_PATH) + $opts[CURLOPT_CAINFO] = CLEANTALK_CASERT_PATH; + break; + + default: + + break; + } + } + unset($preset); + + curl_setopt_array($ch, $opts); + $result = curl_exec($ch); + + // RETURN if async request + if(in_array('async', $presets)) + return true; + + if($result){ + + // Split to array by lines if such preset given + if( in_array( 'split_to_array', $presets ) ) + $result = explode(PHP_EOL, $result); + + // Get code crossPHP method + if(in_array('get_code', $presets)){ + $curl_info = curl_getinfo($ch); + $result = $curl_info['http_code']; + } + + $out = $result; + + }else + $out = array('error' => curl_error($ch)); + + curl_close($ch); + + // Curl not installed. Trying file_get_contents() + }elseif( ini_get( 'allow_url_fopen' ) && ! $curl_only ){ + + // Trying to get code via get_headers() + if( in_array( 'get_code', $presets ) ){ + $headers = get_headers( $url ); + $result = (int) preg_replace( '/.*(\d{3}).*/', '$1', $headers[0] ); + + // Making common request + }else{ + $opts = array( + 'http' => array( + 'method' => in_array( 'get', $presets ) ? 'GET' : 'POST', + 'timeout' => 5, + 'content' => str_replace( "&", "&", http_build_query( $data ) ), + ), + ); + $context = stream_context_create( $opts ); + $result = @file_get_contents( $url, 0, $context ); + } + + $out = $result === false + ? 'FAILED_TO_USE_FILE_GET_CONTENTS' + : $result; + + }else + $out = array('error' => 'CURL not installed and allow_url_fopen is disabled'); + + return $out; + } + + /** + * Merging arrays without reseting numeric keys + * + * @param array $arr1 One-dimentional array + * @param array $arr2 One-dimentional array + * + * @return array Merged array + */ + public static function array_merge__save_numeric_keys($arr1, $arr2) + { + foreach($arr2 as $key => $val){ + $arr1[$key] = $val; + } + return $arr1; + } + + /** + * Merging arrays without reseting numeric keys recursive + * + * @param array $arr1 One-dimentional array + * @param array $arr2 One-dimentional array + * + * @return array Merged array + */ + public static function array_merge__save_numeric_keys__recursive($arr1, $arr2) + { + foreach($arr2 as $key => $val){ + + // Array | array => array + if(isset($arr1[$key]) && is_array($arr1[$key]) && is_array($val)){ + $arr1[$key] = self::array_merge__save_numeric_keys__recursive($arr1[$key], $val); + + // Scalar | array => array + }elseif(isset($arr1[$key]) && !is_array($arr1[$key]) && is_array($val)){ + $tmp = $arr1[$key] = + $arr1[$key] = $val; + $arr1[$key][] = $tmp; + + // array | scalar => array + }elseif(isset($arr1[$key]) && is_array($arr1[$key]) && !is_array($val)){ + $arr1[$key][] = $val; + + // scalar | scalar => scalar + }else{ + $arr1[$key] = $val; + } + } + return $arr1; + } + + /** + * Function removing non UTF8 characters from array|string|object + * + * @param array|object|string $data + * + * @return array|object|string + */ + public static function removeNonUTF8($data) + { + // Array || object + if(is_array($data) || is_object($data)){ + foreach($data as $key => &$val){ + $val = self::removeNonUTF8($val); + } + unset($key, $val); + + //String + }else{ + if(!preg_match('//u', $data)) + $data = 'Nulled. Not UTF8 encoded or malformed.'; + } + return $data; + } + + /** + * Function convert anything to UTF8 and removes non UTF8 characters + * + * @param array|object|string $obj + * @param string $data_codepage + * + * @return mixed(array|object|string) + */ + public static function toUTF8($obj, $data_codepage = null) + { + // Array || object + if(is_array($obj) || is_object($obj)){ + foreach($obj as $key => &$val){ + $val = self::toUTF8($val, $data_codepage); + } + unset($key, $val); + + //String + }else{ + if(!preg_match('//u', $obj) && function_exists('mb_detect_encoding') && function_exists('mb_convert_encoding')){ + $encoding = mb_detect_encoding($obj); + $encoding = $encoding ? $encoding : $data_codepage; + if($encoding) + $obj = mb_convert_encoding($obj, 'UTF-8', $encoding); + } + } + return $obj; + } + + /** + * Function convert from UTF8 + * + * @param array|object|string $obj + * @param string $data_codepage + * + * @return mixed (array|object|string) + */ + public static function fromUTF8($obj, $data_codepage = null) + { + // Array || object + if(is_array($obj) || is_object($obj)){ + foreach($obj as $key => &$val){ + $val = self::fromUTF8($val, $data_codepage); + } + unset($key, $val); + + //String + }else{ + if(preg_match('u', $obj) && function_exists('mb_convert_encoding') && $data_codepage !== null) + $obj = mb_convert_encoding($obj, $data_codepage, 'UTF-8'); + } + return $obj; + } + + /** + * Checks if the string is JSON type + * + * @param string + * + * @return bool + */ + static public function is_json($string) + { + return is_string($string) && is_array(json_decode($string, true)) ? true : false; + } + + /** + * Checks if given string is valid regular expression + * + * @param string $regexp + * + * @return bool + */ + static public function is_regexp($regexp){ + return @preg_match('/' . $regexp . '/', null) !== false; + } + + static public function convert_to_regexp( $string ){ + $string = preg_replace( '/\$/', '\\\\$', $string ); + $string = preg_replace( '/\//', '\/', $string ); + return $string; + } + + static function get_mime_type($data ) + { + if( @file_exists( $data )){ + $mime = mime_content_type( $data ); + }else{ + $finfo = finfo_open(FILEINFO_MIME_TYPE); + $mime = finfo_buffer($finfo, $data); + finfo_close($finfo); + } + return $mime; + } + + static function buffer__trim_and_clear_from_empty_lines( $buffer ){ + $buffer = (array) $buffer; + foreach( $buffer as $indx => &$line ){ + $line = trim( $line ); + if($line === '') + unset( $buffer[$indx] ); + } + return $buffer; + } + + static function buffer__parse__csv( $buffer ){ + $buffer = explode( "\n", $buffer ); + $buffer = self::buffer__trim_and_clear_from_empty_lines( $buffer ); + foreach($buffer as &$line){ + $line = str_getcsv($line, ',', '\''); + } + return $buffer; + } +} \ No newline at end of file diff --git a/cleantalk/lib/CleantalkAP/Common/State.php b/cleantalk/lib/CleantalkAP/Common/State.php new file mode 100644 index 0000000..17bf7b3 --- /dev/null +++ b/cleantalk/lib/CleantalkAP/Common/State.php @@ -0,0 +1,384 @@ + -1, + '2fa_roles' => array('administrator'), + 'block_timer__1_fails' => 3, + 'block_timer__5_fails' => 10, + + // Key + 'spbc_key' => '', + 'custom_key' => 0, + + // Traffic Control + 'traffic_control_enabled' => 1, + 'traffic_control_autoblock_amount' => 1000, + 'traffic_control_autoblock_period' => 3600, + + // Scanner + 'scanner_auto_start' => 1, + 'scanner_outbound_links' => 0, + 'scanner_outbound_links_mirrors' => '', + 'scanner_heuristic_analysis' => 1, + 'scanner_signature_analysis' => 1, + 'scanner_auto_cure' => 1, + 'scanner_frontend_analysis' => 0, + + // Web Application Firewall + 'waf_enabled' => 1, + 'waf_xss_check' => 1, + 'waf_sql_check' => 1, + 'waf_file_check' => 1, + 'waf_exploit_check' => 1, + + // Misc + 'backend_logs_enable' => 1, + 'set_cookies' => 1, + 'forbid_to_show_in_iframes' => 1, + 'show_link_in_login_form' => 1, + 'use_buitin_http_api' => 0, + 'complete_deactivation' => 0, + ); + public $def_data = array( + 'plugin_version' => SPBC_VERSION, + 'user_token' => '', + 'key_is_ok' => false, + 'moderate' => false, + 'logs_last_sent' => null, + 'last_sent_events_count' => null, + 'last_firewall_updated' => null, + 'firewall_entries' => null, + 'last_firewall_send' => null, + 'last_firewall_send_count' => null, + 'notice_show' => null, + 'notice_renew' => false, + 'notice_trial' => false, + 'notice_were_updated' => false, + 'service_id' => '', + 'license_trial' => 0, + 'account_name_ob' => '', + 'salt' => '', + 'scanner' => array( + 'last_signature_update' => null, + 'last_wp_version' => null, + 'cron' => array( + 'state' => 'get_hashes', + 'total_scanned' => 0, + 'offset' => 0, + ), + 'cured' => array(), + 'last_backup' => 0, + ), + 'cron' => array( + 'running' => false, + ), + 'errors' => array( + 'cron' => array( + + ), + ), + 'last_php_log_sent' => 0, + '2fa_keys' => array(), + ); + public $def_network_settings = array( + 'allow_custom_key' => false, + 'allow_cleantalk_cp' => false, + 'key_is_ok' => false, + 'spbc_key' => '', + 'user_token' => '', + 'service_id' => '', + 'moderate' => 0 + ); + + public $def_remote_calls = array( + + // Common + 'close_renew_banner' => array('last_call' => 0,), + 'update_plugin' => array('last_call' => 0,), + 'update_security_firewall' => array('last_call' => 0, 'cooldown' => 3), + 'drop_security_firewall' => array('last_call' => 0,), + 'update_settings' => array('last_call' => 0,), + + // Inner + 'download__quarantine_file' => array('last_call' => 0, 'cooldown' => 3), + + // Backups + 'backup_signatures_files' => array('last_call' => 0,), + 'rollback_repair' => array('last_call' => 0,), + + // Scanner + 'scanner_signatures_update' => array('last_call' => 0,), + 'scanner_clear_hashes' => array('last_call' => 0,), + + 'scanner__controller' => array('last_call' => 0, 'cooldown' => 3), + 'scanner__get_remote_hashes' => array('last_call' => 0,), + 'scanner__count_hashes_plug' => array('last_call' => 0,), + 'scanner__get_remote_hashes__plug' => array('last_call' => 0,), + 'scanner__clear_table' => array('last_call' => 0,), + 'scanner__count_files' => array('last_call' => 0,), + 'scanner__scan' => array('last_call' => 0,), + 'scanner__count_files__by_status' => array('last_call' => 0,), + 'scanner__scan_heuristic' => array('last_call' => 0,), + 'scanner__scan_signatures' => array('last_call' => 0,), + 'scanner__count_cure' => array('last_call' => 0,), + 'scanner__cure' => array('last_call' => 0,), + 'scanner__links_count' => array('last_call' => 0,), + 'scanner__links_scan' => array('last_call' => 0,), + 'scanner__frontend_scan' => array('last_call' => 0,), + ); + + public $def_errors = array(); + + public function __construct($option_prefix, $options = array('settings'), $wpms = false) + { + $this->option_prefix = $option_prefix; + + if($wpms){ + $option = get_site_option($this->option_prefix.'_network_settings'); + $option = is_array($option) ? $option : $this->def_network_settings; + $this->network_settings = new \ArrayObject($option); + } + + foreach($options as $option_name){ + + $option = get_option($this->option_prefix.'_'.$option_name); + + // Default options + if($this->option_prefix.'_'.$option_name === 'spbc_settings'){ + $option = is_array($option) ? array_merge($this->def_settings, $option) : $this->def_settings; + if(!is_main_site()) $option['backend_logs_enable'] = 0; + } + + // Default data + if($this->option_prefix.'_'.$option_name === 'spbc_data'){ + $option = is_array($option) ? array_merge($this->def_data, $option) : $this->def_data; + if(empty($option['salt'])) $option['salt'] = str_pad(rand(0, getrandmax()), 6, '0').str_pad(rand(0, getrandmax()), 6, '0'); + if(empty($option['last_php_log_sent'])) $option['last_php_log_sent'] = time(); + } + + // Default errors + if($this->option_prefix.'_'.$option_name === 'spbc_errors'){ + $option = is_array($option) ? array_merge($this->def_errors, $option) : $this->def_errors; + } + + // Default remote calls + if($this->option_prefix.'_'.$option_name === 'spbc_remote_calls'){ + $option = is_array($option) ? array_merge($this->def_remote_calls, $option) : $this->def_remote_calls; + } + + $this->$option_name = is_array($option) ? new \ArrayObject($option) : $option; + + } + } + + private function getOption($option_name) + { + $option = get_option('spbc_'.$option_name); + $this->$option_name = gettype($option) === 'array' + ? new \ArrayObject($option) + : $option; + } + + /** + * @param string $option_name + * @param bool $use_perfix + * @param bool $autoload + */ + public function save($option_name, $use_perfix = true, $autoload = true) + { + $option_name_to_save = $use_perfix ? $this->option_prefix.'_'.$option_name : $option_name; + $arr = array(); + foreach($this->$option_name as $key => $value){ + $arr[$key] = $value; + } + update_option($option_name_to_save, $arr, $autoload); + } + + public function saveSettings() + { + update_option($this->option_prefix.'_settins', $this->settings); + } + + public function saveData() + { + update_option($this->option_prefix.'_data', $this->data); + } + + public function saveNetworkSettings() + { + update_site_option($this->option_prefix.'_network_settings', $this->network_settings); + } + + public function deleteOption($option_name, $use_prefix = false) + { + if($this->__isset($option_name)){ + $this->__unset($option_name); + delete_option( ($use_prefix ? $this->option_prefix.'_' : '') . $option_name); + } + } + + /** + * Prepares an adds an error to the plugin's data + * + * @param string $type + * @param string $error + * @param string $major_type + * @param bool $set_time + * + * @return void + */ + public function error_add($type, $error, $major_type = null, $set_time = true) + { + $error = is_array($error) + ? $error['error'] + : $error; + + // Exceptions + if( ($type == 'send_logs' && $error == 'NO_LOGS_TO_SEND') || + ($type == 'send_firewall_logs' && $error == 'NO_LOGS_TO_SEND') || + $error == 'LOG_FILE_NOT_EXISTS' + ) + return; + + $error = array( + 'error' => $error, + 'error_time' => $set_time ? current_time('timestamp') : null, + ); + + if(!empty($major_type)){ + $this->errors[$major_type][$type] = $error; + }else{ + $this->errors[$type] = $error; + } + + $this->save('errors'); + } + + /** + * Deletes an error from the plugin's data + * + * @param string $type + * @param bool $save_flag + * @param string $major_type + * + * @return void + */ + public function error_delete($type, $save_flag = false, $major_type = null) + { + if(is_string($type)) + $type = explode(' ', $type); + + foreach($type as $val){ + if($major_type){ + if(isset($this->errors[$major_type][$val])) + unset($this->errors[$major_type][$val]); + }else{ + if(isset($this->errors[$val])) + unset($this->errors[$val]); + } + } + + // Save if flag is set and there are changes + if($save_flag) + $this->save('errors'); + } + + /** + * Deletes all errors from the plugin's data + * + * @param bool $save_flag + * + * @return void + */ + public function error_delete_all($save_flag = false) + { + $this->errors = new \ArrayObject($this->def_errors); + if($save_flag) + $this->save('errors'); + } + + public function error_toggle($add_flag = true, $type, $save_flag = false, $major_type = null){ + if($add_flag) + $this->error_add($type, $save_flag, $major_type); + else + $this->error_delete($type, $save_flag, $major_type); + } + + public function __set($name, $value) + { + $this->storage[$name] = $value; + } + + public function __get($name) + { + if (array_key_exists($name, $this->storage)){ + return $this->storage[$name]; + }else{ + $this->getOption($name); + return $this->storage[$name]; + } + + // return !empty($this->storage[$name]) ? $this->storage[$name] : null; + } + + public function __isset($name) + { + return isset($this->storage[$name]); + } + + public function __unset($name) + { + unset($this->storage[$name]); + } + + public function __call($name, $arguments) + { + error_log ("Calling method '$name' with arguments: " . implode(', ', $arguments). "\n"); + } + + public static function __callStatic($name, $arguments) + { + error_log("Calling static method '$name' with arguments: " . implode(', ', $arguments). "\n"); + } + + public function server(){ + return \CleantalkAP\Variables\Server::getInstance(); + } + public function cookie(){ + return \CleantalkAP\Variables\Cookie::getInstance(); + } + public function request(){ + return \CleantalkAP\Variables\Request::getInstance(); + } + public function post(){ + return \CleantalkAP\Variables\Post::getInstance(); + } + public function get(){ + return \CleantalkAP\Variables\Get::getInstance(); + } +} diff --git a/cleantalk/lib/CleantalkAP/SMF/API.php b/cleantalk/lib/CleantalkAP/SMF/API.php new file mode 100644 index 0000000..7a255c4 --- /dev/null +++ b/cleantalk/lib/CleantalkAP/SMF/API.php @@ -0,0 +1,80 @@ +settings['use_buitin_http_api']){ + + $args = array( + 'body' => $data, + 'timeout' => 5, + 'user-agent' => SPBC_AGENT.' '.get_bloginfo( 'url' ), + ); + + $result = wp_remote_post($url, $args); + + if( is_wp_error( $result ) ) { + $errors = $result->get_error_message(); + $result = false; + }else{ + $result = wp_remote_retrieve_body($result); + } + + // Use Cleantalk CURL version if disabled + }else{ + // Default preset is 'api' + $presets = array( 'api' ); + + // Add ssl to 'presets' if enabled + if( $ssl ) + array_push( $presets, 'ssl' ); + + $result = \CleantalkAP\SMF\Helper::http__request( $url, $data, $presets ); + + // Retry with SSL enabled if failed + if( ! empty ( $result['error'] ) && $ssl === false ) + $result = \CleantalkAP\SMF\Helper::http__request( $url, $data, 'api ssl' ); + } + + return empty($result) || !empty($errors) + ? array('error' => $errors) + : $result; + + } +} \ No newline at end of file diff --git a/cleantalk/lib/CleantalkAP/SMF/Cron.php b/cleantalk/lib/CleantalkAP/SMF/Cron.php new file mode 100644 index 0000000..1534d4a --- /dev/null +++ b/cleantalk/lib/CleantalkAP/SMF/Cron.php @@ -0,0 +1,56 @@ +spbc = $spbc; + // $this->load(); + } + + /** + * Loads saved errors from DB + */ + public function load(){ + $errors = get_option('spbc_errors'); + $this->errors = $errors ? $errors : array(); + } + + /** + * Save loaded errors to DB + */ + public function save(){ + update_option('spbct_errors', $this->errors); + } + + /** + * Adds new error + */ + public static function add(){ + $args = func_get_args(); + if(count($args) === 3){ + static::getInstance()->spbc->error_add( + $args[2], + $args[1], + $args[0] + ); + }else{ + static::getInstance()->spbc->error_add( + $args[0], + $args[1], + ); + } + } + + /** + * Adds new error + */ + public static function delete(){ + $args = func_get_args(); + if(count($args) === 2){ + static::getInstance()->spbc->error_delete( + $args[1], + true, + $args[0], + ); + }else{ + static::getInstance()->spbc->error_delete( + $args[0], + true + ); + } + } +} \ No newline at end of file diff --git a/cleantalk/lib/CleantalkAP/SMF/FireWall.php b/cleantalk/lib/CleantalkAP/SMF/FireWall.php new file mode 100644 index 0000000..dcdee2f --- /dev/null +++ b/cleantalk/lib/CleantalkAP/SMF/FireWall.php @@ -0,0 +1,714 @@ +', + '', + 'javascript:', +// 'data:', + ); + + public $waf_sql_patterns = array( + '-\d\s?union', + ';\s?union', + ';\s?or', + 'pid=\d+\+union\+select\+0x5e2526,0x5e2526,0x5e2526,' + ); + + public $waf_exploit_patterns = array( + '(random.*)?action=update-plugin(.*random)?', + ); + + public $waf_file_mime_check = array( + 'text/x-php', + 'text/plain', + 'image/x-icon', + ); + + public $statuses_priority = array( + 'PASS', + 'DENY', + 'DENY_BY_NETWORK', + 'DENY_BY_DOS', + 'PASS_BY_WHITELIST', + 'PASS_BY_TRUSTED_NETWORK', // Highest + ); + /** + * @var int + */ + + function __construct($params = array()){ + + // TC + $this->tc_enabled = isset($params['tc_enabled']) ? (bool)$params['tc_enabled'] : false; + $this->tc_limit = isset($params['tc_limit']) ? (int)$params['tc_limit'] : 1000; + $this->tc_period = isset($params['tc_period']) ? (int)$params['tc_period'] : 3600; + $this->tc_period = isset($params['tc_period']) ? (int)$params['tc_period'] : 3600; + + $this->chance_to_clean = 100; // from 0 to 1000 + $this->store_interval = 300; // in seconds + + // WAF + $this->waf_enabled = isset($params['waf_enabled']) ? (bool)$params['waf_enabled'] : false; + $this->waf_xss_check = isset($params['waf_xss_check']) ? (bool)$params['waf_xss_check'] : false; + $this->waf_sql_check = isset($params['waf_sql_check']) ? (bool)$params['waf_sql_check'] : false; + $this->waf_file_check = isset($params['waf_file_check']) ? (bool)$params['waf_file_check'] : false; + $this->waf_exploit_check = isset($params['waf_exploit_check']) ? (bool)$params['waf_exploit_check'] : false; + + // MISC + $this->was_logged_in = isset($params['was_logged_in']) ? (bool)$params['was_logged_in'] : false; + + $this->ip_array = (array)static::ip__get(array('real')); + + $this->db = $params['db']; + } + + static public function ip__get($ip_types = array('real', 'remote_addr', 'x_forwarded_for', 'x_real_ip', 'cloud_flare')){ + + $result = (array)SpbcHelper::ip__get($ip_types); + + global $spbc; + + if(isset($_GET['spbct_test_ip'], $_GET['spbct_test'], $spbc->settings['spbc_key']) && $_GET['spbct_test'] == md5($spbc->settings['spbc_key'])){ + $ip_type = SpbcHelper::ip__validate($_GET['spbct_test_ip']); + $test_ip = $ip_type == 'v6' ? SpbcHelper::ip__v6_normalize($_GET['spbct_test_ip']) : $_GET['spbct_test_ip']; + if($ip_type) + $result['test'] = $test_ip; + } + + return $result; + } + + public function ip__test(){ + + global $wpdb; + + $fw_results = array(); + + foreach($this->ip_array as $ip_origin => $current_ip){ + + $ip_type = SpbcHelper::ip__validate($current_ip); + + if($ip_type && $ip_type == 'v4'){ + + $current_ip_v4 = sprintf("%u", ip2long($current_ip)); + + $sql = 'SELECT status, is_personal + FROM `'. SPBC_TBL_FIREWALL_DATA ."` + WHERE spbc_network_4 = $current_ip_v4 & spbc_mask_4 + AND ipv6 = 0;"; + + }elseif($ip_type){ + + $current_ip_txt = explode(':', $current_ip); + $current_ip_1 = hexdec($current_ip_txt[0].$current_ip_txt[1]); + $current_ip_2 = hexdec($current_ip_txt[2].$current_ip_txt[3]); + $current_ip_3 = hexdec($current_ip_txt[4].$current_ip_txt[5]); + $current_ip_4 = hexdec($current_ip_txt[6].$current_ip_txt[7]); + + $sql = 'SELECT status, is_personal + FROM `'. SPBC_TBL_FIREWALL_DATA ."` + WHERE spbc_network_1 = $current_ip_1 & spbc_mask_1 + AND spbc_network_2 = $current_ip_2 & spbc_mask_2 + AND spbc_network_3 = $current_ip_3 & spbc_mask_3 + AND spbc_network_4 = $current_ip_4 & spbc_mask_4 + AND ipv6 = 1;"; + } + + $result = $wpdb->get_results($sql, ARRAY_A); + + // In base + if(!empty($result)){ + + $in_base = true; + foreach($result as $entry){ + switch ($entry['status']) { + case 2: $fw_results[] = array('ip' => $current_ip, 'is_personal' => (bool)$entry['is_personal'], 'status' => 'PASS_BY_TRUSTED_NETWORK',); $this->tc_skip = true; break; + case 1: $fw_results[] = array('ip' => $current_ip, 'is_personal' => (bool)$entry['is_personal'], 'status' => 'PASS_BY_WHITELIST',); $this->tc_skip = true; break; + case 0: $fw_results[] = array('ip' => $current_ip, 'is_personal' => (bool)$entry['is_personal'], 'status' => 'DENY',); break; + case -1: $fw_results[] = array('ip' => $current_ip, 'is_personal' => (bool)$entry['is_personal'], 'status' => 'DENY_BY_NETWORK',); break; + case -2: $fw_results[] = array('ip' => $current_ip, 'is_personal' => (bool)$entry['is_personal'], 'status' => 'DENY_BY_DOS',); break; + } + } + + // Not in base + }else + $fw_results[] = array('ip' => $current_ip, 'is_personal' => false, 'status' => 'PASS',); + } + + $current_fw_result_priority = 0; + foreach ($fw_results as $fw_result) { + $priority = array_search($fw_result['status'], $this->statuses_priority) + ($fw_result['is_personal'] ? count($this->statuses_priority) : 0); + if($priority >= $current_fw_result_priority){ + $current_fw_result_priority = $priority; + $this->result = $fw_result['status']; + $this->passed_ip = $fw_result['ip']; + $this->blocked_ip = $fw_result['ip']; + } + } + + if(!$this->tc_enabled && $priority == 0){ + $this->result = null; + $this->passed_ip = ''; + $this->blocked_ip = ''; + } + } + + public function tc__test(){ + if($this->tc_enabled && !$this->tc_skip && !$this->was_logged_in){ + $this->tc__clear_table(); + $time = time(); + foreach($this->ip_array as $ip_origin => $current_ip){ + $result = $this->db->get_results( + "SELECT SUM(entries) as total_count" + . ' FROM `' . SPBC_TBL_TC_LOG . '`' + . " WHERE ip = '$current_ip' AND interval_start < '$time';", + OBJECT + ); + if(!empty($result) && $result[0]->total_count >= $this->tc_limit){ + $this->result = 'DENY_BY_DOS'; + $this->blocked_ip = $current_ip; + return; + } + } + } + } + + public function tc__update_logs( $ip = array() ){ + $ip = !empty( $ip ) ? $ip : $this->ip_array; + $interval_time = SpbcHelper::time__get_interval_start( $this->store_interval ); + foreach($this->ip_array as $ip_origin => $current_ip){ + $id = md5( $current_ip . $interval_time ); + $this->db->query( + "INSERT INTO " . SPBC_TBL_TC_LOG . " SET + id = '$id', + ip = '$current_ip', + entries = 1, + interval_start = $interval_time + ON DUPLICATE KEY UPDATE + ip = ip, + entries = entries + 1, + interval_start = $interval_time;" + ); + } + } + + + public function tc__clear_table(){ + if( rand( 0, 1000 ) < $this->chance_to_clean ){ + $interval_start = SpbcHelper::time__get_interval_start( $this->tc_period ); + $this->db->query( + 'DELETE + FROM ' . SPBC_TBL_TC_LOG . ' + WHERE interval_start < '. $interval_start .' + LIMIT 100000;' + ); + } + } + + public function waf__test(){ + + if($this->waf_enabled && !in_array($this->result, array('PASS_BY_TRUSTED_NETWORK', 'PASS_BY_WHITELIST'))){ + + // XSS + if($this->waf_xss_check){ + if($this->waf_xss_check($_POST) || $this->waf_xss_check($_GET) || $this->waf_xss_check($_COOKIE)){ + $this->result = 'DENY_BY_WAF_XSS'; + $this->blocked_ip = end($this->ip_array); + } + } + + // SQL-injection + if($this->waf_sql_check){ + if($this->waf_sql_check($_POST) || $this->waf_sql_check($_GET)){ + $this->result = 'DENY_BY_WAF_SQL'; + $this->blocked_ip = end($this->ip_array); + } + } + + // File + if($this->waf_file_check){ + if($this->waf_file_check()){ + $this->result = 'DENY_BY_WAF_FILE'; + $this->blocked_ip = end($this->ip_array); + } + } + + // Exploits + if($this->waf_exploit_check){ + if($this->waf_exploit_check()){ + $this->result = 'DENY_BY_WAF_EXPLOIT'; + $this->blocked_ip = end($this->ip_array); + } + } + } + } + + public function waf_xss_check($arr){ + foreach($arr as $name => $param){ + if(is_array($param)){ + $result = $this->waf_xss_check($param); + if($result === true) + return true; + continue; + } + foreach($this->waf_xss_patterns as $pattern){ + if(stripos($param, $pattern) !== false){ + $this->waf_pattern = array('critical' => $pattern); + return true; + } + } + // Test + if($name == 'spbct_test_waf' && $param == 'xss'){ + $this->waf_pattern = array('critical' => 'test'); + return true; + } + } + } + + public function waf_sql_check($arr){ + foreach($arr as $name => $param){ + if(is_array($param)){ + $result = $this->waf_sql_check($param); + if($result === true) + return true; + continue; + } + foreach($this->waf_sql_patterns as $pattern){ + if(preg_match('/'.$pattern.'/i', $param) === 1){ + $this->waf_pattern = array('critical' => $pattern); + return true; + } + } + // Test + if($name == 'spbct_test_waf' && $param == 'sql'){ + $this->waf_pattern = array('critical' => 'test'); + return true; + } + } + } + + public function waf_exploit_check(){ + $query = filter_input(INPUT_SERVER, 'QUERY_STRING'); + if(!empty($query)){ + foreach($this->waf_exploit_patterns as $pattern){ + if(preg_match('/'.$pattern.'/i', $query) === 1){ + $this->waf_pattern = array('critical' => $pattern); + return true; + } + } + // Test + if(strpos($query, 'spbct_test_waf=exploit') !== false){ + $this->waf_pattern = array('critical' => 'test'); + return true; + } + } + } + + /** + * Check uploaded files for malicious code + * + * @todo Mime tipe detection from file content + * @return boolean Does the file contain malicious code + */ + public function waf_file_check(){ + if(!empty($_FILES)){ + foreach($_FILES as $filez){ + if ((empty($filez['errror']) || $filez['errror'] == UPLOAD_ERR_OK)) { + $filez['tmp_name'] = is_array($filez['tmp_name']) ? $filez['tmp_name'] : array($filez['tmp_name']); + foreach($filez['tmp_name'] as $file){ + if(is_string($file) && is_uploaded_file($file) && is_readable($file) && (function_exists('mime_content_type') && in_array(mime_content_type($file), $this->waf_file_mime_check))){ + $fileh = new SpbcScannerH(null, array('content' => file_get_contents($file))); + if(empty($fileh->error)){ + $fileh->process_file(); + if(!empty($fileh->verdict)){ + foreach($fileh->verdict as $severity => $result){ + $this->waf_pattern[$severity] = reset($result); + } + return true; + } + } + } + } + } + } + } + } + + // AJAX callback for detailes about latest blocked file + public static function waf_file__get_last_blocked_info() + { + check_ajax_referer('spbc_secret_nonce', 'security'); + + global $wpdb; + + $timestamp = intval(Post::get( 'timestamp' )); + + // Select only latest ones. + $result = $wpdb->get_results( + 'SELECT *' + .' FROM '. SPBC_TBL_FIREWALL_LOG + .' WHERE status = "DENY_BY_WAF_FILE" AND entry_timestamp > '.($timestamp - 2) + .' ORDER BY entry_timestamp DESC LIMIT 1;' + , OBJECT + ); + + if($result){ + $result = $result[0]; + $out = array( + 'blocked' => true, + 'warning' => __('Security by CleanTalk: File was blocked by Web Application FireWall.', 'security-malware-firewall'), + 'pattern_title' => __('Detected pattern: ', 'security-malware-firewall'), + 'pattern' => json_decode($result->pattern, true), + ); + }else + $out = array('blocked' => false); + + die(json_encode($out)); + } + + public function _die($service_id, $reason = '', $additional_reason = ''){ + + // Adding block reason + switch($reason){ + case 'DENY': $reason = __('Blacklisted', 'security-malware-firewall'); break; + case 'DENY_BY_NETWORK': $reason = __('Hazardous network', 'security-malware-firewall'); break; + case 'DENY_BY_DOS': $reason = __('Blocked by DoS prevention system', 'security-malware-firewall'); break; + case 'DENY_BY_WAF_XSS': $reason = __('Blocked by Web Application Firewall: XSS atatck detected.', 'security-malware-firewall'); break; + case 'DENY_BY_WAF_SQL': $reason = __('Blocked by Web Application Firewall: SQL-injection detected.', 'security-malware-firewall'); break; + case 'DENY_BY_WAF_EXPLOIT': $reason = __('Blocked by Web Application Firewall: Exploit detected.', 'security-malware-firewall'); break; + case 'DENY_BY_WAF_FILE': $reason = __('Blocked by Web Application Firewall: Malicious files upload.', 'security-malware-firewall'); break; + } + + $spbc_die_page = file_get_contents(SPBC_PLUGIN_DIR . 'inc/spbc_die_page.html'); + + $spbc_die_page = str_replace( "{TITLE}", __('Blocked: Security by CleanTalk', 'security-malware-firewall'), $spbc_die_page ); + $spbc_die_page = str_replace( "{REMOTE_ADDRESS}", $this->blocked_ip, $spbc_die_page ); + $spbc_die_page = str_replace( "{SERVICE_ID}", $service_id, $spbc_die_page ); + $spbc_die_page = str_replace( "{HOST}", $_SERVER['HTTP_HOST'], $spbc_die_page ); + $spbc_die_page = str_replace( "{TEST_TITLE}", (!empty($_GET['spbct_test']) ? __('This is the testing page for Security FireWall', 'security-malware-firewall') : ''), $spbc_die_page ); + $spbc_die_page = str_replace( "{REASON}", $reason, $spbc_die_page ); + $spbc_die_page = str_replace( "{GENERATED_TIMESTAMP}", time(), $spbc_die_page ); + $spbc_die_page = str_replace( "{FALSE_POSITIVE_WARNING}", __('Maybe you\'ve been blocked by a mistake. Please refresh the page (press CTRL + F5) or try again later.', 'security-malware-firewall'), $spbc_die_page ); + + if(headers_sent() === false){ + header('Expires: '.date(DATE_RFC822, mktime(0, 0, 0, 1, 1, 1971))); + header('Cache-Control: no-store, no-cache, must-revalidate'); + header('Cache-Control: post-check=0, pre-check=0', FALSE); + header('Pragma: no-cache'); + header("HTTP/1.0 403 Forbidden"); + $spbc_die_page = str_replace("{GENERATED}", "", $spbc_die_page); + }else{ + $spbc_die_page = str_replace("{GENERATED}", "