diff options
Diffstat (limited to 'inc/auth.php')
-rw-r--r-- | inc/auth.php | 297 |
1 files changed, 244 insertions, 53 deletions
diff --git a/inc/auth.php b/inc/auth.php index 47b29eff7..b793f5d12 100644 --- a/inc/auth.php +++ b/inc/auth.php @@ -40,7 +40,7 @@ function auth_setup() { global $INPUT; global $AUTH_ACL; global $lang; - global $config_cascade; + /* @var Doku_Plugin_Controller $plugin_controller */ global $plugin_controller; $AUTH_ACL = array(); @@ -48,15 +48,15 @@ function auth_setup() { // try to load auth backend from plugins foreach ($plugin_controller->getList('auth') as $plugin) { - if ($conf['authtype'] === $plugin) { - $auth = $plugin_controller->load('auth', $plugin); - break; - } elseif ('auth' . $conf['authtype'] === $plugin) { - // matches old auth backends (pre-Weatherwax) - $auth = $plugin_controller->load('auth', $plugin); - msg('Your authtype setting is deprecated. You must set $conf[\'authtype\'] = "auth' . $conf['authtype'] . '"' - . ' in your configuration (see <a href="https://www.dokuwiki.org/auth">Authentication Backends</a>)',-1,'','',MSG_ADMINS_ONLY); - } + if ($conf['authtype'] === $plugin) { + $auth = $plugin_controller->load('auth', $plugin); + break; + } elseif ('auth' . $conf['authtype'] === $plugin) { + // matches old auth backends (pre-Weatherwax) + $auth = $plugin_controller->load('auth', $plugin); + msg('Your authtype setting is deprecated. You must set $conf[\'authtype\'] = "auth' . $conf['authtype'] . '"' + . ' in your configuration (see <a href="https://www.dokuwiki.org/auth">Authentication Backends</a>)',-1,'','',MSG_ADMINS_ONLY); + } } if(!isset($auth) || !$auth){ @@ -65,10 +65,10 @@ function auth_setup() { } if ($auth->success == false) { - // degrade to unauthenticated user - unset($auth); - auth_logoff(); - msg($lang['authtempfail'], -1); + // degrade to unauthenticated user + unset($auth); + auth_logoff(); + msg($lang['authtempfail'], -1); return false; } @@ -136,22 +136,30 @@ function auth_loadACL() { $acl = file($config_cascade['acl']['default']); - //support user wildcard $out = array(); foreach($acl as $line) { $line = trim($line); - if($line{0} == '#') continue; - list($id,$rest) = preg_split('/\s+/',$line,2); + if(empty($line) || ($line{0} == '#')) continue; // skip blank lines & comments + list($id,$rest) = preg_split('/[ \t]+/',$line,2); + + // substitute user wildcard first (its 1:1) + if(strstr($line, '%USER%')){ + // if user is not logged in, this ACL line is meaningless - skip it + if (!isset($_SERVER['REMOTE_USER'])) continue; + + $id = str_replace('%USER%',cleanID($_SERVER['REMOTE_USER']),$id); + $rest = str_replace('%USER%',auth_nameencode($_SERVER['REMOTE_USER']),$rest); + } + // substitute group wildcard (its 1:m) if(strstr($line, '%GROUP%')){ + // if user is not logged in, grps is empty, no output will be added (i.e. skipped) foreach((array) $USERINFO['grps'] as $grp){ $nid = str_replace('%GROUP%',cleanID($grp),$id); $nrest = str_replace('%GROUP%','@'.auth_nameencode($grp),$rest); $out[] = "$nid\t$nrest"; } } else { - $id = str_replace('%USER%',cleanID($_SERVER['REMOTE_USER']),$id); - $rest = str_replace('%USER%',auth_nameencode($_SERVER['REMOTE_USER']),$rest); $out[] = "$id\t$rest"; } } @@ -207,7 +215,7 @@ function auth_login($user, $pass, $sticky = false, $silent = false) { global $USERINFO; global $conf; global $lang; - /* @var auth_basic $auth */ + /* @var DokuWiki_Auth_Plugin $auth */ global $auth; $sticky ? $sticky = true : $sticky = false; //sanity check @@ -219,8 +227,8 @@ function auth_login($user, $pass, $sticky = false, $silent = false) { if($auth->checkPass($user, $pass)) { // make logininfo globally available $_SERVER['REMOTE_USER'] = $user; - $secret = auth_cookiesalt(!$sticky); //bind non-sticky to session - auth_setCookie($user, PMA_blowfish_encrypt($pass, $secret), $sticky); + $secret = auth_cookiesalt(!$sticky, true); //bind non-sticky to session + auth_setCookie($user, auth_encrypt($pass, $secret), $sticky); return true; } else { //invalid credentials - log off @@ -250,8 +258,8 @@ function auth_login($user, $pass, $sticky = false, $silent = false) { return true; } // no we don't trust it yet - recheck pass but silent - $secret = auth_cookiesalt(!$sticky); //bind non-sticky to session - $pass = PMA_blowfish_decrypt($pass, $secret); + $secret = auth_cookiesalt(!$sticky, true); //bind non-sticky to session + $pass = auth_decrypt($pass, $secret); return auth_login($user, $pass, $sticky, true); } } @@ -294,7 +302,7 @@ function auth_validateToken($token) { * @return string The auth token */ function auth_createToken() { - $token = md5(mt_rand()); + $token = md5(auth_randombytes(16)); @session_start(); // reopen the session if needed $_SESSION[DOKU_COOKIE]['auth']['token'] = $token; session_write_close(); @@ -333,14 +341,18 @@ function auth_browseruid() { * * @author Andreas Gohr <andi@splitbrain.org> * @param bool $addsession if true, the sessionid is added to the salt + * @param bool $secure if security is more important than keeping the old value * @return string */ -function auth_cookiesalt($addsession = false) { +function auth_cookiesalt($addsession = false, $secure = false) { global $conf; $file = $conf['metadir'].'/_htcookiesalt'; + if ($secure || !file_exists($file)) { + $file = $conf['metadir'].'/_htcookiesalt2'; + } $salt = io_readFile($file); if(empty($salt)) { - $salt = uniqid(rand(), true); + $salt = bin2hex(auth_randombytes(64)); io_saveFile($file, $salt); } if($addsession) { @@ -350,6 +362,142 @@ function auth_cookiesalt($addsession = false) { } /** + * Return truly (pseudo) random bytes if available, otherwise fall back to mt_rand + * + * @author Mark Seecof + * @author Michael Hamann <michael@content-space.de> + * @link http://www.php.net/manual/de/function.mt-rand.php#83655 + * @param int $length number of bytes to get + * @return string binary random strings + */ +function auth_randombytes($length) { + $strong = false; + $rbytes = false; + + if (function_exists('openssl_random_pseudo_bytes') + && (version_compare(PHP_VERSION, '5.3.4') >= 0 + || strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') + ) { + $rbytes = openssl_random_pseudo_bytes($length, $strong); + } + + if (!$strong && function_exists('mcrypt_create_iv') + && (version_compare(PHP_VERSION, '5.3.7') >= 0 + || strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') + ) { + $rbytes = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM); + if ($rbytes !== false && strlen($rbytes) === $length) { + $strong = true; + } + } + + // If no strong randoms available, try OS the specific ways + if(!$strong) { + // Unix/Linux platform + $fp = @fopen('/dev/urandom', 'rb'); + if($fp !== false) { + $rbytes = fread($fp, $length); + fclose($fp); + } + + // MS-Windows platform + if(class_exists('COM')) { + // http://msdn.microsoft.com/en-us/library/aa388176(VS.85).aspx + try { + $CAPI_Util = new COM('CAPICOM.Utilities.1'); + $rbytes = $CAPI_Util->GetRandom($length, 0); + + // if we ask for binary data PHP munges it, so we + // request base64 return value. + if($rbytes) $rbytes = base64_decode($rbytes); + } catch(Exception $ex) { + // fail + } + } + } + if(strlen($rbytes) < $length) $rbytes = false; + + // still no random bytes available - fall back to mt_rand() + if($rbytes === false) { + $rbytes = ''; + for ($i = 0; $i < $length; ++$i) { + $rbytes .= chr(mt_rand(0, 255)); + } + } + + return $rbytes; +} + +/** + * Random number generator using the best available source + * + * @author Michael Samuel + * @author Michael Hamann <michael@content-space.de> + * @param int $min + * @param int $max + * @return int + */ +function auth_random($min, $max) { + $abs_max = $max - $min; + + $nbits = 0; + for ($n = $abs_max; $n > 0; $n >>= 1) { + ++$nbits; + } + + $mask = (1 << $nbits) - 1; + do { + $bytes = auth_randombytes(PHP_INT_SIZE); + $integers = unpack('Inum', $bytes); + $integer = $integers["num"] & $mask; + } while ($integer > $abs_max); + + return $min + $integer; +} + +/** + * Encrypt data using the given secret using AES + * + * The mode is CBC with a random initialization vector, the key is derived + * using pbkdf2. + * + * @param string $data The data that shall be encrypted + * @param string $secret The secret/password that shall be used + * @return string The ciphertext + */ +function auth_encrypt($data, $secret) { + $iv = auth_randombytes(16); + $cipher = new Crypt_AES(); + $cipher->setPassword($secret); + + /* + this uses the encrypted IV as IV as suggested in + http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf, Appendix C + for unique but necessarily random IVs. The resulting ciphertext is + compatible to ciphertext that was created using a "normal" IV. + */ + return $cipher->encrypt($iv.$data); +} + +/** + * Decrypt the given AES ciphertext + * + * The mode is CBC, the key is derived using pbkdf2 + * + * @param string $ciphertext The encrypted data + * @param string $secret The secret/password that shall be used + * @return string The decrypted data + */ +function auth_decrypt($ciphertext, $secret) { + $iv = substr($ciphertext, 0, 16); + $cipher = new Crypt_AES(); + $cipher->setPassword($secret); + $cipher->setIV($iv); + + return $cipher->decrypt(substr($ciphertext, 16)); +} + +/** * Log out the current user * * This clears all authentication data and thus log the user @@ -361,7 +509,7 @@ function auth_cookiesalt($addsession = false) { function auth_logoff($keepbc = false) { global $conf; global $USERINFO; - /* @var auth_basic $auth */ + /* @var DokuWiki_Auth_Plugin $auth */ global $auth; // make sure the session is writable (it usually is) @@ -407,7 +555,7 @@ function auth_logoff($keepbc = false) { function auth_ismanager($user = null, $groups = null, $adminonly = false) { global $conf; global $USERINFO; - /* @var auth_basic $auth */ + /* @var DokuWiki_Auth_Plugin $auth */ global $auth; if(!$auth) return false; @@ -460,7 +608,7 @@ function auth_isadmin($user = null, $groups = null) { * @return bool true for membership acknowledged */ function auth_isMember($memberlist, $user, array $groups) { - /* @var auth_basic $auth */ + /* @var DokuWiki_Auth_Plugin $auth */ global $auth; if(!$auth) return false; @@ -526,7 +674,7 @@ function auth_quickaclcheck($id) { function auth_aclcheck($id, $user, $groups) { global $conf; global $AUTH_ACL; - /* @var auth_basic $auth */ + /* @var DokuWiki_Auth_Plugin $auth */ global $auth; // if no ACL is used always return upload rights @@ -568,11 +716,11 @@ function auth_aclcheck($id, $user, $groups) { } //check exact match first - $matches = preg_grep('/^'.preg_quote($id, '/').'\s+(\S+)\s+/u', $AUTH_ACL); + $matches = preg_grep('/^'.preg_quote($id, '/').'[ \t]+([^ \t]+)[ \t]+/', $AUTH_ACL); if(count($matches)) { foreach($matches as $match) { $match = preg_replace('/#.*$/', '', $match); //ignore comments - $acl = preg_split('/\s+/', $match); + $acl = preg_split('/[ \t]+/', $match); if(!$auth->isCaseSensitive() && $acl[1] !== '@ALL') { $acl[1] = utf8_strtolower($acl[1]); } @@ -598,11 +746,11 @@ function auth_aclcheck($id, $user, $groups) { } do { - $matches = preg_grep('/^'.preg_quote($path, '/').'\s+(\S+)\s+/u', $AUTH_ACL); + $matches = preg_grep('/^'.preg_quote($path, '/').'[ \t]+([^ \t]+)[ \t]+/', $AUTH_ACL); if(count($matches)) { foreach($matches as $match) { $match = preg_replace('/#.*$/', '', $match); //ignore comments - $acl = preg_split('/\s+/', $match); + $acl = preg_split('/[ \t]+/', $match); if(!$auth->isCaseSensitive() && $acl[1] !== '@ALL') { $acl[1] = utf8_strtolower($acl[1]); } @@ -660,14 +808,14 @@ function auth_nameencode($name, $skip_group = false) { if(!isset($cache[$name][$skip_group])) { if($skip_group && $name{0} == '@') { - $cache[$name][$skip_group] = '@'.preg_replace( - '/([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f])/e', - "'%'.dechex(ord(substr('\\1',-1)))", substr($name, 1) + $cache[$name][$skip_group] = '@'.preg_replace_callback( + '/([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f])/', + 'auth_nameencode_callback', substr($name, 1) ); } else { - $cache[$name][$skip_group] = preg_replace( - '/([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f])/e', - "'%'.dechex(ord(substr('\\1',-1)))", $name + $cache[$name][$skip_group] = preg_replace_callback( + '/([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f])/', + 'auth_nameencode_callback', $name ); } } @@ -675,6 +823,10 @@ function auth_nameencode($name, $skip_group = false) { return $cache[$name][$skip_group]; } +function auth_nameencode_callback($matches) { + return '%'.dechex(ord(substr($matches[1],-1))); +} + /** * Create a pronouncable password * @@ -703,12 +855,12 @@ function auth_pwgen($foruser = '') { //use thre syllables... for($i = 0; $i < 3; $i++) { - $data['password'] .= $c[mt_rand(0, strlen($c) - 1)]; - $data['password'] .= $v[mt_rand(0, strlen($v) - 1)]; - $data['password'] .= $a[mt_rand(0, strlen($a) - 1)]; + $data['password'] .= $c[auth_random(0, strlen($c) - 1)]; + $data['password'] .= $v[auth_random(0, strlen($v) - 1)]; + $data['password'] .= $a[auth_random(0, strlen($a) - 1)]; } //... and add a nice number and special - $data['password'] .= mt_rand(10, 99).$s[mt_rand(0, strlen($s) - 1)]; + $data['password'] .= auth_random(10, 99).$s[auth_random(0, strlen($s) - 1)]; } $evt->advise_after(); @@ -725,7 +877,7 @@ function auth_pwgen($foruser = '') { */ function auth_sendPassword($user, $password) { global $lang; - /* @var auth_basic $auth */ + /* @var DokuWiki_Auth_Plugin $auth */ global $auth; if(!$auth) return false; @@ -759,7 +911,7 @@ function auth_sendPassword($user, $password) { function register() { global $lang; global $conf; - /* @var auth_basic $auth */ + /* @var DokuWiki_Auth_Plugin $auth */ global $auth; global $INPUT; @@ -828,7 +980,7 @@ function register() { function updateprofile() { global $conf; global $lang; - /* @var auth_basic $auth */ + /* @var DokuWiki_Auth_Plugin $auth */ global $auth; /* @var Input $INPUT */ global $INPUT; @@ -883,7 +1035,7 @@ function updateprofile() { if($conf['profileconfirm']) { if(!$auth->checkPass($_SERVER['REMOTE_USER'], $INPUT->post->str('oldpass'))) { - msg($lang['badlogin'], -1); + msg($lang['badpassconfirm'], -1); return false; } } @@ -892,7 +1044,7 @@ function updateprofile() { // update cookie and session with the changed data if($changes['pass']) { list( /*user*/, $sticky, /*pass*/) = auth_getCookie(); - $pass = PMA_blowfish_encrypt($changes['pass'], auth_cookiesalt(!$sticky)); + $pass = auth_encrypt($changes['pass'], auth_cookiesalt(!$sticky, true)); auth_setCookie($_SERVER['REMOTE_USER'], $pass, (bool) $sticky); } return true; @@ -901,6 +1053,45 @@ function updateprofile() { return false; } +function auth_deleteprofile(){ + global $conf; + global $lang; + /* @var DokuWiki_Auth_Plugin $auth */ + global $auth; + /* @var Input $INPUT */ + global $INPUT; + + if(!$INPUT->post->bool('delete')) return false; + if(!checkSecurityToken()) return false; + + // action prevented or auth module disallows + if(!actionOK('profile_delete') || !$auth->canDo('delUser')) { + msg($lang['profnodelete'], -1); + return false; + } + + if(!$INPUT->post->bool('confirm_delete')){ + msg($lang['profconfdeletemissing'], -1); + return false; + } + + if($conf['profileconfirm']) { + if(!$auth->checkPass($_SERVER['REMOTE_USER'], $INPUT->post->str('oldpass'))) { + msg($lang['badpassconfirm'], -1); + return false; + } + } + + $deleted[] = $_SERVER['REMOTE_USER']; + if($auth->triggerUserMod('delete', array($deleted))) { + // force and immediate logout including removing the sticky cookie + auth_logoff(); + return true; + } + + return false; +} + /** * Send a new password * @@ -918,7 +1109,7 @@ function updateprofile() { function act_resendpwd() { global $lang; global $conf; - /* @var auth_basic $auth */ + /* @var DokuWiki_Auth_Plugin $auth */ global $auth; /* @var Input $INPUT */ global $INPUT; @@ -1007,7 +1198,7 @@ function act_resendpwd() { } // generate auth token - $token = md5(uniqid(mt_rand(), true)); // random secret + $token = md5(auth_randombytes(16)); // random secret $tfile = $conf['cachedir'].'/'.$token{0}.'/'.$token.'.pwauth'; $url = wl('', array('do'=> 'resendpwd', 'pwauth'=> $token), true, '&'); @@ -1084,7 +1275,7 @@ function auth_verifyPassword($clear, $crypt) { */ function auth_setCookie($user, $pass, $sticky) { global $conf; - /* @var auth_basic $auth */ + /* @var DokuWiki_Auth_Plugin $auth */ global $auth; global $USERINFO; |