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