Version Description
17/Feb/2016 =
FEATURE: Automatic backups can take place before updates commissioned via WordPress.Com/JetPack remote site management (requires a not-yet-released version of JetPack - all current releases are insufficient, so please don't file reports about this yet)
FIX: Fixed a further logic error in the advanced backup retention options, potentially relevant if you had more than one extra rule, affecting the oldest backups
TWEAK: Resolve issue on some sites with in-dashboard downloads being interfered with by other site components
TWEAK: Auto-backups now hook to a newly-added more suitable action, on WP 4.4+ (https://core.trac.wordpress.org/ticket/30441)
TWEAK: Make WebDAV library not use a language construct that's not supported by HHVM
TWEAK: Change options in the "Backup Now" dialog as main settings are changed
TWEAK: Show the file options in the "Backup Now" dialog if/when alerting the user that they've chosen inconsistent options
TWEAK: When pruning old backups, save the history to the database at least every 10 seconds, to help with sites with slow network communications and short PHP timeouts
Release Info
Developer | DavidAnderson |
Plugin | UpdraftPlus WordPress Backup Plugin |
Version | 1.11.27 |
Comparing to | |
See all releases |
Code changes from version 1.11.26 to 1.11.27
- admin.php +28 -20
- backup.php +16 -1
- class-updraftplus.php +61 -100
- css/admin.css +0 -4
- includes/class-udrpc.php +817 -0
- includes/updraft-admin-ui.js +133 -15
- readme.txt +13 -2
- restorer.php +1 -1
- updraftplus.php +1 -1
@@ -476,6 +476,7 @@ class UpdraftPlus_Admin {
|
|
476 |
'servererrorcode' => __('The web server returned an error code (try again, or check your web server logs)', 'updraftplus'),
|
477 |
'newuserpass' => __("The new user's RackSpace console password is (this will not be shown again):", 'updraftplus'),
|
478 |
'trying' => __('Trying...', 'updraftplus'),
|
|
|
479 |
'calculating' => __('calculating...','updraftplus'),
|
480 |
'begunlooking' => __('Begun looking for this entity','updraftplus'),
|
481 |
'stilldownloading' => __('Some files are still downloading or being processed - please wait.', 'updraftplus'),
|
@@ -536,13 +537,14 @@ class UpdraftPlus_Admin {
|
|
536 |
'key' => __('Key', 'updraftplus'),
|
537 |
'nokeynamegiven' => sprintf(__("Failure: No %s was given.",'updraftplus'), __('key name','updraftplus')),
|
538 |
'deleting' => __('Deleting...', 'updraftplus'),
|
|
|
539 |
'delete_response_not_understood' => __("We requested to delete the file, but could not understand the server's response", 'updraftplus'),
|
540 |
'testingconnection' => __('Testing connection...', 'updraftplus'),
|
541 |
'send' => __('Send', 'updraftplus'),
|
542 |
'migratemodalheight' => class_exists('UpdraftPlus_Addons_Migrator') ? 555 : 300,
|
543 |
'migratemodalwidth' => class_exists('UpdraftPlus_Addons_Migrator') ? 770 : 500,
|
544 |
'download' => _x('Download', '(verb)', 'updraftplus'),
|
545 |
-
'unsavedsettingsbackup' => __('You have made changes to your settings, and not saved.', 'updraftplus')."\n".__('
|
546 |
'dayselector' => $day_selector,
|
547 |
'mdayselector' => $mday_selector,
|
548 |
'ud_url' => UPDRAFTPLUS_URL,
|
@@ -864,7 +866,7 @@ class UpdraftPlus_Admin {
|
|
864 |
die();
|
865 |
}
|
866 |
|
867 |
-
// This function may die(), depending on the request being made
|
868 |
public function do_updraft_download_backup($findex, $type, $timestamp, $stage, $close_connection_callable = false) {
|
869 |
|
870 |
@set_time_limit(UPDRAFTPLUS_SET_TIME_LIMIT);
|
@@ -910,7 +912,7 @@ class UpdraftPlus_Admin {
|
|
910 |
$fullpath = $updraftplus->backups_dir_location().'/'.$file;
|
911 |
|
912 |
if (2 == $stage) {
|
913 |
-
$updraftplus->spool_file($
|
914 |
// Do not return - we do not want the caller to add any output
|
915 |
die;
|
916 |
}
|
@@ -1078,25 +1080,31 @@ class UpdraftPlus_Admin {
|
|
1078 |
|
1079 |
echo json_encode($this->get_activejobs_list($_GET));
|
1080 |
|
1081 |
-
} elseif (isset($_REQUEST['subaction']) && '
|
1082 |
-
// Use the site URL - this means that if the site URL changes, communication ends; which is the case anyway
|
1083 |
-
$user = wp_get_current_user();
|
1084 |
-
$name_hash = $user->ID;
|
1085 |
-
// Sending the key over https means it doesn't have to travel potentially over insecure http to the user's browser for copy-paste
|
1086 |
-
$send_it_where = defined('UPDRAFTPLUS_REMOTE_SENDKEY_WHERE') ? UPDRAFTPLUS_REMOTE_SENDKEY_WHERE : false;
|
1087 |
-
|
1088 |
-
$extra_info = array(
|
1089 |
-
'user_id' => $user->ID,
|
1090 |
-
'user_login' => $user->user_login,
|
1091 |
-
);
|
1092 |
|
1093 |
-
|
1094 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1095 |
}
|
1096 |
|
1097 |
-
$
|
1098 |
-
|
|
|
|
|
1099 |
die;
|
|
|
1100 |
} elseif (isset($_REQUEST['subaction']) && 'callwpaction' == $_REQUEST['subaction'] && !empty($_REQUEST['wpaction'])) {
|
1101 |
|
1102 |
ob_start();
|
@@ -4056,7 +4064,7 @@ class UpdraftPlus_Admin {
|
|
4056 |
$included = (UpdraftPlus_Options::get_updraft_option("updraft_include_$key", apply_filters("updraftplus_defaultoption_include_".$key, true))) ? 'checked="checked"' : "";
|
4057 |
if ('others' == $key || 'uploads' == $key) {
|
4058 |
|
4059 |
-
$ret .= '<input id="'.$prefix.'updraft_include_'.$key.'" type="checkbox" name="updraft_include_'.$key.'" value="1" '.$included.'> <label '.(('others' == $key) ? 'title="'.sprintf(__('Your wp-content directory server path: %s', 'updraftplus'), WP_CONTENT_DIR).'" ' : '').' for="'.$prefix.'updraft_include_'.$key.'">'.(('others' == $key) ? __('Any other directories found inside wp-content', 'updraftplus') : htmlspecialchars($info['description'])).'</label><br>';
|
4060 |
|
4061 |
if ($show_exclusion_options) {
|
4062 |
$include_exclude = UpdraftPlus_Options::get_updraft_option('updraft_include_'.$key.'_exclude', ('others' == $key) ? UPDRAFT_DEFAULT_OTHERS_EXCLUDE : UPDRAFT_DEFAULT_UPLOADS_EXCLUDE);
|
@@ -4075,7 +4083,7 @@ class UpdraftPlus_Admin {
|
|
4075 |
} else {
|
4076 |
|
4077 |
if ($key != 'more' || true === $include_more || ('sometimes' === $include_more && !empty($include_more_paths))) {
|
4078 |
-
$ret .= "<input id=\"".$prefix."updraft_include_$key\" type=\"checkbox\" name=\"updraft_include_$key\" value=\"1\" $included /><label for=\"".$prefix."updraft_include_$key\"".((isset($info['htmltitle'])) ? ' title="'.htmlspecialchars($info['htmltitle']).'"' : '')."> ".htmlspecialchars($info['description']);
|
4079 |
|
4080 |
$ret .= "</label><br>";
|
4081 |
$ret .= apply_filters("updraftplus_config_option_include_$key", '', $prefix);
|
476 |
'servererrorcode' => __('The web server returned an error code (try again, or check your web server logs)', 'updraftplus'),
|
477 |
'newuserpass' => __("The new user's RackSpace console password is (this will not be shown again):", 'updraftplus'),
|
478 |
'trying' => __('Trying...', 'updraftplus'),
|
479 |
+
'fetching' => __('Fetching...', 'updraftplus'),
|
480 |
'calculating' => __('calculating...','updraftplus'),
|
481 |
'begunlooking' => __('Begun looking for this entity','updraftplus'),
|
482 |
'stilldownloading' => __('Some files are still downloading or being processed - please wait.', 'updraftplus'),
|
537 |
'key' => __('Key', 'updraftplus'),
|
538 |
'nokeynamegiven' => sprintf(__("Failure: No %s was given.",'updraftplus'), __('key name','updraftplus')),
|
539 |
'deleting' => __('Deleting...', 'updraftplus'),
|
540 |
+
'enter_mothership_url' => __('Please enter a valid URL', 'updraftplus'),
|
541 |
'delete_response_not_understood' => __("We requested to delete the file, but could not understand the server's response", 'updraftplus'),
|
542 |
'testingconnection' => __('Testing connection...', 'updraftplus'),
|
543 |
'send' => __('Send', 'updraftplus'),
|
544 |
'migratemodalheight' => class_exists('UpdraftPlus_Addons_Migrator') ? 555 : 300,
|
545 |
'migratemodalwidth' => class_exists('UpdraftPlus_Addons_Migrator') ? 770 : 500,
|
546 |
'download' => _x('Download', '(verb)', 'updraftplus'),
|
547 |
+
'unsavedsettingsbackup' => __('You have made changes to your settings, and not saved.', 'updraftplus')."\n".__('You should save your changes to ensure that they are used for making your backup.','updraftplus'),
|
548 |
'dayselector' => $day_selector,
|
549 |
'mdayselector' => $mday_selector,
|
550 |
'ud_url' => UPDRAFTPLUS_URL,
|
866 |
die();
|
867 |
}
|
868 |
|
869 |
+
// This function may die(), depending on the request being made in $stage
|
870 |
public function do_updraft_download_backup($findex, $type, $timestamp, $stage, $close_connection_callable = false) {
|
871 |
|
872 |
@set_time_limit(UPDRAFTPLUS_SET_TIME_LIMIT);
|
912 |
$fullpath = $updraftplus->backups_dir_location().'/'.$file;
|
913 |
|
914 |
if (2 == $stage) {
|
915 |
+
$updraftplus->spool_file($fullpath);
|
916 |
// Do not return - we do not want the caller to add any output
|
917 |
die;
|
918 |
}
|
1080 |
|
1081 |
echo json_encode($this->get_activejobs_list($_GET));
|
1082 |
|
1083 |
+
} elseif (isset($_REQUEST['subaction']) && 'updraftcentral_delete_key' == $_REQUEST['subaction'] && isset($_REQUEST['key_id'])) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1084 |
|
1085 |
+
global $updraftplus_updraftcentral_main;
|
1086 |
+
if (!is_a($updraftplus_updraftcentral_main, 'UpdraftPlus_UpdraftCentral_Main')) {
|
1087 |
+
echo json_encode(array('error' => 'UpdraftPlus_UpdraftCentral_Main object not found'));
|
1088 |
+
die;
|
1089 |
+
}
|
1090 |
+
|
1091 |
+
echo json_encode($updraftplus_updraftcentral_main->delete_key($_REQUEST['key_id']));
|
1092 |
+
die;
|
1093 |
+
|
1094 |
+
} elseif (isset($_REQUEST['subaction']) && ('updraftcentral_create_key' == $_REQUEST['subaction'] || 'updraftcentral_get_log' == $_REQUEST['subaction'])) {
|
1095 |
+
|
1096 |
+
global $updraftplus_updraftcentral_main;
|
1097 |
+
if (!is_a($updraftplus_updraftcentral_main, 'UpdraftPlus_UpdraftCentral_Main')) {
|
1098 |
+
echo json_encode(array('error' => 'UpdraftPlus_UpdraftCentral_Main object not found'));
|
1099 |
+
die;
|
1100 |
}
|
1101 |
|
1102 |
+
$call_method = substr($_REQUEST['subaction'], 15);
|
1103 |
+
|
1104 |
+
echo json_encode(call_user_func(array($updraftplus_updraftcentral_main, $call_method), $_REQUEST));
|
1105 |
+
|
1106 |
die;
|
1107 |
+
|
1108 |
} elseif (isset($_REQUEST['subaction']) && 'callwpaction' == $_REQUEST['subaction'] && !empty($_REQUEST['wpaction'])) {
|
1109 |
|
1110 |
ob_start();
|
4064 |
$included = (UpdraftPlus_Options::get_updraft_option("updraft_include_$key", apply_filters("updraftplus_defaultoption_include_".$key, true))) ? 'checked="checked"' : "";
|
4065 |
if ('others' == $key || 'uploads' == $key) {
|
4066 |
|
4067 |
+
$ret .= '<input class="updraft_include_entity" id="'.$prefix.'updraft_include_'.$key.'" type="checkbox" name="updraft_include_'.$key.'" value="1" '.$included.'> <label '.(('others' == $key) ? 'title="'.sprintf(__('Your wp-content directory server path: %s', 'updraftplus'), WP_CONTENT_DIR).'" ' : '').' for="'.$prefix.'updraft_include_'.$key.'">'.(('others' == $key) ? __('Any other directories found inside wp-content', 'updraftplus') : htmlspecialchars($info['description'])).'</label><br>';
|
4068 |
|
4069 |
if ($show_exclusion_options) {
|
4070 |
$include_exclude = UpdraftPlus_Options::get_updraft_option('updraft_include_'.$key.'_exclude', ('others' == $key) ? UPDRAFT_DEFAULT_OTHERS_EXCLUDE : UPDRAFT_DEFAULT_UPLOADS_EXCLUDE);
|
4083 |
} else {
|
4084 |
|
4085 |
if ($key != 'more' || true === $include_more || ('sometimes' === $include_more && !empty($include_more_paths))) {
|
4086 |
+
$ret .= "<input class=\"updraft_include_entity\" id=\"".$prefix."updraft_include_$key\" type=\"checkbox\" name=\"updraft_include_$key\" value=\"1\" $included /><label for=\"".$prefix."updraft_include_$key\"".((isset($info['htmltitle'])) ? ' title="'.htmlspecialchars($info['htmltitle']).'"' : '')."> ".htmlspecialchars($info['description']);
|
4087 |
|
4088 |
$ret .= "</label><br>";
|
4089 |
$ret .= apply_filters("updraftplus_config_option_include_$key", '', $prefix);
|
@@ -579,6 +579,7 @@ class UpdraftPlus_Backup {
|
|
579 |
if (empty($backup_to_examine)) {
|
580 |
unset($functional_backup_history[$backup_datestamp]);
|
581 |
unset($backup_history[$backup_datestamp]);
|
|
|
582 |
} else {
|
583 |
$functional_backup_history[$backup_datestamp] = $backup_to_examine;
|
584 |
$backup_history[$backup_datestamp] = $backup_to_examine;
|
@@ -699,7 +700,7 @@ class UpdraftPlus_Backup {
|
|
699 |
|
700 |
// Sending an empty array is not itself a problem - except that the remote storage method may not check that before setting up a connection, which can waste time: especially if this is done every time around the loop.
|
701 |
if (!empty($files_to_prune)) {
|
702 |
-
|
703 |
foreach ($services as $service => $sd) {
|
704 |
$this->prune_file($service, $files_to_prune, $sd[0], $sd[1], $file_sizes);
|
705 |
$updraftplus->record_still_alive();
|
@@ -710,6 +711,7 @@ class UpdraftPlus_Backup {
|
|
710 |
if (empty($backup_to_examine)) {
|
711 |
// unset($functional_backup_history[$backup_datestamp]);
|
712 |
unset($backup_history[$backup_datestamp]);
|
|
|
713 |
} else {
|
714 |
// $functional_backup_history[$backup_datestamp] = $backup_to_examine;
|
715 |
$backup_history[$backup_datestamp] = $backup_to_examine;
|
@@ -728,6 +730,19 @@ class UpdraftPlus_Backup {
|
|
728 |
|
729 |
}
|
730 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
731 |
private function remove_backup_set_if_empty($backup_to_examine, $backup_datestamp, $backupable_entities, $backup_history) {
|
732 |
|
733 |
global $updraftplus;
|
579 |
if (empty($backup_to_examine)) {
|
580 |
unset($functional_backup_history[$backup_datestamp]);
|
581 |
unset($backup_history[$backup_datestamp]);
|
582 |
+
$this->maybe_save_backup_history_and_reschedule($backup_history);
|
583 |
} else {
|
584 |
$functional_backup_history[$backup_datestamp] = $backup_to_examine;
|
585 |
$backup_history[$backup_datestamp] = $backup_to_examine;
|
700 |
|
701 |
// Sending an empty array is not itself a problem - except that the remote storage method may not check that before setting up a connection, which can waste time: especially if this is done every time around the loop.
|
702 |
if (!empty($files_to_prune)) {
|
703 |
+
// Actually delete the files
|
704 |
foreach ($services as $service => $sd) {
|
705 |
$this->prune_file($service, $files_to_prune, $sd[0], $sd[1], $file_sizes);
|
706 |
$updraftplus->record_still_alive();
|
711 |
if (empty($backup_to_examine)) {
|
712 |
// unset($functional_backup_history[$backup_datestamp]);
|
713 |
unset($backup_history[$backup_datestamp]);
|
714 |
+
$this->maybe_save_backup_history_and_reschedule($backup_history);
|
715 |
} else {
|
716 |
// $functional_backup_history[$backup_datestamp] = $backup_to_examine;
|
717 |
$backup_history[$backup_datestamp] = $backup_to_examine;
|
730 |
|
731 |
}
|
732 |
|
733 |
+
// The purpose of this is to save the backup history periodically - for the benefit of setups where the pruning takes longer than the total allow run time (e.g. if the network communications to the remote storage have delays in, and there are a lot of sets to scan)
|
734 |
+
private function maybe_save_backup_history_and_reschedule($backup_history) {
|
735 |
+
static $last_saved_at = 0;
|
736 |
+
if (!$last_saved_at) $last_saved_at = time();
|
737 |
+
if (time() - $last_saved_at >= 10) {
|
738 |
+
global $updraftplus;
|
739 |
+
$updraftplus->log("Retain: saving new backup history, because at least 10 seconds have passed since the last save (sets now: ".count($backup_history).")");
|
740 |
+
UpdraftPlus_Options::update_updraft_option('updraft_backup_history', $backup_history, false);
|
741 |
+
$updraftplus->something_useful_happened();
|
742 |
+
$last_saved_at = time();
|
743 |
+
}
|
744 |
+
}
|
745 |
+
|
746 |
private function remove_backup_set_if_empty($backup_to_examine, $backup_datestamp, $backupable_entities, $backup_history) {
|
747 |
|
748 |
global $updraftplus;
|
@@ -18,7 +18,6 @@ class UpdraftPlus {
|
|
18 |
'onedrive' => 'Microsoft OneDrive',
|
19 |
'ftp' => 'FTP',
|
20 |
'azure' => 'Microsoft Azure',
|
21 |
-
'copycom' => 'Copy.Com',
|
22 |
'sftp' => 'SFTP / SCP',
|
23 |
'googlecloud' => 'Google Cloud',
|
24 |
'webdav' => 'WebDAV',
|
@@ -53,8 +52,11 @@ class UpdraftPlus {
|
|
53 |
|
54 |
public function __construct() {
|
55 |
|
56 |
-
|
57 |
if (is_file(UPDRAFTPLUS_DIR.'/addons/bitcasa.php')) $this->backup_methods['bitcasa'] = 'Bitcasa';
|
|
|
|
|
|
|
58 |
|
59 |
// Initialisation actions - takes place on plugin load
|
60 |
|
@@ -135,81 +137,6 @@ class UpdraftPlus {
|
|
135 |
return $ud_rpc;
|
136 |
}
|
137 |
|
138 |
-
public function create_remote_control_key($name_hash, $extra_info = array(), $post_it = false) {
|
139 |
-
|
140 |
-
$indicator_name = $name_hash.'.central.updraftplus.com';
|
141 |
-
|
142 |
-
$our_keys = UpdraftPlus_Options::get_updraft_option('updraft_central_localkeys');
|
143 |
-
if (!is_array($our_keys)) $our_keys = array();
|
144 |
-
|
145 |
-
if (isset($our_keys[$name_hash])) {
|
146 |
-
unset($our_keys[$name_hash]);
|
147 |
-
}
|
148 |
-
|
149 |
-
$ud_rpc = $this->get_udrpc($indicator_name);
|
150 |
-
|
151 |
-
if (is_object($ud_rpc) && $ud_rpc->generate_new_keypair()) {
|
152 |
-
|
153 |
-
if ($post_it) {
|
154 |
-
// This option allows the key to be sent to the other side via a known-secure channel (e.g. http over SSL), rather than potentially allowing it to travel over an unencrypted channel (e.g. http back to the user's browser). As such, if specified, it is compulsory for it to work.
|
155 |
-
$sent_key = wp_remote_post(
|
156 |
-
$post_it,
|
157 |
-
array(
|
158 |
-
'timeout' => 45,
|
159 |
-
'body' => array(
|
160 |
-
'key' => $ud_rpc->get_key_remote()
|
161 |
-
)
|
162 |
-
)
|
163 |
-
);
|
164 |
-
if (is_wp_error($sent_key) || empty($sent_key)) {
|
165 |
-
$err_msg = sprintf(__('A key was created, but the attempt to register it with %s was unsuccessful - please try again later.', 'updraftplus'), (string)$post_it);
|
166 |
-
if (is_wp_error($sent_key)) $err_msg .= ' '.$sent_key->get_error_message().' ('.$sent_key->get_error_code().')';
|
167 |
-
return array(
|
168 |
-
'r' => $err_msg
|
169 |
-
);
|
170 |
-
}
|
171 |
-
|
172 |
-
$response = json_decode($sent_key['body'], true);
|
173 |
-
|
174 |
-
if (!is_array($response) || !isset($response['key_id']) || !isset($response['key_public'])) {
|
175 |
-
return array(
|
176 |
-
'r' => sprintf(__('A key was created, but the attempt to register it with %s was unsuccessful - please try again later.', 'updraftplus'), (string)$post_it),
|
177 |
-
'raw' => $sent_key['body']
|
178 |
-
);
|
179 |
-
}
|
180 |
-
|
181 |
-
$key_hash = hash('sha256', $ud_rpc->get_key_remote());
|
182 |
-
|
183 |
-
$local_bundle = $ud_rpc->get_portable_bundle('base64_with_count', $extra_info, array('key' => array('key_hash' => $key_hash, 'key_id' => $response['key_id'])));
|
184 |
-
|
185 |
-
} else {
|
186 |
-
$local_bundle = $ud_rpc->get_portable_bundle('base64_with_count', $extra_info, array('key' => $ud_rpc->get_key_remote()));
|
187 |
-
}
|
188 |
-
|
189 |
-
|
190 |
-
$our_keys[$name_hash] = array(
|
191 |
-
'name' => 'Updraft Remote Control',
|
192 |
-
'key' => $ud_rpc->get_key_local(),
|
193 |
-
'extra_info' => $extra_info
|
194 |
-
);
|
195 |
-
// Store the other side's public key
|
196 |
-
if (!empty($response) && is_array($response) && !empty($response['key_public'])) {
|
197 |
-
$our_keys[$name_hash]['publickey_remote'] = $response['key_public'];
|
198 |
-
}
|
199 |
-
UpdraftPlus_Options::update_updraft_option('updraft_central_localkeys', $our_keys);
|
200 |
-
|
201 |
-
return array(
|
202 |
-
'bundle' => $local_bundle,
|
203 |
-
'r' => __('Key created successfully.', 'updraftplus').' '.__('You must copy and paste this key now - it cannot be shown again.', 'updraftplus'),
|
204 |
-
// 'selector' => $this->get_remotesites_selector(array()),
|
205 |
-
// 'ourkeys' => $this->list_our_keys($our_keys),
|
206 |
-
);
|
207 |
-
}
|
208 |
-
|
209 |
-
return false;
|
210 |
-
|
211 |
-
}
|
212 |
-
|
213 |
public function ensure_phpseclib($classes = false, $class_paths = false) {
|
214 |
|
215 |
if (false === strpos(get_include_path(), UPDRAFTPLUS_DIR.'/includes/phpseclib')) set_include_path(get_include_path().PATH_SEPARATOR.UPDRAFTPLUS_DIR.'/includes/phpseclib');
|
@@ -389,15 +316,51 @@ class UpdraftPlus {
|
|
389 |
add_action('all_admin_notices', array($this,'show_admin_warning_unreadablelog') );
|
390 |
}
|
391 |
} elseif (isset( $_GET['page'] ) && $_GET['page'] == 'updraftplus' && $_GET['action'] == 'downloadfile' && isset($_GET['updraftplus_file']) && preg_match('/^backup_([\-0-9]{15})_.*_([0-9a-f]{12})-db([0-9]+)?+\.(gz\.crypt)$/i', $_GET['updraftplus_file']) && UpdraftPlus_Options::user_can_manage()) {
|
|
|
392 |
$updraft_dir = $this->backups_dir_location();
|
393 |
-
$
|
|
|
394 |
if (is_readable($spool_file)) {
|
395 |
$dkey = isset($_GET['decrypt_key']) ? $_GET['decrypt_key'] : "";
|
396 |
-
$this->spool_file(
|
397 |
exit;
|
398 |
} else {
|
399 |
add_action('all_admin_notices', array($this,'show_admin_warning_unreadablefile') );
|
400 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
401 |
}
|
402 |
}
|
403 |
}
|
@@ -434,7 +397,7 @@ class UpdraftPlus {
|
|
434 |
|
435 |
public function show_admin_warning_unreadablefile() {
|
436 |
global $updraftplus_admin;
|
437 |
-
$updraftplus_admin->show_admin_warning('<strong>'.__('UpdraftPlus notice:','updraftplus').'</strong> '.__('The given file could not be read.','updraftplus'));
|
438 |
}
|
439 |
|
440 |
public function plugins_loaded() {
|
@@ -445,22 +408,11 @@ class UpdraftPlus {
|
|
445 |
// The Google Analyticator plugin does something horrible: loads an old version of the Google SDK on init, always - which breaks us
|
446 |
if ((defined('DOING_CRON') && DOING_CRON) || (defined('DOING_AJAX') && DOING_AJAX && isset($_REQUEST['subaction']) && 'backupnow' == $_REQUEST['subaction']) || (isset($_GET['page']) && $_GET['page'] == 'updraftplus')) {
|
447 |
remove_action('init', 'ganalyticator_stats_init');
|
448 |
-
|
449 |
-
define('APP_GCAL_DISABLE', true);
|
450 |
-
return;
|
451 |
}
|
452 |
|
453 |
-
if (
|
454 |
-
if (!file_exists(UPDRAFTPLUS_DIR.'/remote.php')) return;
|
455 |
-
require_once(UPDRAFTPLUS_DIR.'/remote.php');
|
456 |
-
}
|
457 |
-
|
458 |
-
// Remote control keys
|
459 |
-
// These are different from the remote send keys, which are set up in the Migrator add-on
|
460 |
-
$our_keys = UpdraftPlus_Options::get_updraft_option('updraft_central_localkeys');
|
461 |
-
if (is_array($our_keys) && !empty($our_keys)) {
|
462 |
-
$remote_control = new UpdraftPlus_RemoteControl($our_keys);
|
463 |
-
}
|
464 |
|
465 |
}
|
466 |
|
@@ -591,7 +543,7 @@ class UpdraftPlus {
|
|
591 |
@set_time_limit(UPDRAFTPLUS_SET_TIME_LIMIT);
|
592 |
$max_execution_time = (int)@ini_get("max_execution_time");
|
593 |
|
594 |
-
$logline = "UpdraftPlus WordPress backup plugin (https://updraftplus.com): ".$this->version." WP: ".$wp_version." PHP: ".phpversion()." (".@php_uname().") MySQL: $mysql_version Server: ".$_SERVER["SERVER_SOFTWARE"]." safe_mode: $safe_mode max_execution_time: $max_execution_time memory_limit: $memory_limit (used: ${memory_usage}M | ${memory_usage2}M) multisite: ".(
|
595 |
|
596 |
// method_exists causes some faulty PHP installations to segfault, leading to support requests
|
597 |
if (version_compare(phpversion(), '5.2.0', '>=') && extension_loaded('zip')) {
|
@@ -1997,8 +1949,8 @@ class UpdraftPlus {
|
|
1997 |
if (function_exists('doing_action') && doing_action('init') && (doing_action('updraft_backup_database') || doing_action('updraft_backup'))) {
|
1998 |
$last_scheduled_action_called_at = get_option("updraft_last_scheduled_$semaphore");
|
1999 |
// 11 minutes - so, we're assuming that they haven't custom-modified their schedules to run scheduled backups more often than that. If they have, they need also to use the filter to over-ride this check.
|
2000 |
-
|
2001 |
-
|
2002 |
$this->log(sprintf('Scheduled backup aborted - another backup of this type was apparently invoked by the WordPress scheduler only %d seconds ago - the WordPress scheduler invoking events multiple times usually indicates a very overloaded server (or other plugins that mis-use the scheduler)', $seconds_ago));
|
2003 |
return;
|
2004 |
}
|
@@ -2008,7 +1960,15 @@ class UpdraftPlus {
|
|
2008 |
require_once(UPDRAFTPLUS_DIR.'/includes/class-semaphore.php');
|
2009 |
$this->semaphore = UpdraftPlus_Semaphore::factory();
|
2010 |
$this->semaphore->lock_name = $semaphore;
|
2011 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2012 |
if (!$this->semaphore->lock()) {
|
2013 |
$this->log('Failed to gain semaphore lock ('.$semaphore.') - another backup of this type is apparently already active - aborting (if this is wrong - i.e. if the other backup crashed without removing the lock, then another can be started after 3 minutes)');
|
2014 |
return;
|
@@ -3219,7 +3179,7 @@ class UpdraftPlus {
|
|
3219 |
}
|
3220 |
}
|
3221 |
|
3222 |
-
public function spool_file($
|
3223 |
@set_time_limit(900);
|
3224 |
|
3225 |
if (file_exists($fullpath)) {
|
@@ -3230,6 +3190,7 @@ class UpdraftPlus {
|
|
3230 |
|
3231 |
$spooled = false;
|
3232 |
if ('.crypt' == substr($fullpath, -6, 6)) {
|
|
|
3233 |
header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
|
3234 |
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
|
3235 |
$this->spool_crypted_file($fullpath, (string)$encryption);
|
@@ -3241,7 +3202,7 @@ class UpdraftPlus {
|
|
3241 |
require_once(UPDRAFTPLUS_DIR.'/includes/class-partialfileservlet.php');
|
3242 |
|
3243 |
//Prevent the file being read into memory
|
3244 |
-
@
|
3245 |
|
3246 |
if (isset($_SERVER['HTTP_RANGE'])) {
|
3247 |
$range_header = trim($_SERVER['HTTP_RANGE']);
|
18 |
'onedrive' => 'Microsoft OneDrive',
|
19 |
'ftp' => 'FTP',
|
20 |
'azure' => 'Microsoft Azure',
|
|
|
21 |
'sftp' => 'SFTP / SCP',
|
22 |
'googlecloud' => 'Google Cloud',
|
23 |
'webdav' => 'WebDAV',
|
52 |
|
53 |
public function __construct() {
|
54 |
|
55 |
+
// Bitcasa support is deprecated
|
56 |
if (is_file(UPDRAFTPLUS_DIR.'/addons/bitcasa.php')) $this->backup_methods['bitcasa'] = 'Bitcasa';
|
57 |
+
|
58 |
+
// Copy.Com will be closed on 1st May 2016
|
59 |
+
if (is_file(UPDRAFTPLUS_DIR.'/addons/copycom.php')) $this->backup_methods['copycom'] = 'Copy.Com';
|
60 |
|
61 |
// Initialisation actions - takes place on plugin load
|
62 |
|
137 |
return $ud_rpc;
|
138 |
}
|
139 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
140 |
public function ensure_phpseclib($classes = false, $class_paths = false) {
|
141 |
|
142 |
if (false === strpos(get_include_path(), UPDRAFTPLUS_DIR.'/includes/phpseclib')) set_include_path(get_include_path().PATH_SEPARATOR.UPDRAFTPLUS_DIR.'/includes/phpseclib');
|
316 |
add_action('all_admin_notices', array($this,'show_admin_warning_unreadablelog') );
|
317 |
}
|
318 |
} elseif (isset( $_GET['page'] ) && $_GET['page'] == 'updraftplus' && $_GET['action'] == 'downloadfile' && isset($_GET['updraftplus_file']) && preg_match('/^backup_([\-0-9]{15})_.*_([0-9a-f]{12})-db([0-9]+)?+\.(gz\.crypt)$/i', $_GET['updraftplus_file']) && UpdraftPlus_Options::user_can_manage()) {
|
319 |
+
// Though this (venerable) code uses the action 'downloadfile', in fact, it's not that general: it's just for downloading a decrypted copy of encrypted databases, and nothing else
|
320 |
$updraft_dir = $this->backups_dir_location();
|
321 |
+
$file = $_GET['updraftplus_file'];
|
322 |
+
$spool_file = $updraft_dir.'/'.basename($file);
|
323 |
if (is_readable($spool_file)) {
|
324 |
$dkey = isset($_GET['decrypt_key']) ? $_GET['decrypt_key'] : "";
|
325 |
+
$this->spool_file($spool_file, $dkey);
|
326 |
exit;
|
327 |
} else {
|
328 |
add_action('all_admin_notices', array($this,'show_admin_warning_unreadablefile') );
|
329 |
}
|
330 |
+
} elseif ($_GET['action'] == 'updraftplus_spool_file' && !empty($_GET['what']) && !empty($_GET['backup_timestamp']) && is_numeric($_GET['backup_timestamp']) && UpdraftPlus_Options::user_can_manage()) {
|
331 |
+
// At some point, it may be worth merging this with the previous section
|
332 |
+
$updraft_dir = $this->backups_dir_location();
|
333 |
+
|
334 |
+
$findex = isset($_GET['findex']) ? (int)$_GET['findex'] : 0;
|
335 |
+
$backup_timestamp = $_GET['backup_timestamp'];
|
336 |
+
$what = $_GET['what'];
|
337 |
+
|
338 |
+
$backup_history = UpdraftPlus_Options::get_updraft_option('updraft_backup_history');
|
339 |
+
|
340 |
+
$filename = null;
|
341 |
+
if (isset($backup_history[$backup_timestamp])) {
|
342 |
+
if ('db' != substr($what, 0, 2)) {
|
343 |
+
$backupable_entities = $this->get_backupable_file_entities();
|
344 |
+
if (!isset($backupable_entities[$what])) $filename = false;
|
345 |
+
}
|
346 |
+
if (false !== $filename && isset($backup_history[$backup_timestamp][$what])) {
|
347 |
+
if (is_string($backup_history[$backup_timestamp][$what]) && 0 == $findex) {
|
348 |
+
$filename = $backup_history[$backup_timestamp][$what];
|
349 |
+
} elseif (isset($backup_history[$backup_timestamp][$what][$findex])) {
|
350 |
+
$filename = $backup_history[$backup_timestamp][$what][$findex];
|
351 |
+
}
|
352 |
+
}
|
353 |
+
}
|
354 |
+
if (empty($filename) || !is_readable($updraft_dir.'/'.basename($filename))) {
|
355 |
+
echo json_encode(array('result' => __('UpdraftPlus notice:','updraftplus').' '.__('The given file was not found, or could not be read.','updraftplus')));
|
356 |
+
exit;
|
357 |
+
}
|
358 |
+
|
359 |
+
$dkey = isset($_GET['decrypt_key']) ? (string)$_GET['decrypt_key'] : "";
|
360 |
+
|
361 |
+
$this->spool_file($updraft_dir.'/'.basename($filename), $dkey);
|
362 |
+
exit;
|
363 |
+
|
364 |
}
|
365 |
}
|
366 |
}
|
397 |
|
398 |
public function show_admin_warning_unreadablefile() {
|
399 |
global $updraftplus_admin;
|
400 |
+
$updraftplus_admin->show_admin_warning('<strong>'.__('UpdraftPlus notice:','updraftplus').'</strong> '.__('The given file was not found, or could not be read.','updraftplus'));
|
401 |
}
|
402 |
|
403 |
public function plugins_loaded() {
|
408 |
// The Google Analyticator plugin does something horrible: loads an old version of the Google SDK on init, always - which breaks us
|
409 |
if ((defined('DOING_CRON') && DOING_CRON) || (defined('DOING_AJAX') && DOING_AJAX && isset($_REQUEST['subaction']) && 'backupnow' == $_REQUEST['subaction']) || (isset($_GET['page']) && $_GET['page'] == 'updraftplus')) {
|
410 |
remove_action('init', 'ganalyticator_stats_init');
|
411 |
+
// Appointments+ does the same; but provides a cleaner way to disable it
|
412 |
+
@define('APP_GCAL_DISABLE', true);
|
|
|
413 |
}
|
414 |
|
415 |
+
if (file_exists(UPDRAFTPLUS_DIR.'/central/bootstrap.php')) require_once(UPDRAFTPLUS_DIR.'/central/bootstrap.php');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
416 |
|
417 |
}
|
418 |
|
543 |
@set_time_limit(UPDRAFTPLUS_SET_TIME_LIMIT);
|
544 |
$max_execution_time = (int)@ini_get("max_execution_time");
|
545 |
|
546 |
+
$logline = "UpdraftPlus WordPress backup plugin (https://updraftplus.com): ".$this->version." WP: ".$wp_version." PHP: ".phpversion()." (".@php_uname().") MySQL: $mysql_version Server: ".$_SERVER["SERVER_SOFTWARE"]." safe_mode: $safe_mode max_execution_time: $max_execution_time memory_limit: $memory_limit (used: ${memory_usage}M | ${memory_usage2}M) multisite: ".(is_multisite() ? 'Y' : 'N')." mcrypt: ".(function_exists('mcrypt_encrypt') ? 'Y' : 'N')." LANG: ".getenv('LANG')." ZipArchive::addFile: ";
|
547 |
|
548 |
// method_exists causes some faulty PHP installations to segfault, leading to support requests
|
549 |
if (version_compare(phpversion(), '5.2.0', '>=') && extension_loaded('zip')) {
|
1949 |
if (function_exists('doing_action') && doing_action('init') && (doing_action('updraft_backup_database') || doing_action('updraft_backup'))) {
|
1950 |
$last_scheduled_action_called_at = get_option("updraft_last_scheduled_$semaphore");
|
1951 |
// 11 minutes - so, we're assuming that they haven't custom-modified their schedules to run scheduled backups more often than that. If they have, they need also to use the filter to over-ride this check.
|
1952 |
+
$seconds_ago = time() - $last_scheduled_action_called_at;
|
1953 |
+
if ($last_scheduled_action_called_at && $seconds_ago < 660 && apply_filters('updraft_check_repeated_scheduled_backups', true)) {
|
1954 |
$this->log(sprintf('Scheduled backup aborted - another backup of this type was apparently invoked by the WordPress scheduler only %d seconds ago - the WordPress scheduler invoking events multiple times usually indicates a very overloaded server (or other plugins that mis-use the scheduler)', $seconds_ago));
|
1955 |
return;
|
1956 |
}
|
1960 |
require_once(UPDRAFTPLUS_DIR.'/includes/class-semaphore.php');
|
1961 |
$this->semaphore = UpdraftPlus_Semaphore::factory();
|
1962 |
$this->semaphore->lock_name = $semaphore;
|
1963 |
+
|
1964 |
+
$semaphore_log_message = 'Requesting semaphore lock ('.$semaphore.')';
|
1965 |
+
if (!empty($last_scheduled_action_called_at)) {
|
1966 |
+
$semaphore_log_message .= " (apparently via scheduler: last_scheduled_action_called_at=$last_scheduled_action_called_at, seconds_ago=$seconds_ago)";
|
1967 |
+
} else {
|
1968 |
+
$semaphore_log_message .= " (apparently not via scheduler)";
|
1969 |
+
}
|
1970 |
+
|
1971 |
+
$this->log($semaphore_log_message);
|
1972 |
if (!$this->semaphore->lock()) {
|
1973 |
$this->log('Failed to gain semaphore lock ('.$semaphore.') - another backup of this type is apparently already active - aborting (if this is wrong - i.e. if the other backup crashed without removing the lock, then another can be started after 3 minutes)');
|
1974 |
return;
|
3179 |
}
|
3180 |
}
|
3181 |
|
3182 |
+
public function spool_file($fullpath, $encryption = '') {
|
3183 |
@set_time_limit(900);
|
3184 |
|
3185 |
if (file_exists($fullpath)) {
|
3190 |
|
3191 |
$spooled = false;
|
3192 |
if ('.crypt' == substr($fullpath, -6, 6)) {
|
3193 |
+
if (ob_get_level()) @ob_end_clean();
|
3194 |
header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
|
3195 |
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
|
3196 |
$this->spool_crypted_file($fullpath, (string)$encryption);
|
3202 |
require_once(UPDRAFTPLUS_DIR.'/includes/class-partialfileservlet.php');
|
3203 |
|
3204 |
//Prevent the file being read into memory
|
3205 |
+
if (ob_get_level()) @ob_end_clean();
|
3206 |
|
3207 |
if (isset($_SERVER['HTTP_RANGE'])) {
|
3208 |
$range_header = trim($_SERVER['HTTP_RANGE']);
|
@@ -50,10 +50,6 @@
|
|
50 |
list-style: disc inside;
|
51 |
}
|
52 |
|
53 |
-
.updraft-hidden {
|
54 |
-
display:none;
|
55 |
-
}
|
56 |
-
|
57 |
.dashicons-log-fix {
|
58 |
display: inherit;
|
59 |
}
|
50 |
list-style: disc inside;
|
51 |
}
|
52 |
|
|
|
|
|
|
|
|
|
53 |
.dashicons-log-fix {
|
54 |
display: inherit;
|
55 |
}
|
@@ -0,0 +1,817 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/*
|
4 |
+
This class provides methods for encrypting, sending, receiving and decrypting messages of arbitrary length, using standard encryption methods and including protection against replay attacks.
|
5 |
+
|
6 |
+
Example:
|
7 |
+
|
8 |
+
// Set a key and encrypt with it
|
9 |
+
$ud_rpc = new UpdraftPlus_Remote_Communications($name_indicator); // $name_indicator is a key indicator - indicating which key is being used.
|
10 |
+
$ud_rpc->set_key_local($our_private_key);
|
11 |
+
$ud_rpc->set_key_remote($their_public_key);
|
12 |
+
$encrypted = $ud_rpc->encrypt_message('blah blah');
|
13 |
+
|
14 |
+
// Use the saved WP site option
|
15 |
+
$ud_rpc = new UpdraftPlus_Remote_Communications($name_indicator); // $name_indicator is a key indicator - indicating which key is being used.
|
16 |
+
$ud_rpc->set_option_name('udrpc_remotekey');
|
17 |
+
if (!$ud_rpc->get_key_remote()) throw new Exception('...');
|
18 |
+
$encrypted = $ud_rpc->encrypt_message('blah blah');
|
19 |
+
|
20 |
+
// Generate a new key
|
21 |
+
$ud_rpc = new UpdraftPlus_Remote_Communications('myindicator.example.com');
|
22 |
+
$ud_rpc->set_option_name('udrpc_localkey'); // Save as a WP site option
|
23 |
+
$new_pair = $ud_rpc->generate_new_keypair();
|
24 |
+
if ($new_pair) {
|
25 |
+
$local_private_key = $ud_rpc->get_key_local();
|
26 |
+
$remote_public_key = $ud_rpc->get_key_remote();
|
27 |
+
// ...
|
28 |
+
} else {
|
29 |
+
throw new Exception('...');
|
30 |
+
}
|
31 |
+
|
32 |
+
// Send a message
|
33 |
+
$ud_rpc->activate_replay_protection();
|
34 |
+
$ud_rpc->set_destination_url('https://example.com/path/to/wp');
|
35 |
+
$ud_rpc->send_message('ping');
|
36 |
+
$ud_rpc->send_message('somecommand', array('param1' => 'data', 'param2' => 'moredata'));
|
37 |
+
|
38 |
+
// N.B. The data sent needs to be something that will pass json_encode(). So, it may be desirable to base64-encode it first.
|
39 |
+
|
40 |
+
// Create a listener for incoming messages
|
41 |
+
|
42 |
+
add_filter('udrpc_command_somecommand', 'my_function', 10, 3);
|
43 |
+
// function my_function($response, $data, $name_indicator) { ... ; return array('response' => 'my_reply', 'data' => 'any mixed data'); }
|
44 |
+
// Or:
|
45 |
+
// add_filter('udrpc_action', 'some_function', 10, 4); // Function must return something other than false to indicate that it handled the specific command. Any returned value will be sent as the reply.
|
46 |
+
// function some_function($response, $command, $data, $name_indicator) { ...; return array('response' => 'my_reply', 'data' => 'any mixed data'); }
|
47 |
+
$ud_rpc->set_option_name('udrpc_local_private_key');
|
48 |
+
$ud_rpc->activate_replay_protection();
|
49 |
+
if ($ud_rpc->get_key_local()) {
|
50 |
+
// Make sure you call this before the wp_loaded action is fired (e.g. at init)
|
51 |
+
$ud_rpc->create_listener();
|
52 |
+
}
|
53 |
+
|
54 |
+
// Instead of using activate_replay_protection(), you can use activate_sequence_protection() (receiving side) and set_next_send_sequence_id(). They are very similar; but, the sequence number code isn't tested, and is problematic if you may have multiple clients that don't share storage (you can use the current time as a sequence number, but if two clients send at the same millisecond (or whatever granularity you use), you may have problems); whereas the replay protection code relies on database storage on the sending side (not just the receiving).
|
55 |
+
|
56 |
+
*/
|
57 |
+
|
58 |
+
if (!class_exists('UpdraftPlus_Remote_Communications')):
|
59 |
+
class UpdraftPlus_Remote_Communications {
|
60 |
+
|
61 |
+
// Version numbers relate to versions of this PHP library only (i.e. it's not a protocol support number, and version numbers of other compatible libraries (e.g. JavaScript) are not comparable)
|
62 |
+
public $version = '1.2';
|
63 |
+
|
64 |
+
private $key_name_indicator;
|
65 |
+
|
66 |
+
private $key_option_name = false;
|
67 |
+
private $key_remote = false;
|
68 |
+
private $key_local = false;
|
69 |
+
|
70 |
+
private $can_generate = false;
|
71 |
+
|
72 |
+
private $destination_url = false;
|
73 |
+
|
74 |
+
private $maximum_replay_time_difference = 300;
|
75 |
+
private $extra_replay_protection = false;
|
76 |
+
|
77 |
+
private $sequence_protection_tolerance;
|
78 |
+
private $sequence_protection_table;
|
79 |
+
private $sequence_protection_column;
|
80 |
+
private $sequence_protection_where_sql;
|
81 |
+
|
82 |
+
// Debug may log confidential data using $this->log() - so only use when you are in a secure environment
|
83 |
+
private $debug = false;
|
84 |
+
|
85 |
+
private $next_send_sequence_id;
|
86 |
+
|
87 |
+
private $allow_cors_from = array();
|
88 |
+
|
89 |
+
// Default protocol version - this can be over-ridden with set_message_format
|
90 |
+
// Protocol version 1 (which uses only one RSA key-pair, instead of two) is legacy/deprecated
|
91 |
+
private $format = 2;
|
92 |
+
|
93 |
+
public function __construct($key_name_indicator = 'default', $can_generate = false) {
|
94 |
+
$this->set_key_name_indicator($key_name_indicator);
|
95 |
+
}
|
96 |
+
|
97 |
+
public function set_key_name_indicator($key_name_indicator) {
|
98 |
+
$this->key_name_indicator = $key_name_indicator;
|
99 |
+
}
|
100 |
+
|
101 |
+
public function set_can_generate($can_generate = true) {
|
102 |
+
$this->can_generate = $can_generate;
|
103 |
+
}
|
104 |
+
|
105 |
+
// Which sites to allow CORS requests from
|
106 |
+
public function set_allow_cors_from($allow_cors_from) {
|
107 |
+
$this->allow_cors_from = $allow_cors_from;
|
108 |
+
}
|
109 |
+
|
110 |
+
public function set_maximum_replay_time_difference($replay_time_difference) {
|
111 |
+
$this->maximum_replay_time_difference = (int)$replay_time_difference;
|
112 |
+
}
|
113 |
+
|
114 |
+
// This will cause more things to be sent to $this->log()
|
115 |
+
public function set_debug($debug = true) {
|
116 |
+
$this->debug = (bool)$debug;
|
117 |
+
}
|
118 |
+
|
119 |
+
// Sequence protection and replay protection perform similar functions, and using both is often over-kill; the distinction is that sequence protection can be used without needing to do database writes on the sending side (e.g. use the value of time() as the sequence number).
|
120 |
+
// The only rule of sequences is that the receiving side will reject any sequence number that is less than the last previously seen one, within the bounds of the tolerance (but it may also reject those if they are repeats).
|
121 |
+
// The given table/column will record a comma-separated list of recently seen sequences numbers within the tolerance threshold.
|
122 |
+
public function activate_sequence_protection($table, $column, $where_sql, $tolerance = 5) {
|
123 |
+
$this->sequence_protection_tolerance = (int)$tolerance;
|
124 |
+
$this->sequence_protection_table = (string)$table;
|
125 |
+
$this->sequence_protection_column = (string)$column;
|
126 |
+
$this->sequence_protection_where_sql = (string)$where_sql;
|
127 |
+
}
|
128 |
+
|
129 |
+
private function ensure_crypto_loaded() {
|
130 |
+
if (!class_exists('Crypt_Rijndael') || !class_exists('Crypt_RSA') || !class_exists('Crypt_Hash')) {
|
131 |
+
global $updraftplus;
|
132 |
+
// phpseclib 1.x uses deprecated PHP4-style constructors
|
133 |
+
$this->no_deprecation_warnings_on_php7();
|
134 |
+
if (is_a($updraftplus, 'UpdraftPlus')) {
|
135 |
+
$updraftplus->ensure_phpseclib(array('Crypt_Rijndael', 'Crypt_RSA', 'Crypt_Hash'), array('Crypt/Rijndael', 'Crypt/RSA', 'Crypt/Hash'));
|
136 |
+
} elseif (defined('UPDRAFTPLUS_DIR') && file_exists(UPDRAFTPLUS_DIR.'/includes/phpseclib')) {
|
137 |
+
if (false === strpos(get_include_path(), UPDRAFTPLUS_DIR.'/includes/phpseclib')) set_include_path(UPDRAFTPLUS_DIR.'/includes/phpseclib'.PATH_SEPARATOR.get_include_path());
|
138 |
+
if (!class_exists('Crypt_Rijndael')) require_once('Crypt/Rijndael.php');
|
139 |
+
if (!class_exists('Crypt_RSA')) require_once('Crypt/RSA.php');
|
140 |
+
if (!class_exists('Crypt_Hash')) require_once('Crypt/Hash.php');
|
141 |
+
} elseif (file_exists(dirname(__DIR__).'/vendor/phpseclib')) {
|
142 |
+
$pdir = dirname(__DIR__).'/vendor/phpseclib';
|
143 |
+
if (false === strpos(get_include_path(), $pdir)) set_include_path($pdir.PATH_SEPARATOR.get_include_path());
|
144 |
+
if (!class_exists('Crypt_Rijndael')) require_once('Crypt/Rijndael.php');
|
145 |
+
if (!class_exists('Crypt_RSA')) require_once('Crypt/RSA.php');
|
146 |
+
if (!class_exists('Crypt_Hash')) require_once('Crypt/Hash.php');
|
147 |
+
} elseif (file_exists(dirname(__DIR__).'/composer/vendor/phpseclib/phpseclib/phpseclib')) {
|
148 |
+
$pdir = dirname(__DIR__).'/composer/vendor/phpseclib/phpseclib/phpseclib';
|
149 |
+
if (false === strpos(get_include_path(), $pdir)) set_include_path($pdir.PATH_SEPARATOR.get_include_path());
|
150 |
+
if (!class_exists('Crypt_Rijndael')) require_once('Crypt/Rijndael.php');
|
151 |
+
if (!class_exists('Crypt_RSA')) require_once('Crypt/RSA.php');
|
152 |
+
if (!class_exists('Crypt_Hash')) require_once('Crypt/Hash.php');
|
153 |
+
}
|
154 |
+
}
|
155 |
+
}
|
156 |
+
|
157 |
+
// Ugly, but necessary to prevent debug output breaking the conversation when the user has debug turned on
|
158 |
+
private function no_deprecation_warnings_on_php7() {
|
159 |
+
// PHP_MAJOR_VERSION is defined in PHP 5.2.7+
|
160 |
+
// We don't test for PHP > 7 because the specific deprecated element will be removed in PHP 8 - and so no warning should come anyway (and we shouldn't suppress other stuff until we know we need to).
|
161 |
+
if (defined('PHP_MAJOR_VERSION') && PHP_MAJOR_VERSION == 7) {
|
162 |
+
$old_level = error_reporting();
|
163 |
+
$new_level = $old_level & ~E_DEPRECATED;
|
164 |
+
if ($old_level != $new_level) error_reporting($new_level);
|
165 |
+
}
|
166 |
+
}
|
167 |
+
|
168 |
+
public function set_destination_url($destination_url) {
|
169 |
+
$this->destination_url = $destination_url;
|
170 |
+
}
|
171 |
+
|
172 |
+
public function set_option_name($key_option_name) {
|
173 |
+
$this->key_option_name = $key_option_name;
|
174 |
+
}
|
175 |
+
|
176 |
+
// Method to get the remote key
|
177 |
+
public function get_key_remote() {
|
178 |
+
if (empty($this->key_remote) && $this->can_generate) {
|
179 |
+
$this->generate_new_keypair();
|
180 |
+
}
|
181 |
+
return empty($this->key_remote) ? false : $this->key_remote;
|
182 |
+
}
|
183 |
+
|
184 |
+
// Set the remote key
|
185 |
+
public function set_key_remote($key_remote) {
|
186 |
+
$this->key_remote = $key_remote;
|
187 |
+
}
|
188 |
+
|
189 |
+
// Used for sending - when receiving, the format is part of the message
|
190 |
+
public function set_message_format($format = 2) {
|
191 |
+
$this->format = $format;
|
192 |
+
}
|
193 |
+
|
194 |
+
// Method to get the local key
|
195 |
+
public function get_key_local() {
|
196 |
+
if (empty($this->key_local)) {
|
197 |
+
if ($this->key_option_name) {
|
198 |
+
$key_local = get_site_option($this->key_option_name);
|
199 |
+
if ($key_local) {
|
200 |
+
$this->key_local = $key_local;
|
201 |
+
}
|
202 |
+
}
|
203 |
+
}
|
204 |
+
if (empty($this->key_local) && $this->can_generate) {
|
205 |
+
$this->generate_new_keypair();
|
206 |
+
}
|
207 |
+
return empty($this->key_local) ? false : $this->key_local;
|
208 |
+
}
|
209 |
+
|
210 |
+
// Tests whether a supplied string (after trimming) is a valid portable bundle
|
211 |
+
// Valid formats: same as get_portable_bundle()
|
212 |
+
// Returns: (array)an array (which the consumer is free to use - e.g. convert into internationalised string), with keys 'code' and (perhaps) 'data'
|
213 |
+
// Error codes: 'invalid_wrong_length'|'invalid_corrupt'
|
214 |
+
// Success codes: 'success' - then has further keys 'key', 'name_indicator' and 'url' (and anything else that was in the bundle)
|
215 |
+
public function decode_portable_bundle($bundle, $format = 'raw') {
|
216 |
+
$bundle = trim($bundle);
|
217 |
+
if ('base64_with_count' == $format) {
|
218 |
+
if (strlen($bundle) < 5) return array('code' => 'invalid_wrong_length', 'data' => 'too_short');
|
219 |
+
$len = substr($bundle, 0, 4);
|
220 |
+
$bundle = substr($bundle, 4);
|
221 |
+
$len = hexdec($len);
|
222 |
+
if (strlen($bundle) != $len) return array('code' => 'invalid_wrong_length', 'data' => "1,$len,".strlen($bundle));
|
223 |
+
if (false === ($bundle = base64_decode($bundle))) return array('code' => 'invalid_corrupt', 'data' => 'not_base64');
|
224 |
+
if (null === ($bundle = json_decode($bundle, true))) return array('code' => 'invalid_corrupt', 'data' => 'not_json');
|
225 |
+
}
|
226 |
+
if (empty($bundle['key'])) return array('code' => 'invalid_corrupt', 'data' => 'no_key');
|
227 |
+
if (empty($bundle['url'])) return array('code' => 'invalid_corrupt', 'data' => 'no_url');
|
228 |
+
if (empty($bundle['name_indicator'])) return array('code' => 'invalid_corrupt', 'data' => 'no_name_indicator');
|
229 |
+
return $bundle;
|
230 |
+
}
|
231 |
+
|
232 |
+
// Method to get a portable bundle sufficient to contact this site (i.e. remote site - so you need to have generated a key-pair, or stored the remote key somewhere and restored it)
|
233 |
+
// Supported formats: base64_with_count | (default)raw
|
234 |
+
// $extra_info needs to be JSON-serialisable, so be careful about what you put into it.
|
235 |
+
public function get_portable_bundle($format = 'raw', $extra_info = array(), $options = array()) {
|
236 |
+
$site_url = trailingslashit(network_site_url());
|
237 |
+
$bundle = array_merge($extra_info, array(
|
238 |
+
'key' => empty($options['key']) ? $this->get_key_remote() : $options['key'],
|
239 |
+
'name_indicator' => $this->key_name_indicator,
|
240 |
+
'url' => $site_url,
|
241 |
+
));
|
242 |
+
|
243 |
+
if ('base64_with_count' == $format) {
|
244 |
+
$bundle = base64_encode(json_encode($bundle));
|
245 |
+
|
246 |
+
$len = strlen($bundle); // Get the length
|
247 |
+
$len = dechex($len); // The first bytes of the message are the bundle length
|
248 |
+
$len = str_pad($len, 4, '0', STR_PAD_LEFT); // Zero pad
|
249 |
+
|
250 |
+
return $len.$bundle;
|
251 |
+
|
252 |
+
} else {
|
253 |
+
return $bundle;
|
254 |
+
}
|
255 |
+
|
256 |
+
}
|
257 |
+
|
258 |
+
public function set_key_local($key_local) {
|
259 |
+
$this->key_local = $key_local;
|
260 |
+
if ($this->key_option_name) update_site_option($this->key_option_name, $this->key_local);
|
261 |
+
}
|
262 |
+
|
263 |
+
public function generate_new_keypair() {
|
264 |
+
|
265 |
+
$this->ensure_crypto_loaded();
|
266 |
+
|
267 |
+
$rsa = new Crypt_RSA();
|
268 |
+
$keys = $rsa->createKey(2048);
|
269 |
+
|
270 |
+
if (empty($keys['privatekey'])) {
|
271 |
+
$this->set_key_local(false);
|
272 |
+
} else {
|
273 |
+
$this->set_key_local($keys['privatekey']);
|
274 |
+
}
|
275 |
+
|
276 |
+
if (empty($keys['publickey'])) {
|
277 |
+
$this->set_key_remote(false);
|
278 |
+
} else {
|
279 |
+
$this->set_key_remote($keys['publickey']);
|
280 |
+
}
|
281 |
+
|
282 |
+
return empty($keys['publickey']) ? false : true;
|
283 |
+
}
|
284 |
+
|
285 |
+
// A base-64 encoded RSA hash (PKCS_1) of the message digest
|
286 |
+
public function signature_for_message($message, $use_key = false) {
|
287 |
+
|
288 |
+
$hash_algorithm = 'sha256';
|
289 |
+
|
290 |
+
// Sign with the private (local) key
|
291 |
+
if (!$use_key) {
|
292 |
+
if (!$this->key_local) throw new Exception('No signing key has been set');
|
293 |
+
$use_key = $this->key_local;
|
294 |
+
}
|
295 |
+
|
296 |
+
$this->ensure_crypto_loaded();
|
297 |
+
|
298 |
+
$rsa = new Crypt_RSA();
|
299 |
+
$rsa->loadKey($use_key);
|
300 |
+
// This is the older signature mode; phpseclib's default is the preferred CRYPT_RSA_SIGNATURE_PSS; however, Forge JS doesn't yet support this. More info: https://en.wikipedia.org/wiki/PKCS_1
|
301 |
+
$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
|
302 |
+
|
303 |
+
// Don't do this: Crypt_RSA::sign() already calculates the digest of the hash
|
304 |
+
// $hash = new Crypt_Hash($hash_algorithm);
|
305 |
+
// $hashed = $hash->hash($message);
|
306 |
+
|
307 |
+
// if ($this->debug) $this->log("Message hash (hash=$hash_algorithm) (hex): ".bin2hex($hashed));
|
308 |
+
|
309 |
+
// phpseclib defaults to SHA1
|
310 |
+
$rsa->setHash($hash_algorithm);
|
311 |
+
$encrypted = $rsa->sign($message);
|
312 |
+
|
313 |
+
if ($this->debug) $this->log("Signed hash (mode=".CRYPT_RSA_SIGNATURE_PKCS1.") (hex): ".bin2hex($encrypted));
|
314 |
+
|
315 |
+
$signature = base64_encode($encrypted);
|
316 |
+
|
317 |
+
if ($this->debug) $this->log("Message signature (base64): $signature");
|
318 |
+
|
319 |
+
return $signature;
|
320 |
+
}
|
321 |
+
|
322 |
+
// $level is not yet used much
|
323 |
+
private function log($message, $level = 'notice') {
|
324 |
+
// Allow other plugins to do something with the message
|
325 |
+
do_action('udrpc_log', $message, $level, $this->key_name_indicator, $this->debug, $this);
|
326 |
+
if ($level != 'info') error_log("UDRPC (".$this->key_name_indicator.", $level): $message");
|
327 |
+
}
|
328 |
+
|
329 |
+
// Encrypt the message, using the local key (which needs to exist)
|
330 |
+
public function encrypt_message($plaintext, $use_key = false, $key_length=32) {
|
331 |
+
|
332 |
+
if (!$use_key) {
|
333 |
+
if ($this->format == 1) {
|
334 |
+
if (!$this->key_local) throw new Exception('No encryption key has been set');
|
335 |
+
$use_key = $this->key_local;
|
336 |
+
} else {
|
337 |
+
if (!$this->key_remote) throw new Exception('No encryption key has been set');
|
338 |
+
$use_key = $this->key_remote;
|
339 |
+
}
|
340 |
+
}
|
341 |
+
|
342 |
+
$this->ensure_crypto_loaded();
|
343 |
+
|
344 |
+
$rsa = new Crypt_RSA();
|
345 |
+
|
346 |
+
if (defined('UDRPC_PHPSECLIB_ENCRYPTION_MODE')) $rsa->setEncryptionMode(UDRPC_PHPSECLIB_ENCRYPTION_MODE);
|
347 |
+
|
348 |
+
$rij = new Crypt_Rijndael();
|
349 |
+
|
350 |
+
// Generate Random Symmetric Key
|
351 |
+
$sym_key = crypt_random_string($key_length);
|
352 |
+
|
353 |
+
if ($this->debug) $this->log("Unencrypted symmetric key (hex): ".bin2hex($sym_key));
|
354 |
+
|
355 |
+
// Encrypt Message with new Symmetric Key
|
356 |
+
$rij->setKey($sym_key);
|
357 |
+
$ciphertext = $rij->encrypt($plaintext);
|
358 |
+
|
359 |
+
if ($this->debug) $this->log("Encrypted ciphertext (hex): ".bin2hex($ciphertext));
|
360 |
+
|
361 |
+
$ciphertext = base64_encode($ciphertext);
|
362 |
+
|
363 |
+
// Encrypt the Symmetric Key with the Asymmetric Key
|
364 |
+
$rsa->loadKey($use_key);
|
365 |
+
$sym_key = $rsa->encrypt($sym_key);
|
366 |
+
|
367 |
+
if ($this->debug) $this->log("Encrypted symmetric key (hex): ".bin2hex($sym_key));
|
368 |
+
|
369 |
+
// Base 64 encode the symmetric key for transport
|
370 |
+
$sym_key = base64_encode($sym_key);
|
371 |
+
|
372 |
+
if ($this->debug) $this->log("Encrypted symmetric key (b64): ".$sym_key);
|
373 |
+
|
374 |
+
$len = str_pad(dechex(strlen($sym_key)), 3, '0', STR_PAD_LEFT); // Zero pad to be sure.
|
375 |
+
|
376 |
+
// 16 characters of hex is enough for the payload to be to 16 exabytes (giga < tera < peta < exa) of data
|
377 |
+
$cipherlen = str_pad(dechex(strlen($ciphertext)), 16, '0', STR_PAD_LEFT);
|
378 |
+
|
379 |
+
// Concatenate the length, the encrypted symmetric key, and the message
|
380 |
+
return $len.$sym_key.$cipherlen.$ciphertext;
|
381 |
+
|
382 |
+
}
|
383 |
+
|
384 |
+
// Decrypt the message, using the local key (which needs to exist)
|
385 |
+
public function decrypt_message($message) {
|
386 |
+
|
387 |
+
if (!$this->key_local) throw new Exception('No decryption key has been set');
|
388 |
+
|
389 |
+
$this->ensure_crypto_loaded();
|
390 |
+
|
391 |
+
$rsa = new Crypt_RSA();
|
392 |
+
if (defined('UDRPC_PHPSECLIB_ENCRYPTION_MODE')) $rsa->setEncryptionMode(UDRPC_PHPSECLIB_ENCRYPTION_MODE);
|
393 |
+
// Defaults to CRYPT_AES_MODE_CBC
|
394 |
+
$rij = new Crypt_Rijndael();
|
395 |
+
|
396 |
+
// Extract the Symmetric Key
|
397 |
+
$len = substr($message, 0, 3);
|
398 |
+
$len = hexdec($len);
|
399 |
+
$sym_key = substr($message, 3, $len);
|
400 |
+
|
401 |
+
// Extract the encrypted message
|
402 |
+
$cipherlen = substr($message, $len+3, 16);
|
403 |
+
$cipherlen = hexdec($cipherlen);
|
404 |
+
|
405 |
+
$ciphertext = substr($message, $len+19, $cipherlen);
|
406 |
+
$ciphertext = base64_decode($ciphertext);
|
407 |
+
|
408 |
+
// Decrypt the encrypted symmetric key
|
409 |
+
$rsa->loadKey($this->key_local);
|
410 |
+
$sym_key = base64_decode($sym_key);
|
411 |
+
$sym_key = $rsa->decrypt($sym_key);
|
412 |
+
|
413 |
+
// Decrypt the message
|
414 |
+
$rij->setKey($sym_key);
|
415 |
+
return $rij->decrypt($ciphertext);
|
416 |
+
|
417 |
+
}
|
418 |
+
|
419 |
+
// Returns an array - which the caller will then format as required (e.g. use as body in post, or JSON-encode, etc.)
|
420 |
+
public function create_message($command, $data = null, $is_response = false, $use_key_remote = false, $use_key_local = false) {
|
421 |
+
|
422 |
+
if ($is_response) {
|
423 |
+
$send_array = array('response' => $command);
|
424 |
+
} else {
|
425 |
+
$send_array = array('command' => $command);
|
426 |
+
}
|
427 |
+
|
428 |
+
$send_array['time'] = time();
|
429 |
+
// This goes in the encrypted portion as well to prevent replays with a different unencrypted name indicator
|
430 |
+
$send_array['key_name'] = $this->key_name_indicator;
|
431 |
+
|
432 |
+
// This random element means that if the site needs to send two identical commands or responses in the same second, then it can, and still use replay protection
|
433 |
+
$send_array['rand'] = rand(0, PHP_INT_MAX);
|
434 |
+
|
435 |
+
if ($this->next_send_sequence_id) {
|
436 |
+
$send_array['sequence_id'] = $this->next_send_sequence_id;
|
437 |
+
$this->next_send_sequence_id++;
|
438 |
+
}
|
439 |
+
|
440 |
+
if (null !== $data) $send_array['data'] = $data;
|
441 |
+
$send_data = $this->encrypt_message(json_encode($send_array), $use_key_remote);
|
442 |
+
|
443 |
+
$message = array(
|
444 |
+
'format' => $this->format,
|
445 |
+
'key_name' => $this->key_name_indicator,
|
446 |
+
'udrpc_message' => $send_data
|
447 |
+
);
|
448 |
+
|
449 |
+
if ($this->format >= 2) {
|
450 |
+
$signature = $this->signature_for_message($send_data, $use_key_local);
|
451 |
+
$message['signature'] = $signature;
|
452 |
+
}
|
453 |
+
|
454 |
+
return $message;
|
455 |
+
|
456 |
+
}
|
457 |
+
|
458 |
+
// N.B. There's already some time-based replay protection. This can be turned on to beef it up.
|
459 |
+
// This is only for listeners. Replays can only be detection if transients are working on the WP site (which by default only means that the option table is working).
|
460 |
+
public function activate_replay_protection($activate = true) {
|
461 |
+
$this->extra_replay_protection = (bool)$activate;
|
462 |
+
}
|
463 |
+
|
464 |
+
public function set_next_send_sequence_id($id) {
|
465 |
+
$this->next_send_sequence_id = $id;
|
466 |
+
}
|
467 |
+
|
468 |
+
public function send_message($command, $data = null, $timeout = 20) {
|
469 |
+
|
470 |
+
if (empty($this->destination_url)) return new WP_Error('not_initialised', 'RPC error: URL not initialised');
|
471 |
+
|
472 |
+
$message = $this->create_message($command, $data);
|
473 |
+
|
474 |
+
$post = wp_remote_post(
|
475 |
+
$this->destination_url,
|
476 |
+
array(
|
477 |
+
'timeout' => $timeout,
|
478 |
+
'body' => $message
|
479 |
+
)
|
480 |
+
);
|
481 |
+
|
482 |
+
if (is_wp_error($post)) return $post;
|
483 |
+
|
484 |
+
if (empty($post['response']) || empty($post['response']['code'])) return new WP_Error('empty_http_code', 'Unexpected HTTP response code');
|
485 |
+
|
486 |
+
if ($post['response']['code'] < 200 || $post['response']['code'] >= 300) return new WP_Error('unexpected_http_code', 'Unexpected HTTP response code ('.$post['response']['code'].')', $post['response']['code']);
|
487 |
+
|
488 |
+
if (empty($post['body'])) return new WP_Error('empty_response', 'Empty response from remote site');
|
489 |
+
|
490 |
+
$decoded = json_decode((string)$post['body'], true);
|
491 |
+
|
492 |
+
if (empty($decoded)) {
|
493 |
+
$this->log("response from remote site could not be understood: ".substr($post['body'], 0, 100).' ... ');
|
494 |
+
return new WP_Error('response_not_understood', 'Response from remote site could not be understood', $post['body']);
|
495 |
+
}
|
496 |
+
|
497 |
+
if (!is_array($decoded) || empty($decoded['udrpc_message'])) return new WP_Error('response_not_understood', 'Response from remote site was not in the expected format ('.$post['body'].')', $decoded);
|
498 |
+
|
499 |
+
if ($this->format >= 2) {
|
500 |
+
if (empty($decoded['signature'])) {
|
501 |
+
$this->log("No message signature found");
|
502 |
+
die;
|
503 |
+
}
|
504 |
+
if (!$this->key_remote) {
|
505 |
+
$this->log('No signature verification key has been set');
|
506 |
+
die;
|
507 |
+
}
|
508 |
+
if (!$this->verify_signature($decoded['udrpc_message'], $decoded['signature'], $this->key_remote)) {
|
509 |
+
$this->log('Signature verification failed; discarding');
|
510 |
+
die;
|
511 |
+
}
|
512 |
+
}
|
513 |
+
|
514 |
+
|
515 |
+
$decoded = $this->decrypt_message($decoded['udrpc_message']);
|
516 |
+
|
517 |
+
if (!is_string($decoded)) return new WP_Error('not_decrypted', 'Response from remote site was not successfully decrypted', $decoded['udrpc_message']);
|
518 |
+
|
519 |
+
$json_decoded = json_decode($decoded, true);
|
520 |
+
|
521 |
+
if (!is_array($json_decoded) || empty($json_decoded['response']) || empty($json_decoded['time']) || !is_numeric($json_decoded['time'])) return new WP_Error('response_corrupt', 'Response from remote site was not in the expected format', $decoded);
|
522 |
+
|
523 |
+
// Don't do the reply detection until now, because $post['body'] may not be a message that originated from the remote component at all (e.g. an HTTP error)
|
524 |
+
if ($this->extra_replay_protection) {
|
525 |
+
$message_hash = $this->calculate_message_hash((string)$post['body']);
|
526 |
+
if ($this->message_hash_seen($message_hash)) {
|
527 |
+
return new WP_Error('replay_detected', 'Message refused: replay detected', $message_hash);
|
528 |
+
}
|
529 |
+
}
|
530 |
+
|
531 |
+
$time_difference = absint(time() - $json_decoded['time']);
|
532 |
+
if ($time_difference > $this->maximum_replay_time_difference) return array(
|
533 |
+
'response' => 'rpcerror',
|
534 |
+
'data' => array(
|
535 |
+
'code' => 'window_error',
|
536 |
+
'difference' => $time_difference,
|
537 |
+
'maximum_difference' => $this->maximum_replay_time_difference
|
538 |
+
)
|
539 |
+
);
|
540 |
+
|
541 |
+
// Should be an array with keys including 'response' and (if relevant) 'data'
|
542 |
+
return $json_decoded;
|
543 |
+
|
544 |
+
}
|
545 |
+
|
546 |
+
// Returns a boolean indicating whether a listener was created - which depends on whether one was needed (so, false does not necessarily indicate an error condition)
|
547 |
+
public function create_listener() {
|
548 |
+
|
549 |
+
$http_origin = function_exists('get_http_origin') ? get_http_origin() : (empty($_SERVER['HTTP_ORIGIN']) ? '' : $_SERVER['HTTP_ORIGIN']);
|
550 |
+
|
551 |
+
// Create the WP actions to handle incoming commands, handle built-in commands (e.g. ping, create_keys (authenticate with admin creds)), dispatch them to the right place, and die
|
552 |
+
if ( (!empty($_POST) && !empty($_POST['udrpc_message']) && !empty($_POST['format'])) || (!empty($_SERVER['REQUEST_METHOD']) && 'OPTIONS' == $_SERVER['REQUEST_METHOD'] && $http_origin) ) {
|
553 |
+
add_action('wp_loaded', array($this, 'wp_loaded'));
|
554 |
+
add_action('wp_loaded', array($this, 'wp_loaded_final'), 10000);
|
555 |
+
return true;
|
556 |
+
}
|
557 |
+
return false;
|
558 |
+
}
|
559 |
+
|
560 |
+
public function wp_loaded_final() {
|
561 |
+
$message_for = empty($_POST['key_name']) ? '' : (string)$_POST['key_name'];
|
562 |
+
$this->log("Message was received, but not understood by local site (for: $message_for)");
|
563 |
+
die;
|
564 |
+
}
|
565 |
+
|
566 |
+
public function wp_loaded() {
|
567 |
+
// CORS: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
|
568 |
+
// get_http_origin() : since WP 3.4
|
569 |
+
$http_origin = function_exists('get_http_origin') ? get_http_origin() : (empty($_SERVER['HTTP_ORIGIN']) ? '' : $_SERVER['HTTP_ORIGIN']);
|
570 |
+
if (!empty($_SERVER['REQUEST_METHOD']) && 'OPTIONS' == $_SERVER['REQUEST_METHOD'] && $http_origin) {
|
571 |
+
if (in_array($http_origin, $this->allow_cors_from)) {
|
572 |
+
header("Access-Control-Allow-Origin: $http_origin");
|
573 |
+
header("Access-Control-Allow-Credentials: true");
|
574 |
+
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])) header('Access-Control-Allow-Methods: POST, OPTIONS');
|
575 |
+
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) header('Access-Control-Allow-Headers: '.$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']);
|
576 |
+
die;
|
577 |
+
} elseif ($this->debug) {
|
578 |
+
$this->log("Non-allowed CORS from: ".$http_origin);
|
579 |
+
}
|
580 |
+
// Having detected that this is a CORS request, there's nothing more to do. We return, because a different listener might pick it up, even though we didn't.
|
581 |
+
return;
|
582 |
+
}
|
583 |
+
|
584 |
+
// Silently return, rather than dying, in case another instance is able to handle this
|
585 |
+
if (empty($_POST['format']) || (1 != $_POST['format'] && 2 != $_POST['format'])) return;
|
586 |
+
|
587 |
+
$format = $_POST['format'];
|
588 |
+
|
589 |
+
/*
|
590 |
+
|
591 |
+
In format 1 (legacy/obsolete), the one encrypts (the shared AES key) using one half of the key-pair, and decrypts with the other; whereas the other side of the conversation does the reverse when replying (and uses a different shared AES key). Though this is possible in RSA, this is the wrong thing to do - see https://crypto.stackexchange.com/questions/2123/rsa-encryption-with-private-key-and-decryption-with-a-public-key
|
592 |
+
|
593 |
+
In format 2, both sides have their own private and public key. The sender encrypts using the other side's public key, and decrypts using its own private key. Messages are signed (the message digest is SHA-256).
|
594 |
+
|
595 |
+
*/
|
596 |
+
|
597 |
+
// Is this for us?
|
598 |
+
if (empty($_POST['key_name']) || $_POST['key_name'] != $this->key_name_indicator) {
|
599 |
+
return;
|
600 |
+
}
|
601 |
+
|
602 |
+
// wp_unslash() does not exist until after WP 3.5
|
603 |
+
// $udrpc_message = function_exists('wp_unslash') ? wp_unslash($_POST['udrpc_message']) : stripslashes_deep($_POST['udrpc_message']);
|
604 |
+
|
605 |
+
// Data should not have any slashes - it is base64-encoded
|
606 |
+
$udrpc_message = (string)$_POST['udrpc_message'];
|
607 |
+
|
608 |
+
// Check this now, rather than allow the decrypt method to thrown an Exception
|
609 |
+
|
610 |
+
if (empty($this->key_local)) {
|
611 |
+
$this->log("no local key (format 1): cannot decrypt");
|
612 |
+
die;
|
613 |
+
}
|
614 |
+
|
615 |
+
if ($format >= 2) {
|
616 |
+
if (empty($_POST['signature'])) {
|
617 |
+
$this->log("No message signature found");
|
618 |
+
die;
|
619 |
+
}
|
620 |
+
if (!$this->key_remote) {
|
621 |
+
$this->log('No signature verification key has been set');
|
622 |
+
die;
|
623 |
+
}
|
624 |
+
if (!$this->verify_signature($udrpc_message, $_POST['signature'], $this->key_remote)) {
|
625 |
+
$this->log('Signature verification failed; discarding');
|
626 |
+
}
|
627 |
+
}
|
628 |
+
|
629 |
+
try {
|
630 |
+
$udrpc_message = $this->decrypt_message($udrpc_message);
|
631 |
+
} catch (Exception $e) {
|
632 |
+
$this->log("Exception (".get_class($e)."): ".$e->getMessage());
|
633 |
+
die;
|
634 |
+
}
|
635 |
+
|
636 |
+
$udrpc_message = json_decode($udrpc_message, true);
|
637 |
+
|
638 |
+
if (empty($udrpc_message) || !is_array($udrpc_message) || empty($udrpc_message['command']) || !is_string($udrpc_message['command'])) {
|
639 |
+
$this->log("Could not decode JSON on incoming message");
|
640 |
+
die;
|
641 |
+
}
|
642 |
+
|
643 |
+
if (empty($udrpc_message['time'])) {
|
644 |
+
$this->log("No time set in incoming message");
|
645 |
+
die;
|
646 |
+
}
|
647 |
+
|
648 |
+
// Mismatch indicating a replay of the message with a different key name in the unencrypted portion?
|
649 |
+
if (empty($udrpc_message['key_name']) || $_POST['key_name'] != $udrpc_message['key_name']) {
|
650 |
+
$this->log("key_name mismatch between encrypted and unencrypted portions");
|
651 |
+
die;
|
652 |
+
}
|
653 |
+
|
654 |
+
if ($this->extra_replay_protection) {
|
655 |
+
$message_hash = $this->calculate_message_hash((string)$_POST['udrpc_message']);
|
656 |
+
if ($this->message_hash_seen($message_hash)) {
|
657 |
+
$this->log("Message dropped: apparently a replay (hash: $message_hash)");
|
658 |
+
die;
|
659 |
+
}
|
660 |
+
}
|
661 |
+
|
662 |
+
// Do this after the extra replay protection, as that checks hashes within the maximum time window - so don't check the maximum time window until afterwards, to avoid a tiny window (race) in between.
|
663 |
+
$time_difference = absint($udrpc_message['time'] - time());
|
664 |
+
if ($time_difference > $this->maximum_replay_time_difference) {
|
665 |
+
$this->log("Time in incoming message is outside of allowed window ($time_difference > ".$this->maximum_replay_time_difference.")");
|
666 |
+
die;
|
667 |
+
}
|
668 |
+
|
669 |
+
// The sequence number should always be larger than any previously-sent sequence number
|
670 |
+
if ($this->sequence_protection_tolerance) {
|
671 |
+
|
672 |
+
if ($this->debug) $this->log("Sequence protection is active; tolerance: ".$this->sequence_protection_tolerance);
|
673 |
+
|
674 |
+
global $wpdb;
|
675 |
+
|
676 |
+
if (!isset($udrpc_message['sequence_id']) || !is_numeric($udrpc_message['sequence_id'])) {
|
677 |
+
$this->log("a numerical sequence number is required, but none was included in the message - dropping");
|
678 |
+
die;
|
679 |
+
}
|
680 |
+
|
681 |
+
$message_sequence_id = (int)$udrpc_message['sequence_id'];
|
682 |
+
$recently_seen_sequences_ids = $wpdb->get_var($wpdb->prepare("SELECT %s FROM %s LIMIT 1 WHERE ".$this->sequence_protection_where_sql, $this->sequence_protection_column, $this->sequence_protection_table));
|
683 |
+
|
684 |
+
if ('' === $recently_seen_sequences_ids) $recently_seen_sequences_ids = '0';
|
685 |
+
|
686 |
+
$recently_seen_sequences_ids_as_array = explode($recently_seen_sequences_ids, ',');
|
687 |
+
sort($recently_seen_sequences_ids_as_array);
|
688 |
+
|
689 |
+
// Seen before?
|
690 |
+
if (in_array($message_sequence_id, $recently_seen_sequences_ids_as_array)) {
|
691 |
+
$this->log("message with duplicate sequence number received - dropping (received=$message_sequence_id, seen=$recently_seen_sequences_ids)");
|
692 |
+
die;
|
693 |
+
}
|
694 |
+
|
695 |
+
// Within the tolerance threshold? That means: a) either bigger than the max, or b) no more than <tolerance> lower than the least
|
696 |
+
if ($message_sequence_id > max($recently_seen_sequences_ids)) {
|
697 |
+
if ($this->debug) $this->log("Sequence id ($message_sequence_id) is greater than any previous (".max($recently_seen_sequences_ids).") - message is thus OK");
|
698 |
+
// All is well
|
699 |
+
$recently_seen_sequences_ids_as_array[] = $message_sequence_id;
|
700 |
+
} elseif (max($recently_seen_sequences_ids) - $message_sequence_id <= $this->sequence_protection_tolerance) {
|
701 |
+
// All is well - was one of those 'missing' in the sequence
|
702 |
+
if ($this->debug) $this->log("Sequence id ($message_sequence_id) is within tolerance range of previous maximum (".max($recently_seen_sequences_ids).") - message is thus OK");
|
703 |
+
$recently_seen_sequences_ids_as_array[] = $message_sequence_id;
|
704 |
+
} else {
|
705 |
+
$this->log("message received outside of allowed sequence window - dropping (received=$message_sequence_id, seen=$recently_seen_sequences_ids, tolerance=".$this->sequence_protection_tolerance.")");
|
706 |
+
die;
|
707 |
+
}
|
708 |
+
|
709 |
+
// Remove out-of-bounds seen IDs
|
710 |
+
$max_sequence_id_seen = max($recently_seen_sequences_ids_as_array);
|
711 |
+
foreach ($recently_seen_sequences_ids_as_array as $k => $id) {
|
712 |
+
if ($max_sequence_id_seen - $id > $this->sequence_protection_tolerance) {
|
713 |
+
if ($this->debug) $this->log("Removing no-longer-relevant sequence from list of those recently seen: $id");
|
714 |
+
unset($recently_seen_sequences_ids_as_array[$k]);
|
715 |
+
}
|
716 |
+
}
|
717 |
+
|
718 |
+
// Allow reset
|
719 |
+
if ($current_sequence_id > PHP_INT_MAX - 10) {
|
720 |
+
$recently_seen_sequences_ids_as_array = array(0);
|
721 |
+
}
|
722 |
+
|
723 |
+
// Write them back to the database
|
724 |
+
$sql = $wpdb->prepare("UPDATE %s SET %s=%s WHERE ".$this->sequence_protection_where_sql, $this->sequence_protection_table, $this->sequence_protection_column, implode(',', $recently_seen_sequences_ids_as_array));
|
725 |
+
if ($this->debug) $this->log("SQL to send recent sequence IDs back to the database: $sql");
|
726 |
+
$wpdb->query($sql);
|
727 |
+
|
728 |
+
}
|
729 |
+
|
730 |
+
$command = (string)$udrpc_message['command'];
|
731 |
+
$data = empty($udrpc_message['data']) ? null : $udrpc_message['data'];
|
732 |
+
|
733 |
+
if ($http_origin) {
|
734 |
+
header("Access-Control-Allow-Origin: $http_origin");
|
735 |
+
header("Access-Control-Allow-Credentials: true");
|
736 |
+
}
|
737 |
+
|
738 |
+
$this->log("Command received: ".$command, 'info');
|
739 |
+
|
740 |
+
if ('ping' == $command) {
|
741 |
+
echo json_encode($this->create_message('pong', null, true));
|
742 |
+
} else {
|
743 |
+
if (has_filter('udrpc_command_'.$command)) {
|
744 |
+
$command_action_hooked = true;
|
745 |
+
$response = apply_filters('udrpc_command_'.$command, null, $data, $this->key_name_indicator);
|
746 |
+
} else {
|
747 |
+
$response = array('response' => 'rpcerror', 'data' => array('code' => 'unknown_rpc_command', 'command' => $command));
|
748 |
+
}
|
749 |
+
|
750 |
+
$response = apply_filters('udrpc_action', $response, $command, $data, $this->key_name_indicator, $this);
|
751 |
+
|
752 |
+
if (is_array($response)) {
|
753 |
+
|
754 |
+
if ($this->debug) {
|
755 |
+
$this->log("UDRPC response (pre-encoding/encryption): ".serialize($response));
|
756 |
+
}
|
757 |
+
|
758 |
+
$data = isset($response['data']) ? $response['data'] : null;
|
759 |
+
echo json_encode($this->create_message($response['response'], $data, true));
|
760 |
+
}
|
761 |
+
|
762 |
+
}
|
763 |
+
|
764 |
+
die;
|
765 |
+
|
766 |
+
}
|
767 |
+
|
768 |
+
// The hash needs to be in a format that phpseclib likes. phpseclib uses lower case.
|
769 |
+
// Pass in a base64-encoded signature (i.e. just as signature_for_message creates)
|
770 |
+
// Returns a boolean
|
771 |
+
public function verify_signature($message, $signature, $key, $hash_algorithm = 'sha256') {
|
772 |
+
$this->ensure_crypto_loaded();
|
773 |
+
$rsa = new Crypt_RSA();
|
774 |
+
$rsa->setHash(strtolower($hash_algorithm));
|
775 |
+
// This is not the default, but is what we use
|
776 |
+
$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
|
777 |
+
$rsa->loadKey($key);
|
778 |
+
|
779 |
+
// Don't hash it - Crypt_RSA::verify() already does that
|
780 |
+
// $hash = new Crypt_Hash($hash_algorithm);
|
781 |
+
// $hashed = $hash->hash($message);
|
782 |
+
|
783 |
+
$verified = $rsa->verify($message, base64_decode($signature));
|
784 |
+
|
785 |
+
if ($this->debug) $this->log("Signature verification result: ".serialize($verified));
|
786 |
+
|
787 |
+
return $verified;
|
788 |
+
}
|
789 |
+
|
790 |
+
private function calculate_message_hash($message) {
|
791 |
+
return hash('sha256', $message);
|
792 |
+
}
|
793 |
+
|
794 |
+
private function message_hash_seen($message_hash) {
|
795 |
+
// 39 characters - less than the WP site transient name limit (40). Though, we use a normal transient, as these don't auto-load at all times.
|
796 |
+
$transient_name = 'udrpch_'.md5($this->key_name_indicator);
|
797 |
+
$seen_hashes = get_transient($transient_name);
|
798 |
+
if (!is_array($seen_hashes)) $seen_hashes = array();
|
799 |
+
$time_now = time();
|
800 |
+
// $any_changes = false;
|
801 |
+
// Prune the old hashes
|
802 |
+
foreach ($seen_hashes as $hash => $last_seen) {
|
803 |
+
if ($last_seen < $time_now - $this->maximum_replay_time_difference) {
|
804 |
+
// $any_changes = true;
|
805 |
+
unset($seen_hashes[$hash]);
|
806 |
+
}
|
807 |
+
}
|
808 |
+
if (isset($seen_hashes[$message_hash])) {
|
809 |
+
return true;
|
810 |
+
}
|
811 |
+
$seen_hashes[$message_hash] = $time_now;
|
812 |
+
set_transient($transient_name, $seen_hashes, $this->maximum_replay_time_difference);
|
813 |
+
return false;
|
814 |
+
}
|
815 |
+
|
816 |
+
}
|
817 |
+
endif;
|
@@ -16,6 +16,19 @@ function updraft_delete(key, nonce, showremote) {
|
|
16 |
jQuery('#updraft-delete-modal').dialog('open');
|
17 |
}
|
18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
function updraft_deleteallselected() {
|
20 |
var howmany = 0;
|
21 |
var remote_exists = 0;
|
@@ -112,6 +125,12 @@ function updraft_backup_dialog_open() {
|
|
112 |
}
|
113 |
}
|
114 |
|
|
|
|
|
|
|
|
|
|
|
|
|
115 |
|
116 |
function updraft_migrate_dialog_open() {
|
117 |
jQuery('#updraft_migrate_modal_alt').hide();
|
@@ -914,7 +933,6 @@ function updraft_backupnow_go(backupnow_nodb, backupnow_nofiles, backupnow_noclo
|
|
914 |
extradata: extradata
|
915 |
};
|
916 |
|
917 |
-
|
918 |
if ('' != onlythesefileentities) {
|
919 |
params.onlythisfileentity = onlythesefileentities;
|
920 |
}
|
@@ -981,22 +999,92 @@ jQuery(document).ready(function($){
|
|
981 |
e.preventDefault();
|
982 |
updraft_updatehistory(1, 1);
|
983 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
984 |
|
985 |
-
|
986 |
-
|
|
|
|
|
|
|
|
|
|
|
987 |
e.preventDefault();
|
988 |
-
jQuery('#
|
989 |
try {
|
990 |
jQuery.post(ajaxurl, {
|
991 |
action: 'updraft_ajax',
|
992 |
-
subaction: '
|
993 |
nonce: updraft_credentialtest_nonce
|
994 |
}, function(response) {
|
|
|
995 |
try {
|
996 |
resp = jQuery.parseJSON(response);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
997 |
alert(resp.r);
|
|
|
|
|
|
|
998 |
if (resp.hasOwnProperty('bundle')) {
|
999 |
-
jQuery('#
|
1000 |
} else {
|
1001 |
console.log(resp);
|
1002 |
}
|
@@ -1011,6 +1099,36 @@ jQuery(document).ready(function($){
|
|
1011 |
}
|
1012 |
});
|
1013 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1014 |
jQuery('#updraft_reset_sid').click(function(e) {
|
1015 |
e.preventDefault();
|
1016 |
jQuery.post(ajaxurl, {
|
@@ -1380,15 +1498,7 @@ jQuery(document).ready(function($){
|
|
1380 |
var backupnow_nofiles = jQuery('#backupnow_includefiles').is(':checked') ? 0 : 1;
|
1381 |
var backupnow_nocloud = jQuery('#backupnow_includecloud').is(':checked') ? 0 : 1;
|
1382 |
|
1383 |
-
var onlythesefileentities = '';
|
1384 |
-
jQuery('#backupnow_includefiles_moreoptions input[type="checkbox"]').each(function(index) {
|
1385 |
-
if (!jQuery(this).is(':checked')) { return; }
|
1386 |
-
var name = jQuery(this).attr('name');
|
1387 |
-
if (name.substring(0, 16) != 'updraft_include_') { return; }
|
1388 |
-
var entity = name.substring(16);
|
1389 |
-
if (onlythesefileentities != '') { onlythesefileentities += ','; }
|
1390 |
-
onlythesefileentities += entity;
|
1391 |
-
});
|
1392 |
|
1393 |
if ('' == onlythesefileentities && 0 == backupnow_nofiles) {
|
1394 |
alert(updraftlion.nofileschosen);
|
@@ -1919,6 +2029,14 @@ jQuery(document).ready(function($){
|
|
1919 |
var my_image = new Image();
|
1920 |
my_image.src = updraftlion.ud_url+'/images/udlogo-rotating.gif';
|
1921 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1922 |
$('#updraftplus-settings-save').click(function(e) {
|
1923 |
e.preventDefault();
|
1924 |
$.blockUI({ message: '<div style="margin: 8px; font-size:150%;"><img src="'+updraftlion.ud_url+'/images/udlogo-rotating.gif" height="80" width="80" style="padding-bottom:10px;"><br>'+updraftlion.saving+'</div>'});
|
16 |
jQuery('#updraft-delete-modal').dialog('open');
|
17 |
}
|
18 |
|
19 |
+
function backupnow_whichfiles_checked(onlythesefileentities){
|
20 |
+
jQuery('#backupnow_includefiles_moreoptions input[type="checkbox"]').each(function(index) {
|
21 |
+
if (!jQuery(this).is(':checked')) { return; }
|
22 |
+
var name = jQuery(this).attr('name');
|
23 |
+
if (name.substring(0, 16) != 'updraft_include_') { return; }
|
24 |
+
var entity = name.substring(16);
|
25 |
+
if (onlythesefileentities != '') { onlythesefileentities += ','; }
|
26 |
+
onlythesefileentities += entity;
|
27 |
+
});
|
28 |
+
// console.log(onlythesefileentities);
|
29 |
+
return onlythesefileentities;
|
30 |
+
}
|
31 |
+
|
32 |
function updraft_deleteallselected() {
|
33 |
var howmany = 0;
|
34 |
var remote_exists = 0;
|
125 |
}
|
126 |
}
|
127 |
|
128 |
+
var onlythesefileentities = backupnow_whichfiles_checked('');
|
129 |
+
if ('' == onlythesefileentities) {
|
130 |
+
jQuery("#backupnow_includefiles_moreoptions").show();
|
131 |
+
} else {
|
132 |
+
jQuery("#backupnow_includefiles_moreoptions").hide();
|
133 |
+
}
|
134 |
|
135 |
function updraft_migrate_dialog_open() {
|
136 |
jQuery('#updraft_migrate_modal_alt').hide();
|
933 |
extradata: extradata
|
934 |
};
|
935 |
|
|
|
936 |
if ('' != onlythesefileentities) {
|
937 |
params.onlythisfileentity = onlythesefileentities;
|
938 |
}
|
999 |
e.preventDefault();
|
1000 |
updraft_updatehistory(1, 1);
|
1001 |
});
|
1002 |
+
|
1003 |
+
function updraftcentral_keys_setupform(on_page_load) {
|
1004 |
+
var is_other = jQuery('#updraftcentral_mothership_other').is(':checked') ? true : false;
|
1005 |
+
if (is_other) {
|
1006 |
+
jQuery('#updraftcentral_keycreate_mothership').prop('disabled', false);
|
1007 |
+
if (!on_page_load) { jQuery('#updraftcentral_keycreate_mothership').focus(); }
|
1008 |
+
} else {
|
1009 |
+
jQuery('#updraftcentral_keycreate_mothership').prop('disabled', true);
|
1010 |
+
}
|
1011 |
+
}
|
1012 |
|
1013 |
+
jQuery('#updraftcentral_keys').on('change', 'input[type="radio"]', function() {
|
1014 |
+
updraftcentral_keys_setupform(false);
|
1015 |
+
});
|
1016 |
+
// Initial setup (for browsers, e.g. Firefox, that remember form selection state but not DOM state, which can leave an inconsistent state)
|
1017 |
+
updraftcentral_keys_setupform(true);
|
1018 |
+
|
1019 |
+
jQuery('#updraftcentral_keys').on('click', '#updraftcentral_view_log', function(e) {
|
1020 |
e.preventDefault();
|
1021 |
+
jQuery('#updraftcentral_view_log_container').block({ message: '<div style="margin: 8px; font-size:150%;"><img src="'+updraftlion.ud_url+'/images/udlogo-rotating.gif" height="80" width="80" style="padding-bottom:10px;"><br>'+updraftlion.fetching+'</div>'});
|
1022 |
try {
|
1023 |
jQuery.post(ajaxurl, {
|
1024 |
action: 'updraft_ajax',
|
1025 |
+
subaction: 'updraftcentral_get_log',
|
1026 |
nonce: updraft_credentialtest_nonce
|
1027 |
}, function(response) {
|
1028 |
+
jQuery('#updraftcentral_view_log_container').unblock();
|
1029 |
try {
|
1030 |
resp = jQuery.parseJSON(response);
|
1031 |
+
if (resp.hasOwnProperty('log_contents')) {
|
1032 |
+
jQuery('#updraftcentral_view_log_contents').html('<div style="border:1px solid;padding: 2px;max-height: 400px; overflow-y:scroll;">'+resp.log_contents+'</div>');
|
1033 |
+
} else {
|
1034 |
+
console.log(resp);
|
1035 |
+
}
|
1036 |
+
} catch (err) {
|
1037 |
+
alert(updraftlion.unexpectedresponse+' '+ |