Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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

   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());
  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);
  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      // 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      // Check that the method name consists of allowed characters only.
 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);
 307                  $response = xmlrpc_server_call_method($xmlrpcserver, $payload, $remoteclient, array("encoding" => "utf-8"));
 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
 319          if ($rpcrecord = $DB->get_record('mnet_rpc', array('xmlrpcpath' => $method))) {
 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
 326          } else if ('dangerous' == $CFG->mnet_dispatcher_mode && $remoteclient->plaintext_is_ok()) {
 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
 371                  rpc.functionname,
 372                  rpc.xmlrpcpath
 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
 383                  rpc.xmlrpcpath ASC';
 384          if (count($params) > 0) {
 385              $params = array($params[0]);
 386          }
 387          $methods = array();
 388          foreach ($DB->get_records_sql($query, $params) as $result) {
 389              $methods[] = $result->xmlrpcpath;
 390          }
 391          return $methods;
 392      } elseif (in_array($method, array('system.methodSignature', 'system/methodSignature', 'system.methodHelp', 'system/methodHelp'))) {
 393          $query = '
 394              SELECT DISTINCT
 395                  rpc.functionname,
 396                  rpc.help,
 397                  rpc.profile
 398              FROM
 399                  {mnet_host2service} h2s,
 400                  {mnet_service2rpc} s2r,
 401                  {mnet_rpc} rpc
 402              WHERE
 403                  rpc.xmlrpcpath = ? AND
 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) {
 527      global $CFG;
 528      $return = array();
 529      $mnet = get_mnet_environment();
 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      }
 539      return $mnet->public_key;
 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) {
 551      global $CFG, $DB;
 552      $remoteclient = get_mnet_remote_client();
 553  
 554      $id_list = $remoteclient->id;
 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  /**
 579   * Figure out exactly what needs to be called and stashes it in $remoteclient
 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) {
 589      global $CFG;
 590      $remoteclient = get_mnet_remote_client();
 591      // verify that the callpath in the stack matches our records
 592      // callstack will look like array('mod', 'forum', 'lib.php', 'forum_add_instance');
 593      $path = core_component::get_plugin_directory($rpcrecord->plugintype, $rpcrecord->pluginname);
 594      $path = substr($path, strlen($CFG->dirroot)+1); // this is a bit hacky and fragile, it is not guaranteed that plugins are in dirroot
 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              }
 614              if (!is_callable(array($object, $rpcrecord->functionname))) {
 615                  throw new mnet_server_exception(706, "nosuchfunction");
 616              }
 617              $remoteclient->object_to_call($object);
 618          } else {
 619              if (!is_callable(array($rpcrecord->classname, $rpcrecord->functionname))) {
 620                  throw new mnet_server_exception(706, "nosuchfunction");
 621              }
 622              $remoteclient->static_location($rpcrecord->classname);
 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) {
 648      $remoteclient = get_mnet_remote_client();
 649      try {
 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);
 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.
 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
 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'
 672       * @param mixed  $a            params for get_string
 673       */
 674      public function __construct($intcode, $languagekey, $module='mnet', $a=null) {
 675          parent::__construct($languagekey, $module, '', $a);
 676          $this->code    = $intcode;
 677  
 678      }
 679  }
 680