656 return $this->login_compat($username, md5($password), $level); |
656 return $this->login_compat($username, md5($password), $level); |
657 } |
657 } |
658 |
658 |
659 if ( !defined('IN_ENANO_INSTALL') ) |
659 if ( !defined('IN_ENANO_INSTALL') ) |
660 { |
660 { |
661 // Lockout stuff |
661 $locked_out = $this->get_lockout_info($lockout_data); |
662 $threshold = ( $_ = getConfig('lockout_threshold') ) ? intval($_) : 5; |
|
663 $duration = ( $_ = getConfig('lockout_duration') ) ? intval($_) : 15; |
|
664 // convert to minutes |
|
665 $duration = $duration * 60; |
|
666 $policy = ( $x = getConfig('lockout_policy') && in_array(getConfig('lockout_policy'), array('lockout', 'disable', 'captcha')) ) ? getConfig('lockout_policy') : 'lockout'; |
|
667 |
|
668 $timestamp_cutoff = time() - $duration; |
|
669 $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']); |
|
670 $q = $this->sql('SELECT timestamp FROM '.table_prefix.'lockout WHERE timestamp > ' . $timestamp_cutoff . ' AND ipaddr = \'' . $ipaddr . '\' ORDER BY timestamp DESC;'); |
|
671 $fails = $db->numrows(); |
|
672 |
662 |
673 $captcha_good = false; |
663 $captcha_good = false; |
674 if ( $policy == 'captcha' && $captcha_hash && $captcha_code ) |
664 if ( $lockout_data['lockout_policy'] == 'captcha' && $captcha_hash && $captcha_code ) |
675 { |
665 { |
676 // policy is captcha -- check if it's correct, and if so, bypass lockout check |
666 // policy is captcha -- check if it's correct, and if so, bypass lockout check |
677 $real_code = $this->get_captcha($captcha_hash); |
667 $real_code = $this->get_captcha($captcha_hash); |
678 if ( strtolower($real_code) === strtolower($captcha_code) ) |
668 if ( strtolower($real_code) === strtolower($captcha_code) ) |
679 { |
669 { |
680 $captcha_good = true; |
670 $captcha_good = true; |
681 } |
671 } |
682 } |
672 } |
683 if ( $policy != 'disable' && !$captcha_good ) |
673 if ( $policy != 'disable' && !$captcha_good ) |
684 { |
674 { |
685 if ( $fails >= $threshold ) |
675 if ( $lockout_data['lockout_fails'] >= $lockout_data['lockout_threshold'] ) |
686 { |
676 { |
687 // ooh boy, somebody's in trouble ;-) |
677 // ooh boy, somebody's in trouble ;-) |
688 $row = $db->fetchrow(); |
678 $row = $db->fetchrow(); |
689 $db->free_result(); |
679 $db->free_result(); |
690 return array( |
680 return array( |
691 'success' => false, |
681 'success' => false, |
692 'error' => 'locked_out', |
682 'error' => 'locked_out', |
693 'lockout_threshold' => $threshold, |
683 'lockout_threshold' => $lockout_data['lockout_threshold'], |
694 'lockout_duration' => ( $duration / 60 ), |
684 'lockout_duration' => ( $lockout_data['lockout_duration'] ), |
695 'lockout_fails' => $fails, |
685 'lockout_fails' => $lockout_data['lockout_fails'], |
696 'lockout_policy' => $policy, |
686 'lockout_policy' => $lockout_data['lockout_policy'], |
697 'time_rem' => ( $duration / 60 ) - round( ( time() - $row['timestamp'] ) / 60 ), |
687 'time_rem' => $lockout_data['lockout_time_rem'], |
698 'lockout_last_time' => $row['timestamp'] |
688 'lockout_last_time' => $lockout_data['lockout_last_time'] |
699 ); |
689 ); |
700 } |
690 } |
701 } |
691 } |
702 $db->free_result(); |
692 $db->free_result(); |
703 } |
693 } |
727 $this->sql('INSERT INTO ' . table_prefix . "logs(log_type,action,time_id,date_string,author,edit_summary) VALUES\n" |
717 $this->sql('INSERT INTO ' . table_prefix . "logs(log_type,action,time_id,date_string,author,edit_summary) VALUES\n" |
728 . ' (\'security\', \'auth_bad\', '.time().', \''.enano_date('d M Y h:i a').'\', \''.$db->escape($username).'\', ' |
718 . ' (\'security\', \'auth_bad\', '.time().', \''.enano_date('d M Y h:i a').'\', \''.$db->escape($username).'\', ' |
729 . '\''.$db->escape($_SERVER['REMOTE_ADDR']).'\')'); |
719 . '\''.$db->escape($_SERVER['REMOTE_ADDR']).'\')'); |
730 |
720 |
731 // Do we also need to increment the lockout countdown? |
721 // Do we also need to increment the lockout countdown? |
732 if ( @$policy != 'disable' && !defined('IN_ENANO_INSTALL') ) |
722 if ( @$lockout_data['lockout_policy'] != 'disable' && !defined('IN_ENANO_INSTALL') ) |
733 { |
723 { |
734 $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']); |
724 $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']); |
735 // increment fail count |
725 // increment fail count |
736 $this->sql('INSERT INTO '.table_prefix.'lockout(ipaddr, timestamp, action) VALUES(\'' . $ipaddr . '\', ' . time() . ', \'credential\');'); |
726 $this->sql('INSERT INTO '.table_prefix.'lockout(ipaddr, timestamp, action) VALUES(\'' . $ipaddr . '\', ' . time() . ', \'credential\');'); |
737 $fails++; |
727 $lockout_data['lockout_fails']++; |
738 return array( |
728 return array( |
739 'success' => false, |
729 'success' => false, |
740 'error' => ( $fails >= $threshold ) ? 'locked_out' : 'invalid_credentials', |
730 'error' => ( $lockout_data['lockout_fails'] >= $lockout_data['lockout_threshold'] ) ? 'locked_out' : 'invalid_credentials', |
741 'lockout_threshold' => $threshold, |
731 'lockout_threshold' => $lockout_data['lockout_threshold'], |
742 'lockout_duration' => ( $duration / 60 ), |
732 'lockout_duration' => ( $lockout_data['lockout_duration'] ), |
743 'lockout_fails' => $fails, |
733 'lockout_fails' => $lockout_data['lockout_fails'], |
744 'lockout_policy' => $policy |
734 'lockout_policy' => $lockout_data['lockout_policy'] |
745 ); |
735 ); |
746 } |
736 } |
747 |
737 |
748 return array( |
738 return array( |
749 'success' => false, |
739 'success' => false, |
849 $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().', \''.enano_date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')'); |
839 $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().', \''.enano_date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')'); |
850 else |
840 else |
851 $this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary) VALUES(\'security\', \'auth_bad\', '.time().', \''.enano_date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\')'); |
841 $this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary) VALUES(\'security\', \'auth_bad\', '.time().', \''.enano_date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\')'); |
852 |
842 |
853 // Do we also need to increment the lockout countdown? |
843 // Do we also need to increment the lockout countdown? |
854 if ( !defined('IN_ENANO_INSTALL') && $policy != 'disable' ) |
844 if ( !defined('IN_ENANO_INSTALL') && $lockout_data['lockout_policy'] != 'disable' ) |
855 { |
845 { |
856 $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']); |
846 $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']); |
857 // increment fail count |
847 // increment fail count |
858 $this->sql('INSERT INTO '.table_prefix.'lockout(ipaddr, timestamp, action) VALUES(\'' . $ipaddr . '\', ' . time() . ', \'credential\');'); |
848 $this->sql('INSERT INTO '.table_prefix.'lockout(ipaddr, timestamp, action) VALUES(\'' . $ipaddr . '\', ' . time() . ', \'credential\');'); |
859 $fails++; |
849 $lockout_data['lockout_fails']++; |
860 return array( |
850 return array( |
861 'success' => false, |
851 'success' => false, |
862 'error' => ( $fails >= $threshold ) ? 'locked_out' : 'invalid_credentials', |
852 'error' => ( $lockout_data['lockout_fails'] >= $lockout_data['lockout_threshold'] ) ? 'locked_out' : 'invalid_credentials', |
863 'lockout_threshold' => $threshold, |
853 'lockout_threshold' => $lockout_data['lockout_threshold'], |
864 'lockout_duration' => ( $duration / 60 ), |
854 'lockout_duration' => ( $lockout_data['lockout_duration'] ), |
865 'lockout_fails' => $fails, |
855 'lockout_fails' => $lockout_data['lockout_fails'], |
866 'lockout_policy' => $policy |
856 'lockout_policy' => $lockout_data['lockout_policy'] |
867 ); |
857 ); |
868 } |
858 } |
869 |
859 |
870 return array( |
860 return array( |
871 'success' => false, |
861 'success' => false, |
1007 $query = $this->sql('INSERT INTO '.table_prefix.'session_keys(session_key, salt, user_id, auth_level, source_ip, time) VALUES(\''.$thekey.'\', \''.$salt.'\', '.$user_id.', '.$level.', \''.$ip.'\', '.$time.');'); |
997 $query = $this->sql('INSERT INTO '.table_prefix.'session_keys(session_key, salt, user_id, auth_level, source_ip, time) VALUES(\''.$thekey.'\', \''.$salt.'\', '.$user_id.', '.$level.', \''.$ip.'\', '.$time.');'); |
1008 return true; |
998 return true; |
1009 } |
999 } |
1010 |
1000 |
1011 /** |
1001 /** |
|
1002 * Tells us if we're locked out from logging in or not. |
|
1003 * @param reference will be filled with information regarding in-progress lockout |
|
1004 * @return bool True if locked out, false otherwise |
|
1005 */ |
|
1006 |
|
1007 function get_lockout_info(&$lockdata) |
|
1008 { |
|
1009 global $db; |
|
1010 |
|
1011 // this has to be initialized to hide warnings |
|
1012 $lockdata = null; |
|
1013 |
|
1014 // Query database for lockout info |
|
1015 $locked_out = false; |
|
1016 $threshold = ( $_ = getConfig('lockout_threshold') ) ? intval($_) : 5; |
|
1017 $duration = ( $_ = getConfig('lockout_duration') ) ? intval($_) : 15; |
|
1018 // convert to minutes |
|
1019 $duration = $duration * 60; |
|
1020 $policy = ( $x = getConfig('lockout_policy') && in_array(getConfig('lockout_policy'), array('lockout', 'disable', 'captcha')) ) ? getConfig('lockout_policy') : 'lockout'; |
|
1021 if ( $policy != 'disable' ) |
|
1022 { |
|
1023 $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']); |
|
1024 $timestamp_cutoff = time() - $duration; |
|
1025 $q = $this->sql('SELECT timestamp FROM ' . table_prefix . 'lockout WHERE timestamp > ' . $timestamp_cutoff . ' AND ipaddr = \'' . $ipaddr . '\' ORDER BY timestamp DESC;'); |
|
1026 $fails = $db->numrows(); |
|
1027 $row = $db->fetchrow(); |
|
1028 $locked_out = ( $fails >= $threshold ); |
|
1029 $lockdata = array( |
|
1030 'locked_out' => $locked_out, |
|
1031 'lockout_threshold' => $threshold, |
|
1032 'lockout_duration' => ( $duration / 60 ), |
|
1033 'lockout_fails' => $fails, |
|
1034 'lockout_policy' => $policy, |
|
1035 'lockout_last_time' => $row['timestamp'], |
|
1036 'time_rem' => ( $duration / 60 ) - round( ( time() - $row['timestamp'] ) / 60 ), |
|
1037 'captcha' => '' |
|
1038 ); |
|
1039 $db->free_result(); |
|
1040 } |
|
1041 return $locked_out; |
|
1042 } |
|
1043 |
|
1044 /** |
1012 * Creates/restores a guest session |
1045 * Creates/restores a guest session |
1013 * @todo implement real session management for guests |
1046 * @todo implement real session management for guests |
1014 */ |
1047 */ |
1015 |
1048 |
1016 function register_guest_session() |
1049 function register_guest_session() |
1036 $language = ( isset($_GET['lang']) && preg_match('/^[a-z0-9-_]+$/', @$_GET['lang']) ) ? $_GET['lang'] : intval(getConfig('default_language')); |
1069 $language = ( isset($_GET['lang']) && preg_match('/^[a-z0-9-_]+$/', @$_GET['lang']) ) ? $_GET['lang'] : intval(getConfig('default_language')); |
1037 $lang = new Language($language); |
1070 $lang = new Language($language); |
1038 @setlocale(LC_ALL, $lang->lang_code); |
1071 @setlocale(LC_ALL, $lang->lang_code); |
1039 } |
1072 } |
1040 // make a CSRF token |
1073 // make a CSRF token |
1041 $this->csrf_token = sha1($_SERVER['REMOTE_ADDR'] . sha1($this->private_key)); |
1074 $this->csrf_token = hmac_sha1($_SERVER['REMOTE_ADDR'], sha1($this->private_key)); |
1042 } |
1075 } |
1043 |
1076 |
1044 /** |
1077 /** |
1045 * Validates a session key, and returns the userdata associated with the key or false |
1078 * Validates a session key, and returns the userdata associated with the key or false |
1046 * @param string $key The session key to validate |
1079 * @param string $key The session key to validate |
3819 break; |
3852 break; |
3820 case 'getkey': |
3853 case 'getkey': |
3821 |
3854 |
3822 $this->start(); |
3855 $this->start(); |
3823 |
3856 |
3824 // Query database for lockout info |
3857 $locked_out = $this->get_lockout_info($lockdata); |
3825 $locked_out = false; |
|
3826 // are we locked out? |
|
3827 $threshold = ( $_ = getConfig('lockout_threshold') ) ? intval($_) : 5; |
|
3828 $duration = ( $_ = getConfig('lockout_duration') ) ? intval($_) : 15; |
|
3829 // convert to minutes |
|
3830 $duration = $duration * 60; |
|
3831 $policy = ( $x = getConfig('lockout_policy') && in_array(getConfig('lockout_policy'), array('lockout', 'disable', 'captcha')) ) ? getConfig('lockout_policy') : 'lockout'; |
|
3832 if ( $policy != 'disable' ) |
|
3833 { |
|
3834 $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']); |
|
3835 $timestamp_cutoff = time() - $duration; |
|
3836 $q = $this->sql('SELECT timestamp FROM '.table_prefix.'lockout WHERE timestamp > ' . $timestamp_cutoff . ' AND ipaddr = \'' . $ipaddr . '\' ORDER BY timestamp DESC;'); |
|
3837 $fails = $db->numrows(); |
|
3838 $row = $db->fetchrow(); |
|
3839 $locked_out = ( $fails >= $threshold ); |
|
3840 $lockdata = array( |
|
3841 'locked_out' => $locked_out, |
|
3842 'lockout_threshold' => $threshold, |
|
3843 'lockout_duration' => ( $duration / 60 ), |
|
3844 'lockout_fails' => $fails, |
|
3845 'lockout_policy' => $policy, |
|
3846 'lockout_last_time' => $row['timestamp'], |
|
3847 'time_rem' => ( $duration / 60 ) - round( ( time() - $row['timestamp'] ) / 60 ), |
|
3848 'captcha' => '' |
|
3849 ); |
|
3850 $db->free_result(); |
|
3851 } |
|
3852 |
3858 |
3853 $response = array('mode' => 'build_box'); |
3859 $response = array('mode' => 'build_box'); |
3854 $response['allow_diffiehellman'] = $dh_supported; |
3860 $response['allow_diffiehellman'] = $dh_supported; |
3855 |
3861 |
3856 $response['username'] = ( $this->user_logged_in ) ? $this->username : false; |
3862 $response['username'] = ( $this->user_logged_in ) ? $this->username : false; |
3989 ); |
3995 ); |
3990 } |
3996 } |
3991 |
3997 |
3992 $username =& $userinfo['username']; |
3998 $username =& $userinfo['username']; |
3993 $password =& $userinfo['password']; |
3999 $password =& $userinfo['password']; |
|
4000 |
|
4001 // At this point if any extra info was injected into the login data packet, we need to let plugins process it |
|
4002 /** |
|
4003 * Called upon processing an incoming login request. If you added anything to the userinfo object during the jshook |
|
4004 * login_build_userinfo, that will be in the $userinfo array here. Expected return values are: true if your plugin has |
|
4005 * not only succeeded but ALSO issued a session key (bypass the whole Enano builtin login process) and an associative array |
|
4006 * with "mode" set to "error" and an error string in "error" to send an error back to the client. Any return value other |
|
4007 * than these will be ignored. |
|
4008 * @hook login_process_userdata_json |
|
4009 */ |
|
4010 |
|
4011 $code = $plugins->setHook('login_process_userdata_json'); |
|
4012 foreach ( $code as $cmd ) |
|
4013 { |
|
4014 $result = eval($cmd); |
|
4015 if ( $result === true ) |
|
4016 { |
|
4017 return array( |
|
4018 'mode' => 'login_success', |
|
4019 'key' => ( $this->sid_super ) ? $this->sid_super : false |
|
4020 ); |
|
4021 } |
|
4022 else if ( is_array($result) ) |
|
4023 { |
|
4024 if ( isset($result['mode']) && $result['mode'] === 'error' && isset($result['error']) ) |
|
4025 { |
|
4026 return array( |
|
4027 'mode' => 'login_failure', |
|
4028 'error_code' => $result['error'], |
|
4029 // Use this to provide a way to respawn the login box |
|
4030 'respawn_info' => $this->process_login_request(array('mode' => 'getkey')) |
|
4031 ); |
|
4032 } |
|
4033 } |
|
4034 } |
3994 |
4035 |
3995 // If we're logging in with a temp password, attach to the login_password_reset hook to send our JSON response |
4036 // If we're logging in with a temp password, attach to the login_password_reset hook to send our JSON response |
3996 // A bit hackish since it just dies with the response :-( |
4037 // A bit hackish since it just dies with the response :-( |
3997 $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)));'); |
4038 $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)));'); |
3998 |
4039 |