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