Commit | Line | Data |
---|---|---|
d2ac37c1 PL |
1 | <?php |
2 | ||
3 | /** | |
4 | * -----XML-Envelope--------------------------------- | |
5 | * | | | |
6 | * | Encrypted-Symmetric-key---------------- | | |
7 | * | |_____________________________________| | | |
8 | * | | | |
9 | * | Encrypted data------------------------- | | |
10 | * | | | | | |
11 | * | | -XML-Envelope------------------ | | | |
12 | * | | | | | | | |
13 | * | | | --Signature------------- | | | | |
14 | * | | | |______________________| | | | | |
15 | * | | | | | | | |
16 | * | | | --Signed-Payload-------- | | | | |
17 | * | | | | | | | | | |
18 | * | | | | XML-RPC Request | | | | | |
19 | * | | | |______________________| | | | | |
20 | * | | | | | | | |
21 | * | | |_____________________________| | | | |
22 | * | |_____________________________________| | | |
23 | * | | | |
24 | * |________________________________________________| | |
25 | * | |
26 | */ | |
27 | ||
28 | /* Strip encryption envelope (if present) and decrypt data | |
29 | * | |
4d16a274 | 30 | * @param string $rawpostdata The XML that the client sent |
d2ac37c1 PL |
31 | * |
32 | * @throws mnet_server_exception | |
33 | * | |
34 | * @return string XML with any encryption envolope removed | |
35 | */ | |
4d16a274 | 36 | function mnet_server_strip_encryption($rawpostdata) { |
287efec6 | 37 | $remoteclient = get_mnet_remote_client(); |
d2ac37c1 | 38 | $crypt_parser = new mnet_encxml_parser(); |
4d16a274 | 39 | $crypt_parser->parse($rawpostdata); |
287efec6 | 40 | $mnet = get_mnet_environment(); |
d2ac37c1 PL |
41 | |
42 | if (!$crypt_parser->payload_encrypted) { | |
4d16a274 | 43 | return $rawpostdata; |
d2ac37c1 PL |
44 | } |
45 | ||
46 | // Make sure we know who we're talking to | |
287efec6 | 47 | $host_record_exists = $remoteclient->set_wwwroot($crypt_parser->remote_wwwroot); |
d2ac37c1 PL |
48 | |
49 | if (false == $host_record_exists) { | |
50 | throw new mnet_server_exception(7020, 'wrong-wwwroot', $crypt_parser->remote_wwwroot); | |
51 | } | |
52 | ||
53 | // This key is symmetric, and is itself encrypted. Can be decrypted using our private key | |
54 | $key = array_pop($crypt_parser->cipher); | |
55 | // This data is symmetrically encrypted, can be decrypted using the above key | |
56 | $data = array_pop($crypt_parser->cipher); | |
57 | ||
58 | $crypt_parser->free_resource(); | |
59 | $payload = ''; // Initialize payload var | |
60 | ||
61 | // &$payload | |
287efec6 | 62 | $isOpen = openssl_open(base64_decode($data), $payload, base64_decode($key), $mnet->get_private_key()); |
d2ac37c1 | 63 | if ($isOpen) { |
287efec6 | 64 | $remoteclient->was_encrypted(); |
d2ac37c1 PL |
65 | return $payload; |
66 | } | |
67 | ||
68 | // Decryption failed... let's try our archived keys | |
69 | $openssl_history = get_config('mnet', 'openssl_history'); | |
70 | if(empty($openssl_history)) { | |
71 | $openssl_history = array(); | |
72 | set_config('openssl_history', serialize($openssl_history), 'mnet'); | |
73 | } else { | |
74 | $openssl_history = unserialize($openssl_history); | |
75 | } | |
76 | foreach($openssl_history as $keyset) { | |
77 | $keyresource = openssl_pkey_get_private($keyset['keypair_PEM']); | |
78 | $isOpen = openssl_open(base64_decode($data), $payload, base64_decode($key), $keyresource); | |
79 | if ($isOpen) { | |
80 | // It's an older code, sir, but it checks out | |
287efec6 PL |
81 | $remoteclient->was_encrypted(); |
82 | $remoteclient->encrypted_to($keyresource); | |
83 | $remoteclient->set_pushkey(); | |
d2ac37c1 PL |
84 | return $payload; |
85 | } | |
86 | } | |
87 | ||
88 | //If after all that we still couldn't decrypt the message, error out. | |
89 | throw new mnet_server_exception(7023, 'encryption-invalid'); | |
90 | } | |
91 | ||
92 | /* Strip signature envelope (if present), try to verify any signature using our record of remote peer's public key. | |
93 | * | |
94 | * @param string $plaintextmessage XML envelope containing XMLRPC request and signature | |
95 | * | |
96 | * @return string XMLRPC request | |
97 | */ | |
98 | function mnet_server_strip_signature($plaintextmessage) { | |
287efec6 | 99 | $remoteclient = get_mnet_remote_client(); |
d2ac37c1 PL |
100 | $sig_parser = new mnet_encxml_parser(); |
101 | $sig_parser->parse($plaintextmessage); | |
102 | ||
103 | if ($sig_parser->signature == '') { | |
104 | return $plaintextmessage; | |
105 | } | |
106 | ||
107 | // Record that the request was signed in some way | |
287efec6 | 108 | $remoteclient->was_signed(); |
d2ac37c1 PL |
109 | |
110 | // Load any information we have about this mnet peer | |
287efec6 | 111 | $remoteclient->set_wwwroot($sig_parser->remote_wwwroot); |
d2ac37c1 PL |
112 | |
113 | $payload = base64_decode($sig_parser->data_object); | |
114 | $signature = base64_decode($sig_parser->signature); | |
287efec6 | 115 | $certificate = $remoteclient->public_key; |
d2ac37c1 PL |
116 | |
117 | // If we don't have any certificate for the host, don't try to check the signature | |
118 | // Just return the parsed request | |
119 | if ($certificate == false) { | |
120 | return $payload; | |
121 | } | |
122 | ||
123 | // Does the signature match the data and the public cert? | |
124 | $signature_verified = openssl_verify($payload, $signature, $certificate); | |
125 | if ($signature_verified == 0) { | |
126 | // $signature was not generated for $payload using $certificate | |
127 | // Get the key the remote peer is currently publishing: | |
287efec6 | 128 | $currkey = mnet_get_public_key($remoteclient->wwwroot, $remoteclient->application); |
d2ac37c1 PL |
129 | // If the key the remote peer is currently publishing is different to $certificate |
130 | if($currkey != $certificate) { | |
48fb3941 PL |
131 | // if pushkey is already set, it means the request was encrypted to an old key |
132 | // in mnet_server_strip_encryption. | |
133 | // if we call refresh_key() here before pushing out our new key, | |
134 | // and the other site ALSO has a new key, | |
135 | // we'll get into an infinite keyswap loop | |
136 | // so push just bail here, and push out the new key. | |
137 | // the next request will get through to refresh_key | |
138 | if ($remoteclient->pushkey) { | |
139 | return false; | |
140 | } | |
d2ac37c1 | 141 | // Try and get the server's new key through trusted means |
287efec6 | 142 | $remoteclient->refresh_key(); |
d2ac37c1 | 143 | // If we did manage to re-key, try to verify the signature again using the new public key. |
287efec6 | 144 | $certificate = $remoteclient->public_key; |
d2ac37c1 PL |
145 | $signature_verified = openssl_verify($payload, $signature, $certificate); |
146 | } | |
147 | } | |
148 | ||
149 | if ($signature_verified == 1) { | |
287efec6 PL |
150 | $remoteclient->signature_verified(); |
151 | $remoteclient->touch(); | |
d2ac37c1 PL |
152 | } |
153 | ||
154 | $sig_parser->free_resource(); | |
155 | ||
156 | return $payload; | |
157 | } | |
158 | ||
159 | /** | |
160 | * Return the proper XML-RPC content to report an error in the local language. | |
161 | * | |
162 | * @param int $code The ID code of the error message | |
d234faf3 | 163 | * @param string $text The full string of the error message (get_string will <b>not be called</b>) |
d2ac37c1 PL |
164 | * @param string $param The $a param for the error message in the lang file |
165 | * @return string $text The text of the error message | |
166 | */ | |
167 | function mnet_server_fault($code, $text, $param = null) { | |
d2ac37c1 PL |
168 | if (!is_numeric($code)) { |
169 | $code = 0; | |
170 | } | |
171 | $code = intval($code); | |
d234faf3 | 172 | return mnet_server_fault_xml($code, $text); |
d2ac37c1 PL |
173 | } |
174 | ||
175 | /** | |
176 | * Return the proper XML-RPC content to report an error. | |
177 | * | |
178 | * @param int $code The ID code of the error message | |
179 | * @param string $text The error message | |
180 | * @param resource $privatekey The private key that should be used to sign the response | |
181 | * @return string $text The XML text of the error message | |
182 | */ | |
183 | function mnet_server_fault_xml($code, $text, $privatekey = null) { | |
287efec6 | 184 | global $CFG; |
d2ac37c1 PL |
185 | // Replace illegal XML chars - is this already in a lib somewhere? |
186 | $text = str_replace(array('<','>','&','"',"'"), array('<','>','&','"','''), $text); | |
187 | ||
188 | $return = mnet_server_prepare_response('<?xml version="1.0"?> | |
189 | <methodResponse> | |
190 | <fault> | |
191 | <value> | |
192 | <struct> | |
193 | <member> | |
194 | <name>faultCode</name> | |
195 | <value><int>'.$code.'</int></value> | |
196 | </member> | |
197 | <member> | |
198 | <name>faultString</name> | |
199 | <value><string>'.$text.'</string></value> | |
200 | </member> | |
201 | </struct> | |
202 | </value> | |
203 | </fault> | |
204 | </methodResponse>', $privatekey); | |
205 | ||
71f61c41 PL |
206 | if ($code != 7025) { // new key responses |
207 | mnet_debug("XMLRPC Error Response $code: $text"); | |
82c3f5d8 | 208 | //mnet_debug($return); |
d2ac37c1 PL |
209 | } |
210 | ||
211 | return $return; | |
212 | } | |
213 | ||
214 | ||
215 | /** | |
216 | * Package a response in any required envelope, and return it to the client | |
217 | * | |
218 | * @param string $response The XMLRPC response string | |
219 | * @param resource $privatekey The private key to sign the response with | |
220 | * @return string The encoded response string | |
221 | */ | |
222 | function mnet_server_prepare_response($response, $privatekey = null) { | |
287efec6 PL |
223 | $remoteclient = get_mnet_remote_client(); |
224 | if ($remoteclient->request_was_signed) { | |
d2ac37c1 PL |
225 | $response = mnet_sign_message($response, $privatekey); |
226 | } | |
227 | ||
287efec6 PL |
228 | if ($remoteclient->request_was_encrypted) { |
229 | $response = mnet_encrypt_message($response, $remoteclient->public_key); | |
d2ac37c1 PL |
230 | } |
231 | ||
232 | return $response; | |
233 | } | |
234 | ||
235 | /** | |
236 | * If security checks are passed, dispatch the request to the function/method | |
237 | * | |
238 | * The config variable 'mnet_dispatcher_mode' can be: | |
239 | * strict: Only execute functions that are in specific files | |
240 | * off: The default - don't execute anything | |
241 | * | |
242 | * @param string $payload The XML-RPC request | |
243 | * | |
244 | * @throws mnet_server_exception | |
245 | * | |
246 | * @return No return val - just echo the response | |
247 | */ | |
248 | function mnet_server_dispatch($payload) { | |
287efec6 PL |
249 | global $CFG, $DB; |
250 | $remoteclient = get_mnet_remote_client(); | |
d2ac37c1 PL |
251 | // xmlrpc_decode_request returns an array of parameters, and the $method |
252 | // variable (which is passed by reference) is instantiated with the value from | |
253 | // the methodName tag in the xml payload | |
254 | // xmlrpc_decode_request($xml, &$method) | |
255 | $params = xmlrpc_decode_request($payload, $method); | |
256 | ||
257 | // $method is something like: "mod/forum/lib.php/forum_add_instance" | |
258 | // $params is an array of parameters. A parameter might itself be an array. | |
259 | ||
260 | // Whitelist characters that are permitted in a method name | |
261 | // The method name must not begin with a / - avoid absolute paths | |
262 | // A dot character . is only allowed in the filename, i.e. something.php | |
263 | if (0 == preg_match("@^[A-Za-z0-9]+/[A-Za-z0-9/_\.-]+(\.php/)?[A-Za-z0-9_-]+$@",$method)) { | |
264 | throw new mnet_server_exception(713, 'nosuchfunction'); | |
265 | } | |
266 | ||
267 | if(preg_match("/^system\./", $method)) { | |
268 | $callstack = explode('.', $method); | |
269 | } else { | |
270 | $callstack = explode('/', $method); | |
271 | // callstack will look like array('mod', 'forum', 'lib.php', 'forum_add_instance'); | |
272 | } | |
273 | ||
274 | /** | |
275 | * What has the site administrator chosen as his dispatcher setting? | |
276 | * strict: Only execute functions that are in specific files | |
277 | * off: The default - don't execute anything | |
278 | */ | |
279 | ////////////////////////////////////// OFF | |
280 | if (!isset($CFG->mnet_dispatcher_mode) ) { | |
281 | set_config('mnet_dispatcher_mode', 'off'); | |
282 | throw new mnet_server_exception(704, 'nosuchservice'); | |
283 | } elseif ('off' == $CFG->mnet_dispatcher_mode) { | |
284 | throw new mnet_server_exception(704, 'nosuchservice'); | |
285 | ||
286 | ////////////////////////////////////// SYSTEM METHODS | |
287 | } elseif ($callstack[0] == 'system') { | |
288 | $functionname = $callstack[1]; | |
289 | $xmlrpcserver = xmlrpc_server_create(); | |
290 | ||
291 | // register all the system methods | |
292 | $systemmethods = array('listMethods', 'methodSignature', 'methodHelp', 'listServices', 'listFiles', 'retrieveFile', 'keyswap'); | |
293 | foreach ($systemmethods as $m) { | |
294 | // I'm adding the canonical xmlrpc references here, however we've | |
295 | // already forbidden that the period (.) should be allowed in the call | |
296 | // stack, so if someone tries to access our XMLRPC in the normal way, | |
297 | // they'll already have received a RPC server fault message. | |
298 | ||
299 | // Maybe we should allow an easement so that regular XMLRPC clients can | |
300 | // call our system methods, and find out what we have to offer? | |
301 | $handler = 'mnet_system'; | |
302 | if ($m == 'keyswap') { | |
303 | $handler = 'mnet_keyswap'; | |
304 | } | |
305 | if ($method == 'system.' . $m || $method == 'system/' . $m) { | |
306 | xmlrpc_server_register_method($xmlrpcserver, $method, $handler); | |
287efec6 | 307 | $response = xmlrpc_server_call_method($xmlrpcserver, $payload, $remoteclient, array("encoding" => "utf-8")); |
d2ac37c1 PL |
308 | $response = mnet_server_prepare_response($response); |
309 | echo $response; | |
310 | xmlrpc_server_destroy($xmlrpcserver); | |
311 | return; | |
312 | } | |
313 | } | |
314 | throw new mnet_server_exception(7018, 'nosuchfunction'); | |
315 | ||
316 | //////////////////////////////////// NORMAL PLUGIN DISPATCHER | |
317 | } else { | |
318 | // anything else comes from some sort of plugin | |
8586dbe2 | 319 | if ($rpcrecord = $DB->get_record('mnet_rpc', array('xmlrpcpath' => $method))) { |
d2ac37c1 PL |
320 | $response = mnet_server_invoke_plugin_method($method, $callstack, $rpcrecord, $payload); |
321 | $response = mnet_server_prepare_response($response); | |
322 | echo $response; | |
323 | return; | |
324 | // if the rpc record isn't found, check to see if dangerous mode is on | |
325 | ////////////////////////////////////// DANGEROUS | |
287efec6 | 326 | } else if ('dangerous' == $CFG->mnet_dispatcher_mode && $remoteclient->plaintext_is_ok()) { |
d2ac37c1 PL |
327 | $functionname = array_pop($callstack); |
328 | ||
329 | $filename = clean_param(implode('/',$callstack), PARAM_PATH); | |
330 | if (0 == preg_match("/php$/", $filename)) { | |
331 | // Filename doesn't end in 'php'; possible attack? | |
332 | // Generate error response - unable to locate function | |
333 | throw new mnet_server_exception(7012, 'nosuchfunction'); | |
334 | } | |
335 | ||
336 | // The call stack holds the path to any include file | |
337 | $includefile = $CFG->dirroot.'/'.$filename; | |
338 | ||
339 | $response = mnet_server_invoke_dangerous_method($includefile, $functionname, $method, $payload); | |
340 | echo $response; | |
341 | return; | |
342 | } | |
343 | } | |
344 | throw new mnet_server_exception(7012, 'nosuchfunction'); | |
345 | } | |
346 | ||
347 | /** | |
348 | * Execute the system functions - mostly for introspection | |
349 | * | |
350 | * @param string $method XMLRPC method name, e.g. system.listMethods | |
351 | * @param array $params Array of parameters from the XMLRPC request | |
352 | * @param string $hostinfo Hostinfo object from the mnet_host table | |
353 | * | |
354 | * @throws mnet_server_exception | |
355 | * | |
356 | * @return mixed Response data - any kind of PHP variable | |
357 | */ | |
358 | function mnet_system($method, $params, $hostinfo) { | |
359 | global $CFG, $DB; | |
360 | ||
361 | if (empty($hostinfo)) return array(); | |
362 | ||
363 | $id_list = $hostinfo->id; | |
364 | if (!empty($CFG->mnet_all_hosts_id)) { | |
365 | $id_list .= ', '.$CFG->mnet_all_hosts_id; | |
366 | } | |
367 | ||
368 | if ('system.listMethods' == $method || 'system/listMethods' == $method) { | |
369 | $query = ' | |
370 | SELECT DISTINCT | |
8586dbe2 PL |
371 | rpc.functionname, |
372 | rpc.xmlrpcpath | |
d2ac37c1 PL |
373 | FROM |
374 | {mnet_host2service} h2s | |
375 | JOIN {mnet_service2rpc} s2r ON h2s.serviceid = s2r.serviceid | |
376 | JOIN {mnet_rpc} rpc ON s2r.rpcid = rpc.id | |
377 | JOIN {mnet_service} svc ON svc.id = s2r.serviceid | |
378 | WHERE | |
379 | h2s.hostid in ('.$id_list .') AND | |
380 | h2s.publish = 1 AND rpc.enabled = 1 | |
381 | ' . ((count($params) > 0) ? 'AND svc.name = ? ' : '') . ' | |
382 | ORDER BY | |
8586dbe2 | 383 | rpc.xmlrpcpath ASC'; |
d2ac37c1 PL |
384 | if (count($params) > 0) { |
385 | $params = array($params[0]); | |
386 | } | |
387 | $methods = array(); | |
388 | foreach ($DB->get_records_sql($query, $params) as $result) { | |
8586dbe2 | 389 | $methods[] = $result->xmlrpcpath; |
d2ac37c1 PL |
390 | } |
391 | return $methods; | |
392 | } elseif (in_array($method, array('system.methodSignature', 'system/methodSignature', 'system.methodHelp', 'system/methodHelp'))) { | |
393 | $query = ' | |
394 | SELECT DISTINCT | |
8586dbe2 | 395 | rpc.functionname, |
d2ac37c1 PL |
396 | rpc.help, |
397 | rpc.profile | |
398 | FROM | |
399 | {mnet_host2service} h2s, | |
400 | {mnet_service2rpc} s2r, | |
401 | {mnet_rpc} rpc | |
402 | WHERE | |
8586dbe2 | 403 | rpc.xmlrpcpath = ? AND |
d2ac37c1 PL |
404 | s2r.rpcid = rpc.id AND |
405 | h2s.publish = 1 AND rpc.enabled = 1 AND | |
406 | h2s.serviceid = s2r.serviceid AND | |
407 | h2s.hostid in ('.$id_list .')'; | |
408 | $params = array($params[0]); | |
409 | ||
410 | if (!$result = $DB->get_record_sql($query, $params)) { | |
411 | return false; | |
412 | } | |
413 | if (strpos($method, 'methodSignature') !== false) { | |
414 | return unserialize($result->profile); | |
415 | } | |
416 | return $result->help; | |
417 | } elseif ('system.listServices' == $method || 'system/listServices' == $method) { | |
418 | $query = ' | |
419 | SELECT DISTINCT | |
420 | s.id, | |
421 | s.name, | |
422 | s.apiversion, | |
423 | h2s.publish, | |
424 | h2s.subscribe | |
425 | FROM | |
426 | {mnet_host2service} h2s, | |
427 | {mnet_service} s | |
428 | WHERE | |
429 | h2s.serviceid = s.id AND | |
430 | (h2s.publish = 1 OR h2s.subscribe = 1) AND | |
431 | h2s.hostid in ('.$id_list .') | |
432 | ORDER BY | |
433 | s.name ASC'; | |
434 | $params = array(); | |
435 | ||
436 | $result = $DB->get_records_sql($query, $params); | |
437 | $services = array(); | |
438 | ||
439 | if (is_array($result)) { | |
440 | foreach($result as $service) { | |
441 | $services[] = array('name' => $service->name, | |
442 | 'apiversion' => $service->apiversion, | |
443 | 'publish' => $service->publish, | |
444 | 'subscribe' => $service->subscribe); | |
445 | } | |
446 | } | |
447 | ||
448 | return $services; | |
449 | } | |
450 | throw new mnet_server_exception(7019, 'nosuchfunction'); | |
451 | } | |
452 | ||
453 | /** | |
454 | * Invoke a normal style plugin method | |
455 | * This will verify permissions first. | |
456 | * | |
457 | * @param string $method the full xmlrpc method that was called eg auth/mnet/auth.php/user_authorise | |
458 | * @param array $callstack the exploded callstack | |
459 | * @param stdclass $rpcrecord the record from mnet_rpc | |
460 | * | |
461 | * @return mixed the response from the invoked method | |
462 | */ | |
463 | function mnet_server_invoke_plugin_method($method, $callstack, $rpcrecord, $payload) { | |
464 | mnet_verify_permissions($rpcrecord); // will throw exceptions | |
465 | mnet_setup_dummy_method($method, $callstack, $rpcrecord); | |
466 | $methodname = array_pop($callstack); | |
467 | ||
468 | $xmlrpcserver = xmlrpc_server_create(); | |
469 | xmlrpc_server_register_method($xmlrpcserver, $method, 'mnet_server_dummy_method'); | |
470 | $response = xmlrpc_server_call_method($xmlrpcserver, $payload, $methodname, array("encoding" => "utf-8")); | |
471 | xmlrpc_server_destroy($xmlrpcserver); | |
472 | return $response; | |
473 | } | |
474 | ||
475 | /** | |
476 | * Initialize the object (if necessary), execute the method or function, and | |
477 | * return the response | |
478 | * | |
479 | * @param string $includefile The file that contains the object definition | |
480 | * @param string $methodname The name of the method to execute | |
481 | * @param string $method The full path to the method | |
482 | * @param string $payload The XML-RPC request payload | |
483 | * @param string $class The name of the class to instantiate (or false) | |
484 | * | |
485 | * @throws mnet_server_exception | |
486 | * | |
487 | * @return string The XML-RPC response | |
488 | */ | |
489 | function mnet_server_invoke_dangerous_method($includefile, $methodname, $method, $payload) { | |
490 | ||
491 | if (file_exists($CFG->dirroot . $includefile)) { | |
492 | require_once $CFG->dirroot . $includefile; | |
493 | // $callprefix matches the rpc convention | |
494 | // of not having a leading slash | |
495 | $callprefix = preg_replace('!^/!', '', $includefile); | |
496 | } else { | |
497 | throw new mnet_server_exception(705, "nosuchfile"); | |
498 | } | |
499 | ||
500 | if ($functionname != clean_param($functionname, PARAM_PATH)) { | |
501 | throw new mnet_server_exception(7012, "nosuchfunction"); | |
502 | } | |
503 | ||
504 | if (!function_exists($functionname)) { | |
505 | throw new mnet_server_exception(7012, "nosuchfunction"); | |
506 | } | |
507 | $xmlrpcserver = xmlrpc_server_create(); | |
508 | xmlrpc_server_register_method($xmlrpcserver, $method, 'mnet_server_dummy_method'); | |
509 | $response = xmlrpc_server_call_method($xmlrpcserver, $payload, $methodname, array("encoding" => "utf-8")); | |
510 | xmlrpc_server_destroy($xmlrpcserver); | |
511 | return $response; | |
512 | } | |
513 | ||
514 | ||
515 | /** | |
516 | * Accepts a public key from a new remote host and returns the public key for | |
517 | * this host. If 'register all hosts' is turned on, it will bootstrap a record | |
518 | * for the remote host in the mnet_host table (if it's not already there) | |
519 | * | |
520 | * @param string $function XML-RPC requires this but we don't... discard! | |
521 | * @param array $params Array of parameters | |
522 | * $params[0] is the remote wwwroot | |
523 | * $params[1] is the remote public key | |
524 | * @return string The XML-RPC response | |
525 | */ | |
526 | function mnet_keyswap($function, $params) { | |
287efec6 | 527 | global $CFG; |
d2ac37c1 | 528 | $return = array(); |
287efec6 | 529 | $mnet = get_mnet_environment(); |
d2ac37c1 PL |
530 | |
531 | if (!empty($CFG->mnet_register_allhosts)) { | |
532 | $mnet_peer = new mnet_peer(); | |
533 | @list($wwwroot, $pubkey, $application) = each($params); | |
534 | $keyok = $mnet_peer->bootstrap($wwwroot, $pubkey, $application); | |
535 | if ($keyok) { | |
536 | $mnet_peer->commit(); | |
537 | } | |
538 | } | |
287efec6 | 539 | return $mnet->public_key; |
d2ac37c1 PL |
540 | } |
541 | ||
542 | /** | |
543 | * Verify that the requested xmlrpc method can be called | |
544 | * This just checks the method exists in the rpc table and is enabled. | |
545 | * | |
546 | * @param stdclass $rpcrecord the record from mnet_rpc | |
547 | * | |
548 | * @throws mnet_server_exception | |
549 | */ | |
550 | function mnet_verify_permissions($rpcrecord) { | |
287efec6 PL |
551 | global $CFG, $DB; |
552 | $remoteclient = get_mnet_remote_client(); | |
d2ac37c1 | 553 | |
287efec6 | 554 | $id_list = $remoteclient->id; |
d2ac37c1 PL |
555 | if (!empty($CFG->mnet_all_hosts_id)) { |
556 | $id_list .= ', '.$CFG->mnet_all_hosts_id; | |
557 | } | |
558 | ||
559 | $sql = "SELECT | |
560 | r.*, h2s.publish | |
561 | FROM | |
562 | {mnet_rpc} r | |
563 | JOIN {mnet_service2rpc} s2r ON s2r.rpcid = r.id | |
564 | LEFT JOIN {mnet_host2service} h2s ON h2s.serviceid = s2r.serviceid | |
565 | WHERE | |
566 | r.id = ? AND | |
567 | h2s.hostid in ($id_list)"; | |
568 | ||
569 | $params = array($rpcrecord->id); | |
570 | ||
571 | if (!$permission = $DB->get_record_sql($sql, $params)) { | |
572 | throw new mnet_server_exception(7012, "nosuchfunction"); | |
573 | } else if (!$permission->publish || !$permission->enabled) { | |
574 | throw new mnet_server_exception(707, "nosuchfunction"); | |
575 | } | |
576 | } | |
577 | ||
578 | /** | |
287efec6 | 579 | * Figure out exactly what needs to be called and stashes it in $remoteclient |
d2ac37c1 PL |
580 | * Does some further verification that the method is callable |
581 | * | |
582 | * @param string $method the full xmlrpc method that was called eg auth/mnet/auth.php/user_authorise | |
583 | * @param array $callstack the exploded callstack | |
584 | * @param stdclass $rpcrecord the record from mnet_rpc | |
585 | * | |
586 | * @throws mnet_server_exception | |
587 | */ | |
588 | function mnet_setup_dummy_method($method, $callstack, $rpcrecord) { | |
287efec6 PL |
589 | global $CFG; |
590 | $remoteclient = get_mnet_remote_client(); | |
d2ac37c1 PL |
591 | // verify that the callpath in the stack matches our records |
592 | // callstack will look like array('mod', 'forum', 'lib.php', 'forum_add_instance'); | |
1c74b260 | 593 | $path = core_component::get_plugin_directory($rpcrecord->plugintype, $rpcrecord->pluginname); |
ae586328 | 594 | $path = substr($path, strlen($CFG->dirroot)+1); // this is a bit hacky and fragile, it is not guaranteed that plugins are in dirroot |
d2ac37c1 PL |
595 | array_pop($callstack); |
596 | $providedpath = implode('/', $callstack); | |
597 | if ($providedpath != $path . '/' . $rpcrecord->filename) { | |
598 | throw new mnet_server_exception(705, "nosuchfile"); | |
599 | } | |
600 | if (!file_exists($CFG->dirroot . '/' . $providedpath)) { | |
601 | throw new mnet_server_exception(705, "nosuchfile"); | |
602 | } | |
603 | require_once($CFG->dirroot . '/' . $providedpath); | |
604 | if (!empty($rpcrecord->classname)) { | |
605 | if (!class_exists($rpcrecord->classname)) { | |
606 | throw new mnet_server_exception(708, 'nosuchclass'); | |
607 | } | |
608 | if (!$rpcrecord->static) { | |
609 | try { | |
610 | $object = new $rpcrecord->classname; | |
611 | } catch (Exception $e) { | |
612 | throw new mnet_server_exception(709, "classerror"); | |
613 | } | |
8586dbe2 | 614 | if (!is_callable(array($object, $rpcrecord->functionname))) { |
d2ac37c1 PL |
615 | throw new mnet_server_exception(706, "nosuchfunction"); |
616 | } | |
287efec6 | 617 | $remoteclient->object_to_call($object); |
d2ac37c1 | 618 | } else { |
8586dbe2 | 619 | if (!is_callable(array($rpcrecord->classname, $rpcrecord->functionname))) { |
d2ac37c1 PL |
620 | throw new mnet_server_exception(706, "nosuchfunction"); |
621 | } | |
287efec6 | 622 | $remoteclient->static_location($rpcrecord->classname); |
d2ac37c1 PL |
623 | } |
624 | } | |
625 | } | |
626 | ||
627 | /** | |
628 | * Dummy function for the XML-RPC dispatcher - use to call a method on an object | |
629 | * or to call a function | |
630 | * | |
631 | * Translate XML-RPC's strange function call syntax into a more straightforward | |
632 | * PHP-friendly alternative. This dummy function will be called by the | |
633 | * dispatcher, and can be used to call a method on an object, or just a function | |
634 | * | |
635 | * The methodName argument (eg. mnet/testlib/mnet_concatenate_strings) | |
636 | * is ignored. | |
637 | * | |
638 | * @throws mnet_server_exception | |
639 | * | |
640 | * @param string $methodname We discard this - see 'functionname' | |
641 | * @param array $argsarray Each element is an argument to the real | |
642 | * function | |
643 | * @param string $functionname The name of the PHP function you want to call | |
644 | * @return mixed The return value will be that of the real | |
645 | * function, whatever it may be. | |
646 | */ | |
647 | function mnet_server_dummy_method($methodname, $argsarray, $functionname) { | |
287efec6 | 648 | $remoteclient = get_mnet_remote_client(); |
d2ac37c1 | 649 | try { |
287efec6 PL |
650 | if (is_object($remoteclient->object_to_call)) { |
651 | return @call_user_func_array(array($remoteclient->object_to_call,$functionname), $argsarray); | |
652 | } else if (!empty($remoteclient->static_location)) { | |
653 | return @call_user_func_array(array($remoteclient->static_location, $functionname), $argsarray); | |
d2ac37c1 PL |
654 | } else { |
655 | return @call_user_func_array($functionname, $argsarray); | |
656 | } | |
657 | } catch (Exception $e) { | |
658 | exit(mnet_server_fault($e->getCode(), $e->getMessage())); | |
659 | } | |
660 | } | |
661 | /** | |
662 | * mnet server exception. extends moodle_exception, but takes slightly different arguments. | |
d2ac37c1 PL |
663 | * and unlike the rest of moodle, the actual int error code is used. |
664 | * this exception should only be used during an xmlrpc server request, ie, not for client requests. | |
665 | */ | |
666 | class mnet_server_exception extends moodle_exception { | |
667 | ||
668 | /** | |
669 | * @param int $intcode the numerical error associated with this fault. this is <b>not</b> the string errorcode | |
d234faf3 PL |
670 | * @param string $langkey the error message in full (<b>get_string will not be used</b>) |
671 | * @param string $module the language module, defaults to 'mnet' | |
d2ac37c1 PL |
672 | * @param mixed $a params for get_string |
673 | */ | |
d234faf3 PL |
674 | public function __construct($intcode, $languagekey, $module='mnet', $a=null) { |
675 | parent::__construct($languagekey, $module, '', $a); | |
d2ac37c1 | 676 | $this->code = $intcode; |
d2ac37c1 PL |
677 | |
678 | } | |
679 | } | |
680 |