1<?php 2/** 3 * Authentication library 4 * 5 * Including this file will automatically try to login 6 * a user by calling auth_login() 7 * 8 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 9 * @author Andreas Gohr <andi@splitbrain.org> 10 */ 11 12if(!defined('DOKU_INC')) die('meh.'); 13 14// some ACL level defines 15define('AUTH_NONE', 0); 16define('AUTH_READ', 1); 17define('AUTH_EDIT', 2); 18define('AUTH_CREATE', 4); 19define('AUTH_UPLOAD', 8); 20define('AUTH_DELETE', 16); 21define('AUTH_ADMIN', 255); 22 23/** 24 * Initialize the auth system. 25 * 26 * This function is automatically called at the end of init.php 27 * 28 * This used to be the main() of the auth.php 29 * 30 * @todo backend loading maybe should be handled by the class autoloader 31 * @todo maybe split into multiple functions at the XXX marked positions 32 * @triggers AUTH_LOGIN_CHECK 33 * @return bool 34 */ 35function auth_setup() { 36 global $conf; 37 /* @var auth_basic $auth */ 38 global $auth; 39 global $AUTH_ACL; 40 global $lang; 41 $AUTH_ACL = array(); 42 43 if(!$conf['useacl']) return false; 44 45 // load the the backend auth functions and instantiate the auth object XXX 46 if(@file_exists(DOKU_INC.'inc/auth/'.$conf['authtype'].'.class.php')) { 47 require_once(DOKU_INC.'inc/auth/basic.class.php'); 48 require_once(DOKU_INC.'inc/auth/'.$conf['authtype'].'.class.php'); 49 50 $auth_class = "auth_".$conf['authtype']; 51 if(class_exists($auth_class)) { 52 $auth = new $auth_class(); 53 if($auth->success == false) { 54 // degrade to unauthenticated user 55 unset($auth); 56 auth_logoff(); 57 msg($lang['authtempfail'], -1); 58 } 59 } else { 60 nice_die($lang['authmodfailed']); 61 } 62 } else { 63 nice_die($lang['authmodfailed']); 64 } 65 66 if(!$auth) return false; 67 68 // do the login either by cookie or provided credentials XXX 69 if(!isset($_REQUEST['u'])) $_REQUEST['u'] = ''; 70 if(!isset($_REQUEST['p'])) $_REQUEST['p'] = ''; 71 if(!isset($_REQUEST['r'])) $_REQUEST['r'] = ''; 72 $_REQUEST['http_credentials'] = false; 73 if(!$conf['rememberme']) $_REQUEST['r'] = false; 74 75 // handle renamed HTTP_AUTHORIZATION variable (can happen when a fix like 76 // the one presented at 77 // http://www.besthostratings.com/articles/http-auth-php-cgi.html is used 78 // for enabling HTTP authentication with CGI/SuExec) 79 if(isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) 80 $_SERVER['HTTP_AUTHORIZATION'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; 81 // streamline HTTP auth credentials (IIS/rewrite -> mod_php) 82 if(isset($_SERVER['HTTP_AUTHORIZATION'])) { 83 list($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) = 84 explode(':', base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6))); 85 } 86 87 // if no credentials were given try to use HTTP auth (for SSO) 88 if(empty($_REQUEST['u']) && empty($_COOKIE[DOKU_COOKIE]) && !empty($_SERVER['PHP_AUTH_USER'])) { 89 $_REQUEST['u'] = $_SERVER['PHP_AUTH_USER']; 90 $_REQUEST['p'] = $_SERVER['PHP_AUTH_PW']; 91 $_REQUEST['http_credentials'] = true; 92 } 93 94 // apply cleaning 95 $_REQUEST['u'] = $auth->cleanUser($_REQUEST['u']); 96 97 if(isset($_REQUEST['authtok'])) { 98 // when an authentication token is given, trust the session 99 auth_validateToken($_REQUEST['authtok']); 100 } elseif(!is_null($auth) && $auth->canDo('external')) { 101 // external trust mechanism in place 102 $auth->trustExternal($_REQUEST['u'], $_REQUEST['p'], $_REQUEST['r']); 103 } else { 104 $evdata = array( 105 'user' => $_REQUEST['u'], 106 'password' => $_REQUEST['p'], 107 'sticky' => $_REQUEST['r'], 108 'silent' => $_REQUEST['http_credentials'], 109 ); 110 trigger_event('AUTH_LOGIN_CHECK', $evdata, 'auth_login_wrapper'); 111 } 112 113 //load ACL into a global array XXX 114 $AUTH_ACL = auth_loadACL(); 115 116 return true; 117} 118 119/** 120 * Loads the ACL setup and handle user wildcards 121 * 122 * @author Andreas Gohr <andi@splitbrain.org> 123 * @return array 124 */ 125function auth_loadACL() { 126 global $config_cascade; 127 128 if(!is_readable($config_cascade['acl']['default'])) return array(); 129 130 $acl = file($config_cascade['acl']['default']); 131 132 //support user wildcard 133 if(isset($_SERVER['REMOTE_USER'])) { 134 $len = count($acl); 135 for($i = 0; $i < $len; $i++) { 136 if($acl[$i]{0} == '#') continue; 137 list($id, $rest) = preg_split('/\s+/', $acl[$i], 2); 138 $id = str_replace('%USER%', cleanID($_SERVER['REMOTE_USER']), $id); 139 $rest = str_replace('%USER%', auth_nameencode($_SERVER['REMOTE_USER']), $rest); 140 $acl[$i] = "$id\t$rest"; 141 } 142 } 143 return $acl; 144} 145 146/** 147 * Event hook callback for AUTH_LOGIN_CHECK 148 * 149 * @param $evdata 150 * @return bool 151 */ 152function auth_login_wrapper($evdata) { 153 return auth_login( 154 $evdata['user'], 155 $evdata['password'], 156 $evdata['sticky'], 157 $evdata['silent'] 158 ); 159} 160 161/** 162 * This tries to login the user based on the sent auth credentials 163 * 164 * The authentication works like this: if a username was given 165 * a new login is assumed and user/password are checked. If they 166 * are correct the password is encrypted with blowfish and stored 167 * together with the username in a cookie - the same info is stored 168 * in the session, too. Additonally a browserID is stored in the 169 * session. 170 * 171 * If no username was given the cookie is checked: if the username, 172 * crypted password and browserID match between session and cookie 173 * no further testing is done and the user is accepted 174 * 175 * If a cookie was found but no session info was availabe the 176 * blowfish encrypted password from the cookie is decrypted and 177 * together with username rechecked by calling this function again. 178 * 179 * On a successful login $_SERVER[REMOTE_USER] and $USERINFO 180 * are set. 181 * 182 * @author Andreas Gohr <andi@splitbrain.org> 183 * 184 * @param string $user Username 185 * @param string $pass Cleartext Password 186 * @param bool $sticky Cookie should not expire 187 * @param bool $silent Don't show error on bad auth 188 * @return bool true on successful auth 189 */ 190function auth_login($user, $pass, $sticky = false, $silent = false) { 191 global $USERINFO; 192 global $conf; 193 global $lang; 194 /* @var auth_basic $auth */ 195 global $auth; 196 197 $sticky ? $sticky = true : $sticky = false; //sanity check 198 199 if(!$auth) return false; 200 201 if(!empty($user)) { 202 //usual login 203 if($auth->checkPass($user, $pass)) { 204 // make logininfo globally available 205 $_SERVER['REMOTE_USER'] = $user; 206 $secret = auth_cookiesalt(!$sticky); //bind non-sticky to session 207 auth_setCookie($user, PMA_blowfish_encrypt($pass, $secret), $sticky); 208 return true; 209 } else { 210 //invalid credentials - log off 211 if(!$silent) msg($lang['badlogin'], -1); 212 auth_logoff(); 213 return false; 214 } 215 } else { 216 // read cookie information 217 list($user, $sticky, $pass) = auth_getCookie(); 218 if($user && $pass) { 219 // we got a cookie - see if we can trust it 220 221 // get session info 222 $session = $_SESSION[DOKU_COOKIE]['auth']; 223 if(isset($session) && 224 $auth->useSessionCache($user) && 225 ($session['time'] >= time() - $conf['auth_security_timeout']) && 226 ($session['user'] == $user) && 227 ($session['pass'] == sha1($pass)) && //still crypted 228 ($session['buid'] == auth_browseruid()) 229 ) { 230 231 // he has session, cookie and browser right - let him in 232 $_SERVER['REMOTE_USER'] = $user; 233 $USERINFO = $session['info']; //FIXME move all references to session 234 return true; 235 } 236 // no we don't trust it yet - recheck pass but silent 237 $secret = auth_cookiesalt(!$sticky); //bind non-sticky to session 238 $pass = PMA_blowfish_decrypt($pass, $secret); 239 return auth_login($user, $pass, $sticky, true); 240 } 241 } 242 //just to be sure 243 auth_logoff(true); 244 return false; 245} 246 247/** 248 * Checks if a given authentication token was stored in the session 249 * 250 * Will setup authentication data using data from the session if the 251 * token is correct. Will exit with a 401 Status if not. 252 * 253 * @author Andreas Gohr <andi@splitbrain.org> 254 * @param string $token The authentication token 255 * @return boolean true (or will exit on failure) 256 */ 257function auth_validateToken($token) { 258 if(!$token || $token != $_SESSION[DOKU_COOKIE]['auth']['token']) { 259 // bad token 260 header("HTTP/1.0 401 Unauthorized"); 261 print 'Invalid auth token - maybe the session timed out'; 262 unset($_SESSION[DOKU_COOKIE]['auth']['token']); // no second chance 263 exit; 264 } 265 // still here? trust the session data 266 global $USERINFO; 267 $_SERVER['REMOTE_USER'] = $_SESSION[DOKU_COOKIE]['auth']['user']; 268 $USERINFO = $_SESSION[DOKU_COOKIE]['auth']['info']; 269 return true; 270} 271 272/** 273 * Create an auth token and store it in the session 274 * 275 * NOTE: this is completely unrelated to the getSecurityToken() function 276 * 277 * @author Andreas Gohr <andi@splitbrain.org> 278 * @return string The auth token 279 */ 280function auth_createToken() { 281 $token = md5(mt_rand()); 282 @session_start(); // reopen the session if needed 283 $_SESSION[DOKU_COOKIE]['auth']['token'] = $token; 284 session_write_close(); 285 return $token; 286} 287 288/** 289 * Builds a pseudo UID from browser and IP data 290 * 291 * This is neither unique nor unfakable - still it adds some 292 * security. Using the first part of the IP makes sure 293 * proxy farms like AOLs are stil okay. 294 * 295 * @author Andreas Gohr <andi@splitbrain.org> 296 * 297 * @return string a MD5 sum of various browser headers 298 */ 299function auth_browseruid() { 300 $ip = clientIP(true); 301 $uid = ''; 302 $uid .= $_SERVER['HTTP_USER_AGENT']; 303 $uid .= $_SERVER['HTTP_ACCEPT_ENCODING']; 304 $uid .= $_SERVER['HTTP_ACCEPT_LANGUAGE']; 305 $uid .= $_SERVER['HTTP_ACCEPT_CHARSET']; 306 $uid .= substr($ip, 0, strpos($ip, '.')); 307 return md5($uid); 308} 309 310/** 311 * Creates a random key to encrypt the password in cookies 312 * 313 * This function tries to read the password for encrypting 314 * cookies from $conf['metadir'].'/_htcookiesalt' 315 * if no such file is found a random key is created and 316 * and stored in this file. 317 * 318 * @author Andreas Gohr <andi@splitbrain.org> 319 * @param bool $addsession if true, the sessionid is added to the salt 320 * @return string 321 */ 322function auth_cookiesalt($addsession = false) { 323 global $conf; 324 $file = $conf['metadir'].'/_htcookiesalt'; 325 $salt = io_readFile($file); 326 if(empty($salt)) { 327 $salt = uniqid(rand(), true); 328 io_saveFile($file, $salt); 329 } 330 if($addsession) { 331 $salt .= session_id(); 332 } 333 return $salt; 334} 335 336/** 337 * Log out the current user 338 * 339 * This clears all authentication data and thus log the user 340 * off. It also clears session data. 341 * 342 * @author Andreas Gohr <andi@splitbrain.org> 343 * @param bool $keepbc - when true, the breadcrumb data is not cleared 344 */ 345function auth_logoff($keepbc = false) { 346 global $conf; 347 global $USERINFO; 348 /* @var auth_basic $auth */ 349 global $auth; 350 351 // make sure the session is writable (it usually is) 352 @session_start(); 353 354 if(isset($_SESSION[DOKU_COOKIE]['auth']['user'])) 355 unset($_SESSION[DOKU_COOKIE]['auth']['user']); 356 if(isset($_SESSION[DOKU_COOKIE]['auth']['pass'])) 357 unset($_SESSION[DOKU_COOKIE]['auth']['pass']); 358 if(isset($_SESSION[DOKU_COOKIE]['auth']['info'])) 359 unset($_SESSION[DOKU_COOKIE]['auth']['info']); 360 if(!$keepbc && isset($_SESSION[DOKU_COOKIE]['bc'])) 361 unset($_SESSION[DOKU_COOKIE]['bc']); 362 if(isset($_SERVER['REMOTE_USER'])) 363 unset($_SERVER['REMOTE_USER']); 364 $USERINFO = null; //FIXME 365 366 $cookieDir = empty($conf['cookiedir']) ? DOKU_REL : $conf['cookiedir']; 367 if(version_compare(PHP_VERSION, '5.2.0', '>')) { 368 setcookie(DOKU_COOKIE, '', time() - 600000, $cookieDir, '', ($conf['securecookie'] && is_ssl()), true); 369 } else { 370 setcookie(DOKU_COOKIE, '', time() - 600000, $cookieDir, '', ($conf['securecookie'] && is_ssl())); 371 } 372 373 if($auth) $auth->logOff(); 374} 375 376/** 377 * Check if a user is a manager 378 * 379 * Should usually be called without any parameters to check the current 380 * user. 381 * 382 * The info is available through $INFO['ismanager'], too 383 * 384 * @author Andreas Gohr <andi@splitbrain.org> 385 * @see auth_isadmin 386 * @param string $user Username 387 * @param array $groups List of groups the user is in 388 * @param bool $adminonly when true checks if user is admin 389 * @return bool 390 */ 391function auth_ismanager($user = null, $groups = null, $adminonly = false) { 392 global $conf; 393 global $USERINFO; 394 /* @var auth_basic $auth */ 395 global $auth; 396 397 if(!$auth) return false; 398 if(is_null($user)) { 399 if(!isset($_SERVER['REMOTE_USER'])) { 400 return false; 401 } else { 402 $user = $_SERVER['REMOTE_USER']; 403 } 404 } 405 if(is_null($groups)) { 406 $groups = (array) $USERINFO['grps']; 407 } 408 409 // check superuser match 410 if(auth_isMember($conf['superuser'], $user, $groups)) return true; 411 if($adminonly) return false; 412 // check managers 413 if(auth_isMember($conf['manager'], $user, $groups)) return true; 414 415 return false; 416} 417 418/** 419 * Check if a user is admin 420 * 421 * Alias to auth_ismanager with adminonly=true 422 * 423 * The info is available through $INFO['isadmin'], too 424 * 425 * @author Andreas Gohr <andi@splitbrain.org> 426 * @see auth_ismanager() 427 * @param string $user Username 428 * @param array $groups List of groups the user is in 429 * @return bool 430 */ 431function auth_isadmin($user = null, $groups = null) { 432 return auth_ismanager($user, $groups, true); 433} 434 435/** 436 * Match a user and his groups against a comma separated list of 437 * users and groups to determine membership status 438 * 439 * Note: all input should NOT be nameencoded. 440 * 441 * @param $memberlist string commaseparated list of allowed users and groups 442 * @param $user string user to match against 443 * @param $groups array groups the user is member of 444 * @return bool true for membership acknowledged 445 */ 446function auth_isMember($memberlist, $user, array $groups) { 447 /* @var auth_basic $auth */ 448 global $auth; 449 if(!$auth) return false; 450 451 // clean user and groups 452 if(!$auth->isCaseSensitive()) { 453 $user = utf8_strtolower($user); 454 $groups = array_map('utf8_strtolower', $groups); 455 } 456 $user = $auth->cleanUser($user); 457 $groups = array_map(array($auth, 'cleanGroup'), $groups); 458 459 // extract the memberlist 460 $members = explode(',', $memberlist); 461 $members = array_map('trim', $members); 462 $members = array_unique($members); 463 $members = array_filter($members); 464 465 // compare cleaned values 466 foreach($members as $member) { 467 if(!$auth->isCaseSensitive()) $member = utf8_strtolower($member); 468 if($member[0] == '@') { 469 $member = $auth->cleanGroup(substr($member, 1)); 470 if(in_array($member, $groups)) return true; 471 } else { 472 $member = $auth->cleanUser($member); 473 if($member == $user) return true; 474 } 475 } 476 477 // still here? not a member! 478 return false; 479} 480 481/** 482 * Convinience function for auth_aclcheck() 483 * 484 * This checks the permissions for the current user 485 * 486 * @author Andreas Gohr <andi@splitbrain.org> 487 * 488 * @param string $id page ID (needs to be resolved and cleaned) 489 * @return int permission level 490 */ 491function auth_quickaclcheck($id) { 492 global $conf; 493 global $USERINFO; 494 # if no ACL is used always return upload rights 495 if(!$conf['useacl']) return AUTH_UPLOAD; 496 return auth_aclcheck($id, $_SERVER['REMOTE_USER'], $USERINFO['grps']); 497} 498 499/** 500 * Returns the maximum rights a user has for 501 * the given ID or its namespace 502 * 503 * @author Andreas Gohr <andi@splitbrain.org> 504 * 505 * @param string $id page ID (needs to be resolved and cleaned) 506 * @param string $user Username 507 * @param array $groups Array of groups the user is in 508 * @return int permission level 509 */ 510function auth_aclcheck($id, $user, $groups) { 511 global $conf; 512 global $AUTH_ACL; 513 /* @var auth_basic $auth */ 514 global $auth; 515 516 // if no ACL is used always return upload rights 517 if(!$conf['useacl']) return AUTH_UPLOAD; 518 if(!$auth) return AUTH_NONE; 519 520 //make sure groups is an array 521 if(!is_array($groups)) $groups = array(); 522 523 //if user is superuser or in superusergroup return 255 (acl_admin) 524 if(auth_isadmin($user, $groups)) { 525 return AUTH_ADMIN; 526 } 527 528 $ci = ''; 529 if(!$auth->isCaseSensitive()) $ci = 'ui'; 530 531 $user = $auth->cleanUser($user); 532 $groups = array_map(array($auth, 'cleanGroup'), (array) $groups); 533 $user = auth_nameencode($user); 534 535 //prepend groups with @ and nameencode 536 $cnt = count($groups); 537 for($i = 0; $i < $cnt; $i++) { 538 $groups[$i] = '@'.auth_nameencode($groups[$i]); 539 } 540 541 $ns = getNS($id); 542 $perm = -1; 543 544 if($user || count($groups)) { 545 //add ALL group 546 $groups[] = '@ALL'; 547 //add User 548 if($user) $groups[] = $user; 549 } else { 550 $groups[] = '@ALL'; 551 } 552 553 //check exact match first 554 $matches = preg_grep('/^'.preg_quote($id, '/').'\s+(\S+)\s+/'.$ci, $AUTH_ACL); 555 if(count($matches)) { 556 foreach($matches as $match) { 557 $match = preg_replace('/#.*$/', '', $match); //ignore comments 558 $acl = preg_split('/\s+/', $match); 559 if(!in_array($acl[1], $groups)) { 560 continue; 561 } 562 if($acl[2] > AUTH_DELETE) $acl[2] = AUTH_DELETE; //no admins in the ACL! 563 if($acl[2] > $perm) { 564 $perm = $acl[2]; 565 } 566 } 567 if($perm > -1) { 568 //we had a match - return it 569 return $perm; 570 } 571 } 572 573 //still here? do the namespace checks 574 if($ns) { 575 $path = $ns.':*'; 576 } else { 577 $path = '*'; //root document 578 } 579 580 do { 581 $matches = preg_grep('/^'.preg_quote($path, '/').'\s+(\S+)\s+/'.$ci, $AUTH_ACL); 582 if(count($matches)) { 583 foreach($matches as $match) { 584 $match = preg_replace('/#.*$/', '', $match); //ignore comments 585 $acl = preg_split('/\s+/', $match); 586 if(!in_array($acl[1], $groups)) { 587 continue; 588 } 589 if($acl[2] > AUTH_DELETE) $acl[2] = AUTH_DELETE; //no admins in the ACL! 590 if($acl[2] > $perm) { 591 $perm = $acl[2]; 592 } 593 } 594 //we had a match - return it 595 if($perm != -1) { 596 return $perm; 597 } 598 } 599 //get next higher namespace 600 $ns = getNS($ns); 601 602 if($path != '*') { 603 $path = $ns.':*'; 604 if($path == ':*') $path = '*'; 605 } else { 606 //we did this already 607 //looks like there is something wrong with the ACL 608 //break here 609 msg('No ACL setup yet! Denying access to everyone.'); 610 return AUTH_NONE; 611 } 612 } while(1); //this should never loop endless 613 return AUTH_NONE; 614} 615 616/** 617 * Encode ASCII special chars 618 * 619 * Some auth backends allow special chars in their user and groupnames 620 * The special chars are encoded with this function. Only ASCII chars 621 * are encoded UTF-8 multibyte are left as is (different from usual 622 * urlencoding!). 623 * 624 * Decoding can be done with rawurldecode 625 * 626 * @author Andreas Gohr <gohr@cosmocode.de> 627 * @see rawurldecode() 628 */ 629function auth_nameencode($name, $skip_group = false) { 630 global $cache_authname; 631 $cache =& $cache_authname; 632 $name = (string) $name; 633 634 // never encode wildcard FS#1955 635 if($name == '%USER%') return $name; 636 637 if(!isset($cache[$name][$skip_group])) { 638 if($skip_group && $name{0} == '@') { 639 $cache[$name][$skip_group] = '@'.preg_replace( 640 '/([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f])/e', 641 "'%'.dechex(ord(substr('\\1',-1)))", substr($name, 1) 642 ); 643 } else { 644 $cache[$name][$skip_group] = preg_replace( 645 '/([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f])/e', 646 "'%'.dechex(ord(substr('\\1',-1)))", $name 647 ); 648 } 649 } 650 651 return $cache[$name][$skip_group]; 652} 653 654/** 655 * Create a pronouncable password 656 * 657 * @author Andreas Gohr <andi@splitbrain.org> 658 * @link http://www.phpbuilder.com/annotate/message.php3?id=1014451 659 * 660 * @return string pronouncable password 661 */ 662function auth_pwgen() { 663 $pw = ''; 664 $c = 'bcdfghjklmnprstvwz'; //consonants except hard to speak ones 665 $v = 'aeiou'; //vowels 666 $a = $c.$v; //both 667 668 //use two syllables... 669 for($i = 0; $i < 2; $i++) { 670 $pw .= $c[rand(0, strlen($c) - 1)]; 671 $pw .= $v[rand(0, strlen($v) - 1)]; 672 $pw .= $a[rand(0, strlen($a) - 1)]; 673 } 674 //... and add a nice number 675 $pw .= rand(10, 99); 676 677 return $pw; 678} 679 680/** 681 * Sends a password to the given user 682 * 683 * @author Andreas Gohr <andi@splitbrain.org> 684 * @param string $user Login name of the user 685 * @param string $password The new password in clear text 686 * @return bool true on success 687 */ 688function auth_sendPassword($user, $password) { 689 global $lang; 690 /* @var auth_basic $auth */ 691 global $auth; 692 if(!$auth) return false; 693 694 $user = $auth->cleanUser($user); 695 $userinfo = $auth->getUserData($user); 696 697 if(!$userinfo['mail']) return false; 698 699 $text = rawLocale('password'); 700 $trep = array( 701 'FULLNAME' => $userinfo['name'], 702 'LOGIN' => $user, 703 'PASSWORD' => $password 704 ); 705 706 $mail = new Mailer(); 707 $mail->to($userinfo['name'].' <'.$userinfo['mail'].'>'); 708 $mail->subject($lang['regpwmail']); 709 $mail->setBody($text, $trep); 710 return $mail->send(); 711} 712 713/** 714 * Register a new user 715 * 716 * This registers a new user - Data is read directly from $_POST 717 * 718 * @author Andreas Gohr <andi@splitbrain.org> 719 * @return bool true on success, false on any error 720 */ 721function register() { 722 global $lang; 723 global $conf; 724 /* @var auth_basic $auth */ 725 global $auth; 726 727 if(!$_POST['save']) return false; 728 if(!actionOK('register')) return false; 729 730 //clean username 731 $_POST['login'] = trim($auth->cleanUser($_POST['login'])); 732 733 //clean fullname and email 734 $_POST['fullname'] = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/', '', $_POST['fullname'])); 735 $_POST['email'] = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/', '', $_POST['email'])); 736 737 if(empty($_POST['login']) || 738 empty($_POST['fullname']) || 739 empty($_POST['email']) 740 ) { 741 msg($lang['regmissing'], -1); 742 return false; 743 } 744 745 if($conf['autopasswd']) { 746 $pass = auth_pwgen(); // automatically generate password 747 } elseif(empty($_POST['pass']) || 748 empty($_POST['passchk']) 749 ) { 750 msg($lang['regmissing'], -1); // complain about missing passwords 751 return false; 752 } elseif($_POST['pass'] != $_POST['passchk']) { 753 msg($lang['regbadpass'], -1); // complain about misspelled passwords 754 return false; 755 } else { 756 $pass = $_POST['pass']; // accept checked and valid password 757 } 758 759 //check mail 760 if(!mail_isvalid($_POST['email'])) { 761 msg($lang['regbadmail'], -1); 762 return false; 763 } 764 765 //okay try to create the user 766 if(!$auth->triggerUserMod('create', array($_POST['login'], $pass, $_POST['fullname'], $_POST['email']))) { 767 msg($lang['reguexists'], -1); 768 return false; 769 } 770 771 // create substitutions for use in notification email 772 $substitutions = array( 773 'NEWUSER' => $_POST['login'], 774 'NEWNAME' => $_POST['fullname'], 775 'NEWEMAIL' => $_POST['email'], 776 ); 777 778 if(!$conf['autopasswd']) { 779 msg($lang['regsuccess2'], 1); 780 notify('', 'register', '', $_POST['login'], false, $substitutions); 781 return true; 782 } 783 784 // autogenerated password? then send him the password 785 if(auth_sendPassword($_POST['login'], $pass)) { 786 msg($lang['regsuccess'], 1); 787 notify('', 'register', '', $_POST['login'], false, $substitutions); 788 return true; 789 } else { 790 msg($lang['regmailfail'], -1); 791 return false; 792 } 793} 794 795/** 796 * Update user profile 797 * 798 * @author Christopher Smith <chris@jalakai.co.uk> 799 */ 800function updateprofile() { 801 global $conf; 802 global $INFO; 803 global $lang; 804 /* @var auth_basic $auth */ 805 global $auth; 806 807 if(empty($_POST['save'])) return false; 808 if(!checkSecurityToken()) return false; 809 810 if(!actionOK('profile')) { 811 msg($lang['profna'], -1); 812 return false; 813 } 814 815 if($_POST['newpass'] != $_POST['passchk']) { 816 msg($lang['regbadpass'], -1); // complain about misspelled passwords 817 return false; 818 } 819 820 //clean fullname and email 821 $_POST['fullname'] = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/', '', $_POST['fullname'])); 822 $_POST['email'] = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/', '', $_POST['email'])); 823 824 if((empty($_POST['fullname']) && $auth->canDo('modName')) || 825 (empty($_POST['email']) && $auth->canDo('modMail')) 826 ) { 827 msg($lang['profnoempty'], -1); 828 return false; 829 } 830 831 if(!mail_isvalid($_POST['email']) && $auth->canDo('modMail')) { 832 msg($lang['regbadmail'], -1); 833 return false; 834 } 835 836 $changes = array(); 837 if($_POST['fullname'] != $INFO['userinfo']['name'] && $auth->canDo('modName')) $changes['name'] = $_POST['fullname']; 838 if($_POST['email'] != $INFO['userinfo']['mail'] && $auth->canDo('modMail')) $changes['mail'] = $_POST['email']; 839 if(!empty($_POST['newpass']) && $auth->canDo('modPass')) $changes['pass'] = $_POST['newpass']; 840 841 if(!count($changes)) { 842 msg($lang['profnochange'], -1); 843 return false; 844 } 845 846 if($conf['profileconfirm']) { 847 if(!$auth->checkPass($_SERVER['REMOTE_USER'], $_POST['oldpass'])) { 848 msg($lang['badlogin'], -1); 849 return false; 850 } 851 } 852 853 if($result = $auth->triggerUserMod('modify', array($_SERVER['REMOTE_USER'], $changes))) { 854 // update cookie and session with the changed data 855 if($changes['pass']) { 856 list( /*user*/, $sticky, /*pass*/) = auth_getCookie(); 857 $pass = PMA_blowfish_encrypt($changes['pass'], auth_cookiesalt(!$sticky)); 858 auth_setCookie($_SERVER['REMOTE_USER'], $pass, (bool) $sticky); 859 } 860 return true; 861 } 862 863 return false; 864} 865 866/** 867 * Send a new password 868 * 869 * This function handles both phases of the password reset: 870 * 871 * - handling the first request of password reset 872 * - validating the password reset auth token 873 * 874 * @author Benoit Chesneau <benoit@bchesneau.info> 875 * @author Chris Smith <chris@jalakai.co.uk> 876 * @author Andreas Gohr <andi@splitbrain.org> 877 * 878 * @return bool true on success, false on any error 879 */ 880function act_resendpwd() { 881 global $lang; 882 global $conf; 883 /* @var auth_basic $auth */ 884 global $auth; 885 886 if(!actionOK('resendpwd')) { 887 msg($lang['resendna'], -1); 888 return false; 889 } 890 891 $token = preg_replace('/[^a-f0-9]+/', '', $_REQUEST['pwauth']); 892 893 if($token) { 894 // we're in token phase - get user info from token 895 896 $tfile = $conf['cachedir'].'/'.$token{0}.'/'.$token.'.pwauth'; 897 if(!@file_exists($tfile)) { 898 msg($lang['resendpwdbadauth'], -1); 899 unset($_REQUEST['pwauth']); 900 return false; 901 } 902 // token is only valid for 3 days 903 if((time() - filemtime($tfile)) > (3 * 60 * 60 * 24)) { 904 msg($lang['resendpwdbadauth'], -1); 905 unset($_REQUEST['pwauth']); 906 @unlink($tfile); 907 return false; 908 } 909 910 $user = io_readfile($tfile); 911 $userinfo = $auth->getUserData($user); 912 if(!$userinfo['mail']) { 913 msg($lang['resendpwdnouser'], -1); 914 return false; 915 } 916 917 if(!$conf['autopasswd']) { // we let the user choose a password 918 // password given correctly? 919 if(!isset($_REQUEST['pass']) || $_REQUEST['pass'] == '') return false; 920 if($_REQUEST['pass'] != $_REQUEST['passchk']) { 921 msg($lang['regbadpass'], -1); 922 return false; 923 } 924 $pass = $_REQUEST['pass']; 925 926 if(!$auth->triggerUserMod('modify', array($user, array('pass' => $pass)))) { 927 msg('error modifying user data', -1); 928 return false; 929 } 930 931 } else { // autogenerate the password and send by mail 932 933 $pass = auth_pwgen(); 934 if(!$auth->triggerUserMod('modify', array($user, array('pass' => $pass)))) { 935 msg('error modifying user data', -1); 936 return false; 937 } 938 939 if(auth_sendPassword($user, $pass)) { 940 msg($lang['resendpwdsuccess'], 1); 941 } else { 942 msg($lang['regmailfail'], -1); 943 } 944 } 945 946 @unlink($tfile); 947 return true; 948 949 } else { 950 // we're in request phase 951 952 if(!$_POST['save']) return false; 953 954 if(empty($_POST['login'])) { 955 msg($lang['resendpwdmissing'], -1); 956 return false; 957 } else { 958 $user = trim($auth->cleanUser($_POST['login'])); 959 } 960 961 $userinfo = $auth->getUserData($user); 962 if(!$userinfo['mail']) { 963 msg($lang['resendpwdnouser'], -1); 964 return false; 965 } 966 967 // generate auth token 968 $token = md5(auth_cookiesalt().$user); //secret but user based 969 $tfile = $conf['cachedir'].'/'.$token{0}.'/'.$token.'.pwauth'; 970 $url = wl('', array('do'=> 'resendpwd', 'pwauth'=> $token), true, '&'); 971 972 io_saveFile($tfile, $user); 973 974 $text = rawLocale('pwconfirm'); 975 $trep = array( 976 'FULLNAME' => $userinfo['name'], 977 'LOGIN' => $user, 978 'CONFIRM' => $url 979 ); 980 981 $mail = new Mailer(); 982 $mail->to($userinfo['name'].' <'.$userinfo['mail'].'>'); 983 $mail->subject($lang['regpwmail']); 984 $mail->setBody($text, $trep); 985 if($mail->send()) { 986 msg($lang['resendpwdconfirm'], 1); 987 } else { 988 msg($lang['regmailfail'], -1); 989 } 990 return true; 991 } 992 // never reached 993} 994 995/** 996 * Encrypts a password using the given method and salt 997 * 998 * If the selected method needs a salt and none was given, a random one 999 * is chosen. 1000 * 1001 * @author Andreas Gohr <andi@splitbrain.org> 1002 * @param string $clear The clear text password 1003 * @param string $method The hashing method 1004 * @param string $salt A salt, null for random 1005 * @return string The crypted password 1006 */ 1007function auth_cryptPassword($clear, $method = '', $salt = null) { 1008 global $conf; 1009 if(empty($method)) $method = $conf['passcrypt']; 1010 1011 $pass = new PassHash(); 1012 $call = 'hash_'.$method; 1013 1014 if(!method_exists($pass, $call)) { 1015 msg("Unsupported crypt method $method", -1); 1016 return false; 1017 } 1018 1019 return $pass->$call($clear, $salt); 1020} 1021 1022/** 1023 * Verifies a cleartext password against a crypted hash 1024 * 1025 * @author Andreas Gohr <andi@splitbrain.org> 1026 * @param string $clear The clear text password 1027 * @param string $crypt The hash to compare with 1028 * @return bool true if both match 1029 */ 1030function auth_verifyPassword($clear, $crypt) { 1031 $pass = new PassHash(); 1032 return $pass->verify_hash($clear, $crypt); 1033} 1034 1035/** 1036 * Set the authentication cookie and add user identification data to the session 1037 * 1038 * @param string $user username 1039 * @param string $pass encrypted password 1040 * @param bool $sticky whether or not the cookie will last beyond the session 1041 * @return bool 1042 */ 1043function auth_setCookie($user, $pass, $sticky) { 1044 global $conf; 1045 /* @var auth_basic $auth */ 1046 global $auth; 1047 global $USERINFO; 1048 1049 if(!$auth) return false; 1050 $USERINFO = $auth->getUserData($user); 1051 1052 // set cookie 1053 $cookie = base64_encode($user).'|'.((int) $sticky).'|'.base64_encode($pass); 1054 $cookieDir = empty($conf['cookiedir']) ? DOKU_REL : $conf['cookiedir']; 1055 $time = $sticky ? (time() + 60 * 60 * 24 * 365) : 0; //one year 1056 if(version_compare(PHP_VERSION, '5.2.0', '>')) { 1057 setcookie(DOKU_COOKIE, $cookie, $time, $cookieDir, '', ($conf['securecookie'] && is_ssl()), true); 1058 } else { 1059 setcookie(DOKU_COOKIE, $cookie, $time, $cookieDir, '', ($conf['securecookie'] && is_ssl())); 1060 } 1061 // set session 1062 $_SESSION[DOKU_COOKIE]['auth']['user'] = $user; 1063 $_SESSION[DOKU_COOKIE]['auth']['pass'] = sha1($pass); 1064 $_SESSION[DOKU_COOKIE]['auth']['buid'] = auth_browseruid(); 1065 $_SESSION[DOKU_COOKIE]['auth']['info'] = $USERINFO; 1066 $_SESSION[DOKU_COOKIE]['auth']['time'] = time(); 1067 1068 return true; 1069} 1070 1071/** 1072 * Returns the user, (encrypted) password and sticky bit from cookie 1073 * 1074 * @returns array 1075 */ 1076function auth_getCookie() { 1077 if(!isset($_COOKIE[DOKU_COOKIE])) { 1078 return array(null, null, null); 1079 } 1080 list($user, $sticky, $pass) = explode('|', $_COOKIE[DOKU_COOKIE], 3); 1081 $sticky = (bool) $sticky; 1082 $pass = base64_decode($pass); 1083 $user = base64_decode($user); 1084 return array($user, $sticky, $pass); 1085} 1086 1087//Setup VIM: ex: et ts=2 : 1088