includes/sessions.php
changeset 843 4415e50e4e84
parent 832 7152ca0a0ce9
child 857 f3a5a276208c
equal deleted inserted replaced
842:f13bb4f21890 843:4415e50e4e84
   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;
  3860         
  3866         
  3861         // Lockout info
  3867         // Lockout info
  3862         $response['locked_out'] = $locked_out;
  3868         $response['locked_out'] = $locked_out;
  3863         
  3869         
  3864         $response['lockout_info'] = $lockdata;
  3870         $response['lockout_info'] = $lockdata;
  3865         if ( $policy == 'captcha' && $locked_out )
  3871         if ( $lockdata['lockout_policy'] == 'captcha' && $locked_out )
  3866         {
  3872         {
  3867           $response['lockout_info']['captcha'] = $this->make_captcha();
  3873           $response['lockout_info']['captcha'] = $this->make_captcha();
  3868         }
  3874         }
  3869         
  3875         
  3870         // Can we do Diffie-Hellman? If so, generate and stash a public/private key pair.
  3876         // Can we do Diffie-Hellman? If so, generate and stash a public/private key pair.
  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