. * * @package mahara * @subpackage xmlrpc * @author Catalyst IT Ltd * @license http://www.gnu.org/copyleft/gpl.html GNU GPL * @copyright (C) 2006-2008 Catalyst IT Ltd http://catalyst.net.nz * */ defined('INTERNAL') || die(); require_once(get_config('docroot') . 'api/xmlrpc/lib.php'); class Client { private $requesttext = ''; private $signedrequest = ''; private $encryptedrequest = ''; private $timeout = 60; private $params = array(); private $method = ''; function __construct() { return true; } function set_method($method) { if (is_string($method) && preg_match("@^[A-Za-z0-9]+/[A-Za-z0-9/_-]+(\.php/)?[A-Za-z0-9_-]+$@", $method)) { $this->method = $method; } return $this; } function send($wwwroot, $use_cached_peer=true) { $this->peer = get_peer($wwwroot, $use_cached_peer); $this->response = ''; $URL = $this->peer->wwwroot . $this->peer->application->xmlrpcserverurl; $ch = curl_init( $URL ); $this->requesttext = xmlrpc_encode_request($this->method, $this->params, array("encoding" => "utf-8")); $this->signedrequest = xmldsig_envelope($this->requesttext); $this->encryptedrequest = xmlenc_envelope($this->signedrequest, $this->peer->certificate); curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_USERAGENT, 'Mahara'); curl_setopt($ch, CURLOPT_POSTFIELDS, $this->encryptedrequest); curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type: text/xml charset=UTF-8")); $timestamp_send = time(); $this->rawresponse = curl_exec($ch); $response_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); $response_code_prefix = substr($response_code, 0, 1); if ('2' != $response_code_prefix) { if ('4' == $response_code_prefix) { throw new XmlrpcClientException('Client error code: '. $response_code); } else if ('5' == $response_code_prefix) { throw new XmlrpcClientException('An error occurred at the remote server. Code: '. $response_code); } } $timestamp_receive = time(); $remote_timestamp = null; if ($this->rawresponse == false) { throw new XmlrpcClientException('Curl error: '.curl_errno($ch) .':'. curl_error($ch)); return false; } try { $xml = new SimpleXMLElement($this->rawresponse); } catch (Exception $e) { log_debug($this->rawresponse); throw new XmlrpcClientException('Payload is not a valid XML document (payload is above)', 6001); } try { if ($xml->getName() == 'encryptedMessage') { $payload_encrypted = true; $wwwroot = (string)$xml->wwwroot; $payload = xmlenc_envelope_strip($xml); } if ($xml->getName() == 'signedMessage') { $payload_signed = true; $remote_timestamp = $xml->timestamp; $payload = xmldsig_envelope_strip($xml); } } catch (CryptException $e) { throw new XmlrpcClientException("An error occured while decrypting a message sent by $wwwroot. Unable to authenticate the user."); } if ($xml->getName() == 'methodResponse') { $this->response = xmlrpc_decode($payload); // 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; if (!empty($remote_timestamp)) { $remote_timestamp = $remote_timestamp - $hysteresis; $time_offset = $remote_timestamp - $timestamp_send; if ($time_offset > self::get_max_server_time_difference()) { throw new XmlrpcClientException('Time drift ('.$margin_of_error.', '.$time_offset.') is too large.'); } } if (is_array($this->response) && array_key_exists('faultCode', $this->response)) { if ($this->response['faultCode'] == 7025) { log_info('Remote application has sent us a new public key'); // The remote application sent back a new public key, the // old one must have expired if (array_key_exists('faultString', $this->response)) { $details = openssl_x509_parse($this->response['faultString']); if (isset($details['validTo_time_t'])) { $updateobj = (object)array( 'publickey' => $this->response['faultString'], 'publickeyexpires' => $details['validTo_time_t'], ); $whereobj = (object)array( 'wwwroot' => $wwwroot, ); update_record('host', $updateobj, $whereobj); log_info('New key has been imported. Valid until ' . date('Y/m/d h:i:s', $details['validTo_time_t'])); // Send request again. But don't use the cached // peer, look it up again now we've changed the // public key $this->send($wwwroot, false); } else { throw new XmlrpcClientException('Could not parse new public key'); } } else { throw new XmlrpcClientException('Remote site claims to have sent a public key, but they LIE'); } } throw new XmlrpcClientException('Unknown error occured: ' . $this->response['faultCode'] . ': ' . $this->response['faultString']); } // Clean up so object can be re-used. $this->requesttext = ''; $this->signedrequest = ''; $this->encryptedrequest = ''; $this->params = array(); $this->method = ''; return true; } else { throw new XmlrpcClientException('Unrecognized XML document form: ' . $payload); } } /** * 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 $this; } // 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 $this; } private static function get_max_server_time_difference() { $max_time_difference = (int)get_config('xmlrpcmaxservertimedifference'); if ($max_time_difference < 15) { if ($max_time_difference != 0) { // Someone deliberately set it to less than 15 seconds log_warn('Minimum value allowed for "max_server_time_difference" is 15 seconds'); } $max_time_difference = 15; } if ($max_time_difference > 300) { log_warn('Maximum value allowed for "max_server_time_difference" is 300 seconds'); $max_time_difference = 300; } return $max_time_difference; } } ?>