diff --git a/admin/mnet/testclient.php b/admin/mnet/testclient.php new file mode 100644 index 00000000000..1394c6322fa --- /dev/null +++ b/admin/mnet/testclient.php @@ -0,0 +1,195 @@ +dirroot.'/mnet/xmlrpc/client.php'; +require_once($CFG->libdir.'/adminlib.php'); +include_once($CFG->dirroot.'/mnet/lib.php'); + +if ($CFG->mnet_dispatcher_mode === 'off') { + print_error('mnetdisabled', 'mnet'); +} + +require_login(); +admin_externalpage_setup('mnettestclient'); + +$context = get_context_instance(CONTEXT_SYSTEM); +require_capability('moodle/site:config', $context); + +error_reporting(E_ALL); + +admin_externalpage_print_header(); +if (!extension_loaded('openssl')) { + print_error('requiresopenssl', 'mnet', '', NULL, true); +} + +// optional drilling down parameters +$hostid = optional_param('hostid', 0, PARAM_INT); +$servicename = optional_param('servicename', '', PARAM_SAFEDIR); +$methodid = optional_param('method', 0, PARAM_INT); + +$hosts = $DB->get_records('mnet_host'); +$moodleapplicationid = $DB->get_field('mnet_application', 'id', array('name' => 'moodle')); + +$url = new moodle_url('/admin/mnet/testclient.php'); +$PAGE->set_url($url); + +echo $OUTPUT->heading(get_string('hostlist', 'mnet')); +foreach ($hosts as $id => $host) { + if (empty($host->wwwroot) || $host->wwwroot == $CFG->wwwroot) { + continue; + } + $newurl = new moodle_url($url, array('hostid' => $host->id)); + echo '

' . $OUTPUT->link($newurl, $host->wwwroot) . '

