2520258128
changelog follows
[MNET-manual] added version.php, install.xml and upgrade.php patches
Route remote users back to their home server without going through jump.php and land.php
Display app-specific strings in the user view
Display the application icon in the Remote Host block
Hide the 'logs' tab if the application under review is not Moodle
Hide the 'logs' tab if the application under review is not Moodle
Update user record to note that picture == 1 once a picture has been
transferred.
Change 2 to uri - is this fluid?
Add application paramter to bootstrap function
Find the application
Workaround for PHP5.2.2 bug: http://bugs.php.net/bug.php?id=41293
$HTTP_RAW_POST_DATA was not being populated
Ensure we get an application for our Peer
Update the URI to use for MNET
The default 'wantsurl' should be empty
Use the appropriate 'land' url for the remote application
Add hidden form elements for 'application'
Add awareness of new Application concept
Add awareness of new Application concept
Add awareness of new Application concept
Add awareness of new Application concept
Add awareness of new Application concept
Add awareness of new Application concept
Add awareness of new Application concept
317 lines
12 KiB
PHP
317 lines
12 KiB
PHP
<?php
|
|
/**
|
|
* An XML-RPC client
|
|
*
|
|
* @author Donal McMullan donal@catalyst.net.nz
|
|
* @version 0.0.1
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
|
|
* @package mnet
|
|
*/
|
|
|
|
require_once $CFG->dirroot.'/mnet/lib.php';
|
|
|
|
/**
|
|
* Class representing an XMLRPC request against a remote machine
|
|
*/
|
|
class mnet_xmlrpc_client {
|
|
|
|
var $method = '';
|
|
var $params = array();
|
|
var $timeout = 60;
|
|
var $error = array();
|
|
var $response = '';
|
|
|
|
/**
|
|
* Constructor returns true
|
|
*/
|
|
function mnet_xmlrpc_client() {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Allow users to override the default timeout
|
|
* @param int $timeout Request timeout in seconds
|
|
* $return bool True if param is an integer or integer string
|
|
*/
|
|
function set_timeout($timeout) {
|
|
if (!is_integer($timeout)) {
|
|
if (is_numeric($timeout)) {
|
|
$this->timeout = (integer($timeout));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
$this->timeout = $timeout;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Set the path to the method or function we want to execute on the remote
|
|
* machine. Examples:
|
|
* mod/scorm/functionname
|
|
* auth/mnet/methodname
|
|
* In the case of auth and enrolment plugins, an object will be created and
|
|
* the method on that object will be called
|
|
*/
|
|
function set_method($xmlrpcpath) {
|
|
if (is_string($xmlrpcpath)) {
|
|
$this->method = $xmlrpcpath;
|
|
$this->params = array();
|
|
return true;
|
|
}
|
|
$this->method = '';
|
|
$this->params = array();
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Add a parameter to the array of parameters.
|
|
*
|
|
* @param string $argument A transport ID, as defined in lib.php
|
|
* @param string $type The argument type, can be one of:
|
|
* none
|
|
* empty
|
|
* base64
|
|
* boolean
|
|
* datetime
|
|
* double
|
|
* int
|
|
* string
|
|
* array
|
|
* struct
|
|
* In its weakly-typed wisdom, PHP will (currently)
|
|
* ignore everything except datetime and base64
|
|
* @return bool True on success
|
|
*/
|
|
function add_param($argument, $type = 'string') {
|
|
|
|
$allowed_types = array('none',
|
|
'empty',
|
|
'base64',
|
|
'boolean',
|
|
'datetime',
|
|
'double',
|
|
'int',
|
|
'i4',
|
|
'string',
|
|
'array',
|
|
'struct');
|
|
if (!in_array($type, $allowed_types)) {
|
|
return false;
|
|
}
|
|
|
|
if ($type != 'datetime' && $type != 'base64') {
|
|
$this->params[] = $argument;
|
|
return true;
|
|
}
|
|
|
|
// Note weirdness - The type of $argument gets changed to an object with
|
|
// value and type properties.
|
|
// bool xmlrpc_set_type ( string &value, string type )
|
|
xmlrpc_set_type($argument, $type);
|
|
$this->params[] = $argument;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Send the request to the server - decode and return the response
|
|
*
|
|
* @param object $mnet_peer A mnet_peer object with details of the
|
|
* remote host we're connecting to
|
|
* @return mixed A PHP variable, as returned by the
|
|
* remote function
|
|
*/
|
|
function send($mnet_peer) {
|
|
global $CFG, $MNET;
|
|
|
|
$this->uri = $mnet_peer->wwwroot.$mnet_peer->application->xmlrpc_server_url;
|
|
|
|
// Initialize with the target URL
|
|
$ch = curl_init($this->uri);
|
|
|
|
$system_methods = array('system/listMethods', 'system/methodSignature', 'system/methodHelp', 'system/listServices');
|
|
|
|
if (in_array($this->method, $system_methods) ) {
|
|
|
|
// Executing any system method is permitted.
|
|
|
|
} else {
|
|
$id_list = $mnet_peer->id;
|
|
if (!empty($CFG->mnet_all_hosts_id)) {
|
|
$id_list .= ', '.$CFG->mnet_all_hosts_id;
|
|
}
|
|
|
|
// At this point, we don't care if the remote host implements the
|
|
// method we're trying to call. We just want to know that:
|
|
// 1. The method belongs to some service, as far as OUR host knows
|
|
// 2. We are allowed to subscribe to that service on this mnet_peer
|
|
|
|
// Find methods that we subscribe to on this host
|
|
$sql = "
|
|
SELECT
|
|
*
|
|
FROM
|
|
{$CFG->prefix}mnet_rpc r,
|
|
{$CFG->prefix}mnet_service2rpc s2r,
|
|
{$CFG->prefix}mnet_host2service h2s
|
|
WHERE
|
|
r.xmlrpc_path = '{$this->method}' AND
|
|
s2r.rpcid = r.id AND
|
|
s2r.serviceid = h2s.serviceid AND
|
|
h2s.subscribe = '1' AND
|
|
h2s.hostid in ({$id_list})";
|
|
|
|
$permission = get_record_sql($sql);
|
|
if ($permission == false) {
|
|
global $USER;
|
|
$this->error[] = '7:User with ID '. $USER->id .
|
|
' attempted to call unauthorised method '.
|
|
$this->method.' on host '.
|
|
$mnet_peer->wwwroot;
|
|
return false;
|
|
}
|
|
|
|
}
|
|
$this->requesttext = xmlrpc_encode_request($this->method, $this->params, array("encoding" => "utf-8"));
|
|
$rq = $this->requesttext;
|
|
$rq = mnet_sign_message($this->requesttext);
|
|
$this->signedrequest = $rq;
|
|
$rq = mnet_encrypt_message($rq, $mnet_peer->public_key);
|
|
$this->encryptedrequest = $rq;
|
|
|
|
curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
curl_setopt($ch, CURLOPT_POST, true);
|
|
curl_setopt($ch, CURLOPT_USERAGENT, 'Moodle');
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $rq);
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type: text/xml charset=UTF-8"));
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
|
|
|
|
$timestamp_send = time();
|
|
$this->rawresponse = curl_exec($ch);
|
|
$timestamp_receive = time();
|
|
|
|
if ($this->rawresponse == false) {
|
|
$this->error[] = curl_errno($ch) .':'. curl_error($ch);
|
|
return false;
|
|
}
|
|
|
|
$mnet_peer->touch();
|
|
|
|
$crypt_parser = new mnet_encxml_parser();
|
|
$crypt_parser->parse($this->rawresponse);
|
|
|
|
if ($crypt_parser->payload_encrypted) {
|
|
|
|
$key = array_pop($crypt_parser->cipher);
|
|
$data = array_pop($crypt_parser->cipher);
|
|
|
|
$crypt_parser->free_resource();
|
|
|
|
// Initialize payload var
|
|
$payload = '';
|
|
|
|
// &$payload
|
|
$isOpen = openssl_open(base64_decode($data), $payload, base64_decode($key), $MNET->get_private_key());
|
|
|
|
if (!$isOpen) {
|
|
// Decryption failed... let's try our archived keys
|
|
$openssl_history = get_config('mnet', 'openssl_history');
|
|
if(empty($openssl_history)) {
|
|
$openssl_history = array();
|
|
set_config('openssl_history', serialize($openssl_history), 'mnet');
|
|
} else {
|
|
$openssl_history = unserialize($openssl_history);
|
|
}
|
|
foreach($openssl_history as $keyset) {
|
|
$keyresource = openssl_pkey_get_private($keyset['keypair_PEM']);
|
|
$isOpen = openssl_open(base64_decode($data), $payload, base64_decode($key), $keyresource);
|
|
if ($isOpen) {
|
|
// It's an older code, sir, but it checks out
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!$isOpen) {
|
|
trigger_error("None of our keys could open the payload from host {$mnet_peer->wwwroot} with id {$mnet_peer->id}.");
|
|
$this->error[] = '3:No key match';
|
|
return false;
|
|
}
|
|
|
|
if (strpos(substr($payload, 0, 100), '<signedMessage>')) {
|
|
$sig_parser = new mnet_encxml_parser();
|
|
$sig_parser->parse($payload);
|
|
} else {
|
|
$this->error[] = '2:Payload not signed: '.$payload;
|
|
return false;
|
|
}
|
|
|
|
} else {
|
|
$this->error[] = '1:Payload not encrypted ';
|
|
$crypt_parser->free_resource();
|
|
return false;
|
|
}
|
|
|
|
// Margin of error is the time it took the request to complete.
|
|
$margin_of_error = $timestamp_receive - $timestamp_send;
|
|
|
|
// Guess the time gap between sending the request and the remote machine
|
|
// executing the time() function. Marginally better than nothing.
|
|
$hysteresis = ($margin_of_error) / 2;
|
|
|
|
$remote_timestamp = $sig_parser->remote_timestamp - $hysteresis;
|
|
$time_offset = $remote_timestamp - $timestamp_send;
|
|
if ($time_offset > 0) {
|
|
$threshold = get_config('mnet', 'drift_threshold');
|
|
if(empty($threshold)) {
|
|
// We decided 15 seconds was a pretty good arbitrary threshold
|
|
// for time-drift between servers, but you can customize this in
|
|
// the config_plugins table. It's not advised though.
|
|
set_config('drift_threshold', 15, 'mnet');
|
|
$threshold = 15;
|
|
}
|
|
if ($time_offset > $threshold) {
|
|
$this->error[] = '6:Time gap with '.$mnet_peer->name.' ('.$time_offset.' seconds) is greater than the permitted maximum of '.$threshold.' seconds';
|
|
return false;
|
|
}
|
|
}
|
|
|
|
$this->xmlrpcresponse = base64_decode($sig_parser->data_object);
|
|
$this->response = xmlrpc_decode($this->xmlrpcresponse);
|
|
curl_close($ch);
|
|
|
|
// xmlrpc errors are pushed onto the $this->error stack
|
|
if (is_array($this->response) && array_key_exists('faultCode', $this->response)) {
|
|
// The faultCode 7025 means we tried to connect with an old SSL key
|
|
// The faultString is the new key - let's save it and try again
|
|
// The re_key attribute stops us from getting into a loop
|
|
if($this->response['faultCode'] == 7025 && empty($mnet_peer->re_key)) {
|
|
$record = new stdClass();
|
|
$record->id = $mnet_peer->id;
|
|
if($this->response['faultString'] == clean_param($this->response['faultString'], PARAM_PEM)) {
|
|
$record->public_key = $this->response['faultString'];
|
|
$details = openssl_x509_parse($record->public_key);
|
|
if(is_array($details) && isset($details['validTo_time_t'])) {
|
|
$record->public_key_expires = $details['validTo_time_t'];
|
|
update_record('mnet_host', $record);
|
|
$mnet_peer2 = new mnet_peer();
|
|
$mnet_peer2->set_id($record->id);
|
|
$mnet_peer2->re_key = true;
|
|
$this->send($mnet_peer2);
|
|
} else {
|
|
$this->error[] = $this->response['faultCode'] . " : " . $this->response['faultString'];
|
|
}
|
|
} else {
|
|
$this->error[] = $this->response['faultCode'] . " : " . $this->response['faultString'];
|
|
}
|
|
} else {
|
|
$this->error[] = $this->response['faultCode'] . " : " . $this->response['faultString'];
|
|
}
|
|
}
|
|
return empty($this->error);
|
|
}
|
|
}
|
|
?>
|