Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/mnet/xmlrpc/client.php
Go to the documentation of this file.
00001 <?php
00011 require_once $CFG->dirroot.'/mnet/lib.php';
00012 
00016 class mnet_xmlrpc_client {
00017 
00018     var $method   = '';
00019     var $params   = array();
00020     var $timeout  = 60;
00021     var $error    = array();
00022     var $response = '';
00023     var $mnet     = null;
00024 
00028     function mnet_xmlrpc_client() {
00029         // make sure we've got this set up before we try and do anything else
00030         $this->mnet = get_mnet_environment();
00031         return true;
00032     }
00033 
00039     function set_timeout($timeout) {
00040         if (!is_integer($timeout)) {
00041             if (is_numeric($timeout)) {
00042                 $this->timeout = (integer)$timeout;
00043                 return true;
00044             }
00045             return false;
00046         }
00047         $this->timeout = $timeout;
00048         return true;
00049     }
00050 
00059     function set_method($xmlrpcpath) {
00060         if (is_string($xmlrpcpath)) {
00061             $this->method = $xmlrpcpath;
00062             $this->params = array();
00063             return true;
00064         }
00065         $this->method = '';
00066         $this->params = array();
00067         return false;
00068     }
00069 
00089     function add_param($argument, $type = 'string') {
00090 
00091         $allowed_types = array('none',
00092                                'empty',
00093                                'base64',
00094                                'boolean',
00095                                'datetime',
00096                                'double',
00097                                'int',
00098                                'i4',
00099                                'string',
00100                                'array',
00101                                'struct');
00102         if (!in_array($type, $allowed_types)) {
00103             return false;
00104         }
00105 
00106         if ($type != 'datetime' && $type != 'base64') {
00107             $this->params[] = $argument;
00108             return true;
00109         }
00110 
00111         // Note weirdness - The type of $argument gets changed to an object with
00112         // value and type properties.
00113         // bool xmlrpc_set_type ( string &value, string type )
00114         xmlrpc_set_type($argument, $type);
00115         $this->params[] = $argument;
00116         return true;
00117     }
00118 
00127     function send($mnet_peer) {
00128         global $CFG, $DB;
00129 
00130 
00131         if (!$this->permission_to_call($mnet_peer)) {
00132             mnet_debug("tried and wasn't allowed to call a method on $mnet_peer->wwwroot");
00133             return false;
00134         }
00135 
00136         $this->requesttext = xmlrpc_encode_request($this->method, $this->params, array("encoding" => "utf-8", "escaping" => "markup"));
00137         $this->signedrequest = mnet_sign_message($this->requesttext);
00138         $this->encryptedrequest = mnet_encrypt_message($this->signedrequest, $mnet_peer->public_key);
00139 
00140         $httprequest = $this->prepare_http_request($mnet_peer);
00141         curl_setopt($httprequest, CURLOPT_POSTFIELDS, $this->encryptedrequest);
00142 
00143         $timestamp_send    = time();
00144         mnet_debug("about to send the curl request");
00145         $this->rawresponse = curl_exec($httprequest);
00146         mnet_debug("managed to complete a curl request");
00147         $timestamp_receive = time();
00148 
00149         if ($this->rawresponse === false) {
00150             $this->error[] = curl_errno($httprequest) .':'. curl_error($httprequest);
00151             return false;
00152         }
00153         curl_close($httprequest);
00154 
00155         $this->rawresponse = trim($this->rawresponse);
00156 
00157         $mnet_peer->touch();
00158 
00159         $crypt_parser = new mnet_encxml_parser();
00160         $crypt_parser->parse($this->rawresponse);
00161 
00162         // If we couldn't parse the message, or it doesn't seem to have encrypted contents,
00163         // give the most specific error msg available & return
00164         if (!$crypt_parser->payload_encrypted) {
00165             if (! empty($crypt_parser->remoteerror)) {
00166                 $this->error[] = '4: remote server error: ' . $crypt_parser->remoteerror;
00167             } else if (! empty($crypt_parser->error)) {
00168                 $crypt_parser_error = $crypt_parser->error[0];
00169 
00170                 $message = '3:XML Parse error in payload: '.$crypt_parser_error['string']."\n";
00171                 if (array_key_exists('lineno', $crypt_parser_error)) {
00172                     $message .= 'At line number: '.$crypt_parser_error['lineno']."\n";
00173                 }
00174                 if (array_key_exists('line', $crypt_parser_error)) {
00175                     $message .= 'Which reads: '.$crypt_parser_error['line']."\n";
00176                 }
00177                 $this->error[] = $message;
00178             } else {
00179                 $this->error[] = '1:Payload not encrypted ';
00180             }
00181 
00182             $crypt_parser->free_resource();
00183             return false;
00184         }
00185 
00186         $key  = array_pop($crypt_parser->cipher);
00187         $data = array_pop($crypt_parser->cipher);
00188 
00189         $crypt_parser->free_resource();
00190 
00191         // Initialize payload var
00192         $decryptedenvelope = '';
00193 
00194         //                                          &$decryptedenvelope
00195         $isOpen = openssl_open(base64_decode($data), $decryptedenvelope, base64_decode($key), $this->mnet->get_private_key());
00196 
00197         if (!$isOpen) {
00198             // Decryption failed... let's try our archived keys
00199             $openssl_history = get_config('mnet', 'openssl_history');
00200             if(empty($openssl_history)) {
00201                 $openssl_history = array();
00202                 set_config('openssl_history', serialize($openssl_history), 'mnet');
00203             } else {
00204                 $openssl_history = unserialize($openssl_history);
00205             }
00206             foreach($openssl_history as $keyset) {
00207                 $keyresource = openssl_pkey_get_private($keyset['keypair_PEM']);
00208                 $isOpen      = openssl_open(base64_decode($data), $decryptedenvelope, base64_decode($key), $keyresource);
00209                 if ($isOpen) {
00210                     // It's an older code, sir, but it checks out
00211                     break;
00212                 }
00213             }
00214         }
00215 
00216         if (!$isOpen) {
00217             trigger_error("None of our keys could open the payload from host {$mnet_peer->wwwroot} with id {$mnet_peer->id}.");
00218             $this->error[] = '3:No key match';
00219             return false;
00220         }
00221 
00222         if (strpos(substr($decryptedenvelope, 0, 100), '<signedMessage>')) {
00223             $sig_parser = new mnet_encxml_parser();
00224             $sig_parser->parse($decryptedenvelope);
00225         } else {
00226             $this->error[] = '2:Payload not signed: ' . $decryptedenvelope;
00227             return false;
00228         }
00229 
00230         // Margin of error is the time it took the request to complete.
00231         $margin_of_error  = $timestamp_receive - $timestamp_send;
00232 
00233         // Guess the time gap between sending the request and the remote machine
00234         // executing the time() function. Marginally better than nothing.
00235         $hysteresis       = ($margin_of_error) / 2;
00236 
00237         $remote_timestamp = $sig_parser->remote_timestamp - $hysteresis;
00238         $time_offset      = $remote_timestamp - $timestamp_send;
00239         if ($time_offset > 0) {
00240             $threshold = get_config('mnet', 'drift_threshold');
00241             if(empty($threshold)) {
00242                 // We decided 15 seconds was a pretty good arbitrary threshold
00243                 // for time-drift between servers, but you can customize this in
00244                 // the config_plugins table. It's not advised though.
00245                 set_config('drift_threshold', 15, 'mnet');
00246                 $threshold = 15;
00247             }
00248             if ($time_offset > $threshold) {
00249                 $this->error[] = '6:Time gap with '.$mnet_peer->name.' ('.$time_offset.' seconds) is greater than the permitted maximum of '.$threshold.' seconds';
00250                 return false;
00251             }
00252         }
00253 
00254         $this->xmlrpcresponse = base64_decode($sig_parser->data_object);
00255         $this->response       = xmlrpc_decode($this->xmlrpcresponse);
00256 
00257         // xmlrpc errors are pushed onto the $this->error stack
00258         if (is_array($this->response) && array_key_exists('faultCode', $this->response)) {
00259             // The faultCode 7025 means we tried to connect with an old SSL key
00260             // The faultString is the new key - let's save it and try again
00261             // The re_key attribute stops us from getting into a loop
00262             if($this->response['faultCode'] == 7025 && empty($mnet_peer->re_key)) {
00263                 mnet_debug('recieved an old-key fault, so trying to get the new key and update our records');
00264                 // If the new certificate doesn't come thru clean_param() unmolested, error out
00265                 if($this->response['faultString'] != clean_param($this->response['faultString'], PARAM_PEM)) {
00266                     $this->error[] = $this->response['faultCode'] . " : " . $this->response['faultString'];
00267                 }
00268                 $record                     = new stdClass();
00269                 $record->id                 = $mnet_peer->id;
00270                 $record->public_key         = $this->response['faultString'];
00271                 $details                    = openssl_x509_parse($record->public_key);
00272                 if(!isset($details['validTo_time_t'])) {
00273                     $this->error[] = $this->response['faultCode'] . " : " . $this->response['faultString'];
00274                 }
00275                 $record->public_key_expires = $details['validTo_time_t'];
00276                 $DB->update_record('mnet_host', $record);
00277 
00278                 // Create a new peer object populated with the new info & try re-sending the request
00279                 $rekeyed_mnet_peer = new mnet_peer();
00280                 $rekeyed_mnet_peer->set_id($record->id);
00281                 $rekeyed_mnet_peer->re_key = true;
00282                 return $this->send($rekeyed_mnet_peer);
00283             }
00284             if (!empty($CFG->mnet_rpcdebug)) {
00285                 if (get_string_manager()->string_exists('error'.$this->response['faultCode'], 'mnet')) {
00286                     $guidance = get_string('error'.$this->response['faultCode'], 'mnet');
00287                 } else {
00288                     $guidance = '';
00289                 }
00290             } else {
00291                 $guidance = '';
00292             }
00293             $this->error[] = $this->response['faultCode'] . " : " . $this->response['faultString'] ."\n".$guidance;
00294         }
00295 
00296         // ok, it's signed, but is it signed with the right certificate ?
00297         // do this *after* we check for an out of date key
00298         $verified = openssl_verify($this->xmlrpcresponse, base64_decode($sig_parser->signature), $mnet_peer->public_key);
00299         if ($verified != 1) {
00300             $this->error[] = 'Invalid signature';
00301         }
00302 
00303         return empty($this->error);
00304     }
00305 
00313     function permission_to_call($mnet_peer) {
00314         global $DB, $CFG, $USER;
00315 
00316         // Executing any system method is permitted.
00317         $system_methods = array('system/listMethods', 'system/methodSignature', 'system/methodHelp', 'system/listServices');
00318         if (in_array($this->method, $system_methods) ) {
00319             return true;
00320         }
00321 
00322         $hostids = array($mnet_peer->id);
00323         if (!empty($CFG->mnet_all_hosts_id)) {
00324             $hostids[] = $CFG->mnet_all_hosts_id;
00325         }
00326         // At this point, we don't care if the remote host implements the
00327         // method we're trying to call. We just want to know that:
00328         // 1. The method belongs to some service, as far as OUR host knows
00329         // 2. We are allowed to subscribe to that service on this mnet_peer
00330 
00331         list($hostidsql, $hostidparams) = $DB->get_in_or_equal($hostids);
00332 
00333         $sql = "SELECT r.id
00334                   FROM {mnet_remote_rpc} r
00335             INNER JOIN {mnet_remote_service2rpc} s2r ON s2r.rpcid = r.id
00336             INNER JOIN {mnet_host2service} h2s ON h2s.serviceid = s2r.serviceid
00337                  WHERE r.xmlrpcpath = ?
00338                        AND h2s.subscribe = ?
00339                        AND h2s.hostid $hostidsql";
00340 
00341         $params = array($this->method, 1);
00342         $params = array_merge($params, $hostidparams);
00343 
00344         if ($DB->record_exists_sql($sql, $params)) {
00345             return true;
00346         }
00347 
00348         $this->error[] = '7:User with ID '. $USER->id .
00349                          ' attempted to call unauthorised method '.
00350                          $this->method.' on host '.
00351                          $mnet_peer->wwwroot;
00352         return false;
00353     }
00354 
00361     function prepare_http_request ($mnet_peer) {
00362         $this->uri = $mnet_peer->wwwroot . $mnet_peer->application->xmlrpc_server_url;
00363 
00364         // Initialize request the target URL
00365         $httprequest = curl_init($this->uri);
00366         curl_setopt($httprequest, CURLOPT_TIMEOUT, $this->timeout);
00367         curl_setopt($httprequest, CURLOPT_RETURNTRANSFER, true);
00368         curl_setopt($httprequest, CURLOPT_POST, true);
00369         curl_setopt($httprequest, CURLOPT_USERAGENT, 'Moodle');
00370         curl_setopt($httprequest, CURLOPT_HTTPHEADER, array("Content-Type: text/xml charset=UTF-8"));
00371         curl_setopt($httprequest, CURLOPT_SSL_VERIFYPEER, false);
00372         curl_setopt($httprequest, CURLOPT_SSL_VERIFYHOST, 0);
00373         return $httprequest;
00374     }
00375 }
 All Data Structures Namespaces Files Functions Variables Enumerations