mnet MDL-21261 mnet overhaul - adding and removing files I missed in the last big...
[moodle.git] / mnet / xmlrpc / serverlib.php
1 <?php
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  */
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  */
36 function 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);
41     if (!$crypt_parser->payload_encrypted) {
42         return $HTTP_RAW_POST_DATA;
43     }
45     // Make sure we know who we're talking to
46     $host_record_exists = $MNET_REMOTE_CLIENT->set_wwwroot($crypt_parser->remote_wwwroot);
48     if (false == $host_record_exists) {
49         throw new mnet_server_exception(7020, 'wrong-wwwroot', $crypt_parser->remote_wwwroot);
50     }
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);
57     $crypt_parser->free_resource();
58     $payload          = '';    // Initialize payload var
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     }
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
81             $MNET_REMOTE_CLIENT->was_encrypted();
82             $MNET_REMOTE_CLIENT->encrypted_to($keyresource);
83             $MNET_REMOTE_CLIENT->set_pushkey();
84             return $payload;
85         }
86     }
88     //If after all that we still couldn't decrypt the message, error out.
89     throw new mnet_server_exception(7023, 'encryption-invalid');
90 }
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) {
99     global $MNET, $MNET_REMOTE_CLIENT;
100     $sig_parser = new mnet_encxml_parser();
101     $sig_parser->parse($plaintextmessage);
103     if ($sig_parser->signature == '') {
104         return $plaintextmessage;
105     }
107     // Record that the request was signed in some way
108     $MNET_REMOTE_CLIENT->was_signed();
110     // Load any information we have about this mnet peer
111     $MNET_REMOTE_CLIENT->set_wwwroot($sig_parser->remote_wwwroot);
113     $payload = base64_decode($sig_parser->data_object);
114     $signature = base64_decode($sig_parser->signature);
115     $certificate = $MNET_REMOTE_CLIENT->public_key;
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     }
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     }
139     if ($signature_verified == 1) {
140         $MNET_REMOTE_CLIENT->signature_verified();
141         $MNET_REMOTE_CLIENT->touch();
142     }
144     $sig_parser->free_resource();
146     return $payload;
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  */
158 function 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);
165     $string = get_string($text, 'mnet', $param);
166     if (strpos($string, '[[') === 0) {
167         $string = $text;
168     }
170     return mnet_server_fault_xml($code, $string);
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  */
181 function 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);
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);
204     if (!empty($CFG->mnet_rpcdebug)) {
205         trigger_error("XMLRPC Error Response $code: $text");
206         trigger_error(print_r($return,1));
207     }
209     return $return;
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  */
220 function mnet_server_prepare_response($response, $privatekey = null) {
221     global $MNET_REMOTE_CLIENT;
223     if ($MNET_REMOTE_CLIENT->request_was_signed) {
224         $response = mnet_sign_message($response, $privatekey);
225     }
227     if ($MNET_REMOTE_CLIENT->request_was_encrypted) {
228         $response = mnet_encrypt_message($response, $MNET_REMOTE_CLIENT->public_key);
229     }
231     return $response;
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  */
247 function 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);
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.
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     }
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     }
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');
284     ////////////////////////////////////// SYSTEM METHODS
285     } elseif ($callstack[0] == 'system') {
286         $functionname = $callstack[1];
287         $xmlrpcserver = xmlrpc_server_create();
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.
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');
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);
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             }
334             // The call stack holds the path to any include file
335             $includefile = $CFG->dirroot.'/'.$filename;
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');
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  */
356 function mnet_system($method, $params, $hostinfo) {
357     global $CFG, $DB;
359     if (empty($hostinfo)) return array();
361     $id_list = $hostinfo->id;
362     if (!empty($CFG->mnet_all_hosts_id)) {
363         $id_list .= ', '.$CFG->mnet_all_hosts_id;
364     }
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]);
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();
434         $result = $DB->get_records_sql($query, $params);
435         $services = array();
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         }
446         return $services;
447     }
448     throw new mnet_server_exception(7019, 'nosuchfunction');
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  */
461 function 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);
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;
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  */
487 function mnet_server_invoke_dangerous_method($includefile, $methodname, $method, $payload) {
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     }
498     if ($functionname != clean_param($functionname, PARAM_PATH)) {
499         throw new mnet_server_exception(7012, "nosuchfunction");
500     }
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;
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  */
524 function mnet_keyswap($function, $params) {
525     global $CFG, $MNET;
526     $return = array();
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;
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  */
547 function mnet_verify_permissions($rpcrecord) {
548     global $CFG, $MNET_REMOTE_CLIENT, $DB;
550     $id_list = $MNET_REMOTE_CLIENT->id;
551     if (!empty($CFG->mnet_all_hosts_id)) {
552         $id_list .= ', '.$CFG->mnet_all_hosts_id;
553     }
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)";
565     $params = array($rpcrecord->id);
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     }
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  */
584 function 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     }
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  */
641 function mnet_server_dummy_method($methodname, $argsarray, $functionname) {
642     global $MNET_REMOTE_CLIENT;
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     }
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  */
662 class mnet_server_exception extends moodle_exception {
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
674     }