| [ Index ] |
PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008] |
[Summary view] [Print] [Text view]
1 <?php // $Id: lib.php,v 1.16.2.8 2008/08/20 06:00:08 peterbulmer Exp $ 2 /** 3 * Library functions for mnet 4 * 5 * @author Donal McMullan donal@catalyst.net.nz 6 * @version 0.0.1 7 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License 8 * @package mnet 9 */ 10 require_once $CFG->dirroot.'/mnet/xmlrpc/xmlparser.php'; 11 require_once $CFG->dirroot.'/mnet/peer.php'; 12 require_once $CFG->dirroot.'/mnet/environment.php'; 13 14 /// CONSTANTS /////////////////////////////////////////////////////////// 15 16 define('RPC_OK', 0); 17 define('RPC_NOSUCHFILE', 1); 18 define('RPC_NOSUCHCLASS', 2); 19 define('RPC_NOSUCHFUNCTION', 3); 20 define('RPC_FORBIDDENFUNCTION', 4); 21 define('RPC_NOSUCHMETHOD', 5); 22 define('RPC_FORBIDDENMETHOD', 6); 23 24 $MNET = new mnet_environment(); 25 $MNET->init(); 26 27 /** 28 * Strip extraneous detail from a URL or URI and return the hostname 29 * 30 * @param string $uri The URI of a file on the remote computer, optionally 31 * including its http:// prefix like 32 * http://www.example.com/index.html 33 * @return string Just the hostname 34 */ 35 function mnet_get_hostname_from_uri($uri = null) { 36 $count = preg_match("@^(?:http[s]?://)?([A-Z0-9\-\.]+).*@i", $uri, $matches); 37 if ($count > 0) return $matches[1]; 38 return false; 39 } 40 41 /** 42 * Get the remote machine's SSL Cert 43 * 44 * @param string $uri The URI of a file on the remote computer, including 45 * its http:// or https:// prefix 46 * @return string A PEM formatted SSL Certificate. 47 */ 48 function mnet_get_public_key($uri, $application=null) { 49 global $CFG, $MNET; 50 // The key may be cached in the mnet_set_public_key function... 51 // check this first 52 $key = mnet_set_public_key($uri); 53 if ($key != false) { 54 return $key; 55 } 56 57 if (empty($application)) { 58 $application = get_record('mnet_application', 'name', 'moodle'); 59 } 60 61 $rq = xmlrpc_encode_request('system/keyswap', array($CFG->wwwroot, $MNET->public_key, $application->name), array("encoding" => "utf-8")); 62 $ch = curl_init($uri . $application->xmlrpc_server_url); 63 64 curl_setopt($ch, CURLOPT_TIMEOUT, 60); 65 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 66 curl_setopt($ch, CURLOPT_POST, true); 67 curl_setopt($ch, CURLOPT_USERAGENT, 'Moodle'); 68 curl_setopt($ch, CURLOPT_POSTFIELDS, $rq); 69 curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type: text/xml charset=UTF-8")); 70 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 71 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); 72 73 $res = xmlrpc_decode(curl_exec($ch)); 74 75 // check for curl errors 76 $curlerrno = curl_errno($ch); 77 if ($curlerrno!=0) { 78 debugging("Request for $uri failed with curl error $curlerrno"); 79 } 80 81 // check HTTP error code 82 $info = curl_getinfo($ch); 83 if (!empty($info['http_code']) and ($info['http_code'] != 200)) { 84 debugging("Request for $uri failed with HTTP code ".$info['http_code']); 85 } 86 87 curl_close($ch); 88 89 if (!is_array($res)) { // ! error 90 $public_certificate = $res; 91 $credentials=array(); 92 if (strlen(trim($public_certificate))) { 93 $credentials = openssl_x509_parse($public_certificate); 94 $host = $credentials['subject']['CN']; 95 if (strpos($uri, $host) !== false) { 96 mnet_set_public_key($uri, $public_certificate); 97 return $public_certificate; 98 } 99 else { 100 debugging("Request for $uri returned public key for different URI - $host"); 101 } 102 } 103 else { 104 debugging("Request for $uri returned empty response"); 105 } 106 } 107 else { 108 debugging( "Request for $uri returned unexpected result"); 109 } 110 return false; 111 } 112 113 /** 114 * Store a URI's public key in a static variable, or retrieve the key for a URI 115 * 116 * @param string $uri The URI of a file on the remote computer, including its 117 * https:// prefix 118 * @param mixed $key A public key to store in the array OR null. If the key 119 * is null, the function will return the previously stored 120 * key for the supplied URI, should it exist. 121 * @return mixed A public key OR true/false. 122 */ 123 function mnet_set_public_key($uri, $key = null) { 124 static $keyarray = array(); 125 if (isset($keyarray[$uri]) && empty($key)) { 126 return $keyarray[$uri]; 127 } elseif (!empty($key)) { 128 $keyarray[$uri] = $key; 129 return true; 130 } 131 return false; 132 } 133 134 /** 135 * Sign a message and return it in an XML-Signature document 136 * 137 * This function can sign any content, but it was written to provide a system of 138 * signing XML-RPC request and response messages. The message will be base64 139 * encoded, so it does not need to be text. 140 * 141 * We compute the SHA1 digest of the message. 142 * We compute a signature on that digest with our private key. 143 * We link to the public key that can be used to verify our signature. 144 * We base64 the message data. 145 * We identify our wwwroot - this must match our certificate's CN 146 * 147 * The XML-RPC document will be parceled inside an XML-SIG document, which holds 148 * the base64_encoded XML as an object, the SHA1 digest of that document, and a 149 * signature of that document using the local private key. This signature will 150 * uniquely identify the RPC document as having come from this server. 151 * 152 * See the {@Link http://www.w3.org/TR/xmldsig-core/ XML-DSig spec} at the W3c 153 * site 154 * 155 * @param string $message The data you want to sign 156 * @param resource $privatekey The private key to sign the response with 157 * @return string An XML-DSig document 158 */ 159 function mnet_sign_message($message, $privatekey = null) { 160 global $CFG, $MNET; 161 $digest = sha1($message); 162 163 // If the user hasn't supplied a private key (for example, one of our older, 164 // expired private keys, we get the current default private key and use that. 165 if ($privatekey == null) { 166 $privatekey = $MNET->get_private_key(); 167 } 168 169 // The '$sig' value below is returned by reference. 170 // We initialize it first to stop my IDE from complaining. 171 $sig = ''; 172 $bool = openssl_sign($message, $sig, $privatekey); // TODO: On failure? 173 174 $message = '<?xml version="1.0" encoding="iso-8859-1"?> 175 <signedMessage> 176 <Signature Id="MoodleSignature" xmlns="http://www.w3.org/2000/09/xmldsig#"> 177 <SignedInfo> 178 <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/> 179 <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#dsa-sha1"/> 180 <Reference URI="#XMLRPC-MSG"> 181 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/> 182 <DigestValue>'.$digest.'</DigestValue> 183 </Reference> 184 </SignedInfo> 185 <SignatureValue>'.base64_encode($sig).'</SignatureValue> 186 <KeyInfo> 187 <RetrievalMethod URI="'.$CFG->wwwroot.'/mnet/publickey.php"/> 188 </KeyInfo> 189 </Signature> 190 <object ID="XMLRPC-MSG">'.base64_encode($message).'</object> 191 <wwwroot>'.$MNET->wwwroot.'</wwwroot> 192 <timestamp>'.time().'</timestamp> 193 </signedMessage>'; 194 return $message; 195 } 196 197 /** 198 * Encrypt a message and return it in an XML-Encrypted document 199 * 200 * This function can encrypt any content, but it was written to provide a system 201 * of encrypting XML-RPC request and response messages. The message will be 202 * base64 encoded, so it does not need to be text - binary data should work. 203 * 204 * We compute the SHA1 digest of the message. 205 * We compute a signature on that digest with our private key. 206 * We link to the public key that can be used to verify our signature. 207 * We base64 the message data. 208 * We identify our wwwroot - this must match our certificate's CN 209 * 210 * The XML-RPC document will be parceled inside an XML-SIG document, which holds 211 * the base64_encoded XML as an object, the SHA1 digest of that document, and a 212 * signature of that document using the local private key. This signature will 213 * uniquely identify the RPC document as having come from this server. 214 * 215 * See the {@Link http://www.w3.org/TR/xmlenc-core/ XML-ENC spec} at the W3c 216 * site 217 * 218 * @param string $message The data you want to sign 219 * @param string $remote_certificate Peer's certificate in PEM format 220 * @return string An XML-ENC document 221 */ 222 function mnet_encrypt_message($message, $remote_certificate) { 223 global $MNET; 224 225 // Generate a key resource from the remote_certificate text string 226 $publickey = openssl_get_publickey($remote_certificate); 227 228 if ( gettype($publickey) != 'resource' ) { 229 // Remote certificate is faulty. 230 return false; 231 } 232 233 // Initialize vars 234 $encryptedstring = ''; 235 $symmetric_keys = array(); 236 237 // passed by ref -> &$encryptedstring &$symmetric_keys 238 $bool = openssl_seal($message, $encryptedstring, $symmetric_keys, array($publickey)); 239 $message = $encryptedstring; 240 $symmetrickey = array_pop($symmetric_keys); 241 242 $message = '<?xml version="1.0" encoding="iso-8859-1"?> 243 <encryptedMessage> 244 <EncryptedData Id="ED" xmlns="http://www.w3.org/2001/04/xmlenc#"> 245 <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#arcfour"/> 246 <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> 247 <ds:RetrievalMethod URI="#EK" Type="http://www.w3.org/2001/04/xmlenc#EncryptedKey"/> 248 <ds:KeyName>XMLENC</ds:KeyName> 249 </ds:KeyInfo> 250 <CipherData> 251 <CipherValue>'.base64_encode($message).'</CipherValue> 252 </CipherData> 253 </EncryptedData> 254 <EncryptedKey Id="EK" xmlns="http://www.w3.org/2001/04/xmlenc#"> 255 <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/> 256 <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> 257 <ds:KeyName>SSLKEY</ds:KeyName> 258 </ds:KeyInfo> 259 <CipherData> 260 <CipherValue>'.base64_encode($symmetrickey).'</CipherValue> 261 </CipherData> 262 <ReferenceList> 263 <DataReference URI="#ED"/> 264 </ReferenceList> 265 <CarriedKeyName>XMLENC</CarriedKeyName> 266 </EncryptedKey> 267 <wwwroot>'.$MNET->wwwroot.'</wwwroot> 268 </encryptedMessage>'; 269 return $message; 270 } 271 272 /** 273 * Get your SSL keys from the database, or create them (if they don't exist yet) 274 * 275 * Get your SSL keys from the database, or (if they don't exist yet) call 276 * mnet_generate_keypair to create them 277 * 278 * @param string $string The text you want to sign 279 * @return string The signature over that text 280 */ 281 function mnet_get_keypair() { 282 global $CFG; 283 static $keypair = null; 284 if (!is_null($keypair)) return $keypair; 285 if ($result = get_field('config_plugins', 'value', 'plugin', 'mnet', 'name', 'openssl')) { 286 list($keypair['certificate'], $keypair['keypair_PEM']) = explode('@@@@@@@@', $result); 287 $keypair['privatekey'] = openssl_pkey_get_private($keypair['keypair_PEM']); 288 $keypair['publickey'] = openssl_pkey_get_public($keypair['certificate']); 289 return $keypair; 290 } else { 291 $keypair = mnet_generate_keypair(); 292 return $keypair; 293 } 294 } 295 296 /** 297 * Generate public/private keys and store in the config table 298 * 299 * Use the distinguished name provided to create a CSR, and then sign that CSR 300 * with the same credentials. Store the keypair you create in the config table. 301 * If a distinguished name is not provided, create one using the fullname of 302 * 'the course with ID 1' as your organization name, and your hostname (as 303 * detailed in $CFG->wwwroot). 304 * 305 * @param array $dn The distinguished name of the server 306 * @return string The signature over that text 307 */ 308 function mnet_generate_keypair($dn = null, $days=28) { 309 global $CFG, $USER; 310 311 // check if lifetime has been overriden 312 if (!empty($CFG->mnetkeylifetime)) { 313 $days = $CFG->mnetkeylifetime; 314 } 315 316 $host = strtolower($CFG->wwwroot); 317 $host = ereg_replace("^http(s)?://",'',$host); 318 $break = strpos($host.'/' , '/'); 319 $host = substr($host, 0, $break); 320 321 if ($result = get_record_select('course'," id ='".SITEID."' ")) { 322 $organization = $result->fullname; 323 } else { 324 $organization = 'None'; 325 } 326 327 $keypair = array(); 328 329 $country = 'NZ'; 330 $province = 'Wellington'; 331 $locality = 'Wellington'; 332 $email = $CFG->noreplyaddress; 333 334 if(!empty($USER->country)) { 335 $country = $USER->country; 336 } 337 if(!empty($USER->city)) { 338 $province = $USER->city; 339 $locality = $USER->city; 340 } 341 if(!empty($USER->email)) { 342 $email = $USER->email; 343 } 344 345 if (is_null($dn)) { 346 $dn = array( 347 "countryName" => $country, 348 "stateOrProvinceName" => $province, 349 "localityName" => $locality, 350 "organizationName" => $organization, 351 "organizationalUnitName" => 'Moodle', 352 "commonName" => $CFG->wwwroot, 353 "emailAddress" => $email 354 ); 355 } 356 357 // ensure we remove trailing slashes 358 $dn["commonName"] = preg_replace(':/$:', '', $dn["commonName"]); 359 360 $new_key = openssl_pkey_new(); 361 $csr_rsc = openssl_csr_new($dn, $new_key, array('private_key_bits',2048)); 362 $selfSignedCert = openssl_csr_sign($csr_rsc, null, $new_key, $days); 363 unset($csr_rsc); // Free up the resource 364 365 // We export our self-signed certificate to a string. 366 openssl_x509_export($selfSignedCert, $keypair['certificate']); 367 openssl_x509_free($selfSignedCert); 368 369 // Export your public/private key pair as a PEM encoded string. You 370 // can protect it with an optional passphrase if you wish. 371 $export = openssl_pkey_export($new_key, $keypair['keypair_PEM'] /* , $passphrase */); 372 openssl_pkey_free($new_key); 373 unset($new_key); // Free up the resource 374 375 return $keypair; 376 } 377 378 /** 379 * Check that an IP address falls within the given network/mask 380 * ok for export 381 * 382 * @param string $address Dotted quad 383 * @param string $network Dotted quad 384 * @param string $mask A number, e.g. 16, 24, 32 385 * @return bool 386 */ 387 function ip_in_range($address, $network, $mask) { 388 $lnetwork = ip2long($network); 389 $laddress = ip2long($address); 390 391 $binnet = str_pad( decbin($lnetwork),32,"0","STR_PAD_LEFT" ); 392 $firstpart = substr($binnet,0,$mask); 393 394 $binip = str_pad( decbin($laddress),32,"0","STR_PAD_LEFT" ); 395 $firstip = substr($binip,0,$mask); 396 return(strcmp($firstpart,$firstip)==0); 397 } 398 399 /** 400 * Check that a given function (or method) in an include file has been designated 401 * ok for export 402 * 403 * @param string $includefile The path to the include file 404 * @param string $functionname The name of the function (or method) to 405 * execute 406 * @param mixed $class A class name, or false if we're just testing 407 * a function 408 * @return int Zero (RPC_OK) if all ok - appropriate 409 * constant otherwise 410 */ 411 function mnet_permit_rpc_call($includefile, $functionname, $class=false) { 412 global $CFG, $MNET_REMOTE_CLIENT; 413 414 if (file_exists($CFG->dirroot . $includefile)) { 415 include_once $CFG->dirroot . $includefile; 416 // $callprefix matches the rpc convention 417 // of not having a leading slash 418 $callprefix = preg_replace('!^/!', '', $includefile); 419 } else { 420 return RPC_NOSUCHFILE; 421 } 422 423 if ($functionname != clean_param($functionname, PARAM_PATH)) { 424 // Under attack? 425 // Todo: Should really return a much more BROKEN! response 426 return RPC_FORBIDDENMETHOD; 427 } 428 429 $id_list = $MNET_REMOTE_CLIENT->id; 430 if (!empty($CFG->mnet_all_hosts_id)) { 431 $id_list .= ', '.$CFG->mnet_all_hosts_id; 432 } 433 434 // TODO: change to left-join so we can disambiguate: 435 // 1. method doesn't exist 436 // 2. method exists but is prohibited 437 $sql = " 438 SELECT 439 count(r.id) 440 FROM 441 {$CFG->prefix}mnet_host2service h2s, 442 {$CFG->prefix}mnet_service2rpc s2r, 443 {$CFG->prefix}mnet_rpc r 444 WHERE 445 h2s.serviceid = s2r.serviceid AND 446 s2r.rpcid = r.id AND 447 r.xmlrpc_path = '$callprefix/$functionname' AND 448 h2s.hostid in ($id_list) AND 449 h2s.publish = '1'"; 450 451 $permissionobj = record_exists_sql($sql); 452 453 if ($permissionobj === false && 'dangerous' != $CFG->mnet_dispatcher_mode) { 454 return RPC_FORBIDDENMETHOD; 455 } 456 457 // WE'RE LOOKING AT A CLASS/METHOD 458 if (false != $class) { 459 if (!class_exists($class)) { 460 // Generate error response - unable to locate class 461 return RPC_NOSUCHCLASS; 462 } 463 464 $object = new $class(); 465 466 if (!method_exists($object, $functionname)) { 467 // Generate error response - unable to locate method 468 return RPC_NOSUCHMETHOD; 469 } 470 471 if (!method_exists($object, 'mnet_publishes')) { 472 // Generate error response - the class doesn't publish 473 // *any* methods, because it doesn't have an mnet_publishes 474 // method 475 return RPC_FORBIDDENMETHOD; 476 } 477 478 // Get the list of published services - initialise method array 479 $servicelist = $object->mnet_publishes(); 480 $methodapproved = false; 481 482 // If the method is in the list of approved methods, set the 483 // methodapproved flag to true and break 484 foreach($servicelist as $service) { 485 if (in_array($functionname, $service['methods'])) { 486 $methodapproved = true; 487 break; 488 } 489 } 490 491 if (!$methodapproved) { 492 return RPC_FORBIDDENMETHOD; 493 } 494 495 // Stash the object so we can call the method on it later 496 $MNET_REMOTE_CLIENT->object_to_call($object); 497 // WE'RE LOOKING AT A FUNCTION 498 } else { 499 if (!function_exists($functionname)) { 500 // Generate error response - unable to locate function 501 return RPC_NOSUCHFUNCTION; 502 } 503 504 } 505 506 return RPC_OK; 507 } 508 509 function mnet_update_sso_access_control($username, $mnet_host_id, $accessctrl) { 510 $mnethost = get_record('mnet_host', 'id', $mnet_host_id); 511 if ($aclrecord = get_record('mnet_sso_access_control', 'username', $username, 'mnet_host_id', $mnet_host_id)) { 512 // update 513 $aclrecord->accessctrl = $accessctrl; 514 if (update_record('mnet_sso_access_control', $aclrecord)) { 515 add_to_log(SITEID, 'admin/mnet', 'update', 'admin/mnet/access_control.php', 516 "SSO ACL: $accessctrl user '$username' from {$mnethost->name}"); 517 } else { 518 print_error('failedaclwrite', 'mnet', '', $username); 519 return false; 520 } 521 } else { 522 // insert 523 $aclrecord->username = $username; 524 $aclrecord->accessctrl = $accessctrl; 525 $aclrecord->mnet_host_id = $mnet_host_id; 526 if ($id = insert_record('mnet_sso_access_control', $aclrecord)) { 527 add_to_log(SITEID, 'admin/mnet', 'add', 'admin/mnet/access_control.php', 528 "SSO ACL: $accessctrl user '$username' from {$mnethost->name}"); 529 } else { 530 print_error('failedaclwrite', 'mnet', '', $username); 531 return false; 532 } 533 } 534 return true; 535 } 536 537 function mnet_get_peer_host ($mnethostid) { 538 static $hosts; 539 if (!isset($hosts[$mnethostid])) { 540 $host = get_record('mnet_host', 'id', $mnethostid); 541 $hosts[$mnethostid] = $host; 542 } 543 return $hosts[$mnethostid]; 544 } 545 546 /** 547 * Inline function to modify a url string so that mnet users are requested to 548 * log in at their mnet identity provider (if they are not already logged in) 549 * before ultimately being directed to the original url. 550 * 551 * uses global MNETIDPJUMPURL the url which user should initially be directed to 552 * MNETIDPJUMPURL is a URL associated with a moodle networking peer when it 553 * is fulfiling a role as an identity provider (IDP). Different urls for 554 * different peers, the jumpurl is formed partly from the IDP's webroot, and 555 * partly from a predefined local path within that webwroot. 556 * The result of the user hitting MNETIDPJUMPURL is that they will be asked 557 * to login (at their identity provider (if they aren't already)), mnet 558 * will prepare the necessary authentication information, then redirect 559 * them back to somewhere at the content provider(CP) moodle (this moodle) 560 * @param array $url array with 2 elements 561 * 0 - context the url was taken from, possibly just the url, possibly href="url" 562 * 1 - the destination url 563 * @return string the url the remote user should be supplied with. 564 */ 565 function mnet_sso_apply_indirection ($url) { 566 global $MNETIDPJUMPURL; 567 568 $localpart=''; 569 $urlparts = parse_url($url[1]); 570 if($urlparts) { 571 if (isset($urlparts['path'])) { 572 $localpart .= $urlparts['path']; 573 } 574 if (isset($urlparts['query'])) { 575 $localpart .= '?'.$urlparts['query']; 576 } 577 if (isset($urlparts['fragment'])) { 578 $localpart .= '#'.$urlparts['fragment']; 579 } 580 } 581 $indirecturl = $MNETIDPJUMPURL . urlencode($localpart); 582 //If we matched on more than just a url (ie an html link), return the url to an href format 583 if ($url[0] != $url[1]) { 584 $indirecturl = 'href="'.$indirecturl.'"'; 585 } 586 return $indirecturl; 587 } 588 589 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Wed Jan 14 11:33:29 2009 | Cross-referenced by PHPXref 0.7 |