Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]

   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 $rawpostdata 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($rawpostdata) {
  37      $remoteclient = get_mnet_remote_client();
  38      $crypt_parser = new mnet_encxml_parser();
  39      $crypt_parser->parse($rawpostdata);
  40      $mnet = get_mnet_environment();
  41  
  42      if (!$crypt_parser->payload_encrypted) {
  43          return $rawpostdata;
  44      }
  45  
  46      // Make sure we know who we're talking to
  47      $host_record_exists = $remoteclient->set_wwwroot($crypt_parser->remote_wwwroot);
  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
  62      $isOpen = openssl_open(base64_decode($data), $payload, base64_decode($key), $mnet->get_private_key(), 'RC4');
  63      if ($isOpen) {
  64          $remoteclient->was_encrypted();
  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, 'RC4');
  79          if ($isOpen) {
  80              // It's an older code, sir, but it checks out
  81              $remoteclient->was_encrypted();
  82              $remoteclient->encrypted_to($keyresource);
  83              $remoteclient->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   */
  98  function mnet_server_strip_signature($plaintextmessage) {
  99      $remoteclient = get_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      $remoteclient->was_signed();
 109  
 110      // Load any information we have about this mnet peer
 111      $remoteclient->set_wwwroot($sig_parser->remote_wwwroot);
 112  
 113      $payload = base64_decode($sig_parser->data_object);
 114      $signature = base64_decode($sig_parser->signature);
 115      $certificate = $remoteclient->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($remoteclient->wwwroot, $remoteclient->application);
 129          // If the key the remote peer is currently publishing is different to $certificate
 130          if($currkey != $certificate) {
 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              }
 141              // Try and get the server's new key through trusted means
 142              $remoteclient->refresh_key();
 143              // If we did manage to re-key, try to verify the signature again using the new public key.
 144              $certificate = $remoteclient->public_key;
 145              $signature_verified = openssl_verify($payload, $signature, $certificate);
 146          }
 147      }
 148  
 149      if ($signature_verified == 1) {
 150          $remoteclient->signature_verified();
 151          $remoteclient->touch();
 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
 163   * @param  string $text   The full string of the error message (get_string will <b>not be called</b>)
 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) {
 168      if (!is_numeric($code)) {
 169          $code = 0;
 170      }
 171      $code = intval($code);
 172      return mnet_server_fault_xml($code, $text);
 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) {
 184      global $CFG;
 185      // Replace illegal XML chars - is this already in a lib somewhere?
 186      $text = str_replace(array('<','>','&','"',"'"), array('&lt;','&gt;','&amp;','&quot;','&apos;'), $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  
 206      if ($code != 7025) { // new key responses
 207          mnet_debug("XMLRPC Error Response $code: $text");
 208          //mnet_debug($return);
 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) {
 223      $remoteclient = get_mnet_remote_client();
 224      if ($remoteclient->request_was_signed) {
 225          $response = mnet_sign_message($response, $privatekey);
 226      }
 227  
 228      if ($remoteclient->request_was_encrypted) {
 229          $response = mnet_encrypt_message($response, $remoteclient->public_key);
 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) {
 249      global $CFG, $DB;
 250      $remoteclient = get_mnet_remote_client();
 251      // Decode the request to method + params.
 252      $method = null;
 253      $params = null;
 254      $encoder = new \PhpXmlRpc\Encoder();
 255      $orequest = $encoder->decodeXML($payload); // First, to internal PhpXmlRpc\Response structure.
 256      if ($orequest && $orequest instanceof \PhpXmlRpc\Request) {
 257          $method = $orequest->method();
 258          $numparams = $orequest->getNumParams();
 259          for ($i = 0; $i < $numparams; $i++) {
 260              $params[] = $encoder->decode($orequest->getParam($i));
 261          }
 262      }
 263  
 264      // $method is something like: "mod/forum/lib.php/forum_add_instance"
 265      // $params is an array of parameters. A parameter might itself be an array.
 266  
 267      // Check that the method name consists of allowed characters only.
 268      // The method name must not begin with a / - avoid absolute paths
 269      // A dot character . is only allowed in the filename, i.e. something.php
 270      if (0 == preg_match("@^[A-Za-z0-9]+/[A-Za-z0-9/_\.-]+(\.php/)?[A-Za-z0-9_-]+$@",$method)) {
 271          throw new mnet_server_exception(713, 'nosuchfunction');
 272      }
 273  
 274      if(preg_match("/^system\./", $method)) {
 275          $callstack  = explode('.', $method);
 276      } else {
 277          $callstack  = explode('/', $method);
 278          // callstack will look like array('mod', 'forum', 'lib.php', 'forum_add_instance');
 279      }
 280  
 281      /**
 282       * What has the site administrator chosen as his dispatcher setting?
 283       * strict:      Only execute functions that are in specific files
 284       * off:         The default - don't execute anything
 285       */
 286      ////////////////////////////////////// OFF
 287      if (!isset($CFG->mnet_dispatcher_mode) ) {
 288          set_config('mnet_dispatcher_mode', 'off');
 289          throw new mnet_server_exception(704, 'nosuchservice');
 290      } elseif ('off' == $CFG->mnet_dispatcher_mode) {
 291          throw new mnet_server_exception(704, 'nosuchservice');
 292  
 293      ////////////////////////////////////// SYSTEM METHODS
 294      } elseif ($callstack[0] == 'system') {
 295          $functionname = $callstack[1];
 296          $xmlrpcserver = new \PhpXmlRpc\Server();
 297          $xmlrpcserver->functions_parameters_type = 'epivals';
 298          $xmlrpcserver->compress_response = false;
 299  
 300          // register all the system methods
 301          $systemmethods = array('listMethods', 'methodSignature', 'methodHelp', 'listServices', 'listFiles', 'retrieveFile', 'keyswap');
 302          foreach ($systemmethods as $m) {
 303              // I'm adding the canonical xmlrpc references here, however we've
 304              // already forbidden that the period (.) should be allowed in the call
 305              // stack, so if someone tries to access our XMLRPC in the normal way,
 306              // they'll already have received a RPC server fault message.
 307  
 308              // Maybe we should allow an easement so that regular XMLRPC clients can
 309              // call our system methods, and find out what we have to offer?
 310              $handler = 'mnet_system';
 311              if ($m == 'keyswap') {
 312                  $handler = 'mnet_keyswap';
 313              }
 314              if ($method == 'system.' . $m || $method == 'system/' . $m) {
 315                  $xmlrpcserver->add_to_map($method, $handler);
 316                  $xmlrpcserver->user_data = $remoteclient;
 317                  $response = $xmlrpcserver->service($payload, true);
 318                  $response = mnet_server_prepare_response($response);
 319                  echo $response;
 320                  return;
 321              }
 322          }
 323          throw new mnet_server_exception(7018, 'nosuchfunction');
 324  
 325      ////////////////////////////////////  NORMAL PLUGIN DISPATCHER
 326      } else {
 327          // anything else comes from some sort of plugin
 328          if ($rpcrecord = $DB->get_record('mnet_rpc', array('xmlrpcpath' => $method))) {
 329              $response    = mnet_server_invoke_plugin_method($method, $callstack, $rpcrecord, $payload);
 330              $response = mnet_server_prepare_response($response);
 331              echo $response;
 332              return;
 333      // if the rpc record isn't found, check to see if dangerous mode is on
 334      ////////////////////////////////////// DANGEROUS
 335          } else if ('dangerous' == $CFG->mnet_dispatcher_mode && $remoteclient->plaintext_is_ok()) {
 336              $functionname = array_pop($callstack);
 337  
 338              $filename = clean_param(implode('/',$callstack), PARAM_PATH);
 339              if (0 == preg_match("/php$/", $filename)) {
 340                  // Filename doesn't end in 'php'; possible attack?
 341                  // Generate error response - unable to locate function
 342                  throw new mnet_server_exception(7012, 'nosuchfunction');
 343              }
 344  
 345              // The call stack holds the path to any include file
 346              $includefile = $CFG->dirroot.'/'.$filename;
 347  
 348              $response = mnet_server_invoke_dangerous_method($includefile, $functionname, $method, $payload);
 349              echo $response;
 350              return;
 351          }
 352      }
 353      throw new mnet_server_exception(7012, 'nosuchfunction');
 354  }
 355  
 356  /**
 357   * Execute the system functions - mostly for introspection
 358   *
 359   * @param  string  $method    XMLRPC method name, e.g. system.listMethods
 360   * @param  array   $params    Array of parameters from the XMLRPC request
 361   * @param  string  $hostinfo  Hostinfo object from the mnet_host table
 362   *
 363   * @throws mnet_server_exception
 364   *
 365   * @return mixed              Response data - any kind of PHP variable
 366   */
 367  function mnet_system($method, $params, $hostinfo) {
 368      global $CFG, $DB;
 369  
 370      if (empty($hostinfo)) return array();
 371  
 372      $id_list = $hostinfo->id;
 373      if (!empty($CFG->mnet_all_hosts_id)) {
 374          $id_list .= ', '.$CFG->mnet_all_hosts_id;
 375      }
 376  
 377      if ('system.listMethods' == $method || 'system/listMethods' == $method) {
 378          $query = '
 379              SELECT DISTINCT
 380                  rpc.functionname,
 381                  rpc.xmlrpcpath
 382              FROM
 383                  {mnet_host2service} h2s
 384                  JOIN {mnet_service2rpc} s2r ON h2s.serviceid = s2r.serviceid
 385                  JOIN {mnet_rpc} rpc ON s2r.rpcid = rpc.id
 386                  JOIN {mnet_service} svc ON svc.id = s2r.serviceid
 387              WHERE
 388                  h2s.hostid in ('.$id_list .') AND
 389                  h2s.publish = 1 AND rpc.enabled = 1
 390                 ' . ((count($params) > 0) ?  'AND svc.name = ? ' : '') . '
 391              ORDER BY
 392                  rpc.xmlrpcpath ASC';
 393          if (count($params) > 0) {
 394              $params = array($params[0]);
 395          }
 396          $methods = array();
 397          foreach ($DB->get_records_sql($query, $params) as $result) {
 398              $methods[] = $result->xmlrpcpath;
 399          }
 400          return $methods;
 401      } elseif (in_array($method, array('system.methodSignature', 'system/methodSignature', 'system.methodHelp', 'system/methodHelp'))) {
 402          $query = '
 403              SELECT DISTINCT
 404                  rpc.functionname,
 405                  rpc.help,
 406                  rpc.profile
 407              FROM
 408                  {mnet_host2service} h2s,
 409                  {mnet_service2rpc} s2r,
 410                  {mnet_rpc} rpc
 411              WHERE
 412                  rpc.xmlrpcpath = ? AND
 413                  s2r.rpcid = rpc.id AND
 414                  h2s.publish = 1 AND rpc.enabled = 1 AND
 415                  h2s.serviceid = s2r.serviceid AND
 416                  h2s.hostid in ('.$id_list .')';
 417          $params = array($params[0]);
 418  
 419          if (!$result = $DB->get_record_sql($query, $params)) {
 420              return false;
 421          }
 422          if (strpos($method, 'methodSignature') !== false) {
 423              return unserialize($result->profile);
 424          }
 425          return $result->help;
 426      } elseif ('system.listServices' == $method || 'system/listServices' == $method) {
 427          $query = '
 428              SELECT DISTINCT
 429                  s.id,
 430                  s.name,
 431                  s.apiversion,
 432                  h2s.publish,
 433                  h2s.subscribe
 434              FROM
 435                  {mnet_host2service} h2s,
 436                  {mnet_service} s
 437              WHERE
 438                  h2s.serviceid = s.id AND
 439                 (h2s.publish = 1 OR h2s.subscribe = 1) AND
 440                  h2s.hostid in ('.$id_list .')
 441              ORDER BY
 442                  s.name ASC';
 443          $params = array();
 444  
 445          $result = $DB->get_records_sql($query, $params);
 446          $services = array();
 447  
 448          if (is_array($result)) {
 449              foreach($result as $service) {
 450                  $services[] = array('name' => $service->name,
 451                                      'apiversion' => $service->apiversion,
 452                                      'publish' => $service->publish,
 453                                      'subscribe' => $service->subscribe);
 454              }
 455          }
 456  
 457          return $services;
 458      }
 459      throw new mnet_server_exception(7019, 'nosuchfunction');
 460  }
 461  
 462  /**
 463   * Invoke a normal style plugin method
 464   * This will verify permissions first.
 465   *
 466   * @param string   $method the full xmlrpc method that was called eg auth/mnet/auth.php/user_authorise
 467   * @param array    $callstack  the exploded callstack
 468   * @param stdclass $rpcrecord  the record from mnet_rpc
 469   *
 470   * @return mixed the response from the invoked method
 471   */
 472  function mnet_server_invoke_plugin_method($method, $callstack, $rpcrecord, $payload) {
 473      mnet_verify_permissions($rpcrecord); // will throw exceptions
 474      mnet_setup_dummy_method($method, $callstack, $rpcrecord);
 475      $methodname = array_pop($callstack);
 476  
 477      $xmlrpcserver = new \PhpXmlRpc\Server();
 478      $xmlrpcserver->functions_parameters_type = 'epivals';
 479      $xmlrpcserver->compress_response = false;
 480  
 481      $xmlrpcserver->add_to_map($method, 'mnet_server_dummy_method');
 482      $xmlrpcserver->user_data = $methodname;
 483      $response = $xmlrpcserver->service($payload, true);
 484  
 485      return $response;
 486  }
 487  
 488  /**
 489   * Initialize the object (if necessary), execute the method or function, and
 490   * return the response
 491   *
 492   * @param  string  $includefile    The file that contains the object definition
 493   * @param  string  $methodname     The name of the method to execute
 494   * @param  string  $method         The full path to the method
 495   * @param  string  $payload        The XML-RPC request payload
 496   * @param  string  $class          The name of the class to instantiate (or false)
 497   *
 498   * @throws mnet_server_exception
 499   *
 500   * @return string                  The XML-RPC response
 501   */
 502  function mnet_server_invoke_dangerous_method($includefile, $methodname, $method, $payload) {
 503  
 504      if (file_exists($CFG->dirroot . $includefile)) {
 505          require_once $CFG->dirroot . $includefile;
 506          // $callprefix matches the rpc convention
 507          // of not having a leading slash
 508          $callprefix = preg_replace('!^/!', '', $includefile);
 509      } else {
 510          throw new mnet_server_exception(705, "nosuchfile");
 511      }
 512  
 513      if ($functionname != clean_param($functionname, PARAM_PATH)) {
 514          throw new mnet_server_exception(7012, "nosuchfunction");
 515      }
 516  
 517      if (!function_exists($functionname)) {
 518          throw new mnet_server_exception(7012, "nosuchfunction");
 519      }
 520  
 521      $xmlrpcserver = new \PhpXmlRpc\Server();
 522      $xmlrpcserver->functions_parameters_type = 'epivals';
 523      $xmlrpcserver->compress_response = false;
 524  
 525      $xmlrpcserver->add_to_map($method, 'mnet_server_dummy_method');
 526      $xmlrpcserver->user_data = $methodname;
 527      $response = $xmlrpcserver->service($payload, true);
 528  
 529      return $response;
 530  }
 531  
 532  
 533  /**
 534   * Accepts a public key from a new remote host and returns the public key for
 535   * this host. If 'register all hosts' is turned on, it will bootstrap a record
 536   * for the remote host in the mnet_host table (if it's not already there)
 537   *
 538   * @param  string  $function      XML-RPC requires this but we don't... discard!
 539   * @param  array   $params        Array of parameters
 540   *                                $params[0] is the remote wwwroot
 541   *                                $params[1] is the remote public key
 542   * @return string                 The XML-RPC response
 543   */
 544  function mnet_keyswap($function, $params) {
 545      global $CFG;
 546      $return = array();
 547      $mnet = get_mnet_environment();
 548  
 549      if (!empty($CFG->mnet_register_allhosts)) {
 550          $mnet_peer = new mnet_peer();
 551          list($wwwroot, $pubkey, $application) = $params;
 552          $keyok = $mnet_peer->bootstrap($wwwroot, $pubkey, $application);
 553          if ($keyok) {
 554              $mnet_peer->commit();
 555          }
 556      }
 557      return $mnet->public_key;
 558  }
 559  
 560  /**
 561   * Verify that the requested xmlrpc method can be called
 562   * This just checks the method exists in the rpc table and is enabled.
 563   *
 564   * @param stdclass $rpcrecord  the record from mnet_rpc
 565   *
 566   * @throws mnet_server_exception
 567   */
 568  function mnet_verify_permissions($rpcrecord) {
 569      global $CFG, $DB;
 570      $remoteclient = get_mnet_remote_client();
 571  
 572      $id_list = $remoteclient->id;
 573      if (!empty($CFG->mnet_all_hosts_id)) {
 574          $id_list .= ', '.$CFG->mnet_all_hosts_id;
 575      }
 576  
 577      // TODO: Change this to avoid the first column duplicate debugging, keeping current behaviour 100%.
 578  
 579      $sql = "SELECT
 580              r.*, h2s.publish
 581          FROM
 582              {mnet_rpc} r
 583              JOIN {mnet_service2rpc} s2r ON s2r.rpcid = r.id
 584              LEFT JOIN {mnet_host2service} h2s ON h2s.serviceid = s2r.serviceid
 585          WHERE
 586              r.id = ? AND
 587              h2s.hostid in ($id_list)";
 588  
 589      $params = array($rpcrecord->id);
 590  
 591      if (!$permission = $DB->get_record_sql($sql, $params)) {
 592          throw new mnet_server_exception(7012, "nosuchfunction");
 593      } else if (!$permission->publish || !$permission->enabled) {
 594          throw new mnet_server_exception(707, "nosuchfunction");
 595      }
 596  }
 597  
 598  /**
 599   * Figure out exactly what needs to be called and stashes it in $remoteclient
 600   * Does some further verification that the method is callable
 601   *
 602   * @param string   $method the full xmlrpc method that was called eg auth/mnet/auth.php/user_authorise
 603   * @param array    $callstack  the exploded callstack
 604   * @param stdclass $rpcrecord  the record from mnet_rpc
 605   *
 606   * @throws mnet_server_exception
 607   */
 608  function mnet_setup_dummy_method($method, $callstack, $rpcrecord) {
 609      global $CFG;
 610      $remoteclient = get_mnet_remote_client();
 611      // verify that the callpath in the stack matches our records
 612      // callstack will look like array('mod', 'forum', 'lib.php', 'forum_add_instance');
 613      $path = core_component::get_plugin_directory($rpcrecord->plugintype, $rpcrecord->pluginname);
 614      $path = substr($path, strlen($CFG->dirroot)+1); // this is a bit hacky and fragile, it is not guaranteed that plugins are in dirroot
 615      array_pop($callstack);
 616      $providedpath =  implode('/', $callstack);
 617      if ($providedpath != $path . '/' . $rpcrecord->filename) {
 618          throw new mnet_server_exception(705, "nosuchfile");
 619      }
 620      if (!file_exists($CFG->dirroot . '/' . $providedpath)) {
 621          throw new mnet_server_exception(705, "nosuchfile");
 622      }
 623      require_once($CFG->dirroot . '/' . $providedpath);
 624      if (!empty($rpcrecord->classname)) {
 625          if (!class_exists($rpcrecord->classname)) {
 626              throw new mnet_server_exception(708, 'nosuchclass');
 627          }
 628          if (!$rpcrecord->static) {
 629              try {
 630                  $object = new $rpcrecord->classname;
 631              } catch (Exception $e) {
 632                  throw new mnet_server_exception(709, "classerror");
 633              }
 634              if (!is_callable(array($object, $rpcrecord->functionname))) {
 635                  throw new mnet_server_exception(706, "nosuchfunction");
 636              }
 637              $remoteclient->object_to_call($object);
 638          } else {
 639              if (!is_callable(array($rpcrecord->classname, $rpcrecord->functionname))) {
 640                  throw new mnet_server_exception(706, "nosuchfunction");
 641              }
 642              $remoteclient->static_location($rpcrecord->classname);
 643          }
 644      }
 645  }
 646  
 647  /**
 648   * Dummy function for the XML-RPC dispatcher - use to call a method on an object
 649   * or to call a function
 650   *
 651   * Translate XML-RPC's strange function call syntax into a more straightforward
 652   * PHP-friendly alternative. This dummy function will be called by the
 653   * dispatcher, and can be used to call a method on an object, or just a function
 654   *
 655   * The methodName argument (eg. mnet/testlib/mnet_concatenate_strings)
 656   * is ignored.
 657   *
 658   * @throws mnet_server_exception
 659   *
 660   * @param  string  $methodname     We discard this - see 'functionname'
 661   * @param  array   $argsarray      Each element is an argument to the real
 662   *                                 function
 663   * @param  string  $functionname   The name of the PHP function you want to call
 664   * @return mixed                   The return value will be that of the real
 665   *                                 function, whatever it may be.
 666   */
 667  function mnet_server_dummy_method($methodname, $argsarray, $functionname) {
 668      $remoteclient = get_mnet_remote_client();
 669      try {
 670          if (is_object($remoteclient->object_to_call)) {
 671              return @call_user_func_array(array($remoteclient->object_to_call,$functionname), $argsarray);
 672          } else if (!empty($remoteclient->static_location)) {
 673              return @call_user_func_array(array($remoteclient->static_location, $functionname), $argsarray);
 674          } else {
 675              return @call_user_func_array($functionname, $argsarray);
 676          }
 677      } catch (Exception $e) {
 678          exit(mnet_server_fault($e->getCode(), $e->getMessage()));
 679      }
 680  }
 681  /**
 682   * mnet server exception.  extends moodle_exception, but takes slightly different arguments.
 683   * and unlike the rest of moodle, the actual int error code is used.
 684   * this exception should only be used during an xmlrpc server request, ie, not for client requests.
 685   */
 686  class mnet_server_exception extends moodle_exception {
 687  
 688      /**
 689       * @param int    $intcode      the numerical error associated with this fault.  this is <b>not</b> the string errorcode
 690       * @param string $langkey      the error message in full (<b>get_string will not be used</b>)
 691       * @param string $module       the language module, defaults to 'mnet'
 692       * @param mixed  $a            params for get_string
 693       */
 694      public function __construct($intcode, $languagekey, $module='mnet', $a=null) {
 695          parent::__construct($languagekey, $module, '', $a);
 696          $this->code    = $intcode;
 697  
 698      }
 699  }
 700