95 if ( $auth_header ==
"" ) {
96 $auth_realm = $c->system_name;
97 if ( isset($c->per_principal_realm) && $c->per_principal_realm && !empty($_SERVER[
'PATH_INFO']) ) {
98 $principal_name = preg_replace(
'{^/(.*?)/.*$}',
'$1', $_SERVER[
'PATH_INFO']);
99 if ( $principal_name != $_SERVER[
'PATH_INFO'] ) {
100 $auth_realm .=
' - ' . $principal_name;
103 dbg_error_log(
"HTTPAuth",
":AuthFailedResponse Requesting authentication in the '%s' realm", $auth_realm );
104 $auth_header = sprintf(
'WWW-Authenticate: Basic realm="%s"', $auth_realm );
107 header(
'HTTP/1.1 401 Unauthorized',
true, 401 );
108 header(
'Content-type: text/plain; ; charset="utf-8"' );
109 if ( $c->test_mode ) header(
"Request-ID: " . request_id() );
111 header( $auth_header );
112 echo
'Please log in for access to this system.';
113 if ( isset($_SERVER[
'PHP_AUTH_USER']) ) {
114 dbg_error_log(
"ERROR",
"authentication failure for user '%s' from host [%s]", $_SERVER[
'PHP_AUTH_USER'], $_SERVER[
'REMOTE_ADDR'] );
116 dbg_error_log(
"HTTPAuth",
":Session: User is not authorised: %s ", $_SERVER[
'REMOTE_ADDR'] );
118 @ob_flush(); exit(0);
131 if ( !isset($_SERVER[
'AUTHORIZATION']) && isset($_SERVER[
'HTTP_AUTHORIZATION']) && !empty($_SERVER[
'HTTP_AUTHORIZATION']))
132 $_SERVER[
'AUTHORIZATION'] = $_SERVER[
'HTTP_AUTHORIZATION'];
133 if (isset($_SERVER[
'AUTHORIZATION']) && !empty($_SERVER[
'AUTHORIZATION'])) {
134 list ($type, $cred) = explode(
" ", $_SERVER[
'AUTHORIZATION']);
135 if ($type ==
'Basic') {
136 list ($user, $pass) = explode(
":", base64_decode($cred), 2);
137 $_SERVER[
'PHP_AUTH_USER'] = $user;
138 $_SERVER[
'PHP_AUTH_PW'] = $pass;
141 else if ( isset($c->authenticate_hook[
'server_auth_type'])
142 && ( ( isset($_SERVER[
"REMOTE_USER"]) && !empty($_SERVER[
"REMOTE_USER"]) ) ||
143 ( isset($_SERVER[
"REDIRECT_REMOTE_USER"]) && !empty($_SERVER[
"REDIRECT_REMOTE_USER"]) ) ) ) {
144 if ( ( is_array($c->authenticate_hook[
'server_auth_type'])
145 && in_array( strtolower($_SERVER[
'AUTH_TYPE']), array_map(
'strtolower', $c->authenticate_hook[
'server_auth_type'])) )
147 ( !is_array($c->authenticate_hook[
'server_auth_type'])
148 && strtolower($c->authenticate_hook[
'server_auth_type']) == strtolower($_SERVER[
'AUTH_TYPE']) )
153 if (isset($_SERVER[
"REMOTE_USER"]))
154 $_SERVER[
'PHP_AUTH_USER'] = $_SERVER[
'REMOTE_USER'];
156 $_SERVER[
'PHP_AUTH_USER'] = $_SERVER[
'REDIRECT_REMOTE_USER'];
157 $_SERVER[
'PHP_AUTH_PW'] =
'Externally Authenticated';
158 if ( ! isset($c->authenticate_hook[
'call']) ) {
164 $c->authenticate_hook[
'call'] =
'auth_external';
173 if ( isset($_SERVER[
'PHP_AUTH_USER']) ) {
174 if ( $p = $this->
CheckPassword( $_SERVER[
'PHP_AUTH_USER'], $_SERVER[
'PHP_AUTH_PW'] ) ) {
175 if ( isset($p->active) && !isset($p->user_active) ) {
176 trace_bug(
'Some authentication failed to return a dav_principal record and needs fixing.');
177 $p->user_active = $p->active;
184 if ( $p->user_active ) {
191 if ( isset($c->allow_unauthenticated) && $c->allow_unauthenticated ) {
193 $this->logged_in =
false;
219 $realm = $c->system_name;
221 if ( isset($_SERVER[
'HTTP_USER_AGENT']) ) $opaque .= $_SERVER[
'HTTP_USER_AGENT'];
222 if ( isset($_SERVER[
'REMOTE_ADDR']) ) $opaque .= $_SERVER[
'REMOTE_ADDR'];
223 $opaque = sha1($opaque);
225 if ( ! empty($_SERVER[
'PHP_AUTH_DIGEST'])) {
229 if ( $data[
'uri'] != $_SERVER[
'REQUEST_URI'] ) {
230 dbg_error_log(
"ERROR",
" DigestAuth: WTF! URI is '%s' and request URI is '%s'!?!" );
236 $test_user =
new Principal(
'username', $data[
'username']);
238 if ( preg_match(
'{\*(Digest)?\*(.*)}', $test_user->password, $matches ) ) {
239 if ( $matches[1] ==
'Digest' )
243 $A1 = md5($data[
'username'] .
':' . $realm .
':' . $matches[2]);
245 $A2 = md5($_SERVER[
'REQUEST_METHOD'].
':'.$data[
'uri']);
246 $auth_string = $A1.
':'.$data[
'nonce'].
':'.$data[
'nc'].
':'.$data[
'cnonce'].
':'.$data[
'qop'].
':'.$A2;
248 $valid_response = md5($auth_string);
251 if ( $data[
'response'] == $valid_response ) {
265 $nonce = sha1(uniqid(
'',
true));
266 $authheader = sprintf(
'WWW-Authenticate: Digest realm="%s", qop="auth", nonce="%s", opaque="%s", algorithm="MD5"',
267 $realm, $nonce, $opaque );
268 dbg_error_log(
"HTTPAuth", $authheader );
280 $needed_parts = array(
'nonce'=>1,
'nc'=>1,
'cnonce'=>1,
'qop'=>1,
'username'=>1,
'uri'=>1,
'response'=>1);
283 preg_match_all(
'{(\w+)="([^"]+)"}', $auth_header, $matches, PREG_SET_ORDER);
284 foreach ($matches as $m) {
286 $data[$m[1]] = $m[2];
287 unset($needed_parts[$m[1]]);
288 dbg_error_log(
"HTTPAuth",
'Received: %s: %s', $m[1], $m[2] );
291 preg_match_all(
'{(\w+)=([^" ,]+)}', $auth_header, $matches, PREG_SET_ORDER);
292 foreach ($matches as $m) {
294 $data[$m[1]] = $m[2];
295 unset($needed_parts[$m[1]]);
296 dbg_error_log(
"HTTPAuth",
'Received: %s: %s', $m[1], $m[2] );
300 @dbg_error_log(
"HTTPAuth",
'Received: nonce: %s, nc: %s, cnonce: %s, qop: %s, username: %s, uri: %s, response: %s',
301 $data[
'nonce'], $data[
'nc'], $data[
'cnonce'], $data[
'qop'], $data[
'username'], $data[
'uri'], $data[
'response']
303 return $needed_parts ? false : $data;
314 if(isset($c->login_append_domain_if_missing) && $c->login_append_domain_if_missing && !preg_match(
'/@/',$username))
315 $username.=
'@'.$c->domain_name;
317 $cache_result = $this->
_CheckCache($username, $password);
319 if ($cache_result == 0) {
320 # noop as _CheckCache has nothing for this username/password combo
321 # and we need to perform full authentication check.
323 }
else if ($cache_result == 1) {
324 # cached credentials match and are valid
326 if ( $principal =
new Principal(
'username', $username) ) {
327 if ( isset($c->dbg[
'password']) ) dbg_error_log(
"password",
":CheckPassword (cached): Name:%s, Pass:%s, File:%s, Active:%s", $username, $password, $principal->password, ($principal->user_active?
'Yes':
'No') );
328 if ( $principal->user_active ) {
335 # If we fail to find the Principal, then fall through to full authentication below.
336 dbg_error_log(
'password',
'Cache check: Failed to find principal after valid cache result, falling through to full authentication check.');
339 }
else if ($cache_result == 2) {
340 # cached credentials match and are invalid
345 if ( !isset($c->authenticate_hook) || !isset($c->authenticate_hook[
'call'])
346 || !function_exists($c->authenticate_hook[
'call'])
347 || (isset($c->authenticate_hook[
'optional']) && $c->authenticate_hook[
'optional']) )
349 if ( $principal =
new Principal(
'username', $username) ) {
350 if ( isset($c->dbg[
'password']) ) dbg_error_log(
"password",
":CheckPassword: Name:%s, Pass:%s, File:%s, Active:%s", $username, $password, $principal->password, ($principal->user_active?
'Yes':
'No') );
351 if ( $principal->user_active && session_validate_password( $password, $principal->password ) ) {
352 $this->
_SetCache($username, $password,
'pass');
358 if ( isset($c->authenticate_hook) && isset($c->authenticate_hook[
'call']) && function_exists($c->authenticate_hook[
'call']) ) {
370 $principal = call_user_func( $c->authenticate_hook[
'call'], $username, $password );
371 if ( $principal !==
false && !($principal instanceof
Principal) ) {
372 $principal =
new Principal(
'username', $username);
375 if ( $principal ===
false ) {
376 $this->
_SetCache($username, $password,
'fail');
378 $this->
_SetCache($username, $password,
'pass');
384 $this->
_SetCache($username, $password,
'fail');
422 if ( is_string($principal) ) $principal =
new Principal(
'username',$principal);
423 if ( get_class($principal) !=
'Principal' ) {
424 $principal =
new Principal(
'username',$principal->username);
427 if ( !get_class($principal) ==
'Principal' ) {
428 throw new Exception(
'HTTPAuthSession::AssignSessionDetails could not find a Principal object');
430 $this->username = $principal->username();
431 $this->user_no = $principal->user_no();
432 $this->principal_id = $principal->principal_id();
433 $this->email = $principal->email();
434 $this->fullname = $principal->fullname;
435 $this->dav_name = $principal->dav_name();
436 $this->principal = $principal;
439 $this->logged_in =
true;
440 if ( function_exists(
"awl_set_locale") && isset($this->locale) && $this->locale !=
"" ) {
441 awl_set_locale($this->locale);
452 if (! $c->auth_cache)
return 0;
453 if (! isset($c->auth_cache_secret) || $c->auth_cache_secret ==
'') {
454 dbg_error_log(
'LOG',
"HTTPAuth:CheckCache: caching of credentials is enabled, but \$c->auth_cache_secret isn't set, using auth source");
458 $cache = getCacheInstance();
459 if ($cache->isActive() ===
false)
return 0;
461 $cache_ns =
'auth-' . $username;
463 $salt = $cache->get($cache_ns,
'salt');
465 if (!isset($salt) || (is_bool($salt) && $salt ==
false) || $salt ==
'') {
466 dbg_error_log(
'HTTPAuth',
'CheckCache: No stored salt for %s, need to use auth source', $username);
470 # The hashed password to fetch
471 $hash = $this->
_MakeHash($password, $salt);
472 if (! isset($hash)) {
473 dbg_error_log(
'LOG',
'HTTPAuth:CheckCache: failed to generate hash of credential, using auth source');
477 $cached_credentials = $cache->get($cache_ns, $hash);
479 if (isset($cached_credentials)) {
480 if ($cached_credentials ===
'pass') {
481 dbg_error_log(
'HTTPAuth',
'CheckCache: Cached credentials for %s are good and valid', $username);
484 }
else if ($cached_credentials ===
'fail') {
485 dbg_error_log(
'HTTPAuth',
'CheckCache: Cached credentials for %s are good and invalid', $username);
489 dbg_error_log(
'HTTPAuth',
'CheckCache: No stored salted password for %s, need to use auth source', $username);
492 # All hope is lost, we must have failed to find anything decent.
503 if (! $c->auth_cache)
return 0;
504 if (! isset($c->auth_cache_secret) || $c->auth_cache_secret ===
'') {
505 dbg_error_log(
'LOG',
"HTTPAuth:CheckCache: caching of credentials is enabled, \$c->auth_cache_secret isn't set, not caching credential");
509 # Work out the expiry to use, some sites might prefer different TTLs for
511 if ($state ===
'pass') {
512 $expiry = $c->auth_cache_pass;
513 }
else if ($state ===
'fail') {
514 $expiry = $c->auth_cache_fail;
516 dbg_error_log(
'ERROR',
'HTTPAuth:CheckCache: SetCache: Unexpected state %s, bailing out from caching credential.', $state);
520 # Only cache if the expiry is set to non-zero. This allows disabling
521 # caching on a pass or fail basis.
523 dbg_error_log(
'ERROR',
'HTTPAuth:CheckCache: SetCache: Expiry set to 0, not caching credential.', $state);
528 $cache = getCacheInstance();
529 if ($cache->isActive() ===
false)
return 0;
531 $cache_ns =
'auth-' . $username;
533 $salt = $cache->get($cache_ns,
'salt');
535 if (!isset($salt) || (is_bool($salt) && $salt ==
false) || $salt ==
'') {
536 # bcrypt requires the salt is 22 bytes.
537 $salt = $this->_GenerateSalt(22);
539 # We use the default expiry setting for the salt, because the worst
540 # case scenario is that we won't access the cached credentials and
541 # need to fail back to the full authentication source.
542 if (! $cache->set($cache_ns,
'salt', $salt) ) {
543 dbg_error_log(
'ERROR',
'HTTPAuth:CheckCache: SetCache: Failed to store salt, bailing out from caching credential.');
548 # The hashed password to store
549 $hash = $this->
_MakeHash($password, $salt);
550 if (! isset($hash)) {
551 dbg_error_log(
'ERROR',
'HTTPAuth:CheckCache: SetCache: Failed to generate hash, bailing out from caching credential.');
555 if (! $cache->set($cache_ns, $hash, $state, $expiry) ) {
556 dbg_error_log(
'ERROR',
'HTTPAuth:CheckCache: SetCache: Failed to store credential.');
569 $password_salt =
'$2y$07$' . $salt .
'$';
570 $password_peppered = hash_hmac(
'sha256', $password, $c->auth_cache_secret);
571 $password_hashed = crypt($password_peppered, $password_salt);
573 if ($password_hashed == $password_salt || $password_hashed ==
'*0') {
576 return $password_hashed;