--- a/includes/sessions.php Thu Sep 27 15:55:37 2007 -0400
+++ b/includes/sessions.php Sat Nov 03 07:43:35 2007 -0400
@@ -362,6 +362,7 @@
function start()
{
global $db, $session, $paths, $template, $plugins; // Common objects
+ global $lang;
if($this->started) return;
$this->started = true;
$user = false;
@@ -381,6 +382,9 @@
if(!$this->compat && $userdata['account_active'] != 1 && $data[1] != 'Special' && $data[1] != 'Admin')
{
+ $language = intval(getConfig('default_language'));
+ $lang = new Language($language);
+
$this->logout();
$a = getConfig('account_activation');
switch($a)
@@ -480,6 +484,13 @@
}
$user = true;
+ // Set language
+ if ( !defined('ENANO_ALLOW_LOAD_NOLANG') )
+ {
+ $lang_id = intval($userdata['user_lang']);
+ $lang = new Language($lang_id);
+ }
+
if(isset($_REQUEST['auth']) && !$this->sid_super)
{
// Now he thinks he's a moderator. Or maybe even an administrator. Let's find out if he's telling the truth.
@@ -547,14 +558,55 @@
* @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;
+
+ if ( !defined('IN_ENANO_INSTALL') )
+ {
+ // 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,
+ 'time_rem' => ( $duration / 60 ) - round( ( time() - $row['timestamp'] ) / 60 ),
+ 'lockout_last_time' => $row['timestamp']
+ );
+ }
+ $db->free_result();
+ }
+ }
// Instanciate the Rijndael encryption object
$aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
@@ -563,13 +615,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 +643,34 @@
$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.\n$db->latest_query";
// 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' && !defined('IN_ENANO_INSTALL') )
+ {
+ $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,
+ 'time_rem' => ( $duration / 60 ),
+ 'lockout_policy' => $policy
+ );
+ }
+
+ return array(
+ 'success' => false,
+ 'error' => 'invalid_credentials'
+ );
}
$row = $db->fetchrow();
@@ -642,7 +721,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 +744,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 +761,28 @@
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' && !defined('IN_ENANO_INSTALL') )
+ {
+ $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,
+ 'time_rem' => ( $duration / 60 ),
+ 'lockout_policy' => $policy
+ );
+ }
+
+ return array(
+ 'success' => false,
+ 'error' => 'invalid_credentials'
+ );
}
}
@@ -700,6 +808,45 @@
return $this->login_compat($username, $pass_hashed, $level);
}
+ if ( !defined('IN_ENANO_INSTALL') )
+ {
+ // 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,
+ 'time_rem' => $duration - round( ( time() - $row['timestamp'] ) / 60 ),
+ 'lockout_last_time' => $row['timestamp']
+ );
+ }
+ $db->free_result();
+ }
+ }
+
// Instanciate the Rijndael encryption object
$aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
@@ -709,7 +856,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' && !defined('IN_ENANO_INSTALL') )
+ {
+ $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 +933,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 +951,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 +968,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' && !defined('IN_ENANO_INSTALL') )
+ {
+ $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'
+ );
}
}
@@ -857,7 +1060,7 @@
{
// Stash it in a cookie
// For now, make the cookie last forever, we can change this in 1.1.x
- setcookie( 'sid', $session_key, time()+315360000, scriptPath.'/' );
+ setcookie( 'sid', $session_key, time()+315360000, scriptPath.'/', null, ( isset($_SERVER['HTTPS']) ) );
$_COOKIE['sid'] = $session_key;
}
// $keyhash is stored in the database, this is for compatibility with the older DB structure
@@ -919,6 +1122,7 @@
function register_guest_session()
{
global $db, $session, $paths, $template, $plugins; // Common objects
+ global $lang;
$this->username = $_SERVER['REMOTE_ADDR'];
$this->user_level = USER_LEVEL_GUEST;
if($this->compat || defined('IN_ENANO_INSTALL'))
@@ -932,6 +1136,12 @@
$this->style = ( isset($_GET['style']) && file_exists(ENANO_ROOT.'/themes/'.$this->theme . '/css/'.$_GET['style'].'.css' )) ? $_GET['style'] : substr($template->named_theme_list[$this->theme]['default_style'], 0, strlen($template->named_theme_list[$this->theme]['default_style'])-4);
}
$this->user_id = 1;
+ if ( !defined('ENANO_ALLOW_LOAD_NOLANG') )
+ {
+ // This is a VERY special case we are allowing. It lets the installer create languages using the Enano API.
+ $language = intval(getConfig('default_language'));
+ $lang = new Language($language);
+ }
}
/**
@@ -959,7 +1169,7 @@
}
$keyhash = md5($key);
$salt = $db->escape($keydata[3]);
- $query = $db->sql_query('SELECT u.user_id AS uid,u.username,u.password,u.email,u.real_name,u.user_level,u.theme,u.style,u.signature,u.reg_time,u.account_active,u.activation_key,k.source_ip,k.time,k.auth_level,COUNT(p.message_id) AS num_pms,x.* FROM '.table_prefix.'session_keys AS k
+ $query = $db->sql_query('SELECT u.user_id AS uid,u.username,u.password,u.email,u.real_name,u.user_level,u.theme,u.style,u.signature,u.reg_time,u.account_active,u.activation_key,k.source_ip,k.time,k.auth_level,COUNT(p.message_id) AS num_pms,u.user_lang,x.* FROM '.table_prefix.'session_keys AS k
LEFT JOIN '.table_prefix.'users AS u
ON ( u.user_id=k.user_id )
LEFT JOIN '.table_prefix.'users_extra AS x
@@ -1114,7 +1324,10 @@
if($level > USER_LEVEL_CHPREF)
{
$aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
- if(!$this->user_logged_in || $this->auth_level < USER_LEVEL_MOD) return 'success';
+ if(!$this->user_logged_in || $this->auth_level < USER_LEVEL_MOD)
+ {
+ return 'success';
+ }
// Destroy elevated privileges
$keyhash = md5(strrev($this->sid_super));
$this->sql('DELETE FROM '.table_prefix.'session_keys WHERE session_key=\''.$keyhash.'\' AND user_id=\'' . $this->user_id . '\';');
@@ -2117,13 +2330,30 @@
return false;
}
+ // cache of permission objects (to save RAM and SQL queries)
+ static $objcache = array();
+
+ if ( count($objcache) == 0 )
+ {
+ foreach ( $paths->nslist as $key => $_ )
+ {
+ $objcache[$key] = array();
+ }
+ }
+
+ if ( isset($objcache[$namespace][$page_id]) )
+ {
+ return $objcache[$namespace][$page_id];
+ }
+
//if ( !isset( $paths->pages[$paths->nslist[$namespace] . $page_id] ) )
//{
// // Page does not exist
// return false;
//}
- $object = new Session_ACLPageInfo( $page_id, $namespace, $this->acl_types, $this->acl_descs, $this->acl_deps, $this->acl_base_cache );
+ $objcache[$namespace][$page_id] = new Session_ACLPageInfo( $page_id, $namespace, $this->acl_types, $this->acl_descs, $this->acl_deps, $this->acl_base_cache );
+ $object =& $objcache[$namespace][$page_id];
return $object;