includes/sessions.php
changeset 179 36b287f1d85c
parent 178 4c19952406db
child 181 06bdbdfec160
equal deleted inserted replaced
178:4c19952406db 179:36b287f1d85c
   545    * @param string $username The username
   545    * @param string $username The username
   546    * @param string $aes_data The encrypted password, hex-encoded
   546    * @param string $aes_data The encrypted password, hex-encoded
   547    * @param string $aes_key The MD5 hash of the encryption key, hex-encoded
   547    * @param string $aes_key The MD5 hash of the encryption key, hex-encoded
   548    * @param string $challenge The 256-bit MD5 challenge string - first 128 bits should be the hash, the last 128 should be the challenge salt
   548    * @param string $challenge The 256-bit MD5 challenge string - first 128 bits should be the hash, the last 128 should be the challenge salt
   549    * @param int $level The privilege level we're authenticating for, defaults to 0
   549    * @param int $level The privilege level we're authenticating for, defaults to 0
       
   550    * @param array $captcha_hash Optional. If we're locked out and the lockout policy is captcha, this should be the identifier for the code.
       
   551    * @param array $captcha_code Optional. If we're locked out and the lockout policy is captcha, this should be the code the user entered.
   550    * @return string 'success' on success, or error string on failure
   552    * @return string 'success' on success, or error string on failure
   551    */
   553    */
   552    
   554    
   553   function login_with_crypto($username, $aes_data, $aes_key, $challenge, $level = USER_LEVEL_MEMBER)
   555   function login_with_crypto($username, $aes_data, $aes_key, $challenge, $level = USER_LEVEL_MEMBER, $captcha_hash = false, $captcha_code = false)
   554   {
   556   {
   555     global $db, $session, $paths, $template, $plugins; // Common objects
   557     global $db, $session, $paths, $template, $plugins; // Common objects
   556     
   558     
   557     $privcache = $this->private_key;
   559     $privcache = $this->private_key;
       
   560     
       
   561     // Lockout stuff
       
   562     $threshold = ( $_ = getConfig('lockout_threshold') ) ? intval($_) : 5;
       
   563     $duration  = ( $_ = getConfig('lockout_duration') ) ? intval($_) : 15;
       
   564     // convert to minutes
       
   565     $duration  = $duration * 60;
       
   566     $policy = ( $x = getConfig('lockout_policy') && in_array(getConfig('lockout_policy'), array('lockout', 'disable', 'captcha')) ) ? getConfig('lockout_policy') : 'lockout';
       
   567     if ( $policy == 'captcha' && $captcha_hash && $captcha_code )
       
   568     {
       
   569       // policy is captcha -- check if it's correct, and if so, bypass lockout check
       
   570       $real_code = $this->get_captcha($captcha_hash);
       
   571     }
       
   572     if ( $policy != 'disable' && !( $policy == 'captcha' && isset($real_code) && $real_code == $captcha_code ) )
       
   573     {
       
   574       $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']);
       
   575       $timestamp_cutoff = time() - $duration;
       
   576       $q = $this->sql('SELECT timestamp FROM '.table_prefix.'lockout WHERE timestamp > ' . $timestamp_cutoff . ' AND ipaddr = \'' . $ipaddr . '\' ORDER BY timestamp DESC;');
       
   577       $fails = $db->numrows();
       
   578       if ( $fails > $threshold )
       
   579       {
       
   580         // ooh boy, somebody's in trouble ;-)
       
   581         $row = $db->fetchrow();
       
   582         $db->free_result();
       
   583         return array(
       
   584             'success' => false,
       
   585             'error' => 'locked_out',
       
   586             'lockout_threshold' => $threshold,
       
   587             'lockout_duration' => ( $duration / 60 ),
       
   588             'lockout_fails' => $fails,
       
   589             'lockout_policy' => $policy,
       
   590             'lockout_last_time' => $row['timestamp']
       
   591           );
       
   592       }
       
   593       $db->free_result();
       
   594     }
   558     
   595     
   559     // Instanciate the Rijndael encryption object
   596     // Instanciate the Rijndael encryption object
   560     $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
   597     $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
   561     
   598     
   562     // Fetch our decryption key
   599     // Fetch our decryption key
   563     
   600     
   564     $aes_key = $this->fetch_public_key($aes_key);
   601     $aes_key = $this->fetch_public_key($aes_key);
   565     if(!$aes_key)
   602     if(!$aes_key)
   566       return 'Couldn\'t look up public key "'.$aes_key.'" for decryption';
   603       return array(
       
   604         'success' => false,
       
   605         'error' => 'key_not_found'
       
   606         );
   567     
   607     
   568     // Convert the key to a binary string
   608     // Convert the key to a binary string
   569     $bin_key = hexdecode($aes_key);
   609     $bin_key = hexdecode($aes_key);
   570     
   610     
   571     if(strlen($bin_key) != AES_BITS / 8)
   611     if(strlen($bin_key) != AES_BITS / 8)
   572       return 'The decryption key is the wrong length';
   612       return array(
       
   613         'success' => false,
       
   614         'error' => 'key_wrong_length'
       
   615         );
   573     
   616     
   574     // Decrypt our password
   617     // Decrypt our password
   575     $password = $aes->decrypt($aes_data, $bin_key, ENC_HEX);
   618     $password = $aes->decrypt($aes_data, $bin_key, ENC_HEX);
   576     
   619     
   577     // Initialize our success switch
   620     // Initialize our success switch
   583     
   626     
   584     // Select the user data from the table, and decrypt that so we can verify the password
   627     // Select the user data from the table, and decrypt that so we can verify the password
   585     $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 . '\';');
   628     $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 . '\';');
   586     if($db->numrows() < 1)
   629     if($db->numrows() < 1)
   587     {
   630     {
   588       return "The username and/or password is incorrect.";
       
   589       // This wasn't logged in <1.0.2, dunno how it slipped through
   631       // This wasn't logged in <1.0.2, dunno how it slipped through
   590       if($level > USER_LEVEL_MEMBER)
   632       if($level > USER_LEVEL_MEMBER)
   591         $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) . ')');
   633         $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) . ')');
   592       else
   634       else
   593         $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']).'\')');
   635         $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']).'\')');
   594         
   636       
       
   637       if ( $policy != 'disable' )
       
   638       {
       
   639         $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']);
       
   640         // increment fail count
       
   641         $this->sql('INSERT INTO '.table_prefix.'lockout(ipaddr, timestamp, action) VALUES(\'' . $ipaddr . '\', UNIX_TIMESTAMP(), \'credential\');');
       
   642         $fails++;
       
   643         // ooh boy, somebody's in trouble ;-)
       
   644         return array(
       
   645             'success' => false,
       
   646             'error' => ( $fails >= $threshold ) ? 'locked_out' : 'invalid_credentials',
       
   647             'lockout_threshold' => $threshold,
       
   648             'lockout_duration' => ( $duration / 60 ),
       
   649             'lockout_fails' => $fails,
       
   650             'lockout_policy' => $policy
       
   651           );
       
   652       }
       
   653       
       
   654       return array(
       
   655           'success' => false,
       
   656           'error' => 'invalid_credentials'
       
   657         );
   595     }
   658     }
   596     $row = $db->fetchrow();
   659     $row = $db->fetchrow();
   597     
   660     
   598     // Check to see if we're logging in using a temporary password
   661     // Check to see if we're logging in using a temporary password
   599     
   662     
   640       }
   703       }
   641     }
   704     }
   642     if($success)
   705     if($success)
   643     {
   706     {
   644       if($level > $row['user_level'])
   707       if($level > $row['user_level'])
   645         return 'You are not authorized for this level of access.';
   708         return array(
       
   709           'success' => false,
       
   710           'error' => 'too_big_for_britches'
       
   711         );
   646       
   712       
   647       $sess = $this->register_session(intval($row['user_id']), $username, $password, $level);
   713       $sess = $this->register_session(intval($row['user_id']), $username, $password, $level);
   648       if($sess)
   714       if($sess)
   649       {
   715       {
   650         $this->username = $username;
   716         $this->username = $username;
   660         $code = $plugins->setHook('login_success');
   726         $code = $plugins->setHook('login_success');
   661         foreach ( $code as $cmd )
   727         foreach ( $code as $cmd )
   662         {
   728         {
   663           eval($cmd);
   729           eval($cmd);
   664         }
   730         }
   665         return 'success';
   731         return array(
       
   732           'success' => true
       
   733         );
   666       }
   734       }
   667       else
   735       else
   668         return 'Your login credentials were correct, but an internal error occurred while registering the session key in the database.';
   736         return array(
       
   737           'success' => false,
       
   738           'error' => 'backend_fail'
       
   739         );
   669     }
   740     }
   670     else
   741     else
   671     {
   742     {
   672       if($level > USER_LEVEL_MEMBER)
   743       if($level > USER_LEVEL_MEMBER)
   673         $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) . ')');
   744         $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) . ')');
   674       else
   745       else
   675         $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']).'\')');
   746         $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']).'\')');
   676         
   747         
   677       return 'The username and/or password is incorrect.';
   748       // Do we also need to increment the lockout countdown?
       
   749       if ( $policy != 'disable' )
       
   750       {
       
   751         $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']);
       
   752         // increment fail count
       
   753         $this->sql('INSERT INTO '.table_prefix.'lockout(ipaddr, timestamp, action) VALUES(\'' . $ipaddr . '\', UNIX_TIMESTAMP(), \'credential\');');
       
   754         $fails++;
       
   755         return array(
       
   756             'success' => false,
       
   757             'error' => ( $fails >= $threshold ) ? 'locked_out' : 'invalid_credentials',
       
   758             'lockout_threshold' => $threshold,
       
   759             'lockout_duration' => ( $duration / 60 ),
       
   760             'lockout_fails' => $fails,
       
   761             'lockout_policy' => $policy
       
   762           );
       
   763       }
       
   764         
       
   765       return array(
       
   766         'success' => false,
       
   767         'error' => 'invalid_credentials'
       
   768       );
   678     }
   769     }
   679   }
   770   }
   680   
   771   
   681   /**
   772   /**
   682    * Attempts to login without using crypto stuff, mainly for use when the other side doesn't like Javascript
   773    * Attempts to login without using crypto stuff, mainly for use when the other side doesn't like Javascript
   698     if($this->compat)
   789     if($this->compat)
   699     {
   790     {
   700       return $this->login_compat($username, $pass_hashed, $level);
   791       return $this->login_compat($username, $pass_hashed, $level);
   701     }
   792     }
   702     
   793     
       
   794     // Lockout stuff
       
   795     $threshold = ( $_ = getConfig('lockout_threshold') ) ? intval($_) : 5;
       
   796     $duration  = ( $_ = getConfig('lockout_duration') ) ? intval($_) : 15;
       
   797     // convert to minutes
       
   798     $duration  = $duration * 60;
       
   799     $policy = ( $x = getConfig('lockout_policy') && in_array(getConfig('lockout_policy'), array('lockout', 'disable', 'captcha')) ) ? getConfig('lockout_policy') : 'lockout';
       
   800     if ( $policy == 'captcha' && $captcha_hash && $captcha_code )
       
   801     {
       
   802       // policy is captcha -- check if it's correct, and if so, bypass lockout check
       
   803       $real_code = $this->get_captcha($captcha_hash);
       
   804     }
       
   805     if ( $policy != 'disable' && !( $policy == 'captcha' && isset($real_code) && $real_code == $captcha_code ) )
       
   806     {
       
   807       $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']);
       
   808       $timestamp_cutoff = time() - $duration;
       
   809       $q = $this->sql('SELECT timestamp FROM '.table_prefix.'lockout WHERE timestamp > ' . $timestamp_cutoff . ' AND ipaddr = \'' . $ipaddr . '\' ORDER BY timestamp DESC;');
       
   810       $fails = $db->numrows();
       
   811       if ( $fails > $threshold )
       
   812       {
       
   813         // ooh boy, somebody's in trouble ;-)
       
   814         $row = $db->fetchrow();
       
   815         $db->free_result();
       
   816         return array(
       
   817             'success' => false,
       
   818             'error' => 'locked_out',
       
   819             'lockout_threshold' => $threshold,
       
   820             'lockout_duration' => ( $duration / 60 ),
       
   821             'lockout_fails' => $fails,
       
   822             'lockout_policy' => $policy,
       
   823             'lockout_last_time' => $row['timestamp']
       
   824           );
       
   825       }
       
   826       $db->free_result();
       
   827     }
       
   828     
   703     // Instanciate the Rijndael encryption object
   829     // Instanciate the Rijndael encryption object
   704     $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
   830     $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
   705     
   831     
   706     // Initialize our success switch
   832     // Initialize our success switch
   707     $success = false;
   833     $success = false;
   708     
   834     
   709     // Retrieve the real password from the database
   835     // Retrieve the real password from the database
   710     $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)).'\';');
   836     $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)).'\';');
   711     if($db->numrows() < 1)
   837     if($db->numrows() < 1)
   712       return 'The username and/or password is incorrect.';
   838     {
       
   839       // This wasn't logged in <1.0.2, dunno how it slipped through
       
   840       if($level > USER_LEVEL_MEMBER)
       
   841         $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) . ')');
       
   842       else
       
   843         $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']).'\')');
       
   844       
       
   845       // Do we also need to increment the lockout countdown?
       
   846       if ( $policy != 'disable' )
       
   847       {
       
   848         $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']);
       
   849         // increment fail count
       
   850         $this->sql('INSERT INTO '.table_prefix.'lockout(ipaddr, timestamp, action) VALUES(\'' . $ipaddr . '\', UNIX_TIMESTAMP(), \'credential\');');
       
   851         $fails++;
       
   852         return array(
       
   853             'success' => false,
       
   854             'error' => ( $fails >= $threshold ) ? 'locked_out' : 'invalid_credentials',
       
   855             'lockout_threshold' => $threshold,
       
   856             'lockout_duration' => ( $duration / 60 ),
       
   857             'lockout_fails' => $fails,
       
   858             'lockout_policy' => $policy
       
   859           );
       
   860       }
       
   861       
       
   862       return array(
       
   863         'success' => false,
       
   864         'error' => 'invalid_credentials'
       
   865       );
       
   866     }
   713     $row = $db->fetchrow();
   867     $row = $db->fetchrow();
   714     
   868     
   715     // Check to see if we're logging in using a temporary password
   869     // Check to see if we're logging in using a temporary password
   716     
   870     
   717     if((intval($row['temp_password_time']) + 3600*24) > time() )
   871     if((intval($row['temp_password_time']) + 3600*24) > time() )
   756       }
   910       }
   757     }
   911     }
   758     if($success)
   912     if($success)
   759     {
   913     {
   760       if((int)$level > (int)$row['user_level'])
   914       if((int)$level > (int)$row['user_level'])
   761         return 'You are not authorized for this level of access.';
   915         return array(
       
   916           'success' => false,
       
   917           'error' => 'too_big_for_britches'
       
   918         );
   762       $sess = $this->register_session(intval($row['user_id']), $username, $real_pass, $level);
   919       $sess = $this->register_session(intval($row['user_id']), $username, $real_pass, $level);
   763       if($sess)
   920       if($sess)
   764       {
   921       {
   765         if($level > USER_LEVEL_MEMBER)
   922         if($level > USER_LEVEL_MEMBER)
   766           $this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES(\'security\', \'admin_auth_good\', '.time().', \''.date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')');
   923           $this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES(\'security\', \'admin_auth_good\', '.time().', \''.date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')');
   771         foreach ( $code as $cmd )
   928         foreach ( $code as $cmd )
   772         {
   929         {
   773           eval($cmd);
   930           eval($cmd);
   774         }
   931         }
   775         
   932         
   776         return 'success';
   933         return array(
       
   934           'success' => true
       
   935           );
   777       }
   936       }
   778       else
   937       else
   779         return 'Your login credentials were correct, but an internal error occured while registering the session key in the database.';
   938         return array(
       
   939           'success' => false,
       
   940           'error' => 'backend_fail'
       
   941         );
   780     }
   942     }
   781     else
   943     else
   782     {
   944     {
   783       if($level > USER_LEVEL_MEMBER)
   945       if($level > USER_LEVEL_MEMBER)
   784         $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) . ')');
   946         $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) . ')');
   785       else
   947       else
   786         $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']).'\')');
   948         $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']).'\')');
   787         
   949         
   788       return 'The username and/or password is incorrect.';
   950       // Do we also need to increment the lockout countdown?
       
   951       if ( $policy != 'disable' )
       
   952       {
       
   953         $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']);
       
   954         // increment fail count
       
   955         $this->sql('INSERT INTO '.table_prefix.'lockout(ipaddr, timestamp, action) VALUES(\'' . $ipaddr . '\', UNIX_TIMESTAMP(), \'credential\');');
       
   956         $fails++;
       
   957         return array(
       
   958             'success' => false,
       
   959             'error' => ( $fails >= $threshold ) ? 'locked_out' : 'invalid_credentials',
       
   960             'lockout_threshold' => $threshold,
       
   961             'lockout_duration' => ( $duration / 60 ),
       
   962             'lockout_fails' => $fails,
       
   963             'lockout_policy' => $policy
       
   964           );
       
   965       }
       
   966         
       
   967       return array(
       
   968         'success' => false,
       
   969         'error' => 'invalid_credentials'
       
   970       );
   789     }
   971     }
   790   }
   972   }
   791   
   973   
   792   /**
   974   /**
   793    * Attempts to log in using the old table structure and algorithm.
   975    * Attempts to log in using the old table structure and algorithm.