db__table__data = $data_table ?: null; $this->db__table__logs = $log_table ?: null; foreach ($params as $param_name => $param) { $this->$param_name = isset($this->$param_name) ? $param : false; } $this->debug = (bool)Get::get('debug'); } /** * @param $ips */ public function ipAppendAdditional(&$ips) { $this->real_ip = isset($ips['real']) ? $ips['real'] : null; if (Get::get('sfw_test_ip')) { if (Helper::ipValidate(Get::get('sfw_test_ip')) !== false) { $ips['sfw_test'] = Get::get('sfw_test_ip'); $this->test_ip = Get::get('sfw_test_ip'); $this->test = true; } } } /** * Use this method to execute main logic of the module. * * @return array Array of the check results */ public function check() { global $apbct; $results = array(); $status = 0; // Skip by cookie foreach ($this->ip_array as $current_ip) { if (strpos(Cookie::get('ct_sfw_pass_key'), md5($current_ip . $this->api_key)) === 0) { if (Cookie::get('ct_sfw_passed')) { if ( ! headers_sent()) { Cookie::set( 'ct_sfw_passed', '0', time() + 86400 * 3, '/', '', null, true ); } else { $results[] = array( 'ip' => $current_ip, 'is_personal' => false, 'status' => 'PASS_SFW__BY_COOKIE' ); } // Do logging an one passed request $this->updateLog($current_ip, 'PASS_SFW'); if ($this->sfw_counter) { $apbct->data['admin_bar__sfw_counter']['all']++; $apbct->saveData(); } } if (strlen(Cookie::get('ct_sfw_pass_key')) > 32) { $status = substr(Cookie::get('ct_sfw_pass_key'), -1); } if ($status) { $results[] = array( 'ip' => $current_ip, 'is_personal' => false, 'status' => 'PASS_SFW__BY_WHITELIST' ); } return $results; } } // Common check foreach ($this->ip_array as $_origin => $current_ip) { $current_ip_v4 = sprintf("%u", ip2long($current_ip)); for ($needles = array(), $m = 6; $m <= 32; $m++) { $mask = str_repeat('1', $m); $mask = str_pad($mask, 32, '0'); $needles[] = sprintf("%u", bindec($mask & base_convert($current_ip_v4, 10, 2))); } $needles = array_unique($needles); $db_results = $this->db->fetchAll( "SELECT network, mask, status, source FROM " . $this->db__table__data . " WHERE network IN (" . implode(',', $needles) . ") AND network = " . $current_ip_v4 . " & mask AND " . rand(1, 100000) . " ORDER BY mask DESC LIMIT 1" ); if ( ! empty($db_results)) { foreach ($db_results as $db_result) { $result_entry = array( 'ip' => $current_ip, 'network' => Helper::ipLong2ip($db_result['network']) . '/' . Helper::ipMaskLongToNumber((int)$db_result['mask']), 'is_personal' => $db_result['source'], ); if ((int)$db_result['status'] === 1) { $result_entry['status'] = 'PASS_SFW__BY_WHITELIST'; if ( $_origin === 'sfw_test' ) { $result_entry['status'] = 'PASS_SFW__BY_STATUS'; } break; } if ((int)$db_result['status'] === 0) { $this->blocked_ips[] = Helper::ipLong2ip($db_result['network']); $result_entry['status'] = 'DENY_SFW'; } } } else { $result_entry = array( 'ip' => $current_ip, 'is_personal' => null, 'status' => 'PASS_SFW', ); } $results[] = $result_entry; } return $results; } /** * Add entry to SFW log. * Writes to database. * * @param string $ip * @param $status * @param string $network * @param string $source */ public function updateLog($ip, $status, $network = 'NULL', $source = 'NULL') { $id = md5($ip . $this->module_name); $time = time(); $this->db->prepare( "INSERT INTO " . $this->db__table__logs . " SET id = '$id', ip = '$ip', status = '$status', all_entries = 1, blocked_entries = " . (strpos($status, 'DENY') !== false ? 1 : 0) . ", entries_timestamp = '" . $time . "', ua_name = %s, source = $source, network = %s, first_url = %s, last_url = %s ON DUPLICATE KEY UPDATE status = '$status', source = $source, all_entries = all_entries + 1, blocked_entries = blocked_entries" . (strpos($status, 'DENY') !== false ? ' + 1' : '') . ", entries_timestamp = '" . $time . "', ua_name = %s, network = %s, last_url = %s", array( Server::get('HTTP_USER_AGENT'), $network, substr(Server::get('HTTP_HOST') . Server::get('REQUEST_URI'), 0, 100), substr(Server::get('HTTP_HOST') . Server::get('REQUEST_URI'), 0, 100), Server::get('HTTP_USER_AGENT'), $network, substr(Server::get('HTTP_HOST') . Server::get('REQUEST_URI'), 0, 100), ) ); $this->db->execute($this->db->getQuery()); } public function actionsForDenied($result) { global $apbct; if ($this->sfw_counter) { $apbct->data['admin_bar__sfw_counter']['blocked']++; $apbct->saveData(); } } public function actionsForPassed($result) { if ($this->data__cookies_type === 'native' && ! headers_sent()) { $status = $result['status'] === 'PASS_SFW__BY_WHITELIST' ? '1' : '0'; $cookie_val = md5($result['ip'] . $this->api_key) . $status; Cookie::setNativeCookie( 'ct_sfw_pass_key', $cookie_val, time() + 86400 * 30, '/' ); } } /** * Shows DIE page. * Stops script executing. * * @param $result */ public function diePage($result) { global $apbct; // Statistics if ( ! empty($this->blocked_ips)) { reset($this->blocked_ips); $apbct->stats['last_sfw_block']['time'] = time(); $apbct->stats['last_sfw_block']['ip'] = $result['ip']; $apbct->save('stats'); } // File exists? if (file_exists(CLEANTALK_PLUGIN_DIR . "lib/Cleantalk/ApbctWP/Firewall/die_page_sfw.html")) { $this->sfw_die_page = file_get_contents( CLEANTALK_PLUGIN_DIR . "lib/Cleantalk/ApbctWP/Firewall/die_page_sfw.html" ); $js_url = APBCT_URL_PATH . '/js/apbct-public--functions.min.js?' . APBCT_VERSION; $net_count = $apbct->stats['sfw']['entries']; $status = $result['status'] === 'PASS_SFW__BY_WHITELIST' ? '1' : '0'; $cookie_val = md5($result['ip'] . $this->api_key) . $status; $block_message = sprintf( esc_html__('SpamFireWall is checking your browser and IP %s for spam bots', 'cleantalk-spam-protect'), '' . $result['ip'] . '' ); // Translation $replaces = array( '{SFW_DIE_NOTICE_IP}' => $block_message, '{SFW_DIE_MAKE_SURE_JS_ENABLED}' => __( 'To continue working with the web site, please make sure that you have enabled JavaScript.', 'cleantalk-spam-protect' ), '{SFW_DIE_CLICK_TO_PASS}' => __( 'Please click the link below to pass the protection,', 'cleantalk-spam-protect' ), '{SFW_DIE_YOU_WILL_BE_REDIRECTED}' => sprintf( __( 'Or you will be automatically redirected to the requested page after %d seconds.', 'cleantalk-spam-protect' ), 3 ), '{CLEANTALK_TITLE}' => ($this->test ? __( 'This is the testing page for SpamFireWall', 'cleantalk-spam-protect' ) : ''), '{REMOTE_ADDRESS}' => $result['ip'], '{SERVICE_ID}' => $apbct->data['service_id'] . ', ' . $net_count, '{HOST}' => get_home_url() . ', ' . APBCT_VERSION, '{GENERATED}' => '

The page was generated at ' . date('D, d M Y H:i:s') . '

', '{REQUEST_URI}' => Server::get('REQUEST_URI'), // Cookie '{COOKIE_PREFIX}' => '', '{COOKIE_DOMAIN}' => $this->cookie_domain, '{COOKIE_SFW}' => $this->test ? $this->test_ip : $cookie_val, '{COOKIE_ANTICRAWLER}' => hash('sha256', $apbct->api_key . $apbct->data['salt']), // Test '{TEST_TITLE}' => '', '{REAL_IP__HEADER}' => '', '{TEST_IP__HEADER}' => '', '{TEST_IP}' => '', '{REAL_IP}' => '', '{SCRIPT_URL}' => $js_url, // Message about IP status '{MESSAGE_IP_STATUS}' => '', ); /** * Message about IP status */ if (! empty(Get::get('sfw_test_ip'))) { $message_ip_status = __( 'IP in the common blacklist', 'cleantalk-spam-protect' ); $message_ip_status_color = 'red'; if ($result['status'] === 'PASS_SFW__BY_STATUS') { $message_ip_status = __( 'IP in the common whitelist', 'cleantalk-spam-protect' ); $message_ip_status_color = 'green'; } $replaces['{MESSAGE_IP_STATUS}'] = "

$message_ip_status

"; } // Test if ($this->test) { $replaces['{TEST_TITLE}'] = __( 'This is the testing page for SpamFireWall', 'cleantalk-spam-protect' ); $replaces['{REAL_IP__HEADER}'] = 'Real IP:'; $replaces['{TEST_IP__HEADER}'] = 'Test IP:'; $replaces['{TEST_IP}'] = $this->test_ip; $replaces['{REAL_IP}'] = $this->real_ip; } // Debug if ($this->debug) { $debug = '

Headers

' . var_export(apache_request_headers(), true) . '

REMOTE_ADDR

' . Server::get('REMOTE_ADDR') . '

SERVER_ADDR

' . Server::get('REMOTE_ADDR') . '

IP_ARRAY

' . var_export($this->ip_array, true) . '

ADDITIONAL

' . var_export($this->debug_data, true); } $replaces['{DEBUG}'] = isset($debug) ? $debug : ''; foreach ($replaces as $place_holder => $replace) { $this->sfw_die_page = str_replace($place_holder, $replace, $this->sfw_die_page); } } add_action('init', array($this, 'printDiePage')); } public function printDiePage() { global $apbct; parent::diePage(''); $localize_js = array( '_ajax_nonce' => wp_create_nonce('ct_secret_stuff'), '_rest_nonce' => wp_create_nonce('wp_rest'), '_ajax_url' => admin_url('admin-ajax.php', 'relative'), '_rest_url' => esc_url(get_rest_url()), 'data__cookies_type' => $apbct->data['cookies_type'], 'data__ajax_type' => $apbct->data['ajax_type'], 'sfw__random_get' => $apbct->settings['sfw__random_get'] === '1' || ($apbct->settings['sfw__random_get'] === '-1' && apbct_is_cache_plugins_exists()), 'cookiePrefix' => apbct__get_cookie_prefix(), ); $js_jquery_url = includes_url() . 'js/jquery/jquery.min.js'; $replaces = array( '{JQUERY_SCRIPT_URL}' => $js_jquery_url, '{LOCALIZE_SCRIPT}' => 'var ctPublicFunctions = ' . json_encode($localize_js), ); foreach ($replaces as $place_holder => $replace) { $this->sfw_die_page = str_replace($place_holder, $replace, $this->sfw_die_page); } http_response_code(403); // File exists? if (file_exists(CLEANTALK_PLUGIN_DIR . "lib/Cleantalk/ApbctWP/Firewall/die_page_sfw.html")) { die($this->sfw_die_page); } die("IP BLACKLISTED. Blocked by SFW " . $apbct->stats['last_sfw_block']['ip']); } /** * Sends and wipe SFW log * * @param $db * @param $log_table * @param string $ct_key Access key * @param bool $_use_delete_command Determs whether use DELETE or TRUNCATE to delete the logs table data * * @return array|bool array('error' => STRING) */ public static function sendLog($db, $log_table, $ct_key, $_use_delete_command) { //Getting logs $query = "SELECT * FROM $log_table ORDER BY entries_timestamp DESC LIMIT 0," . APBCT_SFW_SEND_LOGS_LIMIT . ";"; $db->fetchAll($query); if (count($db->result)) { $logs = $db->result; //Compile logs $ids_to_delete = array(); $data = array(); foreach ($logs as $_key => &$value) { $ids_to_delete[] = $value['id']; // Converting statuses to API format $value['status'] = $value['status'] === 'DENY_ANTICRAWLER' ? 'BOT_PROTECTION' : $value['status']; $value['status'] = $value['status'] === 'PASS_ANTICRAWLER' ? 'BOT_PROTECTION' : $value['status']; $value['status'] = $value['status'] === 'DENY_ANTICRAWLER_UA' ? 'BOT_PROTECTION' : $value['status']; $value['status'] = $value['status'] === 'PASS_ANTICRAWLER_UA' ? 'BOT_PROTECTION' : $value['status']; $value['status'] = $value['status'] === 'DENY_ANTIFLOOD' ? 'FLOOD_PROTECTION' : $value['status']; $value['status'] = $value['status'] === 'PASS_ANTIFLOOD' ? 'FLOOD_PROTECTION' : $value['status']; $value['status'] = $value['status'] === 'DENY_ANTIFLOOD_UA' ? 'FLOOD_PROTECTION' : $value['status']; $value['status'] = $value['status'] === 'PASS_ANTIFLOOD_UA' ? 'FLOOD_PROTECTION' : $value['status']; $value['status'] = $value['status'] === 'PASS_SFW__BY_COOKIE' ? 'DB_MATCH' : $value['status']; $value['status'] = $value['status'] === 'PASS_SFW' ? 'DB_MATCH' : $value['status']; $value['status'] = $value['status'] === 'DENY_SFW' ? 'DB_MATCH' : $value['status']; $value['status'] = $value['source'] ? 'PERSONAL_LIST_MATCH' : $value['status']; $additional = array(); if ($value['network']) { $additional['nd'] = $value['network']; } if ($value['first_url']) { $additional['fu'] = $value['first_url']; } if ($value['last_url']) { $additional['lu'] = $value['last_url']; } $additional = $additional ?: 'EMPTY_ASSOCIATIVE_ARRAY'; $data[] = array( trim($value['ip']), // IP $value['blocked_entries'], // Count showing of block pages $value['all_entries'] - $value['blocked_entries'], // Count passed requests after block pages $value['entries_timestamp'], // Last timestamp $value['status'], // Status $value['ua_name'], // User-Agent name $value['ua_id'], // User-Agent ID $additional // Network, first URL, last URL ); } unset($value); //Sending the request $result = API::methodSfwLogs($ct_key, $data); //Checking answer and deleting all lines from the table if (empty($result['error'])) { if ($result['rows'] == count($data)) { $db->execute("BEGIN;"); $db->execute("DELETE FROM $log_table WHERE id IN ( '" . implode('\',\'', $ids_to_delete) . "' );"); $db->execute("COMMIT;"); return $result; } return array('error' => 'SENT_AND_RECEIVED_LOGS_COUNT_DOESNT_MACH'); } else { return $result; } } else { return array('rows' => 0); } } public static function directUpdateGetBlackLists($api_key) { // Getting remote file name $result = API::methodGet2sBlacklistsDb($api_key, null, '3_1'); if ( empty($result['error']) ) { return array( 'blacklist' => isset($result['data']) ? $result['data'] : null, 'useragents' => isset($result['data_user_agents']) ? $result['data_user_agents'] : null, 'bl_count' => isset($result['networks_count']) ? $result['networks_count'] : null, 'ua_count' => isset($result['ua_count']) ? $result['ua_count'] : null, ); } return $result; } public static function directUpdate($db, $db__table__data, $blacklists) { if ( ! is_array($blacklists) ) { return array('error' => 'BlackLists is not an array.'); } for ( $count_result = 0; current($blacklists) !== false; ) { $query = "INSERT INTO " . $db__table__data . " (network, mask, status) VALUES "; for ( $i = 0, $values = array(); APBCT_WRITE_LIMIT !== $i && current($blacklists) !== false; $i++, $count_result++, next($blacklists) ) { $entry = current($blacklists); if ( empty($entry) ) { continue; } // Cast result to int $ip = preg_replace('/[^\d]*/', '', $entry[0]); $mask = preg_replace('/[^\d]*/', '', $entry[1]); $private = isset($entry[2]) ? $entry[2] : 0; $values[] = '(' . $ip . ',' . $mask . ',' . $private . ')'; } if ( ! empty($values) ) { $query .= implode(',', $values) . ';'; $result = $db->execute($query); if ( $result === false ) { return array( 'error' => $db->getLastError() ); } } } return $count_result; } /** * Updates SFW local base * * @param $db * @param $db__table__data * @param null|string $file_url File URL with SFW data. * * @return array|int array('error' => STRING) */ public static function updateWriteToDb($db, $db__table__data, $file_url = null) { $file_content = file_get_contents($file_url); if (function_exists('gzdecode')) { $unzipped_content = gzdecode($file_content); if ($unzipped_content !== false) { $data = Helper::bufferParseCsv($unzipped_content); if (empty($data['errors'])) { reset($data); for ($count_result = 0; current($data) !== false;) { $query = "INSERT INTO " . $db__table__data . " (network, mask, status, source) VALUES "; for ( $i = 0, $values = array(); APBCT_WRITE_LIMIT !== $i && current($data) !== false; $i++, $count_result++, next($data) ) { $entry = current($data); if (empty($entry) || empty($entry[0]) || empty($entry[1])) { continue; } // Cast result to int $ip = preg_replace('/[^\d]*/', '', $entry[0]); $mask = preg_replace('/[^\d]*/', '', $entry[1]); $status = isset($entry[2]) ? $entry[2] : 0; $source = isset($entry[3]) ? (int)$entry[3] : 'NULL'; $values[] = "($ip, $mask, $status, $source)"; } if ( ! empty($values)) { $query .= implode(',', $values) . ';'; if ( ! $db->execute($query) ) { return array( 'error' => 'WRITE ERROR: FAILED TO INSERT DATA: ' . $db__table__data . ' DB Error: ' . $db->getLastError() ); } if (file_exists($file_url)) { unlink($file_url); } } } return $count_result; } else { return $data; } } else { return array('error' => 'Can not unpack datafile'); } } else { return array('error' => 'Function gzdecode not exists. Please update your PHP at least to version 5.4 '); } } public static function updateWriteToDbExclusions($db, $db__table__data, $exclusions = array()) { global $wpdb, $apbct; $query = 'INSERT INTO `' . $db__table__data . '` (network, mask, status) VALUES '; //Exclusion for servers IP (SERVER_ADDR) if (Server::get('HTTP_HOST')) { // Do not add exceptions for local hosts if ( ! in_array(Server::getDomain(), array('lc', 'loc', 'lh'))) { $exclusions[] = Helper::dnsResolve(Server::get('HTTP_HOST')); $exclusions[] = '127.0.0.1'; // And delete all 127.0.0.1 entries for local hosts } else { $wpdb->query('DELETE FROM ' . $db__table__data . ' WHERE network = ' . ip2long('127.0.0.1') . ';'); if ($wpdb->rows_affected > 0) { $apbct->fw_stats['expected_networks_count'] -= $wpdb->rows_affected; $apbct->save('fw_stats'); } } } foreach ($exclusions as $exclusion) { if (Helper::ipValidate($exclusion) && sprintf('%u', ip2long($exclusion))) { $query .= '(' . sprintf('%u', ip2long($exclusion)) . ', ' . sprintf( '%u', bindec(str_repeat('1', 32)) ) . ', 1),'; } } if ($exclusions) { $sql_result = $db->execute(substr($query, 0, -1) . ';'); return $sql_result === false ? array('error' => 'COULD_NOT_WRITE_TO_DB 4: ' . $db->getLastError()) : count($exclusions); } return 0; } /** * Creating a temporary updating table * * @param DB $db database handler * @param array|string $table_names Array with table names to create * * @return bool|array */ public static function createTempTables($db, $table_names) { // Cast it to array for simple input $table_names = (array)$table_names; foreach ($table_names as $table_name) { $table_name__temp = $table_name . '_temp'; if ( ! $db->execute('CREATE TABLE IF NOT EXISTS `' . $table_name__temp . '` LIKE `' . $table_name . '`;')) { return array('error' => 'CREATE TEMP TABLES: COULD NOT CREATE ' . $table_name__temp . ' DB Error: ' . $db->getLastError() ); } if ( ! $db->execute('TRUNCATE TABLE `' . $table_name__temp . '`;')) { return array('error' => 'CREATE TEMP TABLES: COULD NOT TRUNCATE' . $table_name__temp . ' DB Error: ' . $db->getLastError() ); } } return true; } /** * Delete tables with given names if they exists * * @param DB $db * @param array|string $table_names Array with table names to delete * * @return bool|array */ public static function dataTablesDelete($db, $table_names) { // Cast it to array for simple input $table_names = (array)$table_names; foreach ($table_names as $table_name) { if ( $db->isTableExists($table_name) && ! $db->execute('DROP TABLE ' . $table_name . ';') ) { return array( 'error' => 'DELETE TABLE: FAILED TO DROP: ' . $table_name . ' DB Error: ' . $db->getLastError() ); } } return true; } /** * Renaming a temporary updating table into production table name * * @param DB $db database handler * @param array|string $table_names Array with table names to rename * * @return bool|array */ public static function renameDataTablesFromTempToMain($db, $table_names) { // Cast it to array for simple input $table_names = (array)$table_names; foreach ($table_names as $table_name) { $table_name__temp = $table_name . '_temp'; if ( ! $db->isTableExists($table_name__temp)) { return array('error' => 'RENAME TABLE: TEMPORARY TABLE IS NOT EXISTS: ' . $table_name__temp); } if ($db->isTableExists($table_name)) { return array('error' => 'RENAME TABLE: MAIN TABLE IS STILL EXISTS: ' . $table_name); } if ( ! $db->execute('ALTER TABLE `' . $table_name__temp . '` RENAME `' . $table_name . '`;') ) { return array( 'error' => 'RENAME TABLE: FAILED TO RENAME: ' . $table_name . ' DB Error: ' . $db->getLastError() ); } } return true; } }