array( 'clean_audit_log_stacktraces', ), '2.0.9' => array( 'update_table_column_to_timestamp', ), '2.0.10' => array( 'delete_aiowps_temp_configs_option', ), '2.1.4' => array( 'update_tables_to_innodb', ) ); /** * Run installer function. * * @return void */ public static function run_installer() { global $wpdb; if (function_exists('is_multisite') && is_multisite() && is_main_site()) { // check if it is a network activation - if so, run the activation function for each blog id $blogids = $wpdb->get_col("SELECT blog_id FROM $wpdb->blogs"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- There doesn't seem to be a documented alternative. Possible alternative get_sites(array('fields' => 'ids', 'number' => 0) but 'number' => 0 is not documented. foreach ($blogids as $blog_id) { switch_to_blog($blog_id); AIOWPSecurity_Installer::create_db_tables(); AIOWPSecurity_Installer::migrate_db_tables(); AIOWPSecurity_Installer::check_tasks(); AIOWPSecurity_Configure_Settings::add_option_values(); AIOWPSecurity_Configure_Settings::update_aiowpsec_db_version(); restore_current_blog(); } } else { AIOWPSecurity_Installer::create_db_tables(); AIOWPSecurity_Installer::migrate_db_tables(); AIOWPSecurity_Installer::check_tasks(); AIOWPSecurity_Configure_Settings::add_option_values(); AIOWPSecurity_Configure_Settings::update_aiowpsec_db_version(); } AIOWPSecurity_Installer::create_db_backup_dir(); // Create a backup dir in the WP uploads directory. } /** * See if any database tasks need to be run, and perform them if so. * * @return void */ public static function check_tasks() { $our_version = AIO_WP_SECURITY_DB_VERSION; $db_version = get_option('aiowpsec_db_version'); // database tasks not need to be run if first time install - false check added if (false != $db_version && version_compare($our_version, $db_version, '>')) { foreach (self::$db_tasks as $version => $updates) { if (version_compare($version, $db_version, '>')) { foreach ($updates as $update) { call_user_func(array(__CLASS__, $update)); } } } } } public static function create_db_tables() { global $wpdb; if (!function_exists('dbDelta')) { require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); } if (function_exists('is_multisite') && is_multisite()) { /* * FIX for multisite table creation case: * Although each table name is defined in a constant inside the wp-security-core.php, * we need to do this step for multisite case because we need to refresh the $wpdb->prefix value * otherwise it will contain the original blog id and not the current id we need. * */ $lockout_tbl_name = $wpdb->prefix.'aiowps_login_lockdown'; $aiowps_global_meta_tbl_name = $wpdb->prefix.'aiowps_global_meta'; $aiowps_event_tbl_name = $wpdb->prefix.'aiowps_events'; $perm_block_tbl_name = $wpdb->prefix.'aiowps_permanent_block'; } else { $lockout_tbl_name = AIOWPSEC_TBL_LOGIN_LOCKOUT; $aiowps_global_meta_tbl_name = AIOWPSEC_TBL_GLOBAL_META_DATA; $aiowps_event_tbl_name = AIOWPSEC_TBL_EVENTS; $perm_block_tbl_name = AIOWPSEC_TBL_PERM_BLOCK; } $message_store_log_tbl_name = AIOWPSEC_TBL_MESSAGE_STORE; $audit_log_tbl_name = AIOWPSEC_TBL_AUDIT_LOG; $debug_log_tbl_name = AIOWPSEC_TBL_DEBUG_LOG; $logged_in_users_tbl_name = AIOWPSEC_TBL_LOGGED_IN_USERS; $charset_collate = ''; if (!empty($wpdb->charset)) { $charset_collate = "DEFAULT CHARACTER SET $wpdb->charset"; } else { $charset_collate = "DEFAULT CHARSET=utf8"; } if (!empty($wpdb->collate)) { $charset_collate .= " COLLATE $wpdb->collate"; } $ld_tbl_sql = "CREATE TABLE " . $lockout_tbl_name . " ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL, user_login VARCHAR(150) NOT NULL, lockdown_date datetime NOT NULL DEFAULT '1000-10-10 10:00:00', created INTEGER UNSIGNED, release_date datetime NOT NULL DEFAULT '1000-10-10 10:00:00', released INTEGER UNSIGNED, failed_login_ip varchar(100) NOT NULL DEFAULT '', lock_reason varchar(128) NOT NULL DEFAULT '', unlock_key varchar(128) NOT NULL DEFAULT '', is_lockout_email_sent tinyint(1) NOT NULL DEFAULT '1', backtrace_log text NOT NULL, ip_lookup_result LONGTEXT DEFAULT NULL, PRIMARY KEY (id), KEY failed_login_ip (failed_login_ip), KEY is_lockout_email_sent (is_lockout_email_sent), KEY unlock_key (unlock_key) ) ENGINE=InnoDB " . $charset_collate . ";"; dbDelta($ld_tbl_sql); $gm_tbl_sql = "CREATE TABLE " . $aiowps_global_meta_tbl_name . " ( meta_id bigint(20) NOT NULL auto_increment, date_time datetime NOT NULL default '1000-10-10 10:00:00', created INTEGER UNSIGNED, meta_key1 varchar(255) NOT NULL, meta_key2 varchar(255) NOT NULL, meta_key3 varchar(255) NOT NULL, meta_key4 varchar(255) NOT NULL, meta_key5 varchar(255) NOT NULL, meta_value1 varchar(255) NOT NULL, meta_value2 text NOT NULL, meta_value3 text NOT NULL, meta_value4 longtext NOT NULL, meta_value5 longtext NOT NULL, PRIMARY KEY (meta_id) ) ENGINE=InnoDB " . $charset_collate . ";"; dbDelta($gm_tbl_sql); $evt_tbl_sql = "CREATE TABLE " . $aiowps_event_tbl_name . " ( id bigint(20) NOT NULL AUTO_INCREMENT, event_type VARCHAR(150) NOT NULL DEFAULT '', username VARCHAR(150), user_id bigint(20), event_date datetime NOT NULL DEFAULT '1000-10-10 10:00:00', created INTEGER UNSIGNED, ip_or_host varchar(100), referer_info varchar(255), url varchar(255), country_code varchar(50), event_data longtext, PRIMARY KEY (id) ) ENGINE=InnoDB " . $charset_collate . ";"; dbDelta($evt_tbl_sql); $pb_tbl_sql = "CREATE TABLE " . $perm_block_tbl_name . " ( id bigint(20) NOT NULL AUTO_INCREMENT, blocked_ip varchar(100) NOT NULL DEFAULT '', block_reason varchar(128) NOT NULL DEFAULT '', country_origin varchar(50) NOT NULL DEFAULT '', blocked_date datetime NOT NULL DEFAULT '1000-10-10 10:00:00', created INTEGER UNSIGNED, unblock tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (id), KEY blocked_ip (blocked_ip) ) ENGINE=InnoDB " . $charset_collate . ";"; dbDelta($pb_tbl_sql); $audit_log_tbl_sql = "CREATE TABLE " . $audit_log_tbl_name . " ( id bigint(20) NOT NULL AUTO_INCREMENT, network_id bigint(20) NOT NULL DEFAULT '0', site_id bigint(20) NOT NULL DEFAULT '0', username varchar(60) NOT NULL DEFAULT '', ip VARCHAR(45) NOT NULL DEFAULT '', level varchar(25) NOT NULL DEFAULT '', event_type varchar(25) NOT NULL DEFAULT '', details text NOT NULL, stacktrace text NOT NULL, created INTEGER UNSIGNED, country_code varchar(50), PRIMARY KEY (id), INDEX username (username), INDEX ip (ip), INDEX level (level), INDEX event_type (event_type) ) ENGINE=InnoDB " . $charset_collate . ";"; dbDelta($audit_log_tbl_sql); $debug_log_tbl_sql = "CREATE TABLE " . $debug_log_tbl_name . " ( id bigint(20) NOT NULL AUTO_INCREMENT, created datetime NOT NULL DEFAULT '1000-10-10 10:00:00', logtime INTEGER UNSIGNED, level varchar(25) NOT NULL DEFAULT '', network_id bigint(20) NOT NULL DEFAULT '0', site_id bigint(20) NOT NULL DEFAULT '0', message text NOT NULL, type varchar(25) NOT NULL DEFAULT '', PRIMARY KEY (id) ) ENGINE=InnoDB " . $charset_collate . ";"; dbDelta($debug_log_tbl_sql); $liu_tbl_sql = "CREATE TABLE " . $logged_in_users_tbl_name . " ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL, username varchar(60) NOT NULL DEFAULT '', ip_address varchar(45) NOT NULL DEFAULT '', site_id bigint(20) NOT NULL, created integer UNSIGNED, expires integer UNSIGNED, PRIMARY KEY (id), UNIQUE KEY unique_user_id (user_id), INDEX created (created), INDEX expires (expires), INDEX user_id (user_id), INDEX site_id (site_id) ) ENGINE=InnoDB " . $charset_collate . ";"; dbDelta($liu_tbl_sql); $message_store_log_tbl_sql = "CREATE TABLE " . $message_store_log_tbl_name . " ( id bigint(20) NOT NULL AUTO_INCREMENT, message_key text NOT NULL, message_value text NOT NULL, created INTEGER UNSIGNED, PRIMARY KEY (id) ) ENGINE=InnoDB " . $charset_collate . ";"; dbDelta($message_store_log_tbl_sql); } /** * This function will handle any database table migrations * * @return void */ public static function migrate_db_tables() { global $wpdb; if (function_exists('is_multisite') && is_multisite()) { /* * FIX for multisite table creation case: * Although each table name is defined in a constant inside the wp-security-core.php, * we need to do this step for multisite case because we need to refresh the $wpdb->prefix value * otherwise it will contain the original blog id and not the current id we need. * */ $failed_login_tbl_name = $wpdb->prefix.'aiowps_failed_logins'; $login_activity_tbl_name = $wpdb->prefix.'aiowps_login_activity'; } else { $failed_login_tbl_name = AIOWPSEC_TBL_FAILED_LOGINS; $login_activity_tbl_name = AIOWPSEC_TBL_USER_LOGIN_ACTIVITY; } $audit_log_tbl_name = AIOWPSEC_TBL_AUDIT_LOG; $network_id = get_current_network_id(); $site_id = get_current_blog_id(); $table_exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->esc_like($failed_login_tbl_name))); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- There doesn't seem to be an alternative. if ($table_exists) { $import_details = array( 'failed_login' => array( 'imported' => true, ) ); $import_details = wp_json_encode($import_details, true); $table_migration_details = array( 'table_migration' => array( 'success' => true, 'from_table' => $failed_login_tbl_name, 'to_table' => $audit_log_tbl_name ) ); if (false === $wpdb->query($wpdb->prepare("INSERT INTO $audit_log_tbl_name (network_id, site_id, username, ip, level, event_type, details, stacktrace, created) SELECT %d AS network_id, %d AS site_id, fl.user_login AS username, fl.login_attempt_ip AS ip, 'warning' AS level, 'Failed login' AS event_type, %s AS details, '' AS stacktrace, UNIX_TIMESTAMP(fl.failed_login_date) AS created FROM $failed_login_tbl_name fl", $network_id, $site_id, $import_details))) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- We can't use %i because our plugin supports wordpress < 6.2. $table_migration_details['table_migration']['success'] = false; do_action('aiowps_record_event', 'table_migration', $table_migration_details, 'error'); } else { do_action('aiowps_record_event', 'table_migration', $table_migration_details, 'info'); $wpdb->query("DROP TABLE IF EXISTS `$failed_login_tbl_name`"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- We can't use %i because our plugin supports wordpress < 6.2. } } $table_exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->esc_like($login_activity_tbl_name))); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- There doesn't seem to be an alternative. if ($table_exists) { $wpdb->query("DROP TABLE IF EXISTS `$login_activity_tbl_name`"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- We can't use %i because our plugin supports wordpress < 6.2. } } /** * This function will run SQL to clean sensitive information from the audit log table stacktrace * * @return void */ public static function clean_audit_log_stacktraces() { global $wpdb; $wpdb->query("UPDATE ".AIOWPSEC_TBL_AUDIT_LOG." SET stacktrace = '' WHERE event_type = 'failed_login' OR event_type = 'successful_login' OR event_type = 'user_registration'"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared -- We can't use %i because our plugin supports wordpress < 6.2. } /** * This function will update the table datetime column to timestamp with backward compability * * @return void */ public static function update_table_column_to_timestamp() { $db_version = get_option('aiowpsec_db_version', '1.0'); if (version_compare('2.0.8', $db_version, '>')) { self::update_column_to_timestamp(AIOWPSEC_TBL_EVENTS, 'event_date', 'created'); self::update_column_to_timestamp(AIOWPSEC_TBL_LOGIN_LOCKOUT, 'lockdown_date', 'created'); self::update_column_to_timestamp(AIOWPSEC_TBL_LOGIN_LOCKOUT, 'release_date', 'released'); } if (version_compare('2.0.9', $db_version, '>')) { self::update_column_to_timestamp(AIOWPSEC_TBL_PERM_BLOCK, 'blocked_date', 'created'); self::update_column_to_timestamp(AIOWPSEC_TBL_GLOBAL_META_DATA, 'date_time', 'created'); self::update_column_to_timestamp(AIOWPSEC_TBL_DEBUG_LOG, 'created', 'logtime'); } } /** * Update the table column to UTC timestamp not depending on the timezone of the user or server settings * * @global wpdb $wpdb * * @param string $table_name * @param string $field_datetime * @param string $field_timestamp * * @return boolean - returns the rows updated or not */ public static function update_column_to_timestamp($table_name, $field_datetime, $field_timestamp) { global $wpdb; //MySQL UNIX_TIMESTAMP will convert datetime based on local timezone not UTC $offset = $wpdb->get_var("SELECT TIMESTAMPDIFF(SECOND, NOW(), UTC_TIMESTAMP())"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- There doesn't seem to be an alternative. if (AIOWPSEC_TBL_PERM_BLOCK == $table_name || AIOWPSEC_TBL_GLOBAL_META_DATA == $table_name || AIOWPSEC_TBL_DEBUG_LOG == $table_name) { //User local settings date time saved offset timezone needs to removed for UTC correct value $offset += AIOWPSecurity_Utility::get_wp_timezone()->getOffset(new DateTime('now', new DateTimeZone('UTC'))); } if (function_exists('is_multisite') && is_multisite() && AIOWPSEC_TBL_EVENTS == $table_name) { $table_name = $wpdb->prefix.'aiowps_events'; } elseif (function_exists('is_multisite') && is_multisite() && AIOWPSEC_TBL_LOGIN_LOCKOUT == $table_name) { $table_name = $wpdb->prefix.'aiowps_login_lockdown'; } elseif (function_exists('is_multisite') && is_multisite() && AIOWPSEC_TBL_PERM_BLOCK == $table_name) { $table_name = $wpdb->prefix.'aiowps_permanent_block'; } elseif (function_exists('is_multisite') && is_multisite() && AIOWPSEC_TBL_GLOBAL_META_DATA == $table_name) { $table_name = $wpdb->prefix.'aiowps_global_meta'; } elseif (function_exists('is_multisite') && is_multisite() && AIOWPSEC_TBL_DEBUG_LOG == $table_name) { $table_name = $wpdb->prefix.'aiowps_debug_log'; } //offset to make sure UTC timestamp updated $wpdb->query($wpdb->prepare("UPDATE $table_name SET $field_timestamp = (UNIX_TIMESTAMP($field_datetime) - %d)", $offset)); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- We can't use %i because our plugin supports wordpress < 6.2. } /** * Deletes the aiowps_temp_configs option if present. * * @return void */ public static function delete_aiowps_temp_configs_option() { delete_option('aiowps_temp_configs'); } /** * Alters all of the AIOS tables to use InnoDB. * * @global wpdb $wpdb * * @return void */ public static function update_tables_to_innodb() { global $wpdb; if (function_exists('is_multisite') && is_multisite()) { /* * FIX for multisite table creation case: * Although each table name is defined in a constant inside the wp-security-core.php, * we need to do this step for multisite case because we need to refresh the $wpdb->prefix value * otherwise it will contain the original blog id and not the current id we need. * */ $lockout_tbl_name = $wpdb->prefix.'aiowps_login_lockdown'; $aiowps_global_meta_tbl_name = $wpdb->prefix.'aiowps_global_meta'; $aiowps_event_tbl_name = $wpdb->prefix.'aiowps_events'; $perm_block_tbl_name = $wpdb->prefix.'aiowps_permanent_block'; } else { $lockout_tbl_name = AIOWPSEC_TBL_LOGIN_LOCKOUT; $aiowps_global_meta_tbl_name = AIOWPSEC_TBL_GLOBAL_META_DATA; $aiowps_event_tbl_name = AIOWPSEC_TBL_EVENTS; $perm_block_tbl_name = AIOWPSEC_TBL_PERM_BLOCK; } $message_store_log_tbl_name = AIOWPSEC_TBL_MESSAGE_STORE; $audit_log_tbl_name = AIOWPSEC_TBL_AUDIT_LOG; $debug_log_tbl_name = AIOWPSEC_TBL_DEBUG_LOG; $logged_in_users_tbl_name = AIOWPSEC_TBL_LOGGED_IN_USERS; // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery -- No alternative. $wpdb->query('ALTER TABLE ' . $lockout_tbl_name . ' ENGINE=InnoDB'); $wpdb->query('ALTER TABLE ' . $aiowps_global_meta_tbl_name . ' ENGINE=InnoDB'); $wpdb->query('ALTER TABLE ' . $aiowps_event_tbl_name . ' ENGINE=InnoDB'); $wpdb->query('ALTER TABLE ' . $perm_block_tbl_name . ' ENGINE=InnoDB'); $wpdb->query('ALTER TABLE ' . $audit_log_tbl_name . ' ENGINE=InnoDB'); $wpdb->query('ALTER TABLE ' . $debug_log_tbl_name . ' ENGINE=InnoDB'); $wpdb->query('ALTER TABLE ' . $logged_in_users_tbl_name . ' ENGINE=InnoDB'); $wpdb->query('ALTER TABLE ' . $message_store_log_tbl_name . ' ENGINE=InnoDB'); // phpcs:enable WordPress.DB.DirectDatabaseQuery } public static function create_db_backup_dir() { global $aio_wp_security; // phpcs:disable WordPress.WP.AlternativeFunctions -- WP_Filesystem is not appropriate here. //Create our folder in the "wp-content" directory $aiowps_dir = WP_CONTENT_DIR . '/' . AIO_WP_SECURITY_BACKUPS_DIR_NAME; if (!is_dir($aiowps_dir) && is_writable(WP_CONTENT_DIR)) { mkdir($aiowps_dir, 0755, true); //Let's also create an empty index.html file in this folder $index_file = $aiowps_dir . '/index.html'; $handle = fopen($index_file, 'w'); //or die('Cannot open file: '.$index_file); fclose($handle); } $server_type = AIOWPSecurity_Utility::get_server_type(); //Only create .htaccess if server is the right type if ('apache' == $server_type || 'litespeed' == $server_type) { $file = $aiowps_dir . '/.htaccess'; if (!file_exists($file)) { //Create an .htacces file //Write some rules which will only allow people originating from wp admin page to download the DB backup $rules = ''; $rules .= 'order deny,allow' . PHP_EOL; $rules .= 'deny from all' . PHP_EOL; $write_result = file_put_contents($file, $rules); if (false === $write_result) { $aio_wp_security->debug_logger->log_debug("Creation of .htaccess file in " . AIO_WP_SECURITY_BACKUPS_DIR_NAME . " directory failed!", 4); } } } // phpcs:enable WordPress.WP.AlternativeFunctions -- WP_Filesystem is not appropriate here. } /** * Setup AIOS cron tasks. * Handles both single and multi-site (NW activation) cases. * * @global type $wpdb * * @return Void */ public static function set_cron_tasks_upon_activation() { require_once(__DIR__.'/wp-security-cronjob-handler.php'); // It is required because we are going to schedule a 15-minute cron event upon activation. add_filter('cron_schedules', array('AIOWPSecurity_Cronjob_Handler', 'cron_schedules')); if (is_multisite() && is_main_site()) { global $wpdb; // check if it is a network activation $blogids = $wpdb->get_col("SELECT blog_id FROM $wpdb->blogs"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- There doesn't seem to be a documented alternative. Possible alternative get_sites(array('fields' => 'ids', 'number' => 0) but 'number' => 0 is not documented. foreach ($blogids as $blog_id) { switch_to_blog($blog_id); AIOWPSecurity_Installer::schedule_cron_events(); do_action('aiowps_activation_complete'); restore_current_blog(); } } else { AIOWPSecurity_Installer::schedule_cron_events(); do_action('aiowps_activation_complete'); } } /** * Helper function for scheduling AIOS cron events. * * @return Void */ public static function schedule_cron_events() { if (!wp_next_scheduled('aios_15_minutes_cron_event')) { wp_schedule_event(time(), 'aios-every-15-minutes', 'aios_15_minutes_cron_event'); //schedule a 15 minutes cron event } if (!wp_next_scheduled('aiowps_hourly_cron_event')) { wp_schedule_event(time(), 'hourly', 'aiowps_hourly_cron_event'); //schedule an hourly cron event } if (!wp_next_scheduled('aiowps_daily_cron_event')) { wp_schedule_event(time(), 'daily', 'aiowps_daily_cron_event'); //schedule an daily cron event } if (!wp_next_scheduled('aiowps_weekly_cron_event')) { wp_schedule_event(time(), 'weekly', 'aiowps_weekly_cron_event'); //schedule an daily cron event } } }