'; +} + +if (!empty($hostid) && array_key_exists($hostid, $hosts)) { + $host = $hosts[$hostid]; + if ($host->applicationid != $moodleapplicationid) { + echo $OUTPUT->notification(get_string('notmoodleapplication', 'mnet')); + } + $mnet_peer = new mnet_peer(); + $mnet_peer->set_wwwroot($host->wwwroot); + + $mnet_request = new mnet_xmlrpc_client(); + + $mnet_request->set_method('system/listServices'); + $mnet_request->send($mnet_peer); + $services = $mnet_request->response; + $yesno = array('No', 'Yes'); + $servicenames = array(); + + echo $OUTPUT->heading(get_string('servicesavailableonhost', 'mnet', $host->wwwroot)); + + $table = new html_table(); + $table->head = array( + get_string('serviceid', 'mnet'), + get_string('service', 'mnet'), + get_string('version', 'mnet'), + get_string('theypublish', 'mnet'), + get_string('theysubscribe', 'mnet'), + get_string('options', 'mnet'), + ); + $table->data = array(); + $sql = 'SELECT s.name, min(r.plugintype) AS plugintype, min(r.pluginname) AS pluginname + FROM {mnet_service} s + JOIN {mnet_service2rpc} s2r ON s2r.serviceid = s.id + JOIN {mnet_rpc} r ON r.id = s2r.rpcid + GROUP BY s.name'; + + $yesno = array(get_string('no'), get_string('yes')); + + $serviceinfo = $DB->get_records_sql($sql); + foreach ($services as $id => $servicedata) { + if (array_key_exists($servicedata['name'], $serviceinfo)) { + $service = $serviceinfo[$servicedata['name']]; + $servicedata['humanname'] = get_string($servicedata['name'].'_name', $service->plugintype . '_' . $service->pluginname); + } else { + $servicedata['humanname'] = get_string('unknown', 'mnet'); + } + $newurl = new moodle_url($url, array('hostid' => $host->id, 'servicename' => $servicedata['name'])); + $table->data[] = array( + $servicedata['name'], + $servicedata['humanname'], + $servicedata['apiversion'], + $yesno[$servicedata['publish']], + $yesno[$servicedata['subscribe']], + $OUTPUT->link($newurl, get_string('listservices', 'mnet')) + ); + + } + echo $OUTPUT->table($table); + + + $mnet_request->set_method('system/listMethods'); + if (isset($servicename) && array_key_exists($servicename, $serviceinfo)) { + echo $OUTPUT->heading(get_string('methodsavailableonhostinservice', 'mnet', array('host' => $host->wwwroot, 'service' => $servicename))); + $service = $serviceinfo[$servicename]; + $mnet_request->add_param($servicename, 'string'); + } else { + echo $OUTPUT->heading(get_string('methodsavailableonhost', 'mnet', $host->wwwroot)); + } + + $mnet_request->send($mnet_peer); + $methods = $mnet_request->response; + + + $table = new html_table(); + $table->head = array( + get_string('method', 'mnet'), + get_string('options', 'mnet'), + ); + $table->data = array(); + + foreach ($methods as $id => $method) { + $params = array('hostid' => $host->id, 'method' => $id); + if (isset($servicename)) { + $params['servicename'] = $servicename; + } + $newurl = new moodle_url($url, $params); + $table->data[] = array( + $method, + $OUTPUT->link($newurl, get_string('inspect', 'mnet')) + ); + } + echo $OUTPUT->table($table); + + if (isset($methodid) && array_key_exists($methodid, $methods)) { + $method = $methods[$methodid]; + + $mnet_request = new mnet_xmlrpc_client(); + $mnet_request->set_method('system/methodSignature'); + $mnet_request->add_param($method, 'string'); + $mnet_request->send($mnet_peer); + $signature = $mnet_request->response; + + echo $OUTPUT->heading(get_string('methodsignature', 'mnet', $method)); + + $table = new html_table(); + $table->head = array( + get_string('position', 'mnet'), + get_string('name', 'mnet'), + get_string('type', 'mnet'), + get_string('description', 'mnet'), + ); + $table->data = array(); + + $params = $signature['parameters']; + foreach ($params as $pos => $details) { + $table->data[] = array( + $pos, + $details['name'], + $details['type'], + $details['description'], + ); + } + $table->data[] = array( + get_string('returnvalue', 'mnet'), + '', + $signature['return']['type'], + $signature['return']['description'] + ); + + echo $OUTPUT->table($table); + + $mnet_request->set_method('system/methodHelp'); + $mnet_request->add_param($method, 'string'); + $mnet_request->send($mnet_peer); + $help = $mnet_request->response; + + echo $OUTPUT->heading(get_string('methodhelp', 'mnet', $method)); + echo(str_replace('\n', '
',$help)); + } +} + +echo $OUTPUT->footer(); +?> diff --git a/auth/mnet/db/mnet.php b/auth/mnet/db/mnet.php new file mode 100644 index 00000000000..6a64312cbcd --- /dev/null +++ b/auth/mnet/db/mnet.php @@ -0,0 +1,52 @@ +. + + +/** + * This file contains the mnet services for the mnet authentication plugin + * + * @since 2.0 + * @package moodlecore + * @subpackage auth + * @copyright 2010 Penny Leach + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +$publishes = array( + 'sso_idp' => array( + 'apiversion' => 1, + 'classname' => 'auth_plugin_mnet', + 'filename' => 'auth.php', + 'methods' => array( + 'user_authorise', + 'keepalive_server', + 'kill_children', + 'refresh_log', + 'fetch_user_image', + 'fetch_theme_info', + 'update_enrolments', + ), + ), + 'sso_sp' => array( + 'apiversion' => 1, + 'classname' => 'auth_plugin_mnet', + 'filename' => 'auth.php', + 'methods' => array( + 'keepalive_client', + 'kill_child' + ) + ) +); diff --git a/enrol/mnet/db/mnet.php b/enrol/mnet/db/mnet.php new file mode 100644 index 00000000000..7edace29b1f --- /dev/null +++ b/enrol/mnet/db/mnet.php @@ -0,0 +1,41 @@ +. + + +/** + * This file contains the mnet services for the mnet enrolment plugin + * + * @since 2.0 + * @package moodlecore + * @subpackage enrolment + * @copyright 2010 Penny Leach + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +$publishes = array( + 'mnet_enrol' => array( + 'apiversion' => 1, + 'classname' => 'enrolment_plugin_mnet', + 'filename' => 'enrol.php', + 'methods' => array( + 'available_courses', + 'user_enrolments', + 'enrol_user', + 'unenrol_user', + 'course_enrolments' + ), + ), +); diff --git a/enrol/mnet/version.php b/enrol/mnet/version.php new file mode 100644 index 00000000000..73d25f9b7a6 --- /dev/null +++ b/enrol/mnet/version.php @@ -0,0 +1,3 @@ +version = 2010012600; diff --git a/mnet/rpclib.php b/mnet/rpclib.php deleted file mode 100644 index b56a3a4de7b..00000000000 --- a/mnet/rpclib.php +++ /dev/null @@ -1,78 +0,0 @@ -first = 'last'; - $this->last = 'first'; - } - - function augment_first($newval) { - $this->first = $this->first.$newval; - return $this->first; - } - - function augment_first_RPC_OK() { - return true; - } - - function mnet_concatenate_strings_RPC_OK() { - return true; - } - function mnet_concatenate_strings($string1='', $string2='', $string3='') { - return $string1.$string2.$string3; - } -} diff --git a/mnet/testclient.php b/mnet/testclient.php deleted file mode 100644 index 6b8bbf938bb..00000000000 --- a/mnet/testclient.php +++ /dev/null @@ -1,144 +0,0 @@ -dirroot.'/mnet/xmlrpc/client.php'; - -if ($CFG->mnet_dispatcher_mode === 'off') { - print_error('mnetdisabled', 'mnet'); -} - -// Site admins only, thanks. -require_login(); -$context = get_context_instance(CONTEXT_SYSTEM); -require_capability('moodle/site:config', $context); - -error_reporting(E_ALL); - -// Some HTML sugar -echo ''; -?> - - -Moodle MNET Test Client -

