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