You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

365 lines
16 KiB

<?php
class AIOWPSecurity_Backup
{
var $last_backup_file_name;//Stores the name of the last backup file when execute_backup function is called
var $last_backup_file_path;
var $last_backup_file_dir_multisite;
function __construct()
{
add_action('aiowps_perform_scheduled_backup_tasks', array(&$this, 'aiowps_scheduled_backup_handler'));
add_action('aiowps_perform_db_cleanup_tasks', array(&$this, 'aiowps_scheduled_db_cleanup_handler'));
}
/**
* Add slashes, sanitize end-of-line characters (?), wrap $value in quotation marks.
* @param string $value
* @return string
*/
function sanitize_db_field($value) {
return '"' . preg_replace( "/".PHP_EOL."/", "\n", addslashes($value) ) . '"';
}
/**
* Write sql dump of $tables to backup file identified by $handle.
* @global wpdb $wpdb WordPress database abstraction object.
* @global AIO_WP_Security $aio_wp_security
* @param resource $handle
* @param array $tables
* @return boolean True, if database tables dump have been successfully written to the backup file, false otherwise.
*/
function write_db_backup_file($handle, $tables)
{
global $wpdb, $aio_wp_security;
$preamble
= "-- All In One WP Security & Firewall {$aio_wp_security->version}" . PHP_EOL
. '-- MySQL dump' . PHP_EOL
. '-- ' . date('Y-m-d H:i:s') . PHP_EOL . PHP_EOL
// When importing the backup, tell database server that our data is in UTF-8...
. "SET NAMES utf8;" . PHP_EOL
// ...and that foreign key checks should be ignored.
. "SET foreign_key_checks = 0;" . PHP_EOL . PHP_EOL
;
if ( !@fwrite( $handle, $preamble ) ) { return false; }
// Loop through each table
foreach ( $tables as $table )
{
$table_name = $table[0];
$result_create_table = $wpdb->get_row( 'SHOW CREATE TABLE `' . $table_name . '`;', ARRAY_N );
if ( empty($result_create_table) ) {
$aio_wp_security->debug_logger->log_debug(__METHOD__ . " - get_row returned NULL for table: ".$table_name, 4);
return false; // Avoid incomplete backups
}
// Drop/create table preamble
$drop_and_create = 'DROP TABLE IF EXISTS `' . $table_name . '`;' . PHP_EOL . PHP_EOL
. $result_create_table[1] . ";" . PHP_EOL . PHP_EOL
;
if ( !@fwrite( $handle, $drop_and_create ) ) { return false; }
// Dump table contents
// Fetch results as row of objects to spare memory.
$result = $wpdb->get_results( 'SELECT * FROM `' . $table_name . '`;', OBJECT );
foreach ( $result as $object_row )
{
// Convert object row to array row: this is what $wpdb->get_results()
// internally does when invoked with ARRAY_N param, but in the process
// it creates new copy of entire results array that eats a lot of memory.
$row = array_values(get_object_vars($object_row));
// Start INSERT statement
if ( !@fwrite( $handle, 'INSERT INTO `' . $table_name . '` VALUES(' ) ) { return false; }
// Loop through all fields and echo them out
foreach ( $row as $idx => $field ) {
// Echo fields separator (except for first loop)
if ( ($idx > 0) && !@fwrite( $handle, ',' ) ) { return false; }
// Echo field content (sanitized)
if ( !@fwrite( $handle, $this->sanitize_db_field($field) ) ) { return false; }
}
// Finish INSERT statement
if ( !@fwrite( $handle, ");" . PHP_EOL ) ) { return false; }
}
// Place two-empty lines after table data
if ( !@fwrite( $handle, PHP_EOL . PHP_EOL ) ) { return false; }
}
return true;
}
/**
* This function will perform a database backup
*/
function execute_backup()
{
global $wpdb, $aio_wp_security;
$is_multi_site = function_exists('is_multisite') && is_multisite();
@ini_set( 'auto_detect_line_endings', true );
@ini_set( 'memory_limit', '512M' );
if ( $is_multi_site )
{
//Let's get the current site's table prefix
$site_pref = esc_sql($wpdb->prefix);
$db_query = "SHOW TABLES LIKE '".$site_pref."%'";
$tables = $wpdb->get_results( $db_query, ARRAY_N );
}
else
{
//get all of the tables
$tables = $wpdb->get_results( 'SHOW TABLES', ARRAY_N );
}
if ( empty($tables) ) {
$aio_wp_security->debug_logger->log_debug(__METHOD__ . " - no tables found!",4);
return false;
}
//Check to see if the main "backups" directory exists - create it otherwise
$aiowps_backup_dir = WP_CONTENT_DIR.'/'.AIO_WP_SECURITY_BACKUPS_DIR_NAME;
if (!AIOWPSecurity_Utility_File::create_dir($aiowps_backup_dir))
{
$aio_wp_security->debug_logger->log_debug(__METHOD__ . " - Creation of DB backup directory failed!",4);
return false;
}
//Generate a random prefix for more secure filenames
$random_suffix = AIOWPSecurity_Utility::generate_alpha_numeric_random_string(10);
if ($is_multi_site)
{
global $current_blog;
$blog_id = $current_blog->blog_id;
//Get the current site name string for use later
$site_name = get_bloginfo('name');
$site_name = strtolower($site_name);
//make alphanumeric
$site_name = preg_replace("/[^a-z0-9_\s-]/", "", $site_name);
//Cleanup multiple instances of dashes or whitespaces
$site_name = preg_replace("/[\s-]+/", " ", $site_name);
//Convert whitespaces and underscore to dash
$site_name = preg_replace("/[\s_]/", "-", $site_name);
$file = 'database-backup-site-name-' . $site_name . '-' . current_time( 'Ymd-His' ) . '-' . $random_suffix;
//We will create a sub dir for the blog using its blog id
$dirpath = $aiowps_backup_dir . '/blogid_' . $blog_id;
//Create a subdirectory for this blog_id
if (!AIOWPSecurity_Utility_File::create_dir($dirpath))
{
$aio_wp_security->debug_logger->log_debug("Creation failed of DB backup directory for the following multisite blog ID: ".$blog_id,4);
return false;
}
}
else
{
$dirpath = $aiowps_backup_dir;
$file = 'database-backup-' . current_time( 'Ymd-His' ) . '-' . $random_suffix;
}
$handle = @fopen( $dirpath . '/' . $file . '.sql', 'w+' );
if ( $handle === false ) {
$aio_wp_security->debug_logger->log_debug("Cannot create DB backup file: {$dirpath}/{$file}.sql", 4);
return false;
}
// Delete old backup files now to avoid polluting backups directory
// with incomplete backups on websites where max execution time is too
// low for database content to be written to a file:
// https://github.com/Arsenal21/all-in-one-wordpress-security/issues/62
$this->aiowps_delete_backup_files($dirpath);
$fw_res = $this->write_db_backup_file($handle, $tables);
@fclose( $handle );
if (!$fw_res)
{
@unlink( $dirpath . '/' . $file . '.sql' );
$aio_wp_security->debug_logger->log_debug(__METHOD__ . " - Write to DB backup file failed",4);
return false;
}
//zip the file
if ( class_exists( 'ZipArchive' ) )
{
$zip = new ZipArchive();
$archive = $zip->open($dirpath . '/' . $file . '.zip', ZipArchive::CREATE);
$zip->addFile($dirpath . '/' . $file . '.sql', $file . '.sql' );
$zip->close();
//delete .sql and keep zip
@unlink( $dirpath . '/' . $file . '.sql' );
$fileext = '.zip';
} else
{
$fileext = '.sql';
}
$this->last_backup_file_name = $file . $fileext;//database-backup-YYYYMMDD-HHIISS-<random-string>.zip or database-backup-YYYYMMDD-HHIISS-<random-string>.sql
$this->last_backup_file_path = $dirpath . '/' . $file . $fileext;
if ($is_multi_site)
{
$this->last_backup_file_dir_multisite = $aiowps_backup_dir . '/blogid_' . $blog_id;
}
$this->aiowps_send_backup_email(); //Send backup file via email if applicable
return true;
}
function aiowps_send_backup_email()
{
global $aio_wp_security;
if ( $aio_wp_security->configs->get_value('aiowps_send_backup_email_address') == '1' )
{
//Get the right email address.
if ( is_email( $aio_wp_security->configs->get_value('aiowps_backup_email_address') ) )
{
$toaddress = $aio_wp_security->configs->get_value('aiowps_backup_email_address');
} else
{
$toaddress = get_site_option( 'admin_email' );
}
$to = $toaddress;
$site_title = get_bloginfo( 'name' );
$from_name = empty($site_title)?'WordPress':$site_title;
$headers = 'From: ' . $from_name . ' <' . get_option('admin_email') . '>' . PHP_EOL;
$subject = __( 'All In One WP Security - Site Database Backup', 'all-in-one-wp-security-and-firewall' ) . ' ' . date( 'l, F jS, Y \a\\t g:i a', current_time( 'timestamp' ) );
$attachment = array( $this->last_backup_file_path );
$message = __( 'Attached is your latest DB backup file for site URL', 'all-in-one-wp-security-and-firewall' ) . ' ' . get_option( 'siteurl' ) . __( ' generated on', 'all-in-one-wp-security-and-firewall' ) . ' ' . date( 'l, F jS, Y \a\\t g:i a', current_time( 'timestamp' ) );
$sendMail = wp_mail( $to, $subject, $message, $headers, $attachment );
if(FALSE === $sendMail){
$aio_wp_security->debug_logger->log_debug("Backup notification email failed to send to ".$to,4);
}
}
}
function aiowps_delete_backup_files($backups_dir)
{
global $aio_wp_security;
$files_to_keep = absint($aio_wp_security->configs->get_value('aiowps_backup_files_stored'));
if ( $files_to_keep > 0 )
{
$aio_wp_security->debug_logger->log_debug(sprintf('DB Backup - Deleting all but %d latest backup file(s) in %s directory.', $files_to_keep, $backups_dir));
$files = AIOWPSecurity_Utility_File::scan_dir_sort_date( $backups_dir );
$count = 0;
foreach ( $files as $file )
{
if ( strpos( $file, 'database-backup' ) !== false )
{
if ( $count >= $files_to_keep )
{
@unlink( $backups_dir . '/' . $file );
}
$count++;
}
}
}
else
{
$aio_wp_security->debug_logger->log_debug('DB Backup - Backup configuration prevents removal of old backup files!', 3);
}
}
function aiowps_scheduled_backup_handler()
{
global $aio_wp_security;
if($aio_wp_security->configs->get_value('aiowps_enable_automated_backups')=='1')
{
$aio_wp_security->debug_logger->log_debug_cron("DB Backup - Scheduled backup is enabled. Checking if a backup needs to be done now...");
$time_now = date_i18n( 'Y-m-d H:i:s' );
$current_time = strtotime($time_now);
$backup_frequency = $aio_wp_security->configs->get_value('aiowps_db_backup_frequency'); //Number of hours or days or months interval per backup
$interval_setting = $aio_wp_security->configs->get_value('aiowps_db_backup_interval'); //Hours/Days/Months
switch($interval_setting)
{
case '0':
$interval = 'hours';
break;
case '1':
$interval = 'days';
break;
case '2':
$interval = 'weeks';
break;
default:
// Fall back to default value, if config is corrupted for some reason.
$interval = 'weeks';
break;
}
$last_backup_time = $aio_wp_security->configs->get_value('aiowps_last_backup_time');
if ($last_backup_time != NULL)
{
$last_backup_time = strtotime($aio_wp_security->configs->get_value('aiowps_last_backup_time'));
$next_backup_time = strtotime("+".abs($backup_frequency).$interval, $last_backup_time);
if ($next_backup_time <= $current_time)
{
//It's time to do a backup
$result = $this->execute_backup();
if ($result)
{
$aio_wp_security->configs->set_value('aiowps_last_backup_time', $time_now);
$aio_wp_security->configs->save_config();
$aio_wp_security->debug_logger->log_debug_cron("DB Backup - Scheduled backup was successfully completed.");
}
else
{
$aio_wp_security->debug_logger->log_debug_cron("DB Backup - Scheduled backup operation failed!",4);
}
}
}
else
{
//Set the last backup time to now so it can trigger for the next scheduled period
$aio_wp_security->configs->set_value('aiowps_last_backup_time', $time_now);
$aio_wp_security->configs->save_config();
}
}
}
function aiowps_scheduled_db_cleanup_handler()
{
global $aio_wp_security;
$aio_wp_security->debug_logger->log_debug_cron("DB Cleanup - checking if a cleanup needs to be done now...");
//Check the events table because this can grow quite large especially when 404 events are being logged
$events_table_name = AIOWPSEC_TBL_EVENTS;
$max_rows_event_table = '5000'; //Keep a max of 5000 rows in the events table
$max_rows_event_table = apply_filters( 'aiowps_max_rows_event_table', $max_rows_event_table );
AIOWPSecurity_Utility::cleanup_table($events_table_name, $max_rows_event_table);
//Check the failed logins table
$failed_logins_table_name = AIOWPSEC_TBL_FAILED_LOGINS;
$max_rows_failed_logins_table = '5000'; //Keep a max of 5000 rows in the events table
$max_rows_failed_logins_table = apply_filters( 'aiowps_max_rows_failed_logins_table', $max_rows_failed_logins_table );
AIOWPSecurity_Utility::cleanup_table($failed_logins_table_name, $max_rows_failed_logins_table);
//Check the login activity table
$login_activity_table_name = AIOWPSEC_TBL_USER_LOGIN_ACTIVITY;
$max_rows_login_activity_table = '5000'; //Keep a max of 5000 rows in the events table
$max_rows_login_activity_table = apply_filters( 'aiowps_max_rows_login_attempts_table', $max_rows_login_activity_table );
AIOWPSecurity_Utility::cleanup_table($login_activity_table_name, $max_rows_login_activity_table);
//Check the global meta table
$global_meta_table_name = AIOWPSEC_TBL_GLOBAL_META_DATA;
$max_rows_global_meta_table = '5000'; //Keep a max of 5000 rows in this table
$max_rows_global_meta_table = apply_filters( 'aiowps_max_rows_global_meta_table', $max_rows_global_meta_table );
AIOWPSecurity_Utility::cleanup_table($global_meta_table_name, $max_rows_global_meta_table);
//Delete any expired _aiowps_captcha_string_info_xxxx transients
AIOWPSecurity_Utility::delete_expired_captcha_transients();
//Keep adding other DB cleanup tasks as they arise...
}
}