--- a/includes/sessions.php Sun Oct 07 17:28:47 2007 -0400
+++ b/includes/sessions.php Sun Oct 07 21:28:36 2007 -0400
@@ -547,15 +547,52 @@
* @param string $aes_key The MD5 hash of the encryption key, hex-encoded
* @param string $challenge The 256-bit MD5 challenge string - first 128 bits should be the hash, the last 128 should be the challenge salt
* @param int $level The privilege level we're authenticating for, defaults to 0
+ * @param array $captcha_hash Optional. If we're locked out and the lockout policy is captcha, this should be the identifier for the code.
+ * @param array $captcha_code Optional. If we're locked out and the lockout policy is captcha, this should be the code the user entered.
* @return string 'success' on success, or error string on failure
*/
- function login_with_crypto($username, $aes_data, $aes_key, $challenge, $level = USER_LEVEL_MEMBER)
+ function login_with_crypto($username, $aes_data, $aes_key, $challenge, $level = USER_LEVEL_MEMBER, $captcha_hash = false, $captcha_code = false)
{
global $db, $session, $paths, $template, $plugins; // Common objects
$privcache = $this->private_key;
+ // Lockout stuff
+ $threshold = ( $_ = getConfig('lockout_threshold') ) ? intval($_) : 5;
+ $duration = ( $_ = getConfig('lockout_duration') ) ? intval($_) : 15;
+ // convert to minutes
+ $duration = $duration * 60;
+ $policy = ( $x = getConfig('lockout_policy') && in_array(getConfig('lockout_policy'), array('lockout', 'disable', 'captcha')) ) ? getConfig('lockout_policy') : 'lockout';
+ if ( $policy == 'captcha' && $captcha_hash && $captcha_code )
+ {
+ // policy is captcha -- check if it's correct, and if so, bypass lockout check
+ $real_code = $this->get_captcha($captcha_hash);
+ }
+ if ( $policy != 'disable' && !( $policy == 'captcha' && isset($real_code) && $real_code == $captcha_code ) )
+ {
+ $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']);
+ $timestamp_cutoff = time() - $duration;
+ $q = $this->sql('SELECT timestamp FROM '.table_prefix.'lockout WHERE timestamp > ' . $timestamp_cutoff . ' AND ipaddr = \'' . $ipaddr . '\' ORDER BY timestamp DESC;');
+ $fails = $db->numrows();
+ if ( $fails > $threshold )
+ {
+ // ooh boy, somebody's in trouble ;-)
+ $row = $db->fetchrow();
+ $db->free_result();
+ return array(
+ 'success' => false,
+ 'error' => 'locked_out',
+ 'lockout_threshold' => $threshold,
+ 'lockout_duration' => ( $duration / 60 ),
+ 'lockout_fails' => $fails,
+ 'lockout_policy' => $policy,
+ 'lockout_last_time' => $row['timestamp']
+ );
+ }
+ $db->free_result();
+ }
+
// Instanciate the Rijndael encryption object
$aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
@@ -563,13 +600,19 @@
$aes_key = $this->fetch_public_key($aes_key);
if(!$aes_key)
- return 'Couldn\'t look up public key "'.$aes_key.'" for decryption';
+ return array(
+ 'success' => false,
+ 'error' => 'key_not_found'
+ );
// Convert the key to a binary string
$bin_key = hexdecode($aes_key);
if(strlen($bin_key) != AES_BITS / 8)
- return 'The decryption key is the wrong length';
+ return array(
+ 'success' => false,
+ 'error' => 'key_wrong_length'
+ );
// Decrypt our password
$password = $aes->decrypt($aes_data, $bin_key, ENC_HEX);
@@ -585,13 +628,33 @@
$this->sql('SELECT password,old_encryption,user_id,user_level,theme,style,temp_password,temp_password_time FROM '.table_prefix.'users WHERE lcase(username)=\''.$db_username_lower.'\' OR username=\'' . $db_username . '\';');
if($db->numrows() < 1)
{
- return "The username and/or password is incorrect.";
// This wasn't logged in <1.0.2, dunno how it slipped through
if($level > USER_LEVEL_MEMBER)
$this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES(\'security\', \'admin_auth_bad\', '.time().', \''.date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')');
else
$this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary) VALUES(\'security\', \'auth_bad\', '.time().', \''.date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\')');
-
+
+ if ( $policy != 'disable' )
+ {
+ $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']);
+ // increment fail count
+ $this->sql('INSERT INTO '.table_prefix.'lockout(ipaddr, timestamp, action) VALUES(\'' . $ipaddr . '\', UNIX_TIMESTAMP(), \'credential\');');
+ $fails++;
+ // ooh boy, somebody's in trouble ;-)
+ return array(
+ 'success' => false,
+ 'error' => ( $fails >= $threshold ) ? 'locked_out' : 'invalid_credentials',
+ 'lockout_threshold' => $threshold,
+ 'lockout_duration' => ( $duration / 60 ),
+ 'lockout_fails' => $fails,
+ 'lockout_policy' => $policy
+ );
+ }
+
+ return array(
+ 'success' => false,
+ 'error' => 'invalid_credentials'
+ );
}
$row = $db->fetchrow();
@@ -642,7 +705,10 @@
if($success)
{
if($level > $row['user_level'])
- return 'You are not authorized for this level of access.';
+ return array(
+ 'success' => false,
+ 'error' => 'too_big_for_britches'
+ );
$sess = $this->register_session(intval($row['user_id']), $username, $password, $level);
if($sess)
@@ -662,10 +728,15 @@
{
eval($cmd);
}
- return 'success';
+ return array(
+ 'success' => true
+ );
}
else
- return 'Your login credentials were correct, but an internal error occurred while registering the session key in the database.';
+ return array(
+ 'success' => false,
+ 'error' => 'backend_fail'
+ );
}
else
{
@@ -674,7 +745,27 @@
else
$this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary) VALUES(\'security\', \'auth_bad\', '.time().', \''.date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\')');
- return 'The username and/or password is incorrect.';
+ // Do we also need to increment the lockout countdown?
+ if ( $policy != 'disable' )
+ {
+ $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']);
+ // increment fail count
+ $this->sql('INSERT INTO '.table_prefix.'lockout(ipaddr, timestamp, action) VALUES(\'' . $ipaddr . '\', UNIX_TIMESTAMP(), \'credential\');');
+ $fails++;
+ return array(
+ 'success' => false,
+ 'error' => ( $fails >= $threshold ) ? 'locked_out' : 'invalid_credentials',
+ 'lockout_threshold' => $threshold,
+ 'lockout_duration' => ( $duration / 60 ),
+ 'lockout_fails' => $fails,
+ 'lockout_policy' => $policy
+ );
+ }
+
+ return array(
+ 'success' => false,
+ 'error' => 'invalid_credentials'
+ );
}
}
@@ -700,6 +791,41 @@
return $this->login_compat($username, $pass_hashed, $level);
}
+ // Lockout stuff
+ $threshold = ( $_ = getConfig('lockout_threshold') ) ? intval($_) : 5;
+ $duration = ( $_ = getConfig('lockout_duration') ) ? intval($_) : 15;
+ // convert to minutes
+ $duration = $duration * 60;
+ $policy = ( $x = getConfig('lockout_policy') && in_array(getConfig('lockout_policy'), array('lockout', 'disable', 'captcha')) ) ? getConfig('lockout_policy') : 'lockout';
+ if ( $policy == 'captcha' && $captcha_hash && $captcha_code )
+ {
+ // policy is captcha -- check if it's correct, and if so, bypass lockout check
+ $real_code = $this->get_captcha($captcha_hash);
+ }
+ if ( $policy != 'disable' && !( $policy == 'captcha' && isset($real_code) && $real_code == $captcha_code ) )
+ {
+ $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']);
+ $timestamp_cutoff = time() - $duration;
+ $q = $this->sql('SELECT timestamp FROM '.table_prefix.'lockout WHERE timestamp > ' . $timestamp_cutoff . ' AND ipaddr = \'' . $ipaddr . '\' ORDER BY timestamp DESC;');
+ $fails = $db->numrows();
+ if ( $fails > $threshold )
+ {
+ // ooh boy, somebody's in trouble ;-)
+ $row = $db->fetchrow();
+ $db->free_result();
+ return array(
+ 'success' => false,
+ 'error' => 'locked_out',
+ 'lockout_threshold' => $threshold,
+ 'lockout_duration' => ( $duration / 60 ),
+ 'lockout_fails' => $fails,
+ 'lockout_policy' => $policy,
+ 'lockout_last_time' => $row['timestamp']
+ );
+ }
+ $db->free_result();
+ }
+
// Instanciate the Rijndael encryption object
$aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
@@ -709,7 +835,35 @@
// Retrieve the real password from the database
$this->sql('SELECT password,old_encryption,user_id,user_level,temp_password,temp_password_time FROM '.table_prefix.'users WHERE lcase(username)=\''.$this->prepare_text(strtolower($username)).'\';');
if($db->numrows() < 1)
- return 'The username and/or password is incorrect.';
+ {
+ // This wasn't logged in <1.0.2, dunno how it slipped through
+ if($level > USER_LEVEL_MEMBER)
+ $this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES(\'security\', \'admin_auth_bad\', '.time().', \''.date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')');
+ else
+ $this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary) VALUES(\'security\', \'auth_bad\', '.time().', \''.date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\')');
+
+ // Do we also need to increment the lockout countdown?
+ if ( $policy != 'disable' )
+ {
+ $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']);
+ // increment fail count
+ $this->sql('INSERT INTO '.table_prefix.'lockout(ipaddr, timestamp, action) VALUES(\'' . $ipaddr . '\', UNIX_TIMESTAMP(), \'credential\');');
+ $fails++;
+ return array(
+ 'success' => false,
+ 'error' => ( $fails >= $threshold ) ? 'locked_out' : 'invalid_credentials',
+ 'lockout_threshold' => $threshold,
+ 'lockout_duration' => ( $duration / 60 ),
+ 'lockout_fails' => $fails,
+ 'lockout_policy' => $policy
+ );
+ }
+
+ return array(
+ 'success' => false,
+ 'error' => 'invalid_credentials'
+ );
+ }
$row = $db->fetchrow();
// Check to see if we're logging in using a temporary password
@@ -758,7 +912,10 @@
if($success)
{
if((int)$level > (int)$row['user_level'])
- return 'You are not authorized for this level of access.';
+ return array(
+ 'success' => false,
+ 'error' => 'too_big_for_britches'
+ );
$sess = $this->register_session(intval($row['user_id']), $username, $real_pass, $level);
if($sess)
{
@@ -773,10 +930,15 @@
eval($cmd);
}
- return 'success';
+ return array(
+ 'success' => true
+ );
}
else
- return 'Your login credentials were correct, but an internal error occured while registering the session key in the database.';
+ return array(
+ 'success' => false,
+ 'error' => 'backend_fail'
+ );
}
else
{
@@ -785,7 +947,27 @@
else
$this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary) VALUES(\'security\', \'auth_bad\', '.time().', \''.date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\')');
- return 'The username and/or password is incorrect.';
+ // Do we also need to increment the lockout countdown?
+ if ( $policy != 'disable' )
+ {
+ $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']);
+ // increment fail count
+ $this->sql('INSERT INTO '.table_prefix.'lockout(ipaddr, timestamp, action) VALUES(\'' . $ipaddr . '\', UNIX_TIMESTAMP(), \'credential\');');
+ $fails++;
+ return array(
+ 'success' => false,
+ 'error' => ( $fails >= $threshold ) ? 'locked_out' : 'invalid_credentials',
+ 'lockout_threshold' => $threshold,
+ 'lockout_duration' => ( $duration / 60 ),
+ 'lockout_fails' => $fails,
+ 'lockout_policy' => $policy
+ );
+ }
+
+ return array(
+ 'success' => false,
+ 'error' => 'invalid_credentials'
+ );
}
}