--- a/includes/sessions.php Sun Oct 25 00:09:11 2009 -0400
+++ b/includes/sessions.php Tue Nov 03 22:08:48 2009 -0500
@@ -676,12 +676,10 @@
* @param string $password The password -OR- the MD5 hash of the password if $already_md5ed is true
* @param bool $already_md5ed This should be set to true if $password is an MD5 hash, and should be false if it's plaintext. Defaults to false.
* @param int $level The privilege level we're authenticating for, defaults to 0
- * @param string $captcha_hash Optional. If we're locked out and the lockout policy is captcha, this should be the identifier for the code.
- * @param string $captcha_code Optional. If we're locked out and the lockout policy is captcha, this should be the code the user entered.
* @param bool $remember Optional. If true, remembers the session for X days. Otherwise, assigns a short session. Defaults to false.
*/
- function login_without_crypto($username, $password, $already_md5ed = false, $level = USER_LEVEL_MEMBER, $captcha_hash = false, $captcha_code = false, $remember = false)
+ function login_without_crypto($username, $password, $already_md5ed = false, $level = USER_LEVEL_MEMBER, $remember = false)
{
global $db, $session, $paths, $template, $plugins; // Common objects
@@ -704,40 +702,6 @@
return $this->login_compat($username, md5($password), $level);
}
- // Lockout check
- if ( !defined('IN_ENANO_INSTALL') )
- {
- $lockout_data = $this->get_lockout_info($lockout_data);
-
- $captcha_good = false;
- if ( $lockout_data['lockout_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 ( strtolower($real_code) === strtolower($captcha_code) )
- {
- $captcha_good = true;
- }
- }
- if ( $lockout_data['lockout_policy'] != 'disable' && !$captcha_good )
- {
- if ( $lockout_data['lockout_fails'] >= $lockout_data['lockout_threshold'] )
- {
- // ooh boy, somebody's in trouble ;-)
- return array(
- 'success' => false,
- 'error' => 'locked_out',
- 'lockout_threshold' => $lockout_data['lockout_threshold'],
- 'lockout_duration' => ( $lockout_data['lockout_duration'] ),
- 'lockout_fails' => $lockout_data['lockout_fails'],
- 'lockout_policy' => $lockout_data['lockout_policy'],
- 'time_rem' => $lockout_data['time_rem'],
- 'lockout_last_time' => $lockout_data['lockout_last_time']
- );
- }
- }
- }
-
// Instanciate the Rijndael encryption object
$aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
@@ -766,7 +730,14 @@
. '\''.$db->escape($_SERVER['REMOTE_ADDR']).'\')');
// Do we also need to increment the lockout countdown?
- if ( @$lockout_data['lockout_policy'] != 'disable' && !defined('IN_ENANO_INSTALL') )
+ if ( !defined('IN_ENANO_INSTALL') )
+ $lockout_data = $this->get_lockout_info();
+ else
+ $lockout_data = array(
+ 'lockout_policy' => 'disable'
+ );
+
+ if ( $lockout_data['lockout_policy'] != 'disable' && !defined('IN_ENANO_INSTALL') )
{
$ipaddr = $db->escape($_SERVER['REMOTE_ADDR']);
// increment fail count
@@ -891,20 +862,11 @@
$this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary) VALUES(\'security\', \'auth_bad\', '.time().', \''.enano_date(ED_DATE | ED_TIME).'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\')');
// Do we also need to increment the lockout countdown?
- if ( !defined('IN_ENANO_INSTALL') && $lockout_data['lockout_policy'] != 'disable' )
+ if ( !defined('IN_ENANO_INSTALL') && getConfig('lockout_policy', 'lockout') !== 'disable' )
{
$ipaddr = $db->escape($_SERVER['REMOTE_ADDR']);
// increment fail count
$this->sql('INSERT INTO '.table_prefix.'lockout(ipaddr, timestamp, action) VALUES(\'' . $ipaddr . '\', ' . time() . ', \'credential\');');
- $lockout_data['lockout_fails']++;
- return array(
- 'success' => false,
- 'error' => ( $lockout_data['lockout_fails'] >= $lockout_data['lockout_threshold'] ) ? 'locked_out' : 'invalid_credentials',
- 'lockout_threshold' => $lockout_data['lockout_threshold'],
- 'lockout_duration' => ( $lockout_data['lockout_duration'] ),
- 'lockout_fails' => $lockout_data['lockout_fails'],
- 'lockout_policy' => $lockout_data['lockout_policy']
- );
}
return array(
@@ -1071,7 +1033,7 @@
* @return bool True if locked out, false otherwise
*/
- function get_lockout_info(&$lockdata)
+ function get_lockout_info()
{
global $db;
@@ -1096,14 +1058,14 @@
$row = $db->fetchrow($q);
$locked_out = ( $fails >= $threshold );
$lockdata = array(
- 'locked_out' => $locked_out,
- 'lockout_threshold' => $threshold,
- 'lockout_duration' => ( $duration / 60 ),
- 'lockout_fails' => $fails,
- 'lockout_policy' => $policy,
- 'lockout_last_time' => $row['timestamp'],
+ 'active' => $locked_out,
+ 'threshold' => $threshold,
+ 'duration' => ( $duration / 60 ),
+ 'fails' => $fails,
+ 'policy' => $policy,
+ 'last_time' => $row['timestamp'],
'time_rem' => $locked_out ? ( $duration / 60 ) - round( ( time() - $row['timestamp'] ) / 60 ) : 0,
- 'captcha' => ''
+ 'captcha' => $policy == 'captcha' ? $this->make_captcha() : ''
);
$db->free_result();
}
@@ -1111,12 +1073,12 @@
{
// disabled; send back default dataset
$lockdata = array(
- 'locked_out' => false,
- 'lockout_threshold' => $threshold,
- 'lockout_duration' => ( $duration / 60 ),
- 'lockout_fails' => 0,
- 'lockout_policy' => $policy,
- 'lockout_last_time' => 0,
+ 'active' => false,
+ 'threshold' => $threshold,
+ 'duration' => ( $duration / 60 ),
+ 'fails' => 0,
+ 'policy' => $policy,
+ 'last_time' => 0,
'time_rem' => 0,
'captcha' => ''
);
@@ -3869,59 +3831,20 @@
// Check for the mode
if ( !isset($req['mode']) )
{
- return array(
- 'mode' => 'error',
- 'error' => 'ERR_JSON_NO_MODE'
- );
+ return $this->get_login_response('api_error', 'ERR_JSON_NO_MODE');
}
// Main processing switch
switch ( $req['mode'] )
{
default:
- return array(
- 'mode' => 'error',
- 'error' => 'ERR_JSON_INVALID_MODE'
- );
+ return $this->get_login_response('api_error', 'ERR_JSON_INVALID_MODE');
break;
case 'getkey':
$this->start();
- $locked_out = $this->get_lockout_info($lockdata);
-
- $response = array('mode' => 'build_box');
- $response['allow_diffiehellman'] = $dh_supported;
-
- $response['username'] = ( $this->user_logged_in ) ? $this->username : false;
- $response['aes_key'] = $this->rijndael_genkey();
-
- $response['extended_time'] = intval(getConfig('session_remember_time', '30'));
-
- // Lockout info
- $response['locked_out'] = $locked_out;
-
- $response['lockout_info'] = $lockdata;
- if ( $lockdata['lockout_policy'] == 'captcha' && $locked_out )
- {
- $response['lockout_info']['captcha'] = $this->make_captcha();
- }
-
- // Can we do Diffie-Hellman? If so, generate and stash a public/private key pair.
- if ( $dh_supported )
- {
- $dh_key_priv = dh_gen_private();
- $dh_key_pub = dh_gen_public($dh_key_priv);
- $dh_key_priv = $_math->str($dh_key_priv);
- $dh_key_pub = $_math->str($dh_key_pub);
- $response['dh_public_key'] = $dh_key_pub;
- // store the keys in the DB
- $q = $db->sql_query('INSERT INTO ' . table_prefix . "diffiehellman( public_key, private_key ) VALUES ( '$dh_key_pub', '$dh_key_priv' );");
- if ( !$q )
- $db->die_json();
- }
-
- return $response;
+ return $this->get_login_response('initial');
break;
case 'login_dh':
// User is requesting a login and has sent Diffie-Hellman data.
@@ -3937,10 +3860,7 @@
// Check the key
if ( !ctype_digit($dh_public) || !ctype_digit($req['dh_client_key']) )
{
- return array(
- 'mode' => 'error',
- 'error' => 'ERR_DH_KEY_NOT_NUMERIC'
- );
+ return $this->get_login_response('api_error', 'ERR_DH_KEY_NOT_NUMERIC');
}
// Fetch private key
@@ -3950,10 +3870,7 @@
if ( $db->numrows() < 1 )
{
- return array(
- 'mode' => 'error',
- 'error' => 'ERR_DH_KEY_NOT_FOUND'
- );
+ return $this->get_login_response('api_error', 'ERR_DH_KEY_NOT_FOUND');
}
list($dh_private, $dh_key_id) = $db->fetchrow_num();
@@ -3972,10 +3889,7 @@
$dh_secret_check = sha1($dh_secret);
if ( $dh_secret_check !== $dh_hash )
{
- return array(
- 'mode' => 'error',
- 'error' => 'ERR_DH_HASH_NO_MATCH',
- );
+ return $this->get_login_response('api_error', 'ERR_DH_HASH_NO_MATCH');
}
// All good! Generate the AES key
@@ -3987,10 +3901,7 @@
$aes_key = $this->fetch_public_key($req['key_aes']);
if ( !$aes_key )
{
- return array(
- 'mode' => 'error',
- 'error' => 'ERR_AES_LOOKUP_FAILED'
- );
+ return $this->get_login_response('api_error', 'ERR_AES_LOOKUP_FAILED');
}
$userinfo_crypt = $req['userinfo'];
}
@@ -4003,10 +3914,7 @@
$userinfo_json = $aes->decrypt($userinfo_crypt, $aes_key, ENC_HEX, true);
if ( !$userinfo_json )
{
- return array(
- 'mode' => 'error',
- 'error' => 'ERR_AES_DECRYPT_FAILED'
- );
+ return $this->get_login_response('api_error', 'ERR_AES_DECRYPT_FAILED');
}
// de-JSON user info
try
@@ -4015,23 +3923,42 @@
}
catch ( Exception $e )
{
- return array(
- 'mode' => 'error',
- 'error' => 'ERR_USERINFO_DECODE_FAILED'
- );
+ return $this->get_login_response('api_error', 'ERR_USERINFO_DECODE_FAILED');
+ }
+
+ case 'login_pt':
+ // plaintext login
+ if ( $req['mode'] == 'login_pt' )
+ {
+ $userinfo = isset($req['userinfo']) ? $req['userinfo'] : array();
}
if ( !isset($userinfo['username']) || !isset($userinfo['password']) )
{
- return array(
- 'mode' => 'error',
- 'error' => 'ERR_USERINFO_MISSING_VALUES'
- );
+ return $this->get_login_response('api_error', 'ERR_USERINFO_MISSING_VALUES');
}
$username =& $userinfo['username'];
$password =& $userinfo['password'];
+ // locked out? check captcha
+ $lockout_data = $this->get_lockout_info();
+ if ( $lockout_data['policy'] == 'captcha' && $lockout_data['active'] )
+ {
+ // policy is captcha -- check if it's correct, and if so, bypass lockout check
+ $real_code = $this->get_captcha($req['captcha_hash']);
+ if ( strtolower($real_code) !== strtolower($req['captcha_code']) )
+ {
+ // captcha is bad
+ return $this->get_login_response('login_failure', 'lockout_bad_captcha');
+ }
+ }
+ else if ( $lockout_data['policy'] == 'lockout' && $lockout_data['active'] )
+ {
+ // we're fully locked out
+ return $this->get_login_response('login_failure', 'lockout_request_denied');
+ }
+
// At this point if any extra info was injected into the login data packet, we need to let plugins process it
/**
* Called upon processing an incoming login request. If you added anything to the userinfo object during the jshook
@@ -4049,12 +3976,12 @@
$result = eval($cmd);
if ( $result === true )
{
- return array(
- 'mode' => 'login_success',
- 'key' => ( $this->sid_super ) ? $this->sid_super : false,
+ return $this->get_login_response('login_success', false, array(
+ 'key' => $this->sid_super,
'user_id' => $this->user_id,
- 'user_level' => $this->user_level
- );
+ 'user_level' => $this->user_level,
+ 'reset' => false
+ ));
}
else if ( is_array($result) )
{
@@ -4063,45 +3990,33 @@
// Pass back any additional information from the error response
$append = $result;
unset($append['mode'], $append['error']);
+ $append['from_plugin'] = true;
- $return = array(
- 'mode' => 'login_failure',
- 'error_code' => $result['error'],
- // Use this to provide a way to respawn the login box
- 'respawn_info' => $this->process_login_request(array('mode' => 'getkey'))
- );
-
- $return = array_merge($append, $return);
- return $return;
+ return $this->get_login_response('login_failure', $result['error'], $append);
}
}
}
- // If we're logging in with a temp password, attach to the login_password_reset hook to send our JSON response
- // A bit hackish since it just dies with the response :-(
- $plugins->attachHook('login_password_reset', '$this->process_login_request(array(\'mode\' => \'respond_password_reset\', \'user_id\' => $row[\'user_id\'], \'temp_password\' => $this->pk_encrypt($password)));');
-
// attempt the login
- // function login_without_crypto($username, $password, $already_md5ed = false, $level = USER_LEVEL_MEMBER, $captcha_hash = false, $captcha_code = false)
- $login_result = $this->login_without_crypto($username, $password, false, intval($req['level']), @$req['captcha_hash'], @$req['captcha_code'], @$req['remember']);
+ $login_result = $this->login_without_crypto($username, $password, false, intval($req['level']), @$req['remember']);
if ( $login_result['success'] )
{
- return array(
- 'mode' => 'login_success',
- 'key' => ( $this->sid_super ) ? $this->sid_super : false,
+ return $this->get_login_response('login_success', false, array(
+ 'key' => $this->sid_super,
'user_id' => $this->user_id,
- 'user_level' => $this->user_level
- );
+ 'user_level' => $this->user_level,
+ ));
+ }
+ else if ( !$login_result['success'] && $login_result['error'] === 'valid_reset' )
+ {
+ return $this->get_login_response('reset_pass_used', false, array(
+ 'redirect_url' => $login_result['redirect_url']
+ ));
}
else
{
- return array(
- 'mode' => 'login_failure',
- 'error_code' => $login_result['error'],
- // Use this to provide a way to respawn the login box
- 'respawn_info' => $this->process_login_request(array('mode' => 'getkey'))
- );
+ return $this->get_login_response('login_failure', 'invalid_credentials');
}
break;
@@ -4164,6 +4079,58 @@
}
+ /**
+ * Generate a packet to send to the client for logins.
+ * @param string mode
+ * @param array
+ * @return array
+ */
+
+ function get_login_response($mode, $error = false, $base = array())
+ {
+ $this->start();
+
+ // init
+ $response = $base;
+ // modules in the packet
+ $response['mode'] = $mode;
+ $response['error'] = $error;
+ $response['crypto'] = $mode !== 'login_success' ? $this->get_login_crypto_packet() : false;
+ $response['lockout'] = $mode !== 'login_success' ? $this->get_lockout_info() : false;
+ $response['extended_time'] = intval(getConfig('session_remember_time', '30'));
+ $response['username'] = $this->user_logged_in ? $this->username : false;
+ return $response;
+ }
+
+ /**
+ * Get a packet of crypto flags for login.
+ * @return array
+ */
+
+ function get_login_crypto_packet()
+ {
+ global $dh_supported, $_math;
+
+ $response = array();
+
+ $response['dh_enable'] = $dh_supported;
+ $response['aes_key'] = $this->rijndael_genkey();
+
+ // Can we do Diffie-Hellman? If so, generate and stash a public/private key pair.
+ if ( $dh_supported )
+ {
+ $dh_key_priv = dh_gen_private();
+ $dh_key_pub = dh_gen_public($dh_key_priv);
+ $dh_key_priv = $_math->str($dh_key_priv);
+ $dh_key_pub = $_math->str($dh_key_pub);
+ $response['dh_public_key'] = $dh_key_pub;
+ // store the keys in the DB
+ $this->sql('INSERT INTO ' . table_prefix . "diffiehellman( public_key, private_key ) VALUES ( '$dh_key_pub', '$dh_key_priv' );");
+ }
+
+ return $response;
+ }
+
}
/**