Files
moodle/mnet/xmlrpc/client.php
T
mjollnir_ 2520258128 Merged Donal's changes to MNET to allow moodle to also network with mahara.
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
2007-07-14 03:00:12 +00:00

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);
}
}
?>