$key) { if (!is_array($key)) return; $ud_rpc = $updraftplus->get_udrpc($name_hash.'.migrator.updraftplus.com'); $ud_rpc->set_message_format(1); $this->receivers[$name_hash] = $ud_rpc; $ud_rpc->set_key_local($key['key']); // Create listener (which causes WP actions to be fired when messages are received) $ud_rpc->activate_replay_protection(); $ud_rpc->create_listener(); } add_filter('udrpc_command_send_chunk', array($this, 'udrpc_command_send_chunk'), 10, 3); add_filter('udrpc_command_get_file_status', array($this, 'udrpc_command_get_file_status'), 10, 3); add_filter('udrpc_command_upload_complete', array($this, 'udrpc_command_upload_complete'), 10, 3); } } protected function initialise_listener_error_handling($hash) { global $updraftplus; $updraftplus->error_reporting_stop_when_logged = true; set_error_handler(array($updraftplus, 'php_error'), E_ALL & ~E_STRICT); $this->php_events = array(); add_filter('updraftplus_logline', array($this, 'updraftplus_logline'), 10, 4); if (!UpdraftPlus_Options::get_updraft_option('updraft_debug_mode')) return; $updraftplus->nonce = $hash; $updraftplus->logfile_open($hash); } protected function return_rpc_message($msg) { if (is_array($msg) && isset($msg['response']) && 'error' == $msg['response']) { global $updraftplus; $updraftplus->log('Unexpected response code in remote communications: '.serialize($msg)); } if (!empty($this->php_events)) { if (!isset($msg['data'])) $msg['data'] = null; $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; } public function updraftplus_logline($line, $nonce, $level, $uniq_id) { if ('notice' === $level && 'php_event' === $uniq_id) { $this->php_events[] = $line; } return $line; } public function udrpc_command_send_chunk($response, $data, $name_indicator) { if (!preg_match('/^([a-f0-9]+)\.migrator.updraftplus.com$/', $name_indicator, $matches)) return $response; $name_hash = $matches[1]; $this->initialise_listener_error_handling($name_hash); global $updraftplus; // send_message('send_chunk', array('file' => $file, 'data' => $chunk, 'start' => $upload_start)) if (!is_array($data)) return $this->return_rpc_message(array('response' => 'error', 'data' => 'invalid_input_expected_array')); if (!isset($data['file'])) return $this->return_rpc_message(array('response' => 'error', 'data' => 'invalid_input_no_file')); if (!isset($data['data'])) return $this->return_rpc_message(array('response' => 'error', 'data' => 'invalid_input_no_data')); if (!isset($data['start'])) return $this->return_rpc_message(array('response' => 'error', 'data' => 'invalid_input_no_start')); // Make sure the parameters are valid if (!is_numeric($data['start']) || absint($data['start']) != $data['start']) return $this->return_rpc_message(array('response' => 'error', 'data' => 'invalid_start')); // Sanity-check the file name $file = $data['file']; if (!preg_match('/(-db\.gz|-db\.gz\.crypt|-db|\.(sql|sql\.gz|sql\.bz2|zip|tar|tar\.bz2|tar\.gz|txt))/i', $file)) return array('response' => 'error', 'data' => 'illegal_file_name1'); if (basename($file) != $file) return $this->return_rpc_message(array('response' => 'error', 'data' => 'invalid_input_illegal_character')); $start = $data['start']; $is_last_chunk = empty($data['last_chunk']) ? 0 : 1; if (!$is_last_chunk) { } else { $orig_file = $file; if (!empty($data['label'])) $label = $data['label']; } $file .= '.tmp'; // Intentionally over-write the variable, in case memory is short and in case PHP's garbage collector is this clever $data = base64_decode($data['data']); $updraft_dir = $updraftplus->backups_dir_location(); $fullpath = $updraft_dir.'/'.$file; $existing_size = file_exists($fullpath) ? filesize($fullpath) : 0; if ($start > $existing_size) { return $this->return_rpc_message(array('response' => 'error', 'data' => "invalid_start_too_big:start=${start},existing_size=${existing_size}")); } if (false == ($fhandle = fopen($fullpath, 'ab'))) { return $this->return_rpc_message(array('response' => 'error', 'data' => 'file_open_failure')); } // fseek() returns 0 for success, or -1 for failure if ($start != $existing_size && -1 == fseek($fhandle, $start)) return $this->return_rpc_message(array('response' => 'error', 'data' => 'fseek_failure')); $write_status = fwrite($fhandle, $data); if (false === $write_status || (false == $write_status && !empty($data))) return $this->return_rpc_message(array('response' => 'error', 'data' => 'fwrite_failure')); @fclose($fhandle); $our_keys = UpdraftPlus_Options::get_updraft_option('updraft_migrator_localkeys'); if (is_array($our_keys) && isset($our_keys[$name_hash]) && !empty($our_keys[$name_hash]['name'])) $updraftplus->log("Received data chunk on key ".$our_keys[$name_hash]['name']. " ($file, ".$start.", is_last=$is_last_chunk)"); if ($is_last_chunk) { if (!rename($fullpath, $updraft_dir.'/'.$orig_file)) return $this->return_rpc_message(array('response' => 'error', 'data' => 'rename_failure')); $only_add_this_file = array('file' => $orig_file); if (isset($label)) $only_add_this_file['label'] = $label; UpdraftPlus_Backup_History::rebuild(false, $only_add_this_file); } return $this->return_rpc_message(array( 'response' => 'file_status', 'data' => $this->get_file_status($file) )); } protected function get_file_status($file) { global $updraftplus; $fullpath = $updraftplus->backups_dir_location().'/'.basename($file); if (file_exists($fullpath)) { $size = filesize($fullpath); $status = 1; } elseif (file_exists($fullpath.'.tmp')) { $size = filesize($fullpath.'.tmp'); $status = 0; } else { $size = 0; $status = 0; } return array( 'size' => $size, 'status' => $status, ); } public function udrpc_command_get_file_status($response, $data, $name_indicator) { if (!preg_match('/^([a-f0-9]+)\.migrator.updraftplus.com$/', $name_indicator, $matches)) return $response; $name_hash = $matches[1]; $this->initialise_listener_error_handling($name_hash); if (!is_string($data)) return $this->return_rpc_message(array('response' => 'error', 'data' => 'invalid_input_expected_string')); if (basename($data) != $data) return $this->return_rpc_message(array('response' => 'error', 'data' => 'invalid_input_illegal_character')); return $this->return_rpc_message(array( 'response' => 'file_status', 'data' => $this->get_file_status($data) )); } /** * This function will return a response to the remote site to acknowledge that we have recieved the upload_complete message and if this is a clone it call the ready_for_restore action * * @param string $response - a string response * @param array $data - an array of data * @param string $name_indicator - a string to identify the request * * @return array - the array response */ public function udrpc_command_upload_complete($response, $data, $name_indicator) { if (!preg_match('/^([a-f0-9]+)\.migrator.updraftplus.com$/', $name_indicator, $matches)) return $response; if (defined('UPDRAFTPLUS_THIS_IS_CLONE')) { do_action('updraftplus_temporary_clone_ready_for_restore'); } return $this->return_rpc_message(array( 'response' => 'file_status', 'data' => '' )); } public function updraftplus_initial_jobdata($initial_jobdata, $options, $split_every) { if (is_array($options) && !empty($options['extradata']) && preg_match('#services=remotesend/(\d+)#', $options['extradata'], $matches)) { // Load the option now - don't wait until send time $site_id = $matches[1]; $remotesites = UpdraftPlus_Options::get_updraft_option('updraft_remotesites'); if (!is_array($remotesites)) $remotesites = array(); if (empty($remotesites[$site_id]) || empty($remotesites[$site_id]['url']) || empty($remotesites[$site_id]['key']) || empty($remotesites[$site_id]['name_indicator'])) { throw new Exception("Remote site id ($site_id) not found - send aborted"); } array_push($initial_jobdata, 'remotesend_info', $remotesites[$site_id]); // Reduce to 100MB if it was above. Since the user isn't expected to directly manipulate these zip files, the potentially higher number of zip files doesn't matter. if ($split_every > 100) array_push($initial_jobdata, 'split_every', 100); } return $initial_jobdata; } public function updraft_printjob_beforewarnings($ret, $jobdata) { if (!empty($jobdata['remotesend_info']) && !empty($jobdata['remotesend_info']['url'])) { $ret .= '

'.__('Backup data will be sent to:', 'updraftplus').' '.htmlspecialchars($jobdata['remotesend_info']['url']).'

'; } return $ret; } public function updraft_remote_ping_test($data) { global $updraftplus; if (!isset($data['id']) || !is_numeric($data['id']) || empty($data['url'])) die; $remotesites = UpdraftPlus_Options::get_updraft_option('updraft_remotesites'); if (!is_array($remotesites)) $remotesites = array(); if (empty($remotesites[$data['id']]) || $data['url'] != $remotesites[$data['id']]['url'] || empty($remotesites[$data['id']]['key']) || empty($remotesites[$data['id']]['name_indicator'])) { echo json_encode(array('e' => 1, 'r' => __('Error:', 'updraftplus').' '.__('site not found', 'updraftplus'))); die(); } try { $updraftplus->error_reporting_stop_when_logged = true; set_error_handler(array($updraftplus, 'php_error'), E_ALL & ~E_STRICT); $this->php_events = array(); add_filter('updraftplus_logline', array($this, 'updraftplus_logline'), 10, 4); $opts = $remotesites[$data['id']]; $ud_rpc = $updraftplus->get_udrpc($opts['name_indicator']); $ud_rpc->set_message_format(1); $ud_rpc->set_key_local($opts['key']); $ud_rpc->set_destination_url($data['url']); $ud_rpc->activate_replay_protection(); do_action('updraftplus_remotesend_udrpc_object_obtained', $ud_rpc, $opts); $response = $ud_rpc->send_message('ping'); restore_error_handler(); if (is_wp_error($response)) { $err_msg = __('Error:', 'updraftplus').' '.$response->get_error_message(); $err_data = $response->get_error_data(); $err_code = $response->get_error_code(); } elseif (!is_array($response) || empty($response['response']) || 'pong' != $response['response']) { $err_msg = __('Error:', 'updraftplus').' '.sprintf(__('You should check that the remote site is online, not firewalled, does not have security modules that may be blocking access, has UpdraftPlus version %s or later active and that the keys have been entered correctly.', 'updraftplus'), '2.10.3'); $err_data = $response; $err_code = 'no_pong'; } if (isset($err_msg)) { $res = array('e' => 1, 'r' => $err_msg); if ($this->url_looks_internal($data['url'])) { $res['moreinfo'] = '

'.sprintf(__('The site URL you are sending to (%s) looks like a local development website. If you are sending from an external network, it is likely that a firewall will be blocking this.', 'updraftplus'), htmlspecialchars($data['url'])).'

'; } // We got several support requests from people who didn't seem to be aware of other methods $msg_try_other_method = '

'.__('If sending directly from site to site does not work for you, then there are three other methods - please try one of these instead.', 'updraftplus').''.__('For longer help, including screenshots, follow this link.', 'updraftplus').'

'; $res['moreinfo'] = isset($res['moreinfo']) ? $res['moreinfo'].$msg_try_other_method : $msg_try_other_method; if (isset($err_data)) $res['data'] = $err_data; if (isset($err_code)) $res['code'] = $err_code; if (!empty($this->php_events)) $res['php_events'] = $this->php_events; echo json_encode($res); die; } $ret = '

'.__('Testing connection...', 'updraftplus').' '.__('OK', 'updraftplus').'

'; global $updraftplus, $updraftplus_admin; $ret .= '
'; $ret .= $updraftplus_admin->files_selector_widgetry('remotesend_', false, false); $service = $updraftplus->just_one(UpdraftPlus_Options::get_updraft_option('updraft_service')); if (is_string($service)) $service = array($service); if (is_array($service) && !empty($service) && array('none') !== $service) { $first_one = true; foreach ($service as $s) { if (!$s) continue; if (isset($updraftplus->backup_methods[$s])) { if ($first_one) { $first_one = false; $ret .= '

'; $ret .= '

'; } $ret .= apply_filters('updraft_backupnow_modal_afteroptions', '', 'remotesend_'); $ret .= ''; echo json_encode(array('success' => 1, 'r' => $ret)); } catch (Exception $e) { echo json_encode(array('e' => 1, 'r' => __('Error:', 'updraftplus').' '.$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')')); } die; } /** * This is used only for an advisory warning - does not have to be able to always detect * * @param string $url */ protected function url_looks_internal($url) { $url_host = strtolower(parse_url($url, PHP_URL_HOST)); if ('localhost' == $url_host || strpos($url_host, '127.') === 0 || strpos($url_host, '10.') === 0 || '::1' == $url_host || strpos($url_host, 'localhost') !== false || substr($url_host, -4, 4) == '.dev') return true; return false; } public function updraft_migrate_key_delete($data) { if (empty($data['keyid'])) die; $our_keys = UpdraftPlus_Options::get_updraft_option('updraft_migrator_localkeys'); if (!is_array($our_keys)) $our_keys = array(); unset($our_keys[$data['keyid']]); UpdraftPlus_Options::update_updraft_option('updraft_migrator_localkeys', $our_keys); echo json_encode(array('ourkeys' => $this->list_our_keys($our_keys))); die; } /** * This function is a wrapper for updraft_migrate_key_create when being called from WP_CLI it allows us to return the created key rather than echo it, by passing return_instead_of_echo as part of $data. * * @param string $string - empty string to filter on * @param array $data - an array of data needed to create the RSA keypair should also include return_instead_of_echo to return the result * * @return string - the RSA remote key */ public function updraft_migrate_key_create_return($string, $data) { return $this->updraft_migrate_key_create($data); } /** * Called upon the WP action updraft_s3_newuser. Dies. * * @param array $data - the posted data * * @return void */ public function updraft_migrate_key_create($data) { if (empty($data['name'])) die; $name = stripslashes($data['name']); $size = (empty($data['size']) || !is_numeric($data['size']) || $data['size'] < 512) ? 2048 : (int) $data['size']; $name_hash = md5($name); // 32 characters $indicator_name = $name_hash.'.migrator.updraftplus.com'; $our_keys = UpdraftPlus_Options::get_updraft_option('updraft_migrator_localkeys'); if (!is_array($our_keys)) $our_keys = array(); if (isset($our_keys[$name_hash])) { echo json_encode(array('e' => 1, 'r' => __('Error:', 'updraftplus').' '.__('A key with this name already exists; you must use a unique name.', 'updraftplus'))); die; } global $updraftplus; $ud_rpc = $updraftplus->get_udrpc($indicator_name); if (is_object($ud_rpc) && $ud_rpc->generate_new_keypair($size)) { $local_bundle = $ud_rpc->get_portable_bundle('base64_with_count'); $our_keys[$name_hash] = array('name' => $name, 'key' => $ud_rpc->get_key_local()); UpdraftPlus_Options::update_updraft_option('updraft_migrator_localkeys', $our_keys); if (isset($data['return_instead_of_echo']) && $data['return_instead_of_echo']) return $local_bundle; echo json_encode(array( 'bundle' => $local_bundle, 'r' => __('Key created successfully.', 'updraftplus').' '.__('You must copy and paste this key on the sending site now - it cannot be shown again.', 'updraftplus'), 'selector' => $this->get_remotesites_selector(array()), 'ourkeys' => $this->list_our_keys($our_keys), )); die; } echo json_encode(array('e' => 1)); die; } public function updraft_migrate_newdestination($data) { global $updraftplus; $ret = array(); if (empty($data['key'])) { $ret['e'] = sprintf(__("Failure: No %s was given.", 'updraftplus'), __('key', 'updraftplus')); } else { $ud_rpc = $updraftplus->get_udrpc(); // A bundle has these keys: key, name_indicator, url $decode_bundle = $ud_rpc->decode_portable_bundle($data['key'], 'base64_with_count'); if (!is_array($decode_bundle) || !empty($decode_bundle['code'])) { $ret['e'] = __('Error:', 'updraftplus'); if (!empty($decode_bundle['code']) && 'invalid_wrong_length' == $decode_bundle['code']) { $ret['e'] .= ' '.__('The entered key was the wrong length - please try again.', 'updraftplus'); } elseif (!empty($decode_bundle['code']) && 'invalid_corrupt' == $decode_bundle['code']) { $ret['e'] .= ' '.__('The entered key was corrupt - please try again.', 'updraftplus').' ('.$decode_bundle['data'].')'; } elseif (empty($decode_bundle['key']) || empty($decode_bundle['url'])) { $ret['e'] .= ' '.__('The entered key was corrupt - please try again.', 'updraftplus'); $ret['data'] = $decode_bundle; } } elseif (empty($decode_bundle['key']) || empty($decode_bundle['url'])) { $ret['e'] = __('Error:', 'updraftplus').' '.__('The entered key was corrupt - please try again.', 'updraftplus'); $ret['data'] = $decode_bundle; } else { if (trailingslashit(network_site_url()) == $decode_bundle['url']) { $ret['e'] = __('Error:', 'updraftplus').' '.__('The entered key does not belong to a remote site (it belongs to this one).', 'updraftplus'); } else { // Store the information $remotesites = UpdraftPlus_Options::get_updraft_option('updraft_remotesites'); if (!is_array($remotesites)) $remotesites = array(); foreach ($remotesites as $k => $rsite) { if (!is_array($rsite)) continue; if ($rsite['url'] == $decode_bundle['url']) unset($remotesites[$k]); } $remotesites[] = $decode_bundle; UpdraftPlus_Options::update_updraft_option('updraft_remotesites', $remotesites); $ret['selector'] = $this->get_remotesites_selector($remotesites); // Return the new HTML widget to the front end $ret['r'] = __('The key was successfully added.', 'updraftplus').' '.__('It is for sending backups to the following site: ', 'updraftplus').htmlspecialchars($decode_bundle['url']); } } } echo json_encode($ret); die; } protected function get_remotesites_selector($remotesites = false) { if (false === $remotesites) { $remotesites = UpdraftPlus_Options::get_updraft_option('updraft_remotesites'); if (!is_array($remotesites)) $remotesites = array(); } if (empty($remotesites)) { return '

'.__('No receiving sites have yet been added.', 'updraftplus').'

'; } else { $ret = '

'; $ret .= ' '; $ret .= '

'; } return $ret; } protected function list_our_keys($our_keys = false) { if (false === $our_keys) { $our_keys = UpdraftPlus_Options::get_updraft_option('updraft_migrator_localkeys'); } if (empty($our_keys)) return ''.__('No keys to allow remote sites to send backup data here have yet been created.', 'updraftplus').''; $ret = ''; $first_one = true; foreach ($our_keys as $k => $key) { if (!is_array($key)) continue; if ($first_one) { $first_one = false; $ret .= '

'.__('Existing keys', 'updraftplus').'
'; } $ret .= htmlspecialchars($key['name']); $ret .= ' - '.__('Delete', 'updraftplus').''; $ret .= '
'; } if ($ret) $ret .= '

'; return $ret; } }