includes/sessions.php
changeset 436 242353360e37
parent 425 fa51b1b5eae6
child 458 c433348f3628
equal deleted inserted replaced
435:a434d60e525d 436:242353360e37
    45    */
    45    */
    46   
    46   
    47   var $username;
    47   var $username;
    48   
    48   
    49   /**
    49   /**
    50    * User ID of currently logged-in user, or -1 if not logged in
    50    * User ID of currently logged-in user, or 1 if not logged in
    51    * @var int
    51    * @var int
    52    */
    52    */
    53   
    53   
    54   var $user_id;
    54   var $user_id = 1;
    55   
    55   
    56   /**
    56   /**
    57    * Real name of currently logged-in user, or blank if not logged in
    57    * Real name of currently logged-in user, or blank if not logged in
    58    * @var string
    58    * @var string
    59    */
    59    */
   152   /**
   152   /**
   153    * What we're allowed to do as far as permissions go. This changes based on the value of the "auth" URI param.
   153    * What we're allowed to do as far as permissions go. This changes based on the value of the "auth" URI param.
   154    * @var string
   154    * @var string
   155    */
   155    */
   156    
   156    
   157   var $auth_level = -1;
   157   var $auth_level = 1;
   158   
   158   
   159   /**
   159   /**
   160    * State variable to track if a session timed out
   160    * State variable to track if a session timed out
   161    * @var bool
   161    * @var bool
   162    */
   162    */
   473           $this->theme =         $userdata['theme'];
   473           $this->theme =         $userdata['theme'];
   474           $this->style =         $userdata['style'];
   474           $this->style =         $userdata['style'];
   475           $this->signature =     $userdata['signature'];
   475           $this->signature =     $userdata['signature'];
   476           $this->reg_time =      $userdata['reg_time'];
   476           $this->reg_time =      $userdata['reg_time'];
   477         }
   477         }
   478         // Small security risk here - it allows someone who has already authenticated as an administrator to store the "super" key in
   478         $this->auth_level =    USER_LEVEL_MEMBER;
   479         // the cookie. Change this to USER_LEVEL_MEMBER to override that. The same 15-minute restriction applies to this "exploit".
       
   480         $this->auth_level =    $userdata['auth_level'];
       
   481         if(!isset($template->named_theme_list[$this->theme]))
   479         if(!isset($template->named_theme_list[$this->theme]))
   482         {
   480         {
   483           if($this->compat || !is_object($template))
   481           if($this->compat || !is_object($template))
   484           {
   482           {
   485             $this->theme = 'oxygen';
   483             $this->theme = 'oxygen';
   573    * @param string $aes_key The MD5 hash of the encryption key, hex-encoded
   571    * @param string $aes_key The MD5 hash of the encryption key, hex-encoded
   574    * @param string $challenge The 256-bit MD5 challenge string - first 128 bits should be the hash, the last 128 should be the challenge salt
   572    * @param string $challenge The 256-bit MD5 challenge string - first 128 bits should be the hash, the last 128 should be the challenge salt
   575    * @param int $level The privilege level we're authenticating for, defaults to 0
   573    * @param int $level The privilege level we're authenticating for, defaults to 0
   576    * @param array $captcha_hash Optional. If we're locked out and the lockout policy is captcha, this should be the identifier for the code.
   574    * @param array $captcha_hash Optional. If we're locked out and the lockout policy is captcha, this should be the identifier for the code.
   577    * @param array $captcha_code Optional. If we're locked out and the lockout policy is captcha, this should be the code the user entered.
   575    * @param array $captcha_code Optional. If we're locked out and the lockout policy is captcha, this should be the code the user entered.
       
   576    * @param bool $lookup_key Optional. If true (default) this queries the database for the "real" encryption key. Else, uses what is given.
   578    * @return string 'success' on success, or error string on failure
   577    * @return string 'success' on success, or error string on failure
   579    */
   578    */
   580    
   579    
   581   function login_with_crypto($username, $aes_data, $aes_key_id, $challenge, $level = USER_LEVEL_MEMBER, $captcha_hash = false, $captcha_code = false)
   580   function login_with_crypto($username, $aes_data, $aes_key_id, $challenge, $level = USER_LEVEL_MEMBER, $captcha_hash = false, $captcha_code = false, $lookup_key = true)
   582   {
   581   {
   583     global $db, $session, $paths, $template, $plugins; // Common objects
   582     global $db, $session, $paths, $template, $plugins; // Common objects
   584     
   583     
   585     $privcache = $this->private_key;
   584     $privcache = $this->private_key;
   586 
   585 
   587     if ( !defined('IN_ENANO_INSTALL') )
   586     if ( !defined('IN_ENANO_INSTALL') )
   588     {
   587     {
       
   588       $timestamp_cutoff = time() - $duration;
       
   589       $q = $this->sql('SELECT timestamp FROM '.table_prefix.'lockout WHERE timestamp > ' . $timestamp_cutoff . ' AND ipaddr = \'' . $ipaddr . '\' ORDER BY timestamp DESC;');
       
   590       $fails = $db->numrows();
   589       // Lockout stuff
   591       // Lockout stuff
   590       $threshold = ( $_ = getConfig('lockout_threshold') ) ? intval($_) : 5;
   592       $threshold = ( $_ = getConfig('lockout_threshold') ) ? intval($_) : 5;
   591       $duration  = ( $_ = getConfig('lockout_duration') ) ? intval($_) : 15;
   593       $duration  = ( $_ = getConfig('lockout_duration') ) ? intval($_) : 15;
   592       // convert to minutes
   594       // convert to minutes
   593       $duration  = $duration * 60;
   595       $duration  = $duration * 60;
   598         $real_code = $this->get_captcha($captcha_hash);
   600         $real_code = $this->get_captcha($captcha_hash);
   599       }
   601       }
   600       if ( $policy != 'disable' && !( $policy == 'captcha' && isset($real_code) && strtolower($real_code) == strtolower($captcha_code) ) )
   602       if ( $policy != 'disable' && !( $policy == 'captcha' && isset($real_code) && strtolower($real_code) == strtolower($captcha_code) ) )
   601       {
   603       {
   602         $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']);
   604         $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']);
   603         $timestamp_cutoff = time() - $duration;
       
   604         $q = $this->sql('SELECT timestamp FROM '.table_prefix.'lockout WHERE timestamp > ' . $timestamp_cutoff . ' AND ipaddr = \'' . $ipaddr . '\' ORDER BY timestamp DESC;');
       
   605         $fails = $db->numrows();
       
   606         if ( $fails >= $threshold )
   605         if ( $fails >= $threshold )
   607         {
   606         {
   608           // ooh boy, somebody's in trouble ;-)
   607           // ooh boy, somebody's in trouble ;-)
   609           $row = $db->fetchrow();
   608           $row = $db->fetchrow();
   610           $db->free_result();
   609           $db->free_result();
   617               'lockout_policy' => $policy,
   616               'lockout_policy' => $policy,
   618               'time_rem' => ( $duration / 60 ) - round( ( time() - $row['timestamp'] ) / 60 ),
   617               'time_rem' => ( $duration / 60 ) - round( ( time() - $row['timestamp'] ) / 60 ),
   619               'lockout_last_time' => $row['timestamp']
   618               'lockout_last_time' => $row['timestamp']
   620             );
   619             );
   621         }
   620         }
   622         $db->free_result();
   621       }
   623       }
   622       $db->free_result();
   624     }
   623     }
   625     
   624     
   626     // Instanciate the Rijndael encryption object
   625     // Instanciate the Rijndael encryption object
   627     $aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
   626     $aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
   628     
   627     
   629     // Fetch our decryption key
   628     // Fetch our decryption key
   630     
   629     
   631     $aes_key = $this->fetch_public_key($aes_key_id);
   630     if ( $lookup_key )
   632     if ( !$aes_key )
   631     {
   633     {
   632       $aes_key = $this->fetch_public_key($aes_key_id);
   634       // It could be that our key cache is full. If it seems larger than 65KB, clear it
   633       if ( !$aes_key )
   635       if ( strlen(getConfig('login_key_cache')) > 65000 )
   634       {
   636       {
   635         // It could be that our key cache is full. If it seems larger than 65KB, clear it
   637         setConfig('login_key_cache', '');
   636         if ( strlen(getConfig('login_key_cache')) > 65000 )
       
   637         {
       
   638           setConfig('login_key_cache', '');
       
   639           return array(
       
   640             'success' => false,
       
   641             'error' => 'key_not_found_cleared',
       
   642             );
       
   643         }
   638         return array(
   644         return array(
   639           'success' => false,
   645           'success' => false,
   640           'error' => 'key_not_found_cleared',
   646           'error' => 'key_not_found'
   641           );
   647           );
   642       }
   648       }
   643       return array(
   649     }
   644         'success' => false,
   650     else
   645         'error' => 'key_not_found'
   651     {
   646         );
   652       $aes_key =& $aes_key_id;
   647     }
   653     }
   648     
   654     
   649     // Convert the key to a binary string
   655     // Convert the key to a binary string
   650     $bin_key = hexdecode($aes_key);
   656     $bin_key = hexdecode($aes_key);
   651     
   657     
   733     }
   739     }
   734     else
   740     else
   735     {
   741     {
   736       // Our password field is up-to-date with the >=1.0RC1 encryption standards, so decrypt the password in the table and see if we have a match; if so then do challenge authentication
   742       // Our password field is up-to-date with the >=1.0RC1 encryption standards, so decrypt the password in the table and see if we have a match; if so then do challenge authentication
   737       $real_pass = $aes->decrypt(hexdecode($row['password']), $this->private_key, ENC_BINARY);
   743       $real_pass = $aes->decrypt(hexdecode($row['password']), $this->private_key, ENC_BINARY);
   738       if($password == $real_pass)
   744       if($password === $real_pass && is_string($password))
   739       {
   745       {
   740         // Yay! We passed AES authentication, now do an MD5 challenge check to make sure we weren't spoofed
   746         // Yay! We passed AES authentication. Previously an MD5 challenge was done here, this was deemed redundant in 1.1.3.
   741         $chal = substr($challenge, 0, 32);
   747         // It didn't seem to provide any additional security...
   742         $salt = substr($challenge, 32, 32);
   748         $success = true;
   743         $correct_challenge = md5( $real_pass . $salt );
       
   744         if($chal == $correct_challenge)
       
   745           $success = true;
       
   746       }
   749       }
   747     }
   750     }
   748     if($success)
   751     if($success)
   749     {
   752     {
   750       if($level > $row['user_level'])
   753       if($level > $row['user_level'])
   751         return array(
   754         return array(
   752           'success' => false,
   755           'success' => false,
   753           'error' => 'too_big_for_britches'
   756           'error' => 'too_big_for_britches'
   754         );
   757         );
       
   758 
       
   759       /*        
       
   760       return array(
       
   761         'success' => false,
       
   762         'error' => 'Successful authentication, but session manager is in debug mode - remove the "return array(...);" in includes/sessions.php:' . ( __LINE__ - 2 )
       
   763       );
       
   764       */
   755       
   765       
   756       $sess = $this->register_session(intval($row['user_id']), $username, $password, $level);
   766       $sess = $this->register_session(intval($row['user_id']), $username, $password, $level);
   757       if($sess)
   767       if($sess)
   758       {
   768       {
   759         $this->username = $username;
   769         $this->username = $username;
   821    * @param string $password The password -OR- the MD5 hash of the password if $already_md5ed is true
   831    * @param string $password The password -OR- the MD5 hash of the password if $already_md5ed is true
   822    * @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.
   832    * @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.
   823    * @param int $level The privilege level we're authenticating for, defaults to 0
   833    * @param int $level The privilege level we're authenticating for, defaults to 0
   824    */
   834    */
   825   
   835   
   826   function login_without_crypto($username, $password, $already_md5ed = false, $level = USER_LEVEL_MEMBER)
   836   function login_without_crypto($username, $password, $already_md5ed = false, $level = USER_LEVEL_MEMBER, $captcha_hash = false, $captcha_code = false)
   827   {
   837   {
   828     global $db, $session, $paths, $template, $plugins; // Common objects
   838     global $db, $session, $paths, $template, $plugins; // Common objects
   829     
   839     
   830     $pass_hashed = ( $already_md5ed ) ? $password : md5($password);
   840     $pass_hashed = ( $already_md5ed ) ? $password : md5($password);
   831     
   841     
   844       // Lockout stuff
   854       // Lockout stuff
   845       $threshold = ( $_ = getConfig('lockout_threshold') ) ? intval($_) : 5;
   855       $threshold = ( $_ = getConfig('lockout_threshold') ) ? intval($_) : 5;
   846       $duration  = ( $_ = getConfig('lockout_duration') ) ? intval($_) : 15;
   856       $duration  = ( $_ = getConfig('lockout_duration') ) ? intval($_) : 15;
   847       // convert to minutes
   857       // convert to minutes
   848       $duration  = $duration * 60;
   858       $duration  = $duration * 60;
       
   859       
       
   860       // get the lockout status
       
   861       $timestamp_cutoff = time() - $duration;
       
   862       $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']);
       
   863       $q = $this->sql('SELECT timestamp FROM '.table_prefix.'lockout WHERE timestamp > ' . $timestamp_cutoff . ' AND ipaddr = \'' . $ipaddr . '\' ORDER BY timestamp DESC;');
       
   864       $fails = $db->numrows();
       
   865       
   849       $policy = ( $x = getConfig('lockout_policy') && in_array(getConfig('lockout_policy'), array('lockout', 'disable', 'captcha')) ) ? getConfig('lockout_policy') : 'lockout';
   866       $policy = ( $x = getConfig('lockout_policy') && in_array(getConfig('lockout_policy'), array('lockout', 'disable', 'captcha')) ) ? getConfig('lockout_policy') : 'lockout';
       
   867       $captcha_good = false;
   850       if ( $policy == 'captcha' && $captcha_hash && $captcha_code )
   868       if ( $policy == 'captcha' && $captcha_hash && $captcha_code )
   851       {
   869       {
   852         // policy is captcha -- check if it's correct, and if so, bypass lockout check
   870         // policy is captcha -- check if it's correct, and if so, bypass lockout check
   853         $real_code = $this->get_captcha($captcha_hash);
   871         $real_code = $this->get_captcha($captcha_hash);
   854       }
   872         $captcha_good = ( strtolower($real_code) === strtolower($captcha_code) );
   855       if ( $policy != 'disable' && !( $policy == 'captcha' && isset($real_code) && $real_code == $captcha_code ) )
   873       }
   856       {
   874       if ( $policy != 'disable' && !$captcha_good )
   857         $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']);
   875       {
   858         $timestamp_cutoff = time() - $duration;
   876         if ( $fails >= $threshold )
   859         $q = $this->sql('SELECT timestamp FROM '.table_prefix.'lockout WHERE timestamp > ' . $timestamp_cutoff . ' AND ipaddr = \'' . $ipaddr . '\' ORDER BY timestamp DESC;');
       
   860         $fails = $db->numrows();
       
   861         if ( $fails > $threshold )
       
   862         {
   877         {
   863           // ooh boy, somebody's in trouble ;-)
   878           // ooh boy, somebody's in trouble ;-)
   864           $row = $db->fetchrow();
   879           $row = $db->fetchrow();
   865           $db->free_result();
   880           $db->free_result();
   866           return array(
   881           return array(
   868               'error' => 'locked_out',
   883               'error' => 'locked_out',
   869               'lockout_threshold' => $threshold,
   884               'lockout_threshold' => $threshold,
   870               'lockout_duration' => ( $duration / 60 ),
   885               'lockout_duration' => ( $duration / 60 ),
   871               'lockout_fails' => $fails,
   886               'lockout_fails' => $fails,
   872               'lockout_policy' => $policy,
   887               'lockout_policy' => $policy,
   873               'time_rem' => $duration - round( ( time() - $row['timestamp'] ) / 60 ),
   888               'time_rem' => ( $duration / 60 ) - round( ( time() - $row['timestamp'] ) / 60 ),
   874               'lockout_last_time' => $row['timestamp']
   889               'lockout_last_time' => $row['timestamp']
   875             );
   890             );
   876         }
   891         }
   877         $db->free_result();
   892       }
   878       }
   893       $db->free_result();
   879     }
   894     }
   880     
   895     
   881     // Instanciate the Rijndael encryption object
   896     // Instanciate the Rijndael encryption object
   882     $aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
   897     $aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
   883     
   898     
  2835   {
  2850   {
  2836     global $db, $session, $paths, $template, $plugins; // Common objects
  2851     global $db, $session, $paths, $template, $plugins; // Common objects
  2837     
  2852     
  2838     if ( !preg_match('/^[a-f0-9]{32}([a-z0-9]{8})?$/', $hash) )
  2853     if ( !preg_match('/^[a-f0-9]{32}([a-z0-9]{8})?$/', $hash) )
  2839     {
  2854     {
       
  2855       die("session manager: bad captcha_hash $hash");
  2840       return false;
  2856       return false;
  2841     }
  2857     }
  2842     
  2858     
  2843     // sanity check
  2859     // sanity check
  2844     if ( !is_valid_ip(@$_SERVER['REMOTE_ADDR']) || !is_int($this->user_id) )
  2860     if ( !is_valid_ip(@$_SERVER['REMOTE_ADDR']) )
       
  2861     {
       
  2862       die("session manager insanity: bad REMOTE_ADDR or invalid UID");
  2845       return false;
  2863       return false;
  2846     
  2864     }
  2847     $q = $this->sql('SELECT code_id, code FROM ' . table_prefix . "captcha WHERE session_id = '$hash' AND source_ip = '{$_SERVER['REMOTE_ADDR']};");
  2865     
       
  2866     $q = $this->sql('SELECT code_id, code FROM ' . table_prefix . "captcha WHERE session_id = '$hash' AND source_ip = '{$_SERVER['REMOTE_ADDR']}';");
  2848     if ( $db->numrows() < 1 )
  2867     if ( $db->numrows() < 1 )
       
  2868     {
       
  2869       die("session manager: no rows for captcha_code $hash");
  2849       return false;
  2870       return false;
       
  2871     }
  2850     
  2872     
  2851     list($code_id, $code) = $db->fetchrow_num();
  2873     list($code_id, $code) = $db->fetchrow_num();
       
  2874     
  2852     $db->free_result();
  2875     $db->free_result();
  2853     $this->sql('DELETE FROM ' . table_prefix . "captcha WHERE code_id = $code_id;");
  2876     $this->sql('DELETE FROM ' . table_prefix . "captcha WHERE code_id = $code_id;");
       
  2877     
  2854     return $code;
  2878     return $code;
  2855   }
  2879   }
  2856   
  2880   
  2857   /**
  2881   /**
  2858    * (AS OF 1.0.2: Deprecated. Captcha codes are now killed on first fetch for security.) Deletes all CAPTCHA codes cached in the DB for this user.
  2882    * (AS OF 1.0.2: Deprecated. Captcha codes are now killed on first fetch for security.) Deletes all CAPTCHA codes cached in the DB for this user.
  2951         </script>
  2975         </script>
  2952         ';
  2976         ';
  2953     return $code;
  2977     return $code;
  2954   }
  2978   }
  2955   
  2979   
       
  2980   /**
       
  2981    * Backend code for the JSON login interface. Basically a frontend to the session API that takes all parameters in one huge array.
       
  2982    * @param array LoginAPI request
       
  2983    * @return array LoginAPI response
       
  2984    */
       
  2985   
       
  2986   function process_login_request($req)
       
  2987   {
       
  2988     global $db, $session, $paths, $template, $plugins; // Common objects
       
  2989     
       
  2990     // Setup EnanoMath and Diffie-Hellman
       
  2991     global $dh_supported;
       
  2992     $dh_supported = true;
       
  2993     try
       
  2994     {
       
  2995       require_once(ENANO_ROOT . '/includes/diffiehellman.php');
       
  2996     }
       
  2997     catch ( Exception $e )
       
  2998     {
       
  2999       $dh_supported = false;
       
  3000     }
       
  3001     global $_math;
       
  3002     
       
  3003     // Check for the mode
       
  3004     if ( !isset($req['mode']) )
       
  3005     {
       
  3006       return array(
       
  3007           'mode' => 'error',
       
  3008           'error' => 'ERR_JSON_NO_MODE'
       
  3009         );
       
  3010     }
       
  3011     
       
  3012     // Main processing switch
       
  3013     switch ( $req['mode'] )
       
  3014     {
       
  3015       default:
       
  3016         return array(
       
  3017             'mode' => 'error',
       
  3018             'error' => 'ERR_JSON_INVALID_MODE'
       
  3019           );
       
  3020         break;
       
  3021       case 'getkey':
       
  3022         
       
  3023         $this->start();
       
  3024         
       
  3025         // Query database for lockout info
       
  3026         $locked_out = false;
       
  3027         // are we locked out?
       
  3028         $threshold = ( $_ = getConfig('lockout_threshold') ) ? intval($_) : 5;
       
  3029         $duration  = ( $_ = getConfig('lockout_duration') ) ? intval($_) : 15;
       
  3030         // convert to minutes
       
  3031         $duration  = $duration * 60;
       
  3032         $policy = ( $x = getConfig('lockout_policy') && in_array(getConfig('lockout_policy'), array('lockout', 'disable', 'captcha')) ) ? getConfig('lockout_policy') : 'lockout';
       
  3033         if ( $policy != 'disable' )
       
  3034         {
       
  3035           $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']);
       
  3036           $timestamp_cutoff = time() - $duration;
       
  3037           $q = $this->sql('SELECT timestamp FROM '.table_prefix.'lockout WHERE timestamp > ' . $timestamp_cutoff . ' AND ipaddr = \'' . $ipaddr . '\' ORDER BY timestamp DESC;');
       
  3038           $fails = $db->numrows();
       
  3039           $row = $db->fetchrow();
       
  3040           $locked_out = ( $fails >= $threshold );
       
  3041           $lockdata = array(
       
  3042               'locked_out' => $locked_out,
       
  3043               'lockout_threshold' => $threshold,
       
  3044               'lockout_duration' => ( $duration / 60 ),
       
  3045               'lockout_fails' => $fails,
       
  3046               'lockout_policy' => $policy,
       
  3047               'lockout_last_time' => $row['timestamp'],
       
  3048               'time_rem' => ( $duration / 60 ) - round( ( time() - $row['timestamp'] ) / 60 ),
       
  3049               'captcha' => ''
       
  3050             );
       
  3051           $db->free_result();
       
  3052         }
       
  3053         
       
  3054         $response = array('mode' => 'build_box');
       
  3055         $response['allow_diffiehellman'] = $dh_supported;
       
  3056         
       
  3057         $response['username'] = ( $this->user_logged_in ) ? $this->username : false;
       
  3058         $response['aes_key'] = $this->rijndael_genkey();
       
  3059         
       
  3060         // Lockout info
       
  3061         $response['locked_out'] = $locked_out;
       
  3062         
       
  3063         $response['lockout_info'] = $lockdata;
       
  3064         if ( $policy == 'captcha' && $locked_out )
       
  3065         {
       
  3066           $response['lockout_info']['captcha'] = $this->make_captcha();
       
  3067         }
       
  3068         
       
  3069         // Can we do Diffie-Hellman? If so, generate and stash a public/private key pair.
       
  3070         if ( $dh_supported )
       
  3071         {
       
  3072           $dh_key_priv = dh_gen_private();
       
  3073           $dh_key_pub = dh_gen_public($dh_key_priv);
       
  3074           $dh_key_priv = $_math->str($dh_key_priv);
       
  3075           $dh_key_pub = $_math->str($dh_key_pub);
       
  3076           $response['dh_public_key'] = $dh_key_pub;
       
  3077           // store the keys in the DB
       
  3078           $q = $db->sql_query('INSERT INTO ' . table_prefix . "diffiehellman( public_key, private_key ) VALUES ( '$dh_key_pub', '$dh_key_priv' );");
       
  3079           if ( !$q )
       
  3080             $db->die_json();
       
  3081         }
       
  3082         
       
  3083         return $response;
       
  3084         break;
       
  3085       case 'login_dh':
       
  3086         // User is requesting a login and has sent Diffie-Hellman data.
       
  3087         
       
  3088         //
       
  3089         // KEY RECONSTRUCTION
       
  3090         //
       
  3091         
       
  3092         $userinfo_crypt = $req['userinfo'];
       
  3093         $dh_public = $req['dh_public_key'];
       
  3094         $dh_hash = $req['dh_secret_hash'];
       
  3095         
       
  3096         // Check the key
       
  3097         if ( !preg_match('/^[0-9]+$/', $dh_public) || !preg_match('/^[0-9]+$/', $req['dh_client_key']) )
       
  3098         {
       
  3099           return array(
       
  3100             'mode' => 'error',
       
  3101             'error' => 'ERR_DH_KEY_NOT_NUMERIC'
       
  3102           );
       
  3103         }
       
  3104         
       
  3105         // Fetch private key
       
  3106         $q = $db->sql_query('SELECT private_key, key_id FROM ' . table_prefix . "diffiehellman WHERE public_key = '$dh_public';");
       
  3107         if ( !$q )
       
  3108           $db->die_json();
       
  3109         
       
  3110         if ( $db->numrows() < 1 )
       
  3111         {
       
  3112           return array(
       
  3113             'mode' => 'error',
       
  3114             'error' => 'ERR_DH_KEY_NOT_FOUND'
       
  3115           );
       
  3116         }
       
  3117         
       
  3118         list($dh_private, $dh_key_id) = $db->fetchrow_num();
       
  3119         $db->free_result();
       
  3120         
       
  3121         // We have the private key, now delete the key pair, we no longer need it
       
  3122         $q = $db->sql_query('DELETE FROM ' . table_prefix . "diffiehellman WHERE key_id = $dh_key_id;");
       
  3123         if ( !$q )
       
  3124           $db->die_json();
       
  3125         
       
  3126         // Generate the shared secret
       
  3127         $dh_secret = dh_gen_shared_secret($dh_private, $req['dh_client_key']);
       
  3128         $dh_secret = $_math->str($dh_secret);
       
  3129         
       
  3130         // Did we get all our math right?
       
  3131         $dh_secret_check = sha1($dh_secret);
       
  3132         if ( $dh_secret_check !== $dh_hash )
       
  3133         {
       
  3134           return array(
       
  3135             'mode' => 'error',
       
  3136             'error' => 'ERR_DH_HASH_NO_MATCH'
       
  3137           );
       
  3138         }
       
  3139         
       
  3140         // All good! Generate the AES key
       
  3141         $aes_key = substr(sha256($dh_secret), 0, ( AES_BITS / 4 ));
       
  3142       case 'login_aes':
       
  3143         if ( $req['mode'] == 'login_aes' )
       
  3144         {
       
  3145           // login_aes-specific code
       
  3146           $aes_key = $this->fetch_public_key($req['key_aes']);
       
  3147           if ( !$aes_key )
       
  3148           {
       
  3149             return array(
       
  3150               'mode' => 'error',
       
  3151               'error' => 'ERR_AES_LOOKUP_FAILED'
       
  3152             );
       
  3153           }
       
  3154           $userinfo_crypt = $req['userinfo'];
       
  3155         }
       
  3156         // shared between the two systems from here on out
       
  3157         
       
  3158         // decrypt user info
       
  3159         $aes_key = hexdecode($aes_key);
       
  3160         $aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
       
  3161         $userinfo_json = $aes->decrypt($userinfo_crypt, $aes_key, ENC_HEX);
       
  3162         if ( !$userinfo_json )
       
  3163         {
       
  3164           return array(
       
  3165             'mode' => 'error',
       
  3166             'error' => 'ERR_AES_DECRYPT_FAILED'
       
  3167           );
       
  3168         }
       
  3169         // de-JSON user info
       
  3170         try
       
  3171         {
       
  3172           $userinfo = enano_json_decode($userinfo_json);
       
  3173         }
       
  3174         catch ( Exception $e )
       
  3175         {
       
  3176           return array(
       
  3177             'mode' => 'error',
       
  3178             'error' => 'ERR_USERINFO_DECODE_FAILED'
       
  3179           );
       
  3180         }
       
  3181         
       
  3182         if ( !isset($userinfo['username']) || !isset($userinfo['password']) )
       
  3183         {
       
  3184           return array(
       
  3185             'mode' => 'error',
       
  3186             'error' => 'ERR_USERINFO_MISSING_VALUES'
       
  3187           );
       
  3188         }
       
  3189         
       
  3190         $username =& $userinfo['username'];
       
  3191         $password =& $userinfo['password'];
       
  3192         
       
  3193         // attempt the login
       
  3194         // function login_without_crypto($username, $password, $already_md5ed = false, $level = USER_LEVEL_MEMBER, $captcha_hash = false, $captcha_code = false)
       
  3195         $login_result = $this->login_without_crypto($username, $password, false, intval($req['level']), @$req['captcha_hash'], @$req['captcha_code']);
       
  3196         
       
  3197         if ( $login_result['success'] )
       
  3198         {
       
  3199           return array(
       
  3200               'mode' => 'login_success',
       
  3201               'key' => ( $this->sid_super ) ? $this->sid_super : false
       
  3202             );
       
  3203         }
       
  3204         else
       
  3205         {
       
  3206           return array(
       
  3207               'mode' => 'login_failure',
       
  3208               'error_code' => $login_result['error'],
       
  3209               // Use this to provide a way to respawn the login box
       
  3210               'respawn_info' => $this->process_login_request(array('mode' => 'getkey'))
       
  3211             );
       
  3212         }
       
  3213         
       
  3214         break;
       
  3215     }
       
  3216     
       
  3217   }
       
  3218   
  2956 }
  3219 }
  2957 
  3220 
  2958 /**
  3221 /**
  2959  * Class used to fetch permissions for a specific page. Used internally by SessionManager.
  3222  * Class used to fetch permissions for a specific page. Used internally by SessionManager.
  2960  * @package Enano
  3223  * @package Enano