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.

252 lines
9.9 KiB

<?php
if (!defined('UPDRAFTCENTRAL_CLIENT_DIR')) die('No access.');
/**
* This class is the basic glue between the lower-level UpdraftPlus_Remote_Communications (UDRPC) class, and UpdraftPlus. It does not contain actual commands themselves; the class names to use for actual commands are passed in as a parameter to the constructor.
*/
class UpdraftPlus_UpdraftCentral_Listener {
public $udrpc_version;
private $ud = null;
private $receivers = array();
private $extra_info = array();
private $php_events = array();
private $commands = array();
private $current_udrpc = null;
private $command_classes;
public function __construct($keys = array(), $command_classes = array()) {
global $updraftplus;
$this->ud = $updraftplus;
// It seems impossible for this condition to result in a return; but it seems Plesk can do something odd within the control panel that causes a problem - see HS#6276
if (!is_a($this->ud, 'UpdraftPlus')) return;
$this->command_classes = $command_classes;
foreach ($keys as $name_hash => $key) {
// publickey_remote isn't necessarily set yet, depending on the key exchange method
if (!is_array($key) || empty($key['extra_info']) || empty($key['publickey_remote'])) continue;
$indicator = $name_hash.'.central.updraftplus.com';
$ud_rpc = $this->ud->get_udrpc($indicator);
$this->udrpc_version = $ud_rpc->version;
// Only turn this on if you are comfortable with potentially anything appearing in your PHP error log
if (defined('UPDRAFTPLUS_UDRPC_FORCE_DEBUG') && UPDRAFTPLUS_UDRPC_FORCE_DEBUG) $ud_rpc->set_debug(true);
$this->receivers[$indicator] = $ud_rpc;
$this->extra_info[$indicator] = isset($key['extra_info']) ? $key['extra_info'] : null;
$ud_rpc->set_key_local($key['key']);
$ud_rpc->set_key_remote($key['publickey_remote']);
// Create listener (which causes WP actions to be fired when messages are received)
$ud_rpc->activate_replay_protection();
if (!empty($key['extra_info']) && isset($key['extra_info']['mothership'])) {
$mothership = $key['extra_info']['mothership'];
unset($url);
if ('__updraftpluscom' == $mothership) {
$url = 'https://updraftplus.com';
} elseif (false != ($parsed = parse_url($key['extra_info']['mothership'])) && is_array($parsed)) {
$url = $parsed['scheme'].'://'.$parsed['host'];
}
if (!empty($url)) $ud_rpc->set_allow_cors_from(array($url));
}
$ud_rpc->create_listener();
}
// If we ever need to expand beyond a single GET action, this can/should be generalised and put into the commands class
if (!empty($_GET['udcentral_action']) && 'login' == $_GET['udcentral_action']) {
// auth_redirect() does not return, according to the documentation; but the code shows that it can
// auth_redirect();
if (!empty($_GET['login_id']) && is_numeric($_GET['login_id']) && !empty($_GET['login_key'])) {
$login_user = get_user_by('id', $_GET['login_id']);
include_once(ABSPATH.WPINC.'/version.php');
if (is_a($login_user, 'WP_User') || (version_compare($wp_version, '3.5', '<') && !empty($login_user->ID))) {
// Allow site implementers to disable this functionality
$allow_autologin = apply_filters('updraftcentral_allow_autologin', true, $login_user);
if ($allow_autologin) {
$login_key = get_user_meta($login_user->ID, 'updraftcentral_login_key', true);
if (is_array($login_key) && !empty($login_key['created']) && $login_key['created'] > time() - 60 && !empty($login_key['key']) && $login_key['key'] == $_GET['login_key']) {
$autologin = empty($login_key['redirect_url']) ? network_admin_url() : $login_key['redirect_url'];
}
}
}
}
if (!empty($autologin)) {
// Allow use once only
delete_user_meta($login_user->ID, 'updraftcentral_login_key');
$this->autologin_user($login_user, $autologin);
}
}
add_filter('udrpc_action', array($this, 'udrpc_action'), 10, 5);
add_filter('updraftcentral_get_command_info', array($this, 'updraftcentral_get_command_info'), 10, 2);
}
/**
* Retrieves command class information and includes class file if class
* is currently not available.
*
* @param mixed $response The default response to return if the submitted command does not exists
* @param string $command The command to parse and check
* @return array Contains the following command information "command_php_class", "class_prefix" and "command"
*/
public function updraftcentral_get_command_info($response, $command) {
if (!preg_match('/^([a-z0-9]+)\.(.*)$/', $command, $matches)) return $response;
$class_prefix = $matches[1];
$command = $matches[2];
// We only handle some commands - the others, we let something else deal with
if (!isset($this->command_classes[$class_prefix])) return $response;
$command_php_class = $this->command_classes[$class_prefix];
$command_base_class_at = apply_filters('updraftcentral_command_base_class_at', UPDRAFTCENTRAL_CLIENT_DIR.'/commands.php');
if (!class_exists('UpdraftCentral_Commands')) include_once($command_base_class_at);
// Second parameter has been passed since
do_action('updraftcentral_command_class_wanted', $command_php_class);
if (!class_exists($command_php_class)) {
if (file_exists(UPDRAFTCENTRAL_CLIENT_DIR.'/modules/'.$class_prefix.'.php')) {
include_once(UPDRAFTCENTRAL_CLIENT_DIR.'/modules/'.$class_prefix.'.php');
}
}
return array(
'command_php_class' => $command_php_class,
'class_prefix' => $class_prefix,
'command' => $command
);
}
/**
* Do verification before calling this method
*
* @param WP_User|Object $user user object for autologin
* @param boolean $redirect_url Redirect URL
*/
private function autologin_user($user, $redirect_url = false) {
if (!is_user_logged_in()) {
// $user = get_user_by('id', $user_id);
// Don't check that it's a WP_User - that's WP 3.5+ only
if (!is_object($user) || empty($user->ID)) return;
wp_set_current_user($user->ID, $user->user_login);
wp_set_auth_cookie($user->ID);
do_action('wp_login', $user->user_login, $user);
}
if ($redirect_url) {
wp_safe_redirect($redirect_url);
exit;
}
}
public function udrpc_action($response, $command, $data, $key_name_indicator, $ud_rpc) {
if (empty($this->receivers[$key_name_indicator])) return $response;
$this->initialise_listener_error_handling($key_name_indicator);
$command_info = apply_filters('updraftcentral_get_command_info', false, $command);
if (!$command_info) return $response;
$class_prefix = $command_info['class_prefix'];
$command = $command_info['command'];
$command_php_class = $command_info['command_php_class'];
if (empty($this->commands[$class_prefix])) {
if (class_exists($command_php_class)) {
$this->commands[$class_prefix] = new $command_php_class($this);
}
}
$command_class = isset($this->commands[$class_prefix]) ? $this->commands[$class_prefix] : new stdClass;
if ('_' == substr($command, 0, 1) || !is_a($command_class, $command_php_class) || (!method_exists($command_class, $command) && !method_exists($command_class, '__call'))) {
if (defined('UPDRAFTPLUS_UDRPC_FORCE_DEBUG') && UPDRAFTPLUS_UDRPC_FORCE_DEBUG) error_log("Unknown RPC command received: ".$command);
return $this->return_rpc_message(array('response' => 'rpcerror', 'data' => array('code' => 'unknown_rpc_command', 'data' => array('prefix' => $class_prefix, 'command' => $command, 'class' => $command_php_class))));
}
$extra_info = isset($this->extra_info[$key_name_indicator]) ? $this->extra_info[$key_name_indicator] : null;
// Make it so that current_user_can() checks can apply + work
if (!empty($extra_info['user_id'])) wp_set_current_user($extra_info['user_id']);
$this->current_udrpc = $ud_rpc;
do_action('updraftcentral_listener_pre_udrpc_action', $command, $command_class, $data, $extra_info);
global $updraftplus;
if (is_a($updraftplus, 'UpdraftPlus')) $updraftplus->register_wp_http_option_hooks();
// Despatch
$msg = apply_filters('updraftcentral_listener_udrpc_action', call_user_func(array($command_class, $command), $data, $extra_info), $command_class, $class_prefix, $command, $data, $extra_info);
if (is_a($updraftplus, 'UpdraftPlus')) $updraftplus->register_wp_http_option_hooks(false);
return $this->return_rpc_message($msg);
}
public function get_current_udrpc() {
return $this->current_udrpc;
}
private function initialise_listener_error_handling($hash) {
$this->ud->error_reporting_stop_when_logged = true;
set_error_handler(array($this->ud, 'php_error'), E_ALL & ~E_STRICT);
$this->php_events = array();
@ob_start();
add_filter('updraftplus_logline', array($this, 'updraftplus_logline'), 10, 4);
if (!UpdraftPlus_Options::get_updraft_option('updraft_debug_mode')) return;
}
public function updraftplus_logline($line, $nonce, $level, $uniq_id) {
if ('notice' === $level && 'php_event' === $uniq_id) {
$this->php_events[] = $line;
}
return $line;
}
public function return_rpc_message($msg) {
if (is_array($msg) && isset($msg['response']) && 'error' == $msg['response']) {
$this->ud->log('Unexpected response code in remote communications: '.serialize($msg));
}
$caught_output = @ob_get_contents();
@ob_end_clean();
// If turning output-catching off, turn this on instead:
// $caught_output = ''; @ob_end_flush();
// If there's higher-level output buffering going on, then get rid of that
if (ob_get_level()) ob_end_clean();
if ($caught_output) {
if (!isset($msg['data'])) $msg['data'] = null;
$msg['data'] = array('caught_output' => $caught_output, 'previous_data' => $msg['data']);
$already_rearranged_data = true;
}
if (!empty($this->php_events)) {
if (!isset($msg['data'])) $msg['data'] = null;
if (!empty($already_rearranged_data)) {
$msg['data']['php_events'] = array();
} else {
$msg['data'] = array('php_events' => array(), 'previous_data' => $msg['data']);
}
foreach ($this->php_events as $logline) {
$msg['data']['php_events'][] = $logline;
}
}
restore_error_handler();
return $msg;
}
}