Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/mnet/xmlrpc/serverlib.php
Go to the documentation of this file.
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('&lt;','&gt;','&amp;','&quot;','&apos;'), $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 
 All Data Structures Namespaces Files Functions Variables Enumerations