|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 00028 /* Strip encryption envelope (if present) and decrypt data 00029 * 00030 * @param string $HTTP_RAW_POST_DATA The XML that the client sent 00031 * 00032 * @throws mnet_server_exception 00033 * 00034 * @return string XML with any encryption envolope removed 00035 */ 00036 function mnet_server_strip_encryption($HTTP_RAW_POST_DATA) { 00037 $remoteclient = get_mnet_remote_client(); 00038 $crypt_parser = new mnet_encxml_parser(); 00039 $crypt_parser->parse($HTTP_RAW_POST_DATA); 00040 $mnet = get_mnet_environment(); 00041 00042 if (!$crypt_parser->payload_encrypted) { 00043 return $HTTP_RAW_POST_DATA; 00044 } 00045 00046 // Make sure we know who we're talking to 00047 $host_record_exists = $remoteclient->set_wwwroot($crypt_parser->remote_wwwroot); 00048 00049 if (false == $host_record_exists) { 00050 throw new mnet_server_exception(7020, 'wrong-wwwroot', $crypt_parser->remote_wwwroot); 00051 } 00052 00053 // This key is symmetric, and is itself encrypted. Can be decrypted using our private key 00054 $key = array_pop($crypt_parser->cipher); 00055 // This data is symmetrically encrypted, can be decrypted using the above key 00056 $data = array_pop($crypt_parser->cipher); 00057 00058 $crypt_parser->free_resource(); 00059 $payload = ''; // Initialize payload var 00060 00061 // &$payload 00062 $isOpen = openssl_open(base64_decode($data), $payload, base64_decode($key), $mnet->get_private_key()); 00063 if ($isOpen) { 00064 $remoteclient->was_encrypted(); 00065 return $payload; 00066 } 00067 00068 // Decryption failed... let's try our archived keys 00069 $openssl_history = get_config('mnet', 'openssl_history'); 00070 if(empty($openssl_history)) { 00071 $openssl_history = array(); 00072 set_config('openssl_history', serialize($openssl_history), 'mnet'); 00073 } else { 00074 $openssl_history = unserialize($openssl_history); 00075 } 00076 foreach($openssl_history as $keyset) { 00077 $keyresource = openssl_pkey_get_private($keyset['keypair_PEM']); 00078 $isOpen = openssl_open(base64_decode($data), $payload, base64_decode($key), $keyresource); 00079 if ($isOpen) { 00080 // It's an older code, sir, but it checks out 00081 $remoteclient->was_encrypted(); 00082 $remoteclient->encrypted_to($keyresource); 00083 $remoteclient->set_pushkey(); 00084 return $payload; 00085 } 00086 } 00087 00088 //If after all that we still couldn't decrypt the message, error out. 00089 throw new mnet_server_exception(7023, 'encryption-invalid'); 00090 } 00091 00092 /* Strip signature envelope (if present), try to verify any signature using our record of remote peer's public key. 00093 * 00094 * @param string $plaintextmessage XML envelope containing XMLRPC request and signature 00095 * 00096 * @return string XMLRPC request 00097 */ 00098 function mnet_server_strip_signature($plaintextmessage) { 00099 $remoteclient = get_mnet_remote_client(); 00100 $sig_parser = new mnet_encxml_parser(); 00101 $sig_parser->parse($plaintextmessage); 00102 00103 if ($sig_parser->signature == '') { 00104 return $plaintextmessage; 00105 } 00106 00107 // Record that the request was signed in some way 00108 $remoteclient->was_signed(); 00109 00110 // Load any information we have about this mnet peer 00111 $remoteclient->set_wwwroot($sig_parser->remote_wwwroot); 00112 00113 $payload = base64_decode($sig_parser->data_object); 00114 $signature = base64_decode($sig_parser->signature); 00115 $certificate = $remoteclient->public_key; 00116 00117 // If we don't have any certificate for the host, don't try to check the signature 00118 // Just return the parsed request 00119 if ($certificate == false) { 00120 return $payload; 00121 } 00122 00123 // Does the signature match the data and the public cert? 00124 $signature_verified = openssl_verify($payload, $signature, $certificate); 00125 if ($signature_verified == 0) { 00126 // $signature was not generated for $payload using $certificate 00127 // Get the key the remote peer is currently publishing: 00128 $currkey = mnet_get_public_key($remoteclient->wwwroot, $remoteclient->application); 00129 // If the key the remote peer is currently publishing is different to $certificate 00130 if($currkey != $certificate) { 00131 // if pushkey is already set, it means the request was encrypted to an old key 00132 // in mnet_server_strip_encryption. 00133 // if we call refresh_key() here before pushing out our new key, 00134 // and the other site ALSO has a new key, 00135 // we'll get into an infinite keyswap loop 00136 // so push just bail here, and push out the new key. 00137 // the next request will get through to refresh_key 00138 if ($remoteclient->pushkey) { 00139 return false; 00140 } 00141 // Try and get the server's new key through trusted means 00142 $remoteclient->refresh_key(); 00143 // If we did manage to re-key, try to verify the signature again using the new public key. 00144 $certificate = $remoteclient->public_key; 00145 $signature_verified = openssl_verify($payload, $signature, $certificate); 00146 } 00147 } 00148 00149 if ($signature_verified == 1) { 00150 $remoteclient->signature_verified(); 00151 $remoteclient->touch(); 00152 } 00153 00154 $sig_parser->free_resource(); 00155 00156 return $payload; 00157 } 00158 00167 function mnet_server_fault($code, $text, $param = null) { 00168 if (!is_numeric($code)) { 00169 $code = 0; 00170 } 00171 $code = intval($code); 00172 return mnet_server_fault_xml($code, $text); 00173 } 00174 00183 function mnet_server_fault_xml($code, $text, $privatekey = null) { 00184 global $CFG; 00185 // Replace illegal XML chars - is this already in a lib somewhere? 00186 $text = str_replace(array('<','>','&','"',"'"), array('<','>','&','"','''), $text); 00187 00188 $return = mnet_server_prepare_response('<?xml version="1.0"?> 00189 <methodResponse> 00190 <fault> 00191 <value> 00192 <struct> 00193 <member> 00194 <name>faultCode</name> 00195 <value><int>'.$code.'</int></value> 00196 </member> 00197 <member> 00198 <name>faultString</name> 00199 <value><string>'.$text.'</string></value> 00200 </member> 00201 </struct> 00202 </value> 00203 </fault> 00204 </methodResponse>', $privatekey); 00205 00206 if ($code != 7025) { // new key responses 00207 mnet_debug("XMLRPC Error Response $code: $text"); 00208 //mnet_debug($return); 00209 } 00210 00211 return $return; 00212 } 00213 00214 00222 function mnet_server_prepare_response($response, $privatekey = null) { 00223 $remoteclient = get_mnet_remote_client(); 00224 if ($remoteclient->request_was_signed) { 00225 $response = mnet_sign_message($response, $privatekey); 00226 } 00227 00228 if ($remoteclient->request_was_encrypted) { 00229 $response = mnet_encrypt_message($response, $remoteclient->public_key); 00230 } 00231 00232 return $response; 00233 } 00234 00248 function mnet_server_dispatch($payload) { 00249 global $CFG, $DB; 00250 $remoteclient = get_mnet_remote_client(); 00251 // xmlrpc_decode_request returns an array of parameters, and the $method 00252 // variable (which is passed by reference) is instantiated with the value from 00253 // the methodName tag in the xml payload 00254 // xmlrpc_decode_request($xml, &$method) 00255 $params = xmlrpc_decode_request($payload, $method); 00256 00257 // $method is something like: "mod/forum/lib.php/forum_add_instance" 00258 // $params is an array of parameters. A parameter might itself be an array. 00259 00260 // Whitelist characters that are permitted in a method name 00261 // The method name must not begin with a / - avoid absolute paths 00262 // A dot character . is only allowed in the filename, i.e. something.php 00263 if (0 == preg_match("@^[A-Za-z0-9]+/[A-Za-z0-9/_\.-]+(\.php/)?[A-Za-z0-9_-]+$@",$method)) { 00264 throw new mnet_server_exception(713, 'nosuchfunction'); 00265 } 00266 00267 if(preg_match("/^system\./", $method)) { 00268 $callstack = explode('.', $method); 00269 } else { 00270 $callstack = explode('/', $method); 00271 // callstack will look like array('mod', 'forum', 'lib.php', 'forum_add_instance'); 00272 } 00273 00279 00280 if (!isset($CFG->mnet_dispatcher_mode) ) { 00281 set_config('mnet_dispatcher_mode', 'off'); 00282 throw new mnet_server_exception(704, 'nosuchservice'); 00283 } elseif ('off' == $CFG->mnet_dispatcher_mode) { 00284 throw new mnet_server_exception(704, 'nosuchservice'); 00285 00287 } elseif ($callstack[0] == 'system') { 00288 $functionname = $callstack[1]; 00289 $xmlrpcserver = xmlrpc_server_create(); 00290 00291 // register all the system methods 00292 $systemmethods = array('listMethods', 'methodSignature', 'methodHelp', 'listServices', 'listFiles', 'retrieveFile', 'keyswap'); 00293 foreach ($systemmethods as $m) { 00294 // I'm adding the canonical xmlrpc references here, however we've 00295 // already forbidden that the period (.) should be allowed in the call 00296 // stack, so if someone tries to access our XMLRPC in the normal way, 00297 // they'll already have received a RPC server fault message. 00298 00299 // Maybe we should allow an easement so that regular XMLRPC clients can 00300 // call our system methods, and find out what we have to offer? 00301 $handler = 'mnet_system'; 00302 if ($m == 'keyswap') { 00303 $handler = 'mnet_keyswap'; 00304 } 00305 if ($method == 'system.' . $m || $method == 'system/' . $m) { 00306 xmlrpc_server_register_method($xmlrpcserver, $method, $handler); 00307 $response = xmlrpc_server_call_method($xmlrpcserver, $payload, $remoteclient, array("encoding" => "utf-8")); 00308 $response = mnet_server_prepare_response($response); 00309 echo $response; 00310 xmlrpc_server_destroy($xmlrpcserver); 00311 return; 00312 } 00313 } 00314 throw new mnet_server_exception(7018, 'nosuchfunction'); 00315 00317 } else { 00318 // anything else comes from some sort of plugin 00319 if ($rpcrecord = $DB->get_record('mnet_rpc', array('xmlrpcpath' => $method))) { 00320 $response = mnet_server_invoke_plugin_method($method, $callstack, $rpcrecord, $payload); 00321 $response = mnet_server_prepare_response($response); 00322 echo $response; 00323 return; 00324 // if the rpc record isn't found, check to see if dangerous mode is on 00326 } else if ('dangerous' == $CFG->mnet_dispatcher_mode && $remoteclient->plaintext_is_ok()) { 00327 $functionname = array_pop($callstack); 00328 00329 $filename = clean_param(implode('/',$callstack), PARAM_PATH); 00330 if (0 == preg_match("/php$/", $filename)) { 00331 // Filename doesn't end in 'php'; possible attack? 00332 // Generate error response - unable to locate function 00333 throw new mnet_server_exception(7012, 'nosuchfunction'); 00334 } 00335 00336 // The call stack holds the path to any include file 00337 $includefile = $CFG->dirroot.'/'.$filename; 00338 00339 $response = mnet_server_invoke_dangerous_method($includefile, $functionname, $method, $payload); 00340 echo $response; 00341 return; 00342 } 00343 } 00344 throw new mnet_server_exception(7012, 'nosuchfunction'); 00345 } 00346 00358 function mnet_system($method, $params, $hostinfo) { 00359 global $CFG, $DB; 00360 00361 if (empty($hostinfo)) return array(); 00362 00363 $id_list = $hostinfo->id; 00364 if (!empty($CFG->mnet_all_hosts_id)) { 00365 $id_list .= ', '.$CFG->mnet_all_hosts_id; 00366 } 00367 00368 if ('system.listMethods' == $method || 'system/listMethods' == $method) { 00369 $query = ' 00370 SELECT DISTINCT 00371 rpc.functionname, 00372 rpc.xmlrpcpath 00373 FROM 00374 {mnet_host2service} h2s 00375 JOIN {mnet_service2rpc} s2r ON h2s.serviceid = s2r.serviceid 00376 JOIN {mnet_rpc} rpc ON s2r.rpcid = rpc.id 00377 JOIN {mnet_service} svc ON svc.id = s2r.serviceid 00378 WHERE 00379 h2s.hostid in ('.$id_list .') AND 00380 h2s.publish = 1 AND rpc.enabled = 1 00381 ' . ((count($params) > 0) ? 'AND svc.name = ? ' : '') . ' 00382 ORDER BY 00383 rpc.xmlrpcpath ASC'; 00384 if (count($params) > 0) { 00385 $params = array($params[0]); 00386 } 00387 $methods = array(); 00388 foreach ($DB->get_records_sql($query, $params) as $result) { 00389 $methods[] = $result->xmlrpcpath; 00390 } 00391 return $methods; 00392 } elseif (in_array($method, array('system.methodSignature', 'system/methodSignature', 'system.methodHelp', 'system/methodHelp'))) { 00393 $query = ' 00394 SELECT DISTINCT 00395 rpc.functionname, 00396 rpc.help, 00397 rpc.profile 00398 FROM 00399 {mnet_host2service} h2s, 00400 {mnet_service2rpc} s2r, 00401 {mnet_rpc} rpc 00402 WHERE 00403 rpc.xmlrpcpath = ? AND 00404 s2r.rpcid = rpc.id AND 00405 h2s.publish = 1 AND rpc.enabled = 1 AND 00406 h2s.serviceid = s2r.serviceid AND 00407 h2s.hostid in ('.$id_list .')'; 00408 $params = array($params[0]); 00409 00410 if (!$result = $DB->get_record_sql($query, $params)) { 00411 return false; 00412 } 00413 if (strpos($method, 'methodSignature') !== false) { 00414 return unserialize($result->profile); 00415 } 00416 return $result->help; 00417 } elseif ('system.listServices' == $method || 'system/listServices' == $method) { 00418 $query = ' 00419 SELECT DISTINCT 00420 s.id, 00421 s.name, 00422 s.apiversion, 00423 h2s.publish, 00424 h2s.subscribe 00425 FROM 00426 {mnet_host2service} h2s, 00427 {mnet_service} s 00428 WHERE 00429 h2s.serviceid = s.id AND 00430 (h2s.publish = 1 OR h2s.subscribe = 1) AND 00431 h2s.hostid in ('.$id_list .') 00432 ORDER BY 00433 s.name ASC'; 00434 $params = array(); 00435 00436 $result = $DB->get_records_sql($query, $params); 00437 $services = array(); 00438 00439 if (is_array($result)) { 00440 foreach($result as $service) { 00441 $services[] = array('name' => $service->name, 00442 'apiversion' => $service->apiversion, 00443 'publish' => $service->publish, 00444 'subscribe' => $service->subscribe); 00445 } 00446 } 00447 00448 return $services; 00449 } 00450 throw new mnet_server_exception(7019, 'nosuchfunction'); 00451 } 00452 00463 function mnet_server_invoke_plugin_method($method, $callstack, $rpcrecord, $payload) { 00464 mnet_verify_permissions($rpcrecord); // will throw exceptions 00465 mnet_setup_dummy_method($method, $callstack, $rpcrecord); 00466 $methodname = array_pop($callstack); 00467 00468 $xmlrpcserver = xmlrpc_server_create(); 00469 xmlrpc_server_register_method($xmlrpcserver, $method, 'mnet_server_dummy_method'); 00470 $response = xmlrpc_server_call_method($xmlrpcserver, $payload, $methodname, array("encoding" => "utf-8")); 00471 xmlrpc_server_destroy($xmlrpcserver); 00472 return $response; 00473 } 00474 00489 function mnet_server_invoke_dangerous_method($includefile, $methodname, $method, $payload) { 00490 00491 if (file_exists($CFG->dirroot . $includefile)) { 00492 require_once $CFG->dirroot . $includefile; 00493 // $callprefix matches the rpc convention 00494 // of not having a leading slash 00495 $callprefix = preg_replace('!^/!', '', $includefile); 00496 } else { 00497 throw new mnet_server_exception(705, "nosuchfile"); 00498 } 00499 00500 if ($functionname != clean_param($functionname, PARAM_PATH)) { 00501 throw new mnet_server_exception(7012, "nosuchfunction"); 00502 } 00503 00504 if (!function_exists($functionname)) { 00505 throw new mnet_server_exception(7012, "nosuchfunction"); 00506 } 00507 $xmlrpcserver = xmlrpc_server_create(); 00508 xmlrpc_server_register_method($xmlrpcserver, $method, 'mnet_server_dummy_method'); 00509 $response = xmlrpc_server_call_method($xmlrpcserver, $payload, $methodname, array("encoding" => "utf-8")); 00510 xmlrpc_server_destroy($xmlrpcserver); 00511 return $response; 00512 } 00513 00514 00526 function mnet_keyswap($function, $params) { 00527 global $CFG; 00528 $return = array(); 00529 $mnet = get_mnet_environment(); 00530 00531 if (!empty($CFG->mnet_register_allhosts)) { 00532 $mnet_peer = new mnet_peer(); 00533 @list($wwwroot, $pubkey, $application) = each($params); 00534 $keyok = $mnet_peer->bootstrap($wwwroot, $pubkey, $application); 00535 if ($keyok) { 00536 $mnet_peer->commit(); 00537 } 00538 } 00539 return $mnet->public_key; 00540 } 00541 00550 function mnet_verify_permissions($rpcrecord) { 00551 global $CFG, $DB; 00552 $remoteclient = get_mnet_remote_client(); 00553 00554 $id_list = $remoteclient->id; 00555 if (!empty($CFG->mnet_all_hosts_id)) { 00556 $id_list .= ', '.$CFG->mnet_all_hosts_id; 00557 } 00558 00559 $sql = "SELECT 00560 r.*, h2s.publish 00561 FROM 00562 {mnet_rpc} r 00563 JOIN {mnet_service2rpc} s2r ON s2r.rpcid = r.id 00564 LEFT JOIN {mnet_host2service} h2s ON h2s.serviceid = s2r.serviceid 00565 WHERE 00566 r.id = ? AND 00567 h2s.hostid in ($id_list)"; 00568 00569 $params = array($rpcrecord->id); 00570 00571 if (!$permission = $DB->get_record_sql($sql, $params)) { 00572 throw new mnet_server_exception(7012, "nosuchfunction"); 00573 } else if (!$permission->publish || !$permission->enabled) { 00574 throw new mnet_server_exception(707, "nosuchfunction"); 00575 } 00576 } 00577 00588 function mnet_setup_dummy_method($method, $callstack, $rpcrecord) { 00589 global $CFG; 00590 $remoteclient = get_mnet_remote_client(); 00591 // verify that the callpath in the stack matches our records 00592 // callstack will look like array('mod', 'forum', 'lib.php', 'forum_add_instance'); 00593 $path = get_plugin_directory($rpcrecord->plugintype, $rpcrecord->pluginname); 00594 $path = substr($path, strlen($CFG->dirroot)+1); // this is a bit hacky and fragile, it is not guaranteed that plugins are in dirroot 00595 array_pop($callstack); 00596 $providedpath = implode('/', $callstack); 00597 if ($providedpath != $path . '/' . $rpcrecord->filename) { 00598 throw new mnet_server_exception(705, "nosuchfile"); 00599 } 00600 if (!file_exists($CFG->dirroot . '/' . $providedpath)) { 00601 throw new mnet_server_exception(705, "nosuchfile"); 00602 } 00603 require_once($CFG->dirroot . '/' . $providedpath); 00604 if (!empty($rpcrecord->classname)) { 00605 if (!class_exists($rpcrecord->classname)) { 00606 throw new mnet_server_exception(708, 'nosuchclass'); 00607 } 00608 if (!$rpcrecord->static) { 00609 try { 00610 $object = new $rpcrecord->classname; 00611 } catch (Exception $e) { 00612 throw new mnet_server_exception(709, "classerror"); 00613 } 00614 if (!is_callable(array($object, $rpcrecord->functionname))) { 00615 throw new mnet_server_exception(706, "nosuchfunction"); 00616 } 00617 $remoteclient->object_to_call($object); 00618 } else { 00619 if (!is_callable(array($rpcrecord->classname, $rpcrecord->functionname))) { 00620 throw new mnet_server_exception(706, "nosuchfunction"); 00621 } 00622 $remoteclient->static_location($rpcrecord->classname); 00623 } 00624 } 00625 } 00626 00647 function mnet_server_dummy_method($methodname, $argsarray, $functionname) { 00648 $remoteclient = get_mnet_remote_client(); 00649 try { 00650 if (is_object($remoteclient->object_to_call)) { 00651 return @call_user_func_array(array($remoteclient->object_to_call,$functionname), $argsarray); 00652 } else if (!empty($remoteclient->static_location)) { 00653 return @call_user_func_array(array($remoteclient->static_location, $functionname), $argsarray); 00654 } else { 00655 return @call_user_func_array($functionname, $argsarray); 00656 } 00657 } catch (Exception $e) { 00658 exit(mnet_server_fault($e->getCode(), $e->getMessage())); 00659 } 00660 } 00666 class mnet_server_exception extends moodle_exception { 00667 00674 public function __construct($intcode, $languagekey, $module='mnet', $a=null) { 00675 parent::__construct($languagekey, $module, '', $a); 00676 $this->code = $intcode; 00677 00678 } 00679 } 00680