Hosts

-get_records('mnet_host'); - -foreach ($hosts as $id => $host) { - // Skip the 'all hosts' option - if(empty($host->wwwroot)) continue; - // Skip localhost - if($host->wwwroot == $CFG->wwwroot) continue; - // Skip non-moodle non-mahara hosts - if($host->applicationid != 1 && $host->applicationid != 2) continue; //TODO: get rid of magic numbers. - echo '

'.$host->wwwroot."

\n"; -} - -if (!empty($_GET['hostid']) && array_key_exists($_GET['hostid'], $hosts)) { - $host = $hosts[$_GET['hostid']]; - $mnet_peer = new mnet_peer(); - $mnet_peer->set_wwwroot($host->wwwroot); - - $mnet_request = new mnet_xmlrpc_client(); - - // Tell it the path to the method that we want to execute - $mnet_request->set_method('system/listServices'); - $mnet_request->send($mnet_peer); - $services = $mnet_request->response; - $yesno = array('No', 'Yes'); - $servicenames = array(); - - echo '

Services available on host: '.$host->wwwroot .'

'; - foreach ($services as $id => $service) { - $sql = 'select c.id, c.parent_type, c.parent from {mnet_service2rpc} a, {mnet_service} b, {mnet_rpc} c where a.serviceid = b.id and b.name=? and c.id = a.rpcid '; - - echo ' - '; - if ($detail = $DB->get_record_sql($sql, array($service['name']))) { - $service['humanname'] = get_string($service['name'].'_name', $detail->parent_type.'_'.$detail->parent); - echo ''; - } else { - $service['humanname'] = $service['name']; - echo ''; - } - echo ' - - - - - '."\n"; - $servicenames[$service['name']] = $service; - } - echo '
  Service ID    Service    Version    They Publish    They Subscribe  
'.$service['name'].''.$service['humanname'].' unknown '.$service['apiversion'].''.$yesno[$service['publish']].''.$yesno[$service['subscribe']].'List methods
'; - - - - if (isset($_GET['service']) && array_key_exists($_GET['service'], $servicenames)) { - $service = $servicenames[$_GET['service']]; - // Tell it the path to the method that we want to execute - $mnet_request->set_method('system/listMethods'); - $mnet_request->add_param($service['name'], 'string'); - $mnet_request->send($mnet_peer); - $methods = $mnet_request->response; - - echo '

Methods in the '.$service['humanname'] .' service

'; - foreach ($methods as $id => $method) { - echo ''."\n"; - } - echo '
MethodOptions
'.$method.' Inspect
'; - } else { - // Tell it the path to the method that we want to execute - $mnet_request->set_method('system/listMethods'); - $mnet_request->send($mnet_peer); - $methods = $mnet_request->response; - - echo '

Methods '.$host->wwwroot .'

'; - foreach ($methods as $id => $method) { - echo ''."\n"; - } - echo '
MethodOptions
'.$method.' Inspect
'; - } - - if (isset($_GET['method']) && array_key_exists($_GET['method'], $methods)) { - $method = $methods[$_GET['method']]; - - $mnet_request = new mnet_xmlrpc_client(); - - // Tell it the path to the method that we want to execute - $mnet_request->set_method('system/methodSignature'); - $mnet_request->add_param($method, 'string'); - $mnet_request->send($mnet_peer); - $signature = $mnet_request->response; - echo '

Method signature for '.$method.':

'; - $params = array_pop($signature); - foreach ($params as $pos => $details) { - echo ''; - } - echo '
PositionTypeDescription
'.$pos.''.$details['type'].''.$details['description'].'
'; - - // Tell it the path to the method that we want to execute - $mnet_request->set_method('system/methodHelp'); - $mnet_request->add_param($method, 'string'); - $mnet_request->send($mnet_peer); - $help = $mnet_request->response; - echo '

Help details from docblock for '.$method.':

'; - echo(str_replace('\n', '
',$help)); - echo ''; - } -} - - -?> - - diff --git a/mnet/xmlrpc/serverlib.php b/mnet/xmlrpc/serverlib.php new file mode 100644 index 00000000000..be0cef5194e --- /dev/null +++ b/mnet/xmlrpc/serverlib.php @@ -0,0 +1,676 @@ +parse($HTTP_RAW_POST_DATA); + + if (!$crypt_parser->payload_encrypted) { + return $HTTP_RAW_POST_DATA; + } + + // Make sure we know who we're talking to + $host_record_exists = $MNET_REMOTE_CLIENT->set_wwwroot($crypt_parser->remote_wwwroot); + + if (false == $host_record_exists) { + throw new mnet_server_exception(7020, 'wrong-wwwroot', $crypt_parser->remote_wwwroot); + } + + // This key is symmetric, and is itself encrypted. Can be decrypted using our private key + $key = array_pop($crypt_parser->cipher); + // This data is symmetrically encrypted, can be decrypted using the above key + $data = array_pop($crypt_parser->cipher); + + $crypt_parser->free_resource(); + $payload = ''; // Initialize payload var + + // &$payload + $isOpen = openssl_open(base64_decode($data), $payload, base64_decode($key), $MNET->get_private_key()); + if ($isOpen) { + $MNET_REMOTE_CLIENT->was_encrypted(); + return $payload; + } + + // 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 + + $MNET_REMOTE_CLIENT->was_encrypted(); + $MNET_REMOTE_CLIENT->encrypted_to($keyresource); + $MNET_REMOTE_CLIENT->set_pushkey(); + return $payload; + } + } + + //If after all that we still couldn't decrypt the message, error out. + throw new mnet_server_exception(7023, 'encryption-invalid'); +} + +/* Strip signature envelope (if present), try to verify any signature using our record of remote peer's public key. + * + * @param string $plaintextmessage XML envelope containing XMLRPC request and signature + * + * @return string XMLRPC request + */ +function mnet_server_strip_signature($plaintextmessage) { + global $MNET, $MNET_REMOTE_CLIENT; + $sig_parser = new mnet_encxml_parser(); + $sig_parser->parse($plaintextmessage); + + if ($sig_parser->signature == '') { + return $plaintextmessage; + } + + // Record that the request was signed in some way + $MNET_REMOTE_CLIENT->was_signed(); + + // Load any information we have about this mnet peer + $MNET_REMOTE_CLIENT->set_wwwroot($sig_parser->remote_wwwroot); + + $payload = base64_decode($sig_parser->data_object); + $signature = base64_decode($sig_parser->signature); + $certificate = $MNET_REMOTE_CLIENT->public_key; + + // If we don't have any certificate for the host, don't try to check the signature + // Just return the parsed request + if ($certificate == false) { + return $payload; + } + + // Does the signature match the data and the public cert? + $signature_verified = openssl_verify($payload, $signature, $certificate); + if ($signature_verified == 0) { + // $signature was not generated for $payload using $certificate + // Get the key the remote peer is currently publishing: + $currkey = mnet_get_public_key($MNET_REMOTE_CLIENT->wwwroot, $MNET_REMOTE_CLIENT->application); + // If the key the remote peer is currently publishing is different to $certificate + if($currkey != $certificate) { + // Try and get the server's new key through trusted means + $MNET_REMOTE_CLIENT->refresh_key(); + // If we did manage to re-key, try to verify the signature again using the new public key. + $certificate = $MNET_REMOTE_CLIENT->public_key; + $signature_verified = openssl_verify($payload, $signature, $certificate); + } + } + + if ($signature_verified == 1) { + $MNET_REMOTE_CLIENT->signature_verified(); + $MNET_REMOTE_CLIENT->touch(); + } + + $sig_parser->free_resource(); + + return $payload; +} + +/** + * Return the proper XML-RPC content to report an error in the local language. + * + * @param int $code The ID code of the error message + * @param string $text The array-key of the error message in the lang file + * or the full string (will be detected by the function + * @param string $param The $a param for the error message in the lang file + * @return string $text The text of the error message + */ +function mnet_server_fault($code, $text, $param = null) { + global $MNET_REMOTE_CLIENT; + if (!is_numeric($code)) { + $code = 0; + } + $code = intval($code); + + $string = get_string($text, 'mnet', $param); + if (strpos($string, '[[') === 0) { + $string = $text; + } + + return mnet_server_fault_xml($code, $string); +} + +/** + * Return the proper XML-RPC content to report an error. + * + * @param int $code The ID code of the error message + * @param string $text The error message + * @param resource $privatekey The private key that should be used to sign the response + * @return string $text The XML text of the error message + */ +function mnet_server_fault_xml($code, $text, $privatekey = null) { + global $MNET_REMOTE_CLIENT, $CFG; + // Replace illegal XML chars - is this already in a lib somewhere? + $text = str_replace(array('<','>','&','"',"'"), array('<','>','&','"','''), $text); + + $return = mnet_server_prepare_response(' + + + + + + faultCode + '.$code.' + + + faultString + '.$text.' + + + + +', $privatekey); + + if (!empty($CFG->mnet_rpcdebug)) { + trigger_error("XMLRPC Error Response $code: $text"); + trigger_error(print_r($return,1)); + } + + return $return; +} + + +/** + * Package a response in any required envelope, and return it to the client + * + * @param string $response The XMLRPC response string + * @param resource $privatekey The private key to sign the response with + * @return string The encoded response string + */ +function mnet_server_prepare_response($response, $privatekey = null) { + global $MNET_REMOTE_CLIENT; + + if ($MNET_REMOTE_CLIENT->request_was_signed) { + $response = mnet_sign_message($response, $privatekey); + } + + if ($MNET_REMOTE_CLIENT->request_was_encrypted) { + $response = mnet_encrypt_message($response, $MNET_REMOTE_CLIENT->public_key); + } + + return $response; +} + +/** + * If security checks are passed, dispatch the request to the function/method + * + * The config variable 'mnet_dispatcher_mode' can be: + * strict: Only execute functions that are in specific files + * off: The default - don't execute anything + * + * @param string $payload The XML-RPC request + * + * @throws mnet_server_exception + * + * @return No return val - just echo the response + */ +function mnet_server_dispatch($payload) { + global $CFG, $MNET_REMOTE_CLIENT, $DB; + // xmlrpc_decode_request returns an array of parameters, and the $method + // variable (which is passed by reference) is instantiated with the value from + // the methodName tag in the xml payload + // xmlrpc_decode_request($xml, &$method) + $params = xmlrpc_decode_request($payload, $method); + + // $method is something like: "mod/forum/lib.php/forum_add_instance" + // $params is an array of parameters. A parameter might itself be an array. + + // Whitelist characters that are permitted in a method name + // The method name must not begin with a / - avoid absolute paths + // A dot character . is only allowed in the filename, i.e. something.php + if (0 == preg_match("@^[A-Za-z0-9]+/[A-Za-z0-9/_\.-]+(\.php/)?[A-Za-z0-9_-]+$@",$method)) { + throw new mnet_server_exception(713, 'nosuchfunction'); + } + + if(preg_match("/^system\./", $method)) { + $callstack = explode('.', $method); + } else { + $callstack = explode('/', $method); + // callstack will look like array('mod', 'forum', 'lib.php', 'forum_add_instance'); + } + + /** + * What has the site administrator chosen as his dispatcher setting? + * strict: Only execute functions that are in specific files + * off: The default - don't execute anything + */ + ////////////////////////////////////// OFF + if (!isset($CFG->mnet_dispatcher_mode) ) { + set_config('mnet_dispatcher_mode', 'off'); + throw new mnet_server_exception(704, 'nosuchservice'); + } elseif ('off' == $CFG->mnet_dispatcher_mode) { + throw new mnet_server_exception(704, 'nosuchservice'); + + ////////////////////////////////////// SYSTEM METHODS + } elseif ($callstack[0] == 'system') { + $functionname = $callstack[1]; + $xmlrpcserver = xmlrpc_server_create(); + + // register all the system methods + $systemmethods = array('listMethods', 'methodSignature', 'methodHelp', 'listServices', 'listFiles', 'retrieveFile', 'keyswap'); + foreach ($systemmethods as $m) { + // I'm adding the canonical xmlrpc references here, however we've + // already forbidden that the period (.) should be allowed in the call + // stack, so if someone tries to access our XMLRPC in the normal way, + // they'll already have received a RPC server fault message. + + // Maybe we should allow an easement so that regular XMLRPC clients can + // call our system methods, and find out what we have to offer? + $handler = 'mnet_system'; + if ($m == 'keyswap') { + $handler = 'mnet_keyswap'; + } + if ($method == 'system.' . $m || $method == 'system/' . $m) { + xmlrpc_server_register_method($xmlrpcserver, $method, $handler); + $response = xmlrpc_server_call_method($xmlrpcserver, $payload, $MNET_REMOTE_CLIENT, array("encoding" => "utf-8")); + $response = mnet_server_prepare_response($response); + echo $response; + xmlrpc_server_destroy($xmlrpcserver); + return; + } + } + throw new mnet_server_exception(7018, 'nosuchfunction'); + + //////////////////////////////////// NORMAL PLUGIN DISPATCHER + } else { + // anything else comes from some sort of plugin + if ($rpcrecord = $DB->get_record('mnet_rpc', array('xmlrpc_path' => $method))) { + $response = mnet_server_invoke_plugin_method($method, $callstack, $rpcrecord, $payload); + $response = mnet_server_prepare_response($response); + echo $response; + return; + // if the rpc record isn't found, check to see if dangerous mode is on + ////////////////////////////////////// DANGEROUS + } else if ('dangerous' == $CFG->mnet_dispatcher_mode && $MNET_REMOTE_CLIENT->plaintext_is_ok()) { + $functionname = array_pop($callstack); + + $filename = clean_param(implode('/',$callstack), PARAM_PATH); + if (0 == preg_match("/php$/", $filename)) { + // Filename doesn't end in 'php'; possible attack? + // Generate error response - unable to locate function + throw new mnet_server_exception(7012, 'nosuchfunction'); + } + + // The call stack holds the path to any include file + $includefile = $CFG->dirroot.'/'.$filename; + + $response = mnet_server_invoke_dangerous_method($includefile, $functionname, $method, $payload); + echo $response; + return; + } + } + throw new mnet_server_exception(7012, 'nosuchfunction'); +} + +/** + * Execute the system functions - mostly for introspection + * + * @param string $method XMLRPC method name, e.g. system.listMethods + * @param array $params Array of parameters from the XMLRPC request + * @param string $hostinfo Hostinfo object from the mnet_host table + * + * @throws mnet_server_exception + * + * @return mixed Response data - any kind of PHP variable + */ +function mnet_system($method, $params, $hostinfo) { + global $CFG, $DB; + + if (empty($hostinfo)) return array(); + + $id_list = $hostinfo->id; + if (!empty($CFG->mnet_all_hosts_id)) { + $id_list .= ', '.$CFG->mnet_all_hosts_id; + } + + if ('system.listMethods' == $method || 'system/listMethods' == $method) { + $query = ' + SELECT DISTINCT + rpc.function_name, + rpc.xmlrpc_path + FROM + {mnet_host2service} h2s + JOIN {mnet_service2rpc} s2r ON h2s.serviceid = s2r.serviceid + JOIN {mnet_rpc} rpc ON s2r.rpcid = rpc.id + JOIN {mnet_service} svc ON svc.id = s2r.serviceid + WHERE + h2s.hostid in ('.$id_list .') AND + h2s.publish = 1 AND rpc.enabled = 1 + ' . ((count($params) > 0) ? 'AND svc.name = ? ' : '') . ' + ORDER BY + rpc.xmlrpc_path ASC'; + if (count($params) > 0) { + $params = array($params[0]); + } + $methods = array(); + foreach ($DB->get_records_sql($query, $params) as $result) { + $methods[] = $result->xmlrpc_path; + } + return $methods; + } elseif (in_array($method, array('system.methodSignature', 'system/methodSignature', 'system.methodHelp', 'system/methodHelp'))) { + $query = ' + SELECT DISTINCT + rpc.function_name, + rpc.help, + rpc.profile + FROM + {mnet_host2service} h2s, + {mnet_service2rpc} s2r, + {mnet_rpc} rpc + WHERE + rpc.xmlrpc_path = ? AND + s2r.rpcid = rpc.id AND + h2s.publish = 1 AND rpc.enabled = 1 AND + h2s.serviceid = s2r.serviceid AND + h2s.hostid in ('.$id_list .')'; + $params = array($params[0]); + + if (!$result = $DB->get_record_sql($query, $params)) { + return false; + } + if (strpos($method, 'methodSignature') !== false) { + return unserialize($result->profile); + } + return $result->help; + } elseif ('system.listServices' == $method || 'system/listServices' == $method) { + $query = ' + SELECT DISTINCT + s.id, + s.name, + s.apiversion, + h2s.publish, + h2s.subscribe + FROM + {mnet_host2service} h2s, + {mnet_service} s + WHERE + h2s.serviceid = s.id AND + (h2s.publish = 1 OR h2s.subscribe = 1) AND + h2s.hostid in ('.$id_list .') + ORDER BY + s.name ASC'; + $params = array(); + + $result = $DB->get_records_sql($query, $params); + $services = array(); + + if (is_array($result)) { + foreach($result as $service) { + $services[] = array('name' => $service->name, + 'apiversion' => $service->apiversion, + 'publish' => $service->publish, + 'subscribe' => $service->subscribe); + } + } + + return $services; + } + throw new mnet_server_exception(7019, 'nosuchfunction'); +} + +/** + * Invoke a normal style plugin method + * This will verify permissions first. + * + * @param string $method the full xmlrpc method that was called eg auth/mnet/auth.php/user_authorise + * @param array $callstack the exploded callstack + * @param stdclass $rpcrecord the record from mnet_rpc + * + * @return mixed the response from the invoked method + */ +function mnet_server_invoke_plugin_method($method, $callstack, $rpcrecord, $payload) { + mnet_verify_permissions($rpcrecord); // will throw exceptions + mnet_setup_dummy_method($method, $callstack, $rpcrecord); + $methodname = array_pop($callstack); + + $xmlrpcserver = xmlrpc_server_create(); + xmlrpc_server_register_method($xmlrpcserver, $method, 'mnet_server_dummy_method'); + $response = xmlrpc_server_call_method($xmlrpcserver, $payload, $methodname, array("encoding" => "utf-8")); + xmlrpc_server_destroy($xmlrpcserver); + return $response; +} + +/** + * Initialize the object (if necessary), execute the method or function, and + * return the response + * + * @param string $includefile The file that contains the object definition + * @param string $methodname The name of the method to execute + * @param string $method The full path to the method + * @param string $payload The XML-RPC request payload + * @param string $class The name of the class to instantiate (or false) + * + * @throws mnet_server_exception + * + * @return string The XML-RPC response + */ +function mnet_server_invoke_dangerous_method($includefile, $methodname, $method, $payload) { + + if (file_exists($CFG->dirroot . $includefile)) { + require_once $CFG->dirroot . $includefile; + // $callprefix matches the rpc convention + // of not having a leading slash + $callprefix = preg_replace('!^/!', '', $includefile); + } else { + throw new mnet_server_exception(705, "nosuchfile"); + } + + if ($functionname != clean_param($functionname, PARAM_PATH)) { + throw new mnet_server_exception(7012, "nosuchfunction"); + } + + if (!function_exists($functionname)) { + throw new mnet_server_exception(7012, "nosuchfunction"); + } + $xmlrpcserver = xmlrpc_server_create(); + xmlrpc_server_register_method($xmlrpcserver, $method, 'mnet_server_dummy_method'); + $response = xmlrpc_server_call_method($xmlrpcserver, $payload, $methodname, array("encoding" => "utf-8")); + xmlrpc_server_destroy($xmlrpcserver); + return $response; +} + + +/** + * Accepts a public key from a new remote host and returns the public key for + * this host. If 'register all hosts' is turned on, it will bootstrap a record + * for the remote host in the mnet_host table (if it's not already there) + * + * @param string $function XML-RPC requires this but we don't... discard! + * @param array $params Array of parameters + * $params[0] is the remote wwwroot + * $params[1] is the remote public key + * @return string The XML-RPC response + */ +function mnet_keyswap($function, $params) { + global $CFG, $MNET; + $return = array(); + + if (!empty($CFG->mnet_register_allhosts)) { + $mnet_peer = new mnet_peer(); + @list($wwwroot, $pubkey, $application) = each($params); + $keyok = $mnet_peer->bootstrap($wwwroot, $pubkey, $application); + if ($keyok) { + $mnet_peer->commit(); + } + } + return $MNET->public_key; +} + +/** + * Verify that the requested xmlrpc method can be called + * This just checks the method exists in the rpc table and is enabled. + * + * @param stdclass $rpcrecord the record from mnet_rpc + * + * @throws mnet_server_exception + */ +function mnet_verify_permissions($rpcrecord) { + global $CFG, $MNET_REMOTE_CLIENT, $DB; + + $id_list = $MNET_REMOTE_CLIENT->id; + if (!empty($CFG->mnet_all_hosts_id)) { + $id_list .= ', '.$CFG->mnet_all_hosts_id; + } + + $sql = "SELECT + r.*, h2s.publish + FROM + {mnet_rpc} r + JOIN {mnet_service2rpc} s2r ON s2r.rpcid = r.id + LEFT JOIN {mnet_host2service} h2s ON h2s.serviceid = s2r.serviceid + WHERE + r.id = ? AND + h2s.hostid in ($id_list)"; + + $params = array($rpcrecord->id); + + if (!$permission = $DB->get_record_sql($sql, $params)) { + throw new mnet_server_exception(7012, "nosuchfunction"); + } else if (!$permission->publish || !$permission->enabled) { + throw new mnet_server_exception(707, "nosuchfunction"); + } +} + +/** + * Figure out exactly what needs to be called and stashes it in $MNET_REMOTE_CLIENT + * Does some further verification that the method is callable + * + * @param string $method the full xmlrpc method that was called eg auth/mnet/auth.php/user_authorise + * @param array $callstack the exploded callstack + * @param stdclass $rpcrecord the record from mnet_rpc + * + * @throws mnet_server_exception + */ +function mnet_setup_dummy_method($method, $callstack, $rpcrecord) { + global $MNET_REMOTE_CLIENT, $CFG; + // verify that the callpath in the stack matches our records + // callstack will look like array('mod', 'forum', 'lib.php', 'forum_add_instance'); + $path = get_plugin_directory($rpcrecord->plugintype, $rpcrecord->pluginname, false); + array_pop($callstack); + $providedpath = implode('/', $callstack); + if ($providedpath != $path . '/' . $rpcrecord->filename) { + throw new mnet_server_exception(705, "nosuchfile"); + } + if (!file_exists($CFG->dirroot . '/' . $providedpath)) { + throw new mnet_server_exception(705, "nosuchfile"); + } + require_once($CFG->dirroot . '/' . $providedpath); + if (!empty($rpcrecord->classname)) { + if (!class_exists($rpcrecord->classname)) { + throw new mnet_server_exception(708, 'nosuchclass'); + } + if (!$rpcrecord->static) { + try { + $object = new $rpcrecord->classname; + } catch (Exception $e) { + throw new mnet_server_exception(709, "classerror"); + } + if (!is_callable(array($object, $rpcrecord->function_name))) { + throw new mnet_server_exception(706, "nosuchfunction"); + } + $MNET_REMOTE_CLIENT->object_to_call($object); + } else { + if (!is_callable(array($rpcrecord->classname, $rpcrecord->function_name))) { + throw new mnet_server_exception(706, "nosuchfunction"); + } + $MNET_REMOTE_CLIENT->static_location($rpcrecord->classname); + } + } +} + +/** + * Dummy function for the XML-RPC dispatcher - use to call a method on an object + * or to call a function + * + * Translate XML-RPC's strange function call syntax into a more straightforward + * PHP-friendly alternative. This dummy function will be called by the + * dispatcher, and can be used to call a method on an object, or just a function + * + * The methodName argument (eg. mnet/testlib/mnet_concatenate_strings) + * is ignored. + * + * @throws mnet_server_exception + * + * @param string $methodname We discard this - see 'functionname' + * @param array $argsarray Each element is an argument to the real + * function + * @param string $functionname The name of the PHP function you want to call + * @return mixed The return value will be that of the real + * function, whatever it may be. + */ +function mnet_server_dummy_method($methodname, $argsarray, $functionname) { + global $MNET_REMOTE_CLIENT; + + try { + if (is_object($MNET_REMOTE_CLIENT->object_to_call)) { + return @call_user_func_array(array($MNET_REMOTE_CLIENT->object_to_call,$functionname), $argsarray); + } else if (!empty($MNET_REMOTE_CLIENT->static_location)) { + return @call_user_func_array(array($MNET_REMOTE_CLIENT->static_location, $functionname), $argsarray); + } else { + return @call_user_func_array($functionname, $argsarray); + } + } catch (Exception $e) { + exit(mnet_server_fault($e->getCode(), $e->getMessage())); + } +} +/** + * mnet server exception. extends moodle_exception, but takes slightly different arguments. + * error strings are always in mnet, so $module is not necessary, + * and unlike the rest of moodle, the actual int error code is used. + * this exception should only be used during an xmlrpc server request, ie, not for client requests. + */ +class mnet_server_exception extends moodle_exception { + + /** + * @param int $intcode the numerical error associated with this fault. this is not the string errorcode + * @param string $languagekey the key to the language string for the error message, or the text of the message (mnet_server_fault figures it out for you) if you're not using mnet error string file + * @param mixed $a params for get_string + */ + public function __construct($intcode, $languagekey, $a=null) { + parent::__construct($languagekey, 'mnet', '', $a); + $this->code = $intcode; + $this->message = $languagekey; // override this because mnet_server_fault (our handler) uses this directly + + } +} + diff --git a/portfolio/mahara/db/mnet.php b/portfolio/mahara/db/mnet.php new file mode 100644 index 00000000000..1e7ba596d17 --- /dev/null +++ b/portfolio/mahara/db/mnet.php @@ -0,0 +1,38 @@ +. + + +/** + * This file contains the mnet services for the mahara portfolio plugin + * + * @since 2.0 + * @package moodlecore + * @subpackage portfolio + * @copyright 2010 Penny Leach + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +$publishes = array( + 'pf' => array( + 'apiversion' => 1, + 'classname' => 'portfolio_plugin_mahara', + 'filename' => 'lib.php', + 'methods' => array( + 'fetch_file' + ), + ), +); + diff --git a/repository/remotemoodle/db/mnet.php b/repository/remotemoodle/db/mnet.php new file mode 100644 index 00000000000..53727c7ca2c --- /dev/null +++ b/repository/remotemoodle/db/mnet.php @@ -0,0 +1,39 @@ +. + + +/** + * This file contains the mnet services for the remotemoodle repository plugin + * + * @since 2.0 + * @package moodlecore + * @subpackage repository + * @copyright 2010 Penny Leach + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +$publishes = array( + 'remoterep' => array( + 'apiversion' => 1, + 'classname' => 'repository_remotemoodle', + 'filename' => 'repository.class.php', + 'methods' => array( + 'getFileList', + 'retrieveFile', + ), + ), +);