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