includes/sessions.php
changeset 1 fe660c52c48f
child 13 fdd6b9dd42c3
equal deleted inserted replaced
0:902822492a68 1:fe660c52c48f
       
     1 <?php
       
     2 
       
     3 /*
       
     4  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
       
     5  * Version 1.0 (Banshee)
       
     6  * Copyright (C) 2006-2007 Dan Fuhry
       
     7  * sessions.php - everything related to security and user management
       
     8  *
       
     9  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
       
    10  * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
       
    11  *
       
    12  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
       
    13  * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
       
    14  */
       
    15  
       
    16 // Prepare a string for insertion into a MySQL database
       
    17 function filter($str) { return $db->escape($str); }
       
    18 
       
    19 /**
       
    20  * Anything and everything related to security and user management. This includes AES encryption, which is illegal in some countries.
       
    21  * Documenting the API was not easy - I hope you folks enjoy it.
       
    22  * @package Enano
       
    23  * @subpackage Session manager
       
    24  * @category security, user management, logins, etc.
       
    25  */
       
    26 
       
    27 class sessionManager {
       
    28   
       
    29   # Variables
       
    30   
       
    31   /**
       
    32    * Whether we're logged in or not
       
    33    * @var bool
       
    34    */
       
    35    
       
    36   var $user_logged_in = false;
       
    37   
       
    38   /**
       
    39    * Our current low-privilege session key
       
    40    * @var string
       
    41    */
       
    42   
       
    43   var $sid;
       
    44   
       
    45   /**
       
    46    * Username of currently logged-in user, or IP address if not logged in
       
    47    * @var string
       
    48    */
       
    49   
       
    50   var $username;
       
    51   
       
    52   /**
       
    53    * User ID of currently logged-in user, or -1 if not logged in
       
    54    * @var int
       
    55    */
       
    56   
       
    57   var $user_id;
       
    58   
       
    59   /**
       
    60    * Real name of currently logged-in user, or blank if not logged in
       
    61    * @var string
       
    62    */
       
    63   
       
    64   var $real_name;
       
    65   
       
    66   /**
       
    67    * E-mail address of currently logged-in user, or blank if not logged in
       
    68    * @var string
       
    69    */
       
    70   
       
    71   var $email;
       
    72   
       
    73   /**
       
    74    * User level of current user
       
    75    * USER_LEVEL_GUEST: guest
       
    76    * USER_LEVEL_MEMBER: regular user
       
    77    * USER_LEVEL_CHPREF: default - pseudo-level that allows changing password and e-mail address (requires re-authentication)
       
    78    * USER_LEVEL_MOD: moderator
       
    79    * USER_LEVEL_ADMIN: administrator
       
    80    * @var int
       
    81    */
       
    82   
       
    83   var $user_level;
       
    84   
       
    85   /**
       
    86    * High-privilege session key
       
    87    * @var string or false if not running on high-level authentication
       
    88    */
       
    89   
       
    90   var $sid_super;
       
    91   
       
    92   /**
       
    93    * The user's theme preference, defaults to $template->default_theme
       
    94    * @var string
       
    95    */
       
    96   
       
    97   var $theme;
       
    98   
       
    99   /**
       
   100    * The user's style preference, or style auto-detected based on theme if not logged in
       
   101    * @var string
       
   102    */
       
   103   
       
   104   var $style;
       
   105   
       
   106   /**
       
   107    * Signature of current user - appended to comments, etc.
       
   108    * @var string
       
   109    */
       
   110   
       
   111   var $signature;
       
   112   
       
   113   /**
       
   114    * UNIX timestamp of when we were registered, or 0 if not logged in
       
   115    * @var int
       
   116    */
       
   117   
       
   118   var $reg_time;
       
   119   
       
   120   /**
       
   121    * MD5 hash of the current user's password, if applicable
       
   122    * @var string OR bool false
       
   123    */
       
   124    
       
   125   var $password_hash;
       
   126   
       
   127   /**
       
   128    * The number of unread private messages this user has.
       
   129    * @var int
       
   130    */
       
   131   
       
   132   var $unread_pms = 0;
       
   133   
       
   134   /**
       
   135    * AES key used to encrypt passwords and session key info - irreversibly destroyed when disallow_password_grab() is called
       
   136    * @var string
       
   137    */
       
   138    
       
   139   var $private_key;
       
   140   
       
   141   /**
       
   142    * Regex that defines a valid username, minus the ^ and $, these are added later
       
   143    * @var string
       
   144    */
       
   145    
       
   146    var $valid_username = '([A-Za-z0-9 \!\@\(\)-]+)';
       
   147    
       
   148   /**
       
   149    * What we're allowed to do as far as permissions go. This changes based on the value of the "auth" URI param.
       
   150    * @var string
       
   151    */
       
   152    
       
   153   var $auth_level = -1;
       
   154   
       
   155   /**
       
   156    * State variable to track if a session timed out
       
   157    * @var bool
       
   158    */
       
   159   
       
   160   var $sw_timed_out = false;
       
   161   
       
   162   /**
       
   163    * Switch to track if we're started or not.
       
   164    * @access private
       
   165    * @var bool
       
   166    */
       
   167    
       
   168   var $started = false;
       
   169   
       
   170   /**
       
   171    * Switch to control compatibility mode (for older Enano websites being upgraded)
       
   172    * @access private
       
   173    * @var bool
       
   174    */
       
   175    
       
   176   var $compat = false;
       
   177   
       
   178   /**
       
   179    * Our list of permission types.
       
   180    * @access private
       
   181    * @var array
       
   182    */
       
   183    
       
   184   var $acl_types = Array();
       
   185   
       
   186   /**
       
   187    * The list of descriptions for the permission types
       
   188    * @var array
       
   189    */
       
   190    
       
   191   var $acl_descs = Array();
       
   192   
       
   193   /**
       
   194    * A list of dependencies for ACL types.
       
   195    * @var array
       
   196    */
       
   197    
       
   198   var $acl_deps = Array();
       
   199   
       
   200   /**
       
   201    * Our tell-all list of permissions.
       
   202    * @access private - or, preferably, protected
       
   203    * @var array
       
   204    */
       
   205    
       
   206   var $perms = Array();
       
   207   
       
   208   /**
       
   209    * A cache variable - saved after sitewide permissions are checked but before page-specific permissions.
       
   210    * @var array
       
   211    * @access private
       
   212    */
       
   213   
       
   214   var $acl_base_cache = Array();
       
   215   
       
   216   /**
       
   217    * Stores the scope information for ACL types.
       
   218    * @var array
       
   219    * @access private
       
   220    */
       
   221    
       
   222   var $acl_scope = Array();
       
   223   
       
   224   /**
       
   225    * Array to track which default permissions are being used
       
   226    * @var array
       
   227    * @access private
       
   228    */
       
   229    
       
   230   var $acl_defaults_used = Array();
       
   231   
       
   232   /**
       
   233    * Array to track group membership.
       
   234    * @var array
       
   235    */
       
   236    
       
   237   var $groups = Array();
       
   238   
       
   239   /**
       
   240    * Associative array to track group modship.
       
   241    * @var array
       
   242    */
       
   243    
       
   244   var $group_mod = Array();
       
   245   
       
   246   # Basic functions
       
   247    
       
   248   /**
       
   249    * Constructor.
       
   250    */
       
   251    
       
   252   function __construct()
       
   253   {
       
   254     global $db, $session, $paths, $template, $plugins; // Common objects
       
   255     include(ENANO_ROOT.'/config.php');
       
   256     unset($dbhost, $dbname, $dbuser, $dbpasswd);
       
   257     if(isset($crypto_key))
       
   258     {
       
   259       $this->private_key = $crypto_key;
       
   260       $this->private_key = hexdecode($this->private_key);
       
   261     }
       
   262     else
       
   263     {
       
   264       if(is_writable(ENANO_ROOT.'/config.php'))
       
   265       {
       
   266         // Generate and stash a private key
       
   267         // This should only happen during an automated silent gradual migration to the new encryption platform.
       
   268         $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
       
   269         $this->private_key = $aes->gen_readymade_key();
       
   270         
       
   271         $config = file_get_contents(ENANO_ROOT.'/config.php');
       
   272         if(!$config)
       
   273         {
       
   274           die('$session->__construct(): can\'t get the contents of config.php');
       
   275         }
       
   276         
       
   277         $config = str_replace("?>", "\$crypto_key = '{$this->private_key}';\n?>", $config);
       
   278         // And while we're at it...
       
   279         $config = str_replace('MIDGET_INSTALLED', 'ENANO_INSTALLED', $config);
       
   280         $fh = @fopen(ENANO_ROOT.'/config.php', 'w');
       
   281         if ( !$fh ) 
       
   282         {
       
   283           die('$session->__construct(): Couldn\'t open config file for writing to store the private key, I tried to avoid something like this...');
       
   284         }
       
   285         
       
   286         fwrite($fh, $config);
       
   287         fclose($fh);
       
   288       }
       
   289       else
       
   290       {
       
   291         die_semicritical('Crypto error', '<p>No private key was found in the config file, and we can\'t generate one because we don\'t have write access to the config file. Please CHMOD config.php to 666 or 777 and reload this page.</p>');
       
   292       }
       
   293     }
       
   294     // Check for compatibility mode
       
   295     if(defined('IN_ENANO_INSTALL'))
       
   296     {
       
   297       $q = $db->sql_query('SELECT old_encryption FROM '.table_prefix.'users LIMIT 1;');
       
   298       if(!$q)
       
   299       {
       
   300         $error = mysql_error();
       
   301         if(strstr($error, "Unknown column 'old_encryption'"))
       
   302           $this->compat = true;
       
   303         else
       
   304           $db->_die('This should never happen and is a bug - the only error that was supposed to happen here didn\'t happen. (sessions.php in constructor, during compat mode check)');
       
   305       }
       
   306       $db->free_result();
       
   307     }
       
   308   }
       
   309   
       
   310   /**
       
   311    * PHP 4 compatible constructor.
       
   312    */
       
   313    
       
   314   function sessionManager()
       
   315   {
       
   316     $this->__construct();
       
   317   }
       
   318   
       
   319   /**
       
   320    * Wrapper function to sanitize strings for MySQL and HTML
       
   321    * @param string $text The text to sanitize
       
   322    * @return string
       
   323    */
       
   324   
       
   325   function prepare_text($text)
       
   326   {
       
   327     global $db;
       
   328     return $db->escape(htmlspecialchars($text));
       
   329   }
       
   330   
       
   331   /**
       
   332    * Makes a SQL query and handles error checking
       
   333    * @param string $query The SQL query to make
       
   334    * @return resource
       
   335    */
       
   336   
       
   337   function sql($query)
       
   338   {
       
   339     global $db, $session, $paths, $template, $plugins; // Common objects
       
   340     $result = $db->sql_query($query);
       
   341     if(!$result)
       
   342     {
       
   343       $db->_die('The error seems to have occurred somewhere in the session management code.');
       
   344     }
       
   345     return $result;
       
   346   }
       
   347   
       
   348   # Session restoration and permissions
       
   349   
       
   350   /**
       
   351    * Initializes the basic state of things, including most user prefs, login data, cookie stuff
       
   352    */
       
   353   
       
   354   function start()
       
   355   {
       
   356     global $db, $session, $paths, $template, $plugins; // Common objects
       
   357     if($this->started) return;
       
   358     $this->started = true;
       
   359     $user = false;
       
   360     if(isset($_COOKIE['sid']))
       
   361     {
       
   362       if($this->compat)
       
   363       {
       
   364         $userdata = $this->compat_validate_session($_COOKIE['sid']);
       
   365       }
       
   366       else
       
   367       {
       
   368         $userdata = $this->validate_session($_COOKIE['sid']);
       
   369       }
       
   370       if(is_array($userdata))
       
   371       {
       
   372         $data = RenderMan::strToPageID($paths->get_pageid_from_url());
       
   373         
       
   374         if(!$this->compat && $userdata['account_active'] != 1 && $data[1] != 'Special' && $data[1] != 'Admin')
       
   375         {
       
   376           $this->logout();
       
   377           $a = getConfig('account_activation');
       
   378           switch($a)
       
   379           {
       
   380             case 'none':
       
   381             default:
       
   382               $solution = 'Your account was most likely deactivated by an administrator. Please contact the site administration for further assistance.';
       
   383               break;
       
   384             case 'user':
       
   385               $solution = 'Please check your e-mail; you should have been sent a message with instructions on how to activate your account. If you do not receive an e-mail from this site within 24 hours, please contact the site administration for further assistance.';
       
   386               break;
       
   387             case 'admin':
       
   388               $solution = 'This website has been configured so that all user accounts must be activated by the administrator before they can be used, so your account will most likely be activated the next time the one of the administrators visits the site.';
       
   389               break;
       
   390           }
       
   391           die_semicritical('Account error', '<p>It appears that your user account has not yet been activated. '.$solution.'</p>');
       
   392         }
       
   393         
       
   394         $this->sid = $_COOKIE['sid'];
       
   395         $this->user_logged_in = true;
       
   396         $this->user_id =       intval($userdata['user_id']);
       
   397         $this->username =      $userdata['username'];
       
   398         $this->password_hash = $userdata['password'];
       
   399         $this->user_level =    intval($userdata['user_level']);
       
   400         $this->real_name =     $userdata['real_name'];
       
   401         $this->email =         $userdata['email'];
       
   402         $this->unread_pms =    $userdata['num_pms'];
       
   403         if(!$this->compat)
       
   404         {
       
   405           $this->theme =         $userdata['theme'];
       
   406           $this->style =         $userdata['style'];
       
   407           $this->signature =     $userdata['signature'];
       
   408           $this->reg_time =      $userdata['reg_time'];
       
   409         }
       
   410         // Small security risk here - it allows someone who has already authenticated as an administrator to store the "super" key in
       
   411         // the cookie. Change this to USER_LEVEL_MEMBER to override that. The same 15-minute restriction applies to this "exploit".
       
   412         $this->auth_level =    $userdata['auth_level'];
       
   413         if(!isset($template->named_theme_list[$this->theme]))
       
   414         {
       
   415           if($this->compat || !is_object($template))
       
   416           {
       
   417             $this->theme = 'oxygen';
       
   418             $this->style = 'bleu';
       
   419           }
       
   420           else
       
   421           {
       
   422             $this->theme = $template->default_theme;
       
   423             $this->style = $template->default_style;
       
   424           }
       
   425         }
       
   426         $user = true;
       
   427         
       
   428         if(isset($_REQUEST['auth']) && !$this->sid_super)
       
   429         {
       
   430           // Now he thinks he's a moderator. Or maybe even an administrator. Let's find out if he's telling the truth.
       
   431           if($this->compat)
       
   432           {
       
   433             $key = $_REQUEST['auth'];
       
   434             $super = $this->compat_validate_session($key);
       
   435           }
       
   436           else
       
   437           {
       
   438             $key = strrev($_REQUEST['auth']);
       
   439             $super = $this->validate_session($key);
       
   440           }
       
   441           if(is_array($super))
       
   442           {
       
   443             $this->auth_level = intval($super['auth_level']);
       
   444             $this->sid_super = $_REQUEST['auth'];
       
   445           }
       
   446         }
       
   447       }
       
   448     }
       
   449     if(!$user)
       
   450     {
       
   451       //exit;
       
   452       $this->register_guest_session();
       
   453     }
       
   454     if(!$this->compat)
       
   455     {
       
   456       // init groups
       
   457       $q = $this->sql('SELECT g.group_name,g.group_id,m.is_mod FROM '.table_prefix.'groups AS g
       
   458           LEFT JOIN '.table_prefix.'group_members AS m
       
   459             ON g.group_id=m.group_id
       
   460           WHERE ( m.user_id='.$this->user_id.' 
       
   461             OR g.group_name=\'Everyone\')
       
   462             ' . ( enano_version() == '1.0RC1' ? '' : 'AND ( m.pending != 1 OR m.pending IS NULL )' ) . '
       
   463           ORDER BY group_id ASC;'); // Make sure "Everyone" comes first so the permissions can be overridden
       
   464       if($row = $db->fetchrow())
       
   465       {
       
   466         do {
       
   467           $this->groups[$row['group_id']] = $row['group_name'];
       
   468           $this->group_mod[$row['group_id']] = ( intval($row['is_mod']) == 1 );
       
   469         } while($row = $db->fetchrow());
       
   470       }
       
   471       else
       
   472       {
       
   473         die('No group info');
       
   474       }
       
   475     }
       
   476     $this->check_banlist();
       
   477     
       
   478     if ( isset ( $_GET['printable'] ) )
       
   479     {
       
   480       $this->theme = 'printable';
       
   481       $this->style = 'default';
       
   482     }
       
   483     
       
   484   }
       
   485   
       
   486   # Logins
       
   487   
       
   488   /**
       
   489    * Attempts to perform a login using crypto functions
       
   490    * @param string $username The username
       
   491    * @param string $aes_data The encrypted password, hex-encoded
       
   492    * @param string $aes_key The MD5 hash of the encryption key, hex-encoded
       
   493    * @param string $challenge The 256-bit MD5 challenge string - first 128 bits should be the hash, the last 128 should be the challenge salt
       
   494    * @param int $level The privilege level we're authenticating for, defaults to 0
       
   495    * @return string 'success' on success, or error string on failure
       
   496    */
       
   497    
       
   498   function login_with_crypto($username, $aes_data, $aes_key, $challenge, $level = USER_LEVEL_MEMBER)
       
   499   {
       
   500     global $db, $session, $paths, $template, $plugins; // Common objects
       
   501     
       
   502     $privcache = $this->private_key;
       
   503     
       
   504     // Instanciate the Rijndael encryption object
       
   505     $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
       
   506     
       
   507     // Fetch our decryption key
       
   508     
       
   509     $aes_key = $this->fetch_public_key($aes_key);
       
   510     if(!$aes_key)
       
   511       return 'Couldn\'t look up public key "'.$aes_key.'" for decryption';
       
   512     
       
   513     // Convert the key to a binary string
       
   514     $bin_key = hexdecode($aes_key);
       
   515     
       
   516     if(strlen($bin_key) != AES_BITS / 8)
       
   517       return 'The decryption key is the wrong length';
       
   518     
       
   519     // Decrypt our password
       
   520     $password = $aes->decrypt($aes_data, $bin_key, ENC_HEX);
       
   521     
       
   522     // Initialize our success switch
       
   523     $success = false;
       
   524     
       
   525     // Select the user data from the table, and decrypt that so we can verify the password
       
   526     $this->sql('SELECT password,old_encryption,user_id,user_level,theme,style,temp_password,temp_password_time FROM '.table_prefix.'users WHERE lcase(username)=\''.$this->prepare_text(strtolower($username)).'\';');
       
   527     if($db->numrows() < 1)
       
   528       return 'The username and/or password is incorrect.';
       
   529     $row = $db->fetchrow();
       
   530     
       
   531     // Check to see if we're logging in using a temporary password
       
   532     
       
   533     if((intval($row['temp_password_time']) + 3600*24) > time() )
       
   534     {
       
   535       $temp_pass = $aes->decrypt( $row['temp_password'], $this->private_key, ENC_HEX );
       
   536       if( $temp_pass == $password )
       
   537       {
       
   538         $url = makeUrlComplete('Special', 'PasswordReset/stage2/' . $row['user_id'] . '/' . $row['temp_password']);
       
   539         
       
   540         $code = $plugins->setHook('login_password_reset');
       
   541         foreach ( $code as $cmd )
       
   542         {
       
   543           eval($cmd);
       
   544         }
       
   545         
       
   546         redirect($url, 'Login sucessful', 'Please wait while you are transferred to the Password Reset form.');
       
   547         exit;
       
   548       }
       
   549     }
       
   550     
       
   551     if($row['old_encryption'] == 1)
       
   552     {
       
   553       // The user's password is stored using the obsolete and insecure MD5 algorithm, so we'll update the field with the new password
       
   554       if(md5($password) == $row['password'])
       
   555       {
       
   556         $pass_stashed = $aes->encrypt($password, $this->private_key, ENC_HEX);
       
   557         $this->sql('UPDATE '.table_prefix.'users SET password=\''.$pass_stashed.'\',old_encryption=0 WHERE user_id='.$row['user_id'].';');
       
   558         $success = true;
       
   559       }
       
   560     }
       
   561     else
       
   562     {
       
   563       // 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
       
   564       $real_pass = $aes->decrypt(hexdecode($row['password']), $this->private_key, ENC_BINARY);
       
   565       if($password == $real_pass)
       
   566       {
       
   567         // Yay! We passed AES authentication, now do an MD5 challenge check to make sure we weren't spoofed
       
   568         $chal = substr($challenge, 0, 32);
       
   569         $salt = substr($challenge, 32, 32);
       
   570         $correct_challenge = md5( $real_pass . $salt );
       
   571         if($chal == $correct_challenge)
       
   572           $success = true;
       
   573       }
       
   574     }
       
   575     if($success)
       
   576     {
       
   577       if($level > $row['user_level'])
       
   578         return 'You are not authorized for this level of access.';
       
   579       
       
   580       $sess = $this->register_session(intval($row['user_id']), $username, $password, $level);
       
   581       if($sess)
       
   582       {
       
   583         $this->username = $username;
       
   584         $this->user_id = intval($row['user_id']);
       
   585         $this->theme = $row['theme'];
       
   586         $this->style = $row['style'];
       
   587         
       
   588         if($level > USER_LEVEL_MEMBER)
       
   589           $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) . ')');
       
   590         else
       
   591           $this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary) VALUES(\'security\', \'auth_good\', '.time().', \''.date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\')');
       
   592         
       
   593         $code = $plugins->setHook('login_success');
       
   594         foreach ( $code as $cmd )
       
   595         {
       
   596           eval($cmd);
       
   597         }
       
   598         return 'success';
       
   599       }
       
   600       else
       
   601         return 'Your login credentials were correct, but an internal error occurred while registering the session key in the database.';
       
   602     }
       
   603     else
       
   604     {
       
   605       if($level > USER_LEVEL_MEMBER)
       
   606         $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) . ')');
       
   607       else
       
   608         $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']).'\')');
       
   609         
       
   610       return 'The username and/or password is incorrect.';
       
   611     }
       
   612   }
       
   613   
       
   614   /**
       
   615    * Attempts to login without using crypto stuff, mainly for use when the other side doesn't like Javascript
       
   616    * This method of authentication is inherently insecure, there's really nothing we can do about it except hope and pray that everyone moves to Firefox
       
   617    * Technically it still uses crypto, but it only decrypts the password already stored, which is (obviously) required for authentication
       
   618    * @param string $username The username
       
   619    * @param string $password The password -OR- the MD5 hash of the password if $already_md5ed is true
       
   620    * @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.
       
   621    * @param int $level The privilege level we're authenticating for, defaults to 0
       
   622    */
       
   623   
       
   624   function login_without_crypto($username, $password, $already_md5ed = false, $level = USER_LEVEL_MEMBER)
       
   625   {
       
   626     global $db, $session, $paths, $template, $plugins; // Common objects
       
   627     
       
   628     $pass_hashed = ( $already_md5ed ) ? $password : md5($password);
       
   629     
       
   630     // Perhaps we're upgrading Enano?
       
   631     if($this->compat)
       
   632     {
       
   633       return $this->login_compat($username, $pass_hashed, $level);
       
   634     }
       
   635     
       
   636     // Instanciate the Rijndael encryption object
       
   637     $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
       
   638     
       
   639     // Initialize our success switch
       
   640     $success = false;
       
   641     
       
   642     // Retrieve the real password from the database
       
   643     $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)).'\';');
       
   644     if($db->numrows() < 1)
       
   645       return 'The username and/or password is incorrect.';
       
   646     $row = $db->fetchrow();
       
   647     
       
   648     // Check to see if we're logging in using a temporary password
       
   649     
       
   650     if((intval($row['temp_password_time']) + 3600*24) > time() )
       
   651     {
       
   652       $temp_pass = $aes->decrypt( $row['temp_password'], $this->private_key, ENC_HEX );
       
   653       if( md5($temp_pass) == $pass_hashed )
       
   654       {
       
   655         $code = $plugins->setHook('login_password_reset');
       
   656         foreach ( $code as $cmd )
       
   657         {
       
   658           eval($cmd);
       
   659         }
       
   660         
       
   661         header('Location: ' . makeUrlComplete('Special', 'PasswordReset/stage2/' . $row['user_id'] . '/' . $row['temp_password']) );
       
   662         
       
   663         exit;
       
   664       }
       
   665     }
       
   666     
       
   667     if($row['old_encryption'] == 1)
       
   668     {
       
   669       // The user's password is stored using the obsolete and insecure MD5 algorithm - we'll update the field with the new password
       
   670       if($pass_hashed == $row['password'] && !$already_md5ed)
       
   671       {
       
   672         $pass_stashed = $aes->encrypt($password, $this->private_key, ENC_HEX);
       
   673         $this->sql('UPDATE '.table_prefix.'users SET password=\''.$pass_stashed.'\',old_encryption=0 WHERE user_id='.$row['user_id'].';');
       
   674         $success = true;
       
   675       }
       
   676       elseif($pass_hashed == $row['password'] && $already_md5ed)
       
   677       {
       
   678         // We don't have the real password so don't bother with encrypting it, just call it success and get out of here
       
   679         $success = true;
       
   680       }
       
   681     }
       
   682     else
       
   683     {
       
   684       // 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
       
   685       $real_pass = $aes->decrypt($row['password'], $this->private_key);
       
   686       if($pass_hashed == md5($real_pass))
       
   687       {
       
   688         $success = true;
       
   689       }
       
   690     }
       
   691     if($success)
       
   692     {
       
   693       if((int)$level > (int)$row['user_level'])
       
   694         return 'You are not authorized for this level of access.';
       
   695       $sess = $this->register_session(intval($row['user_id']), $username, $real_pass, $level);
       
   696       if($sess)
       
   697       {
       
   698         if($level > USER_LEVEL_MEMBER)
       
   699           $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) . ')');
       
   700         else
       
   701           $this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary) VALUES(\'security\', \'auth_good\', '.time().', \''.date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\')');
       
   702         
       
   703         $code = $plugins->setHook('login_success');
       
   704         foreach ( $code as $cmd )
       
   705         {
       
   706           eval($cmd);
       
   707         }
       
   708         return 'success';
       
   709       }
       
   710       else
       
   711         return 'Your login credentials were correct, but an internal error occured while registering the session key in the database.';
       
   712     }
       
   713     else
       
   714     {
       
   715       if($level > USER_LEVEL_MEMBER)
       
   716         $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) . ')');
       
   717       else
       
   718         $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']).'\')');
       
   719         
       
   720       return 'The username and/or password is incorrect.';
       
   721     }
       
   722   }
       
   723   
       
   724   /**
       
   725    * Attempts to log in using the old table structure and algorithm.
       
   726    * @param string $username
       
   727    * @param string $password This should be an MD5 hash
       
   728    * @return string 'success' if successful, or error message on failure
       
   729    */
       
   730   
       
   731   function login_compat($username, $password, $level = 0)
       
   732   {
       
   733     global $db, $session, $paths, $template, $plugins; // Common objects
       
   734     $pass_hashed =& $password;
       
   735     $this->sql('SELECT password,user_id,user_level FROM '.table_prefix.'users WHERE username=\''.$this->prepare_text($username).'\';');
       
   736     if($db->numrows() < 1)
       
   737       return 'The username and/or password is incorrect.';
       
   738     $row = $db->fetchrow();
       
   739     if($row['password'] == $password)
       
   740     {
       
   741       if((int)$level > (int)$row['user_level'])
       
   742         return 'You are not authorized for this level of access.';
       
   743       $sess = $this->register_session_compat(intval($row['user_id']), $username, $password, $level);
       
   744       if($sess)
       
   745         return 'success';
       
   746       else
       
   747         return 'Your login credentials were correct, but an internal error occured while registering the session key in the database.';
       
   748     }
       
   749     else
       
   750     {
       
   751       return 'The username and/or password is incorrect.';
       
   752     }
       
   753   }
       
   754   
       
   755   /**
       
   756    * Registers a session key in the database. This function *ASSUMES* that the username and password have already been validated!
       
   757    * Basically the session key is a base64-encoded cookie (encrypted with the site's private key) that says "u=[username];p=[sha1 of password]"
       
   758    * @param int $user_id
       
   759    * @param string $username
       
   760    * @param string $password
       
   761    * @param int $level The level of access to grant, defaults to USER_LEVEL_MEMBER
       
   762    * @return bool
       
   763    */
       
   764    
       
   765   function register_session($user_id, $username, $password, $level = USER_LEVEL_MEMBER)
       
   766   {
       
   767     $salt = md5(microtime() . mt_rand());
       
   768     $passha1 = sha1($password);
       
   769     $session_key = "u=$username;p=$passha1;s=$salt";
       
   770     $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
       
   771     $session_key = $aes->encrypt($session_key, $this->private_key, ENC_HEX);
       
   772     if($level > USER_LEVEL_MEMBER)
       
   773     {
       
   774       $hexkey = strrev($session_key);
       
   775       $this->sid_super = $hexkey;
       
   776       $_GET['auth'] = $hexkey;
       
   777     }
       
   778     else
       
   779     {
       
   780       setcookie( 'sid', $session_key, time()+315360000, scriptPath.'/' );
       
   781       $_COOKIE['sid'] = $session_key;
       
   782     }
       
   783     $keyhash = md5($session_key);
       
   784     $ip = ip2hex($_SERVER['REMOTE_ADDR']);
       
   785     if(!$ip)
       
   786       die('$session->register_session: Remote-Addr was spoofed');
       
   787     $time = time();
       
   788     if(!is_int($user_id))
       
   789       die('Somehow an SQL injection attempt crawled into our session registrar! (1)');
       
   790     if(!is_int($level))
       
   791       die('Somehow an SQL injection attempt crawled into our session registrar! (2)');
       
   792     
       
   793     $query = $this->sql('INSERT INTO '.table_prefix.'session_keys(session_key, salt, user_id, auth_level, source_ip, time) VALUES(\''.$keyhash.'\', \''.$salt.'\', '.$user_id.', '.$level.', \''.$ip.'\', '.$time.');');
       
   794     return true;
       
   795   }
       
   796   
       
   797   /**
       
   798    * Identical to register_session in nature, but uses the old login/table structure. DO NOT use this.
       
   799    * @see sessionManager::register_session()
       
   800    * @access private
       
   801    */
       
   802   
       
   803   function register_session_compat($user_id, $username, $password, $level = 0)
       
   804   {
       
   805     $salt = md5(microtime() . mt_rand());
       
   806     $thekey = md5($password . $salt);
       
   807     if($level > 0)
       
   808     {
       
   809       $this->sid_super = $thekey;
       
   810     }
       
   811     else
       
   812     {
       
   813       setcookie( 'sid', $thekey, time()+315360000, scriptPath.'/' );
       
   814       $_COOKIE['sid'] = $thekey;
       
   815     }
       
   816     $ip = ip2hex($_SERVER['REMOTE_ADDR']);
       
   817     if(!$ip)
       
   818       die('$session->register_session: Remote-Addr was spoofed');
       
   819     $time = time();
       
   820     if(!is_int($user_id))
       
   821       die('Somehow an SQL injection attempt crawled into our session registrar! (1)');
       
   822     if(!is_int($level))
       
   823       die('Somehow an SQL injection attempt crawled into our session registrar! (2)');
       
   824     $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.');');
       
   825     return true;
       
   826   }
       
   827   
       
   828   /**
       
   829    * Creates/restores a guest session
       
   830    * @todo implement real session management for guests
       
   831    */
       
   832    
       
   833   function register_guest_session()
       
   834   {
       
   835     global $db, $session, $paths, $template, $plugins; // Common objects
       
   836     $this->username = $_SERVER['REMOTE_ADDR'];
       
   837     $this->user_level = USER_LEVEL_GUEST;
       
   838     if($this->compat || defined('IN_ENANO_INSTALL'))
       
   839     {
       
   840       $this->theme = 'oxygen';
       
   841       $this->style = 'bleu';
       
   842     }
       
   843     else
       
   844     {
       
   845       $this->theme = ( isset($_GET['theme']) && isset($template->named_theme_list[$_GET['theme']])) ? $_GET['theme'] : $template->default_theme;
       
   846       $this->style = ( isset($_GET['style']) && file_exists(ENANO_ROOT.'/themes/'.$this->theme . '/css/'.$_GET['style'].'.css' )) ? $_GET['style'] : substr($template->named_theme_list[$this->theme]['default_style'], 0, strlen($template->named_theme_list[$this->theme]['default_style'])-4);
       
   847     }
       
   848     $this->user_id = 1;
       
   849   }
       
   850   
       
   851   /**
       
   852    * Validates a session key, and returns the userdata associated with the key or false
       
   853    * @param string $key The session key to validate
       
   854    * @return array Keys are 'user_id', 'username', 'email', 'real_name', 'user_level', 'theme', 'style', 'signature', 'reg_time', 'account_active', 'activation_key', and 'auth_level' or bool false if validation failed. The key 'auth_level' is the maximum authorization level that this key provides.
       
   855    */
       
   856    
       
   857   function validate_session($key)
       
   858   {
       
   859     global $db, $session, $paths, $template, $plugins; // Common objects
       
   860     $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE, true);
       
   861     $decrypted_key = $aes->decrypt($key, $this->private_key, ENC_HEX);
       
   862     
       
   863     if ( !$decrypted_key )
       
   864     {
       
   865       die_semicritical('AES encryption error', '<p>Something went wrong during the AES decryption process.</p><pre>'.print_r($decrypted_key, true).'</pre>');
       
   866     }
       
   867     
       
   868     $n = preg_match('/^u='.$this->valid_username.';p=([A-Fa-f0-9]+?);s=([A-Fa-f0-9]+?)$/', $decrypted_key, $keydata);
       
   869     if($n < 1)
       
   870     {
       
   871       // echo '(debug) $session->validate_session: Key does not match regex<br />Decrypted key: '.$decrypted_key;
       
   872       return false;
       
   873     }
       
   874     $keyhash = md5($key);
       
   875     $salt = $db->escape($keydata[3]);
       
   876     $query = $this->sql('SELECT u.user_id AS uid,u.username,u.password,u.email,u.real_name,u.user_level,u.theme,u.style,u.signature,u.reg_time,u.account_active,u.activation_key,k.source_ip,k.time,k.auth_level,COUNT(p.message_id) AS num_pms,x.* FROM '.table_prefix.'session_keys AS k
       
   877                            LEFT JOIN '.table_prefix.'users AS u
       
   878                              ON ( u.user_id=k.user_id )
       
   879                            LEFT JOIN '.table_prefix.'users_extra AS x
       
   880                              ON ( u.user_id=x.user_id OR x.user_id IS NULL )
       
   881                            LEFT JOIN '.table_prefix.'privmsgs AS p
       
   882                              ON ( p.message_to=u.username AND p.message_read=0 )
       
   883                            WHERE k.session_key=\''.$keyhash.'\'
       
   884                              AND k.salt=\''.$salt.'\'
       
   885                            GROUP BY u.user_id;');
       
   886     if($db->numrows() < 1)
       
   887     {
       
   888       // echo '(debug) $session->validate_session: Key was not found in database<br />';
       
   889       return false;
       
   890     }
       
   891     $row = $db->fetchrow();
       
   892     $row['user_id'] =& $row['uid'];
       
   893     $ip = ip2hex($_SERVER['REMOTE_ADDR']);
       
   894     if($row['auth_level'] > $row['user_level'])
       
   895     {
       
   896       // Failed authorization check
       
   897       // echo '(debug) $session->validate_session: access to this auth level denied<br />';
       
   898       return false;
       
   899     }
       
   900     if($ip != $row['source_ip'])
       
   901     {
       
   902       // Failed IP address check
       
   903       // echo '(debug) $session->validate_session: IP address mismatch<br />';
       
   904       return false;
       
   905     }
       
   906     
       
   907     // Do the password validation
       
   908     $real_pass = $aes->decrypt($row['password'], $this->private_key, ENC_HEX);
       
   909     
       
   910     //die('<pre>'.print_r($keydata, true).'</pre>');
       
   911     if(sha1($real_pass) != $keydata[2])
       
   912     {
       
   913       // Failed password check
       
   914       // echo '(debug) $session->validate_session: encrypted password is wrong<br />Real password: '.$real_pass.'<br />Real hash: '.sha1($real_pass).'<br />User hash: '.$keydata[2];
       
   915       return false;
       
   916     }
       
   917     
       
   918     $time_now = time();
       
   919     $time_key = $row['time'] + 900;
       
   920     if($time_now > $time_key && $row['auth_level'] > USER_LEVEL_MEMBER)
       
   921     {
       
   922       // Session timed out
       
   923       // echo '(debug) $session->validate_session: super session timed out<br />';
       
   924       $this->sw_timed_out = true;
       
   925       return false;
       
   926     }
       
   927     
       
   928     // If this is an elevated-access session key, update the time
       
   929     if( $row['auth_level'] > USER_LEVEL_MEMBER )
       
   930     {
       
   931       $this->sql('UPDATE '.table_prefix.'session_keys SET time='.time().' WHERE session_key=\''.$keyhash.'\';');
       
   932     }
       
   933     
       
   934     $row['password'] = md5($real_pass);
       
   935     return $row;
       
   936   }
       
   937   
       
   938   /**
       
   939    * Validates a session key, and returns the userdata associated with the key or false. Optimized for compatibility with the old MD5-based auth system.
       
   940    * @param string $key The session key to validate
       
   941    * @return array Keys are 'user_id', 'username', 'email', 'real_name', 'user_level', 'theme', 'style', 'signature', 'reg_time', 'account_active', 'activation_key', and 'auth_level' or bool false if validation failed. The key 'auth_level' is the maximum authorization level that this key provides.
       
   942    */
       
   943    
       
   944   function compat_validate_session($key)
       
   945   {
       
   946     global $db, $session, $paths, $template, $plugins; // Common objects
       
   947     $key = $db->escape($key);
       
   948     
       
   949     $query = $this->sql('SELECT u.user_id,u.username,u.password,u.email,u.real_name,u.user_level,k.source_ip,k.salt,k.time,k.auth_level FROM '.table_prefix.'session_keys AS k
       
   950                            LEFT JOIN '.table_prefix.'users AS u
       
   951                              ON u.user_id=k.user_id
       
   952                            WHERE k.session_key=\''.$key.'\';');
       
   953     if($db->numrows() < 1)
       
   954     {
       
   955       // echo '(debug) $session->validate_session: Key '.$key.' was not found in database<br />';
       
   956       return false;
       
   957     }
       
   958     $row = $db->fetchrow();
       
   959     $ip = ip2hex($_SERVER['REMOTE_ADDR']);
       
   960     if($row['auth_level'] > $row['user_level'])
       
   961     {
       
   962       // Failed authorization check
       
   963       // echo '(debug) $session->validate_session: user not authorized for this access level';
       
   964       return false;
       
   965     }
       
   966     if($ip != $row['source_ip'])
       
   967     {
       
   968       // Failed IP address check
       
   969       // echo '(debug) $session->validate_session: IP address mismatch; IP in table: '.$row['source_ip'].'; reported IP: '.$ip.'';
       
   970       return false;
       
   971     }
       
   972     
       
   973     // Do the password validation
       
   974     $real_key = md5($row['password'] . $row['salt']);
       
   975     
       
   976     //die('<pre>'.print_r($keydata, true).'</pre>');
       
   977     if($real_key != $key)
       
   978     {
       
   979       // Failed password check
       
   980       // echo '(debug) $session->validate_session: supplied password is wrong<br />Real key: '.$real_key.'<br />User key: '.$key;
       
   981       return false;
       
   982     }
       
   983     
       
   984     $time_now = time();
       
   985     $time_key = $row['time'] + 900;
       
   986     if($time_now > $time_key && $row['auth_level'] >= 1)
       
   987     {
       
   988       $this->sw_timed_out = true;
       
   989       // Session timed out
       
   990       // echo '(debug) $session->validate_session: super session timed out<br />';
       
   991       return false;
       
   992     }
       
   993     
       
   994     return $row;
       
   995   }
       
   996    
       
   997   /**
       
   998    * Demotes us to one less than the specified auth level. AKA destroys elevated authentication and/or logs out the user, depending on $level
       
   999    * @param int $level How low we should go - USER_LEVEL_MEMBER means demote to USER_LEVEL_GUEST, and anything more powerful than USER_LEVEL_MEMBER means demote to USER_LEVEL_MEMBER
       
  1000    * @return string 'success' if successful, or error on failure
       
  1001    */
       
  1002    
       
  1003   function logout($level = USER_LEVEL_MEMBER)
       
  1004   {
       
  1005     global $db, $session, $paths, $template, $plugins; // Common objects
       
  1006     $ou = $this->username;
       
  1007     $oid = $this->user_id;
       
  1008     if($level > USER_LEVEL_CHPREF)
       
  1009     {
       
  1010       $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
       
  1011       if(!$this->user_logged_in || $this->auth_level < USER_LEVEL_MOD) return 'success';
       
  1012       // Destroy elevated privileges
       
  1013       $keyhash = md5(strrev($this->sid_super));
       
  1014       $this->sql('DELETE FROM '.table_prefix.'session_keys WHERE session_key=\''.$keyhash.'\' AND user_id=\'' . $this->user_id . '\';');
       
  1015       $this->sid_super = false;
       
  1016       $this->auth_level = USER_LEVEL_MEMBER;
       
  1017     }
       
  1018     else
       
  1019     {
       
  1020       if($this->user_logged_in)
       
  1021       {
       
  1022         // Completely destroy our session
       
  1023         if($this->auth_level > USER_LEVEL_CHPREF)
       
  1024         {
       
  1025           $this->logout(USER_LEVEL_ADMIN);
       
  1026         }
       
  1027         $this->sql('DELETE FROM '.table_prefix.'session_keys WHERE session_key=\''.md5($this->sid).'\';');
       
  1028         setcookie( 'sid', '', time()-(3600*24), scriptPath.'/' );
       
  1029       }
       
  1030     }
       
  1031     $code = $plugins->setHook('logout_success'); // , Array('level'=>$level,'old_username'=>$ou,'old_user_id'=>$oid));
       
  1032     foreach ( $code as $cmd )
       
  1033     {
       
  1034       eval($cmd);
       
  1035     }
       
  1036     return 'success';
       
  1037   }
       
  1038   
       
  1039   # Miscellaneous stuff
       
  1040   
       
  1041   /**
       
  1042    * Appends the high-privilege session key to the URL if we are authorized to do high-privilege stuff
       
  1043    * @param string $url The URL to add session data to
       
  1044    * @return string
       
  1045    */
       
  1046   
       
  1047   function append_sid($url)
       
  1048   {
       
  1049     $sep = ( strstr($url, '?') ) ? '&' : '?';
       
  1050     if ( $this->sid_super )
       
  1051     {
       
  1052       $url = $url . $sep . 'auth=' . urlencode($this->sid_super);
       
  1053       // echo($this->sid_super.'<br/>');
       
  1054     }
       
  1055     return $url;
       
  1056   }
       
  1057   
       
  1058   /**
       
  1059    * Grabs the user's password MD5
       
  1060    * @return string, or bool false if access denied
       
  1061    */
       
  1062    
       
  1063   function grab_password_hash()
       
  1064   {
       
  1065     if(!$this->password_hash) return false;
       
  1066     return $this->password_hash;
       
  1067   }
       
  1068   
       
  1069   /**
       
  1070    * Destroys the user's password MD5 in memory
       
  1071    */
       
  1072   
       
  1073   function disallow_password_grab()
       
  1074   {
       
  1075     $this->password_hash = false;
       
  1076     return false;
       
  1077   }
       
  1078   
       
  1079   /**
       
  1080    * Generates an AES key and stashes it in the database
       
  1081    * @return string Hex-encoded AES key
       
  1082    */
       
  1083    
       
  1084   function rijndael_genkey()
       
  1085   {
       
  1086     $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
       
  1087     $key = $aes->gen_readymade_key();
       
  1088     $keys = getConfig('login_key_cache');
       
  1089     if(is_string($keys))
       
  1090       $keys .= $key;
       
  1091     else
       
  1092       $keys = $key;
       
  1093     setConfig('login_key_cache', $keys);
       
  1094     return $key;
       
  1095   }
       
  1096   
       
  1097   /**
       
  1098    * Generate a totally random 128-bit value for MD5 challenges
       
  1099    * @return string
       
  1100    */
       
  1101    
       
  1102   function dss_rand()
       
  1103   {
       
  1104     $aes = new AESCrypt();
       
  1105     $random = $aes->randkey(128);
       
  1106     unset($aes);
       
  1107     return md5(microtime() . $random);
       
  1108   }
       
  1109   
       
  1110   /**
       
  1111    * Fetch a cached login public key using the MD5sum as an identifier. Each key can only be fetched once before it is destroyed.
       
  1112    * @param string $md5 The MD5 sum of the key
       
  1113    * @return string, or bool false on failure
       
  1114    */
       
  1115    
       
  1116   function fetch_public_key($md5)
       
  1117   {
       
  1118     $keys = getConfig('login_key_cache');
       
  1119     $keys = enano_str_split($keys, AES_BITS / 4);
       
  1120     
       
  1121     foreach($keys as $i => $k)
       
  1122     {
       
  1123       if(md5($k) == $md5)
       
  1124       {
       
  1125         unset($keys[$i]);
       
  1126         if(count($keys) > 0)
       
  1127         {
       
  1128           if ( strlen(getConfig('login_key_cache') ) > 64000 )
       
  1129           {
       
  1130             // This should only need to be done once every month or so for an average-size site
       
  1131             setConfig('login_key_cache', '');
       
  1132           }
       
  1133           else
       
  1134           {
       
  1135             $keys = implode('', array_values($keys));
       
  1136             setConfig('login_key_cache', $keys);
       
  1137           }
       
  1138         }
       
  1139         else
       
  1140         {
       
  1141           setConfig('login_key_cache', '');
       
  1142         }
       
  1143         return $k;
       
  1144       }
       
  1145     }
       
  1146     // Couldn't find the key...
       
  1147     return false;
       
  1148   }
       
  1149   
       
  1150   /**
       
  1151    * Adds a user to a group.
       
  1152    * @param int User ID
       
  1153    * @param int Group ID
       
  1154    * @param bool Group moderator - defaults to false
       
  1155    * @return bool True on success, false on failure
       
  1156    */
       
  1157   
       
  1158   function add_user_to_group($user_id, $group_id, $is_mod = false)
       
  1159   {
       
  1160     global $db, $session, $paths, $template, $plugins; // Common objects
       
  1161     
       
  1162     // Validation
       
  1163     if ( !is_int($user_id) || !is_int($group_id) || !is_bool($is_mod) )
       
  1164       return false;
       
  1165     if ( $user_id < 1 || $group_id < 1 )
       
  1166       return false;
       
  1167     
       
  1168     $mod_switch = ( $is_mod ) ? '1' : '0';
       
  1169     $q = $this->sql('SELECT member_id,is_mod FROM '.table_prefix.'group_members WHERE user_id=' . $user_id . ' AND group_id=' . $group_id . ';');
       
  1170     if ( !$q )
       
  1171       $db->_die();
       
  1172     if ( $db->numrows() < 1 )
       
  1173     {
       
  1174       // User is not in group
       
  1175       $this->sql('INSERT INTO '.table_prefix.'group_members(user_id,group_id,is_mod) VALUES(' . $user_id . ', ' . $group_id . ', ' . $mod_switch . ');');
       
  1176       return true;
       
  1177     }
       
  1178     else
       
  1179     {
       
  1180       $row = $db->fetchrow();
       
  1181       // Update modship status
       
  1182       if ( strval($row['is_mod']) == $mod_switch )
       
  1183       {
       
  1184         // Modship unchanged
       
  1185         return true;
       
  1186       }
       
  1187       else
       
  1188       {
       
  1189         // Modship changed
       
  1190         $this->sql('UPDATE '.table_prefix.'group_members SET is_mod=' . $mod_switch . ' WHERE member_id=' . $row['member_id'] . ';');
       
  1191         return true;
       
  1192       }
       
  1193     }
       
  1194     return false;
       
  1195   }
       
  1196   
       
  1197   /**
       
  1198    * Removes a user from a group.
       
  1199    * @param int User ID
       
  1200    * @param int Group ID
       
  1201    * @return bool True on success, false on failure
       
  1202    * @todo put a little more error checking in...
       
  1203    */
       
  1204   
       
  1205   function remove_user_from_group($user_id, $group_id)
       
  1206   {
       
  1207     if ( !is_int($user_id) || !is_int($group_id) )
       
  1208       return false;
       
  1209     $this->sql('DELETE FROM '.table_prefix."group_members WHERE user_id=$user_id AND group_id=$group_id;");
       
  1210     return true;
       
  1211   }
       
  1212   
       
  1213   /**
       
  1214    * Checks the banlist to ensure that we're an allowed user. Doesn't return anything because it dies if the user is banned.
       
  1215    */
       
  1216    
       
  1217   function check_banlist()
       
  1218   {
       
  1219     global $db, $session, $paths, $template, $plugins; // Common objects
       
  1220     if($this->compat)
       
  1221       $q = $this->sql('SELECT ban_id,ban_type,ban_value,is_regex FROM '.table_prefix.'banlist ORDER BY ban_type;');
       
  1222     else
       
  1223       $q = $this->sql('SELECT ban_id,ban_type,ban_value,is_regex,reason FROM '.table_prefix.'banlist ORDER BY ban_type;');
       
  1224     if(!$q) $db->_die('The banlist data could not be selected.');
       
  1225     $banned = false;
       
  1226     while($row = $db->fetchrow())
       
  1227     {
       
  1228       if($this->compat)
       
  1229         $row['reason'] = 'None available - session manager is in compatibility mode';
       
  1230       switch($row['ban_type'])
       
  1231       {
       
  1232       case BAN_IP:
       
  1233         if(intval($row['is_regex'])==1) {
       
  1234           if(preg_match('#'.$row['ban_value'].'#i', $_SERVER['REMOTE_ADDR']))
       
  1235           {
       
  1236             $banned = true;
       
  1237             $reason = $row['reason'];
       
  1238           }
       
  1239         }
       
  1240         else {
       
  1241           if($row['ban_value']==$_SERVER['REMOTE_ADDR']) { $banned = true; $reason = $row['reason']; }
       
  1242         }
       
  1243         break;
       
  1244       case BAN_USER:
       
  1245         if(intval($row['is_regex'])==1) {
       
  1246           if(preg_match('#'.$row['ban_value'].'#i', $this->username))
       
  1247           {
       
  1248             $banned = true;
       
  1249             $reason = $row['reason'];
       
  1250           }
       
  1251         }
       
  1252         else {
       
  1253           if($row['ban_value']==$this->username) { $banned = true; $reason = $row['reason']; }
       
  1254         }
       
  1255         break;
       
  1256       case BAN_EMAIL:
       
  1257         if(intval($row['is_regex'])==1) {
       
  1258           if(preg_match('#'.$row['ban_value'].'#i', $this->email))
       
  1259           {
       
  1260             $banned = true;
       
  1261             $reason = $row['reason'];
       
  1262           }
       
  1263         }
       
  1264         else {
       
  1265           if($row['ban_value']==$this->email) { $banned = true; $reason = $row['reason']; }
       
  1266         }
       
  1267         break;
       
  1268       default:
       
  1269         die('Ban error: rule "'.$row['ban_value'].'" has an invalid type ('.$row['ban_type'].')');
       
  1270       }
       
  1271     }
       
  1272     if($banned && $paths->get_pageid_from_url() != $paths->nslist['Special'].'CSS')
       
  1273     {
       
  1274       // This guy is banned - kill the session, kill the database connection, bail out, and be pretty about it
       
  1275       die_semicritical('Ban notice', '<div class="error-box">You have been banned from this website. Please contact the site administrator for more information.<br /><br />Reason:<br />'.$reason.'</div>');
       
  1276       exit;
       
  1277     }
       
  1278   }
       
  1279   
       
  1280   # Registration
       
  1281   
       
  1282   /**
       
  1283    * Registers a user. This does not perform any type of login.
       
  1284    * @param string $username
       
  1285    * @param string $password This should be unencrypted.
       
  1286    * @param string $email
       
  1287    * @param string $real_name Optional, defaults to ''.
       
  1288    */
       
  1289    
       
  1290   function create_user($username, $password, $email, $real_name = '') {
       
  1291     global $db, $session, $paths, $template, $plugins; // Common objects
       
  1292     
       
  1293     // Initialize AES
       
  1294     $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
       
  1295     
       
  1296     if(!preg_match('#^'.$this->valid_username.'$#', $username)) return 'The username you chose contains invalid characters.';
       
  1297     $username = $this->prepare_text($username);
       
  1298     $email = $this->prepare_text($email);
       
  1299     $real_name = $this->prepare_text($real_name);
       
  1300     $password = $aes->encrypt($password, $this->private_key, ENC_HEX);
       
  1301     
       
  1302     $nameclause = ( $real_name != '' ) ? ' OR real_name=\''.$real_name.'\'' : '';
       
  1303     $q = $this->sql('SELECT * FROM '.table_prefix.'users WHERE lcase(username)=\''.strtolower($username).'\' OR email=\''.$email.'\''.$nameclause.';');
       
  1304     if($db->numrows() > 0) {
       
  1305       $r = 'The ';
       
  1306       $i=0;
       
  1307       $row = $db->fetchrow();
       
  1308       // Wow! An error checker that actually speaks English with the properest grammar! :-P
       
  1309       if($row['username'] == $username) { $r .= 'username'; $i++; }
       
  1310       if($row['email'] == $email) { if($i) $r.=', '; $r .= 'e-mail address'; $i++; }
       
  1311       if($row['real_name'] == $real_name && $real_name != '') { if($i) $r.=', and '; $r .= 'real name'; $i++; }
       
  1312       $r .= ' that you entered ';
       
  1313       $r .= ( $i == 1 ) ? 'is' : 'are';
       
  1314       $r .= ' already in use by another user.';
       
  1315       return $r;
       
  1316     }
       
  1317     
       
  1318     // Require the account to be activated?
       
  1319     switch(getConfig('account_activation'))
       
  1320     {
       
  1321       case 'none':
       
  1322       default:
       
  1323         $active = '1';
       
  1324         break;
       
  1325       case 'user':
       
  1326         $active = '0';
       
  1327         break;
       
  1328       case 'admin':
       
  1329         $active = '0';
       
  1330         break;
       
  1331     }
       
  1332     
       
  1333     // Generate a totally random activation key
       
  1334     $actkey = sha1 ( microtime() . mt_rand() );
       
  1335 
       
  1336     // We good, create the user    
       
  1337     $this->sql('INSERT INTO '.table_prefix.'users ( username, password, email, real_name, theme, style, reg_time, account_active, activation_key, user_level ) VALUES ( \''.$username.'\', \''.$password.'\', \''.$email.'\', \''.$real_name.'\', \''.$template->default_theme.'\', \''.$template->default_style.'\', '.time().', '.$active.', \''.$actkey.'\', '.USER_LEVEL_CHPREF.' )');
       
  1338     
       
  1339     // Require the account to be activated?
       
  1340     switch(getConfig('account_activation'))
       
  1341     {
       
  1342       case 'none':
       
  1343       default:
       
  1344         break;
       
  1345       case 'user':
       
  1346         $a = $this->send_activation_mail($username);
       
  1347         if(!$a)
       
  1348         {
       
  1349           $this->admin_activation_request($username);
       
  1350           return 'The activation e-mail could not be sent due to an internal error. This could possibly be due to an incorrect SMTP configuration. A request has been sent to the administrator to activate your account for you. ' . $a;
       
  1351         }
       
  1352         break;
       
  1353       case 'admin':
       
  1354         $this->admin_activation_request($username);
       
  1355         break;
       
  1356     }
       
  1357     
       
  1358     // Leave some data behind for the hook
       
  1359     $code = $plugins->setHook('user_registered'); // , Array('username'=>$username));
       
  1360     foreach ( $code as $cmd )
       
  1361     {
       
  1362       eval($cmd);
       
  1363     }
       
  1364     
       
  1365     // $this->register_session($username, $password);
       
  1366     return 'success';
       
  1367   }
       
  1368   
       
  1369   /**
       
  1370    * Attempts to send an e-mail to the specified user with activation instructions.
       
  1371    * @param string $u The usernamd of the user requesting activation
       
  1372    * @return bool true on success, false on failure
       
  1373    */
       
  1374    
       
  1375   function send_activation_mail($u, $actkey = false)
       
  1376   {
       
  1377     global $db, $session, $paths, $template, $plugins; // Common objects
       
  1378     $q = $this->sql('SELECT username,email FROM '.table_prefix.'users WHERE user_id=1 OR user_level=' . USER_LEVEL_ADMIN . ' ORDER BY user_id ASC;');
       
  1379     $un = $db->fetchrow();
       
  1380     $admin_user = $un['username'];
       
  1381     $q = $this->sql('SELECT username,activation_key,account_active,email FROM '.table_prefix.'users WHERE username=\''.$db->escape($u).'\';');
       
  1382     $r = $db->fetchrow();
       
  1383     if ( empty($r['email']) )
       
  1384       $db->_die('BUG: $session->send_activation_mail(): no e-mail address in row');
       
  1385     $message = 'Dear '.$u.',
       
  1386 Thank you for registering on '.getConfig('site_name').'. Your account creation is almost complete. To complete the registration process, please click the following link or paste it into your web browser:
       
  1387     
       
  1388 ';
       
  1389     if(isset($_SERVER['HTTPS'])) $prot = 'https';
       
  1390     else $prot = 'http';                                                                           
       
  1391     if($_SERVER['SERVER_PORT'] == '80') $p = '';
       
  1392     else $p = ':'.$_SERVER['SERVER_PORT'];
       
  1393     $sidbak = false;
       
  1394     if($this->sid_super)
       
  1395       $sidbak = $this->sid_super;
       
  1396     $this->sid_super = false;
       
  1397     $aklink = makeUrlNS('Special', 'ActivateAccount/'.str_replace(' ', '_', $u).'/'. ( ( is_string($actkey) ) ? $actkey : $r['activation_key'] ) );
       
  1398     if($sidbak)
       
  1399       $this->sid_super = $sidbak;
       
  1400     unset($sidbak);
       
  1401     $message .= "$prot://".$_SERVER['HTTP_HOST'].$p.$aklink;
       
  1402       $message .= "\n\nSincerely yours, \n$admin_user and the ".$_SERVER['HTTP_HOST']." administration team";
       
  1403     error_reporting(E_ALL);
       
  1404     dc_dump($r, 'session: about to send activation e-mail to '.$r['email']);
       
  1405     if(getConfig('smtp_enabled') == '1')
       
  1406     {
       
  1407       $result = smtp_send_email($r['email'], getConfig('site_name').' website account activation', preg_replace("#(?<!\r)\n#s", "\n", $message), getConfig('contact_email'));
       
  1408       if($result == 'success') $result = true;
       
  1409       else { echo $result; $result = false; }
       
  1410     } else {
       
  1411       $result = mail($r['email'], getConfig('site_name').' website account activation', preg_replace("#(?<!\r)\n#s", "\n", $message), 'From: '.getConfig('contact_email'));
       
  1412     }
       
  1413     return $result;
       
  1414   }
       
  1415   
       
  1416   /**
       
  1417    * Sends an e-mail to a user so they can reset their password.
       
  1418    * @param int $user The user ID, or username if it's a string
       
  1419    * @return bool true on success, false on failure
       
  1420    */
       
  1421    
       
  1422   function mail_password_reset($user)
       
  1423   {
       
  1424     global $db, $session, $paths, $template, $plugins; // Common objects
       
  1425     if(is_int($user))
       
  1426     {
       
  1427       $q = $this->sql('SELECT user_id,username,email FROM '.table_prefix.'users WHERE user_id='.$user.';'); // This is SAFE! This is only called if $user is an integer
       
  1428     }
       
  1429     elseif(is_string($user))
       
  1430     {
       
  1431       $q = $this->sql('SELECT user_id,username,email FROM '.table_prefix.'users WHERE username=\''.$db->escape($user).'\';');
       
  1432     }
       
  1433     else
       
  1434     {
       
  1435       return false;
       
  1436     }
       
  1437     
       
  1438     $row = $db->fetchrow();
       
  1439     $temp_pass = $this->random_pass();
       
  1440     
       
  1441     $this->register_temp_password($row['user_id'], $temp_pass);
       
  1442     
       
  1443     $site_name = getConfig('site_name');
       
  1444     
       
  1445     $message = "Dear {$row['username']},
       
  1446     
       
  1447 Someone (hopefully you) on the {$site_name} website requested that a new password be created.
       
  1448 
       
  1449 The request was sent from the IP address {$_SERVER['REMOTE_ADDR']}.
       
  1450 
       
  1451 If you did not request the new password, then you do not need to do anything; the password will be invalidated after 24 hours.
       
  1452 
       
  1453 If you did request this password, then please log in using the password shown below:
       
  1454 
       
  1455   Password: {$temp_pass}
       
  1456   
       
  1457 After you log in using this password, you will be able to reset your real password. You can only log in using this temporary password once.
       
  1458 
       
  1459 Sincerely yours,
       
  1460 The {$site_name} administration team
       
  1461 ";
       
  1462     
       
  1463     if(getConfig('smtp_enabled') == '1')
       
  1464     {
       
  1465       $result = smtp_send_email($row['email'], getConfig('site_name').' password reset', preg_replace("#(?<!\r)\n#s", "\n", $message), getConfig('contact_email'));
       
  1466       if($result == 'success')
       
  1467       {
       
  1468         $result = true;
       
  1469       }
       
  1470       else
       
  1471       {
       
  1472         echo '<p>'.$result.'</p>';
       
  1473         $result = false;
       
  1474       }
       
  1475     } else {
       
  1476       $result = mail($row['email'], getConfig('site_name').' password reset', preg_replace("#(?<!\r)\n#s", "\n", $message), 'From: '.getConfig('contact_email'));
       
  1477     }
       
  1478     return $result;
       
  1479   }
       
  1480   
       
  1481   /**
       
  1482    * Sets the temporary password for the specified user to whatever is specified.
       
  1483    * @param int $user_id
       
  1484    * @param string $password
       
  1485    * @return bool
       
  1486    */
       
  1487    
       
  1488   function register_temp_password($user_id, $password)
       
  1489   {
       
  1490     $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
       
  1491     $temp_pass = $aes->encrypt($password, $this->private_key, ENC_HEX);
       
  1492     $this->sql('UPDATE '.table_prefix.'users SET temp_password=\'' . $temp_pass . '\',temp_password_time='.time().' WHERE user_id='.intval($user_id).';');
       
  1493   }
       
  1494   
       
  1495   /**
       
  1496    * Sends a request to the admin panel to have the username $u activated.
       
  1497    * @param string $u The username of the user requesting activation
       
  1498    */
       
  1499   
       
  1500   function admin_activation_request($u)
       
  1501   {
       
  1502     global $db;
       
  1503     $this->sql('INSERT INTO '.table_prefix.'logs(log_type, action, time_id, date_string, author, edit_summary) VALUES(\'admin\', \'activ_req\', '.time().', \''.date('d M Y h:i a').'\', \''.$this->username.'\', \''.$db->escape($u).'\');');
       
  1504   }
       
  1505   
       
  1506   /**
       
  1507    * Activates a user account. If the action fails, a report is sent to the admin.
       
  1508    * @param string $user The username of the user requesting activation
       
  1509    * @param string $key The activation key
       
  1510    */
       
  1511   
       
  1512   function activate_account($user, $key)
       
  1513   {
       
  1514     global $db, $session, $paths, $template, $plugins; // Common objects
       
  1515     $this->sql('UPDATE '.table_prefix.'users SET account_active=1 WHERE username=\''.$db->escape($user).'\' AND activation_key=\''.$db->escape($key).'\';');
       
  1516     $r = mysql_affected_rows();
       
  1517     if ( $r > 0 )
       
  1518     {
       
  1519       $e = $this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary) VALUES(\'security\', \'activ_good\', '.time().', \''.date('d M Y h:i a').'\', \''.$db->escape($user).'\', \''.$_SERVER['REMOTE_ADDR'].'\')');
       
  1520     }
       
  1521     else
       
  1522     {
       
  1523       $e = $this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary) VALUES(\'security\', \'activ_bad\', '.time().', \''.date('d M Y h:i a').'\', \''.$db->escape($user).'\', \''.$_SERVER['REMOTE_ADDR'].'\')');
       
  1524     }
       
  1525     return $r;
       
  1526   }
       
  1527   
       
  1528   /**
       
  1529    * For a given user level identifier (USER_LEVEL_*), returns a string describing that user level.
       
  1530    * @param int User level
       
  1531    * @return string
       
  1532    */
       
  1533   
       
  1534   function userlevel_to_string($user_level)
       
  1535   {
       
  1536     switch ( $user_level )
       
  1537     {
       
  1538       case USER_LEVEL_GUEST:
       
  1539         return 'Low - guest privileges';
       
  1540       case USER_LEVEL_MEMBER:
       
  1541         return 'Standard - normal member level';
       
  1542       case USER_LEVEL_CHPREF:
       
  1543         return 'Medium - user can change his/her own e-mail address and password';
       
  1544       case USER_LEVEL_MOD:
       
  1545         return 'High - moderator privileges';
       
  1546       case USER_LEVEL_ADMIN:
       
  1547         return 'Highest - administrative privileges';
       
  1548       default:
       
  1549         return "Unknown ($user_level)";
       
  1550     }
       
  1551   }
       
  1552   
       
  1553   /**
       
  1554    * Updates a user's information in the database. Note that any of the values except $user_id can be false if you want to preserve the old values.
       
  1555    * @param int $user_id The user ID of the user to update - this cannot be changed
       
  1556    * @param string $username The new username
       
  1557    * @param string $old_pass The current password - only required if sessionManager::$user_level < USER_LEVEL_ADMIN. This should usually be an UNENCRYPTED string. This can also be an array - if it is, key 0 is treated as data AES-encrypted with key 1
       
  1558    * @param string $password The new password
       
  1559    * @param string $email The new e-mail address
       
  1560    * @param string $realname The new real name
       
  1561    * @param string $signature The updated forum/comment signature
       
  1562    * @param int $user_level The updated user level
       
  1563    * @return string 'success' if successful, or array of error strings on failure
       
  1564    */
       
  1565    
       
  1566   function update_user($user_id, $username = false, $old_pass = false, $password = false, $email = false, $realname = false, $signature = false, $user_level = false)
       
  1567   {
       
  1568     global $db, $session, $paths, $template, $plugins; // Common objects
       
  1569     
       
  1570     // Create some arrays
       
  1571     
       
  1572     $errors = Array(); // Used to hold error strings
       
  1573     $strs = Array();   // Sub-query statements
       
  1574     
       
  1575     // Scan the user ID for problems
       
  1576     if(intval($user_id) < 1) $errors[] = 'SQL injection attempt';
       
  1577     
       
  1578     // Instanciate the AES encryption class
       
  1579     $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
       
  1580     
       
  1581     // If all of our input vars are false, then we've effectively done our job so get out of here
       
  1582     if($username === false && $password === false && $email === false && $realname === false && $signature === false && $user_level === false)
       
  1583     {
       
  1584    // echo 'debug: $session->update_user(): success (no changes requested)';
       
  1585       return 'success';
       
  1586     }
       
  1587     
       
  1588     // Initialize our authentication check
       
  1589     $authed = false;
       
  1590     
       
  1591     // Verify the inputted password
       
  1592     if(is_string($old_pass))
       
  1593     {
       
  1594       $q = $this->sql('SELECT password FROM '.table_prefix.'users WHERE user_id='.$user_id.';');
       
  1595       if($db->numrows() < 1)
       
  1596       {
       
  1597         $errors[] = 'The password data could not be selected for verification.';
       
  1598       }
       
  1599       else
       
  1600       {
       
  1601         $row = $db->fetchrow();
       
  1602         $real = $aes->decrypt($row['password'], $this->private_key, ENC_HEX);
       
  1603         if($real == $old_pass)
       
  1604           $authed = true;
       
  1605       }
       
  1606     }
       
  1607     
       
  1608     elseif(is_array($old_pass))
       
  1609     {
       
  1610       $old_pass = $aes->decrypt($old_pass[0], $old_pass[1]);
       
  1611       $q = $this->sql('SELECT password FROM '.table_prefix.'users WHERE user_id='.$user_id.';');
       
  1612       if($db->numrows() < 1)
       
  1613       {
       
  1614         $errors[] = 'The password data could not be selected for verification.';
       
  1615       }
       
  1616       else
       
  1617       {
       
  1618         $row = $db->fetchrow();
       
  1619         $real = $aes->decrypt($row['password'], $this->private_key, ENC_HEX);
       
  1620         if($real == $old_pass)
       
  1621           $authed = true;
       
  1622       }
       
  1623     }
       
  1624     
       
  1625     // Initialize our query
       
  1626     $q = 'UPDATE '.table_prefix.'users SET ';
       
  1627     
       
  1628     if($this->auth_level >= USER_LEVEL_ADMIN || $authed) // Need the current password in order to update the e-mail address, change the username, or reset the password
       
  1629     {
       
  1630       // Username
       
  1631       if(is_string($username))
       
  1632       {
       
  1633         // Check the username for problems
       
  1634         if(!preg_match('#^'.$this->valid_username.'$#', $username))
       
  1635           $errors[] = 'The username you entered contains invalid characters.';
       
  1636         $strs[] = 'username=\''.$db->escape($username).'\'';
       
  1637       }
       
  1638       // Password
       
  1639       if(is_string($password) && strlen($password) >= 6)
       
  1640       {
       
  1641         // Password needs to be encrypted before being stashed
       
  1642         $encpass = $aes->encrypt($password, $this->private_key, ENC_HEX);
       
  1643         if(!$encpass)
       
  1644           $errors[] = 'The password could not be encrypted due to an internal error.';
       
  1645         $strs[] = 'password=\''.$encpass.'\'';
       
  1646       }
       
  1647       // E-mail addy
       
  1648       if(is_string($email))
       
  1649       {
       
  1650         // I didn't write this regex.
       
  1651         if(!preg_match('/^(?:[\w\d]+\.?)+@(?:(?:[\w\d]\-?)+\.)+\w{2,4}$/', $email))
       
  1652           $errors[] = 'The e-mail address you entered is invalid.';
       
  1653         $strs[] = 'email=\''.$db->escape($email).'\'';
       
  1654       }
       
  1655     }
       
  1656     // Real name
       
  1657     if(is_string($realname))
       
  1658     {
       
  1659       $strs[] = 'real_name=\''.$db->escape($realname).'\'';
       
  1660     }
       
  1661     // Forum/comment signature
       
  1662     if(is_string($signature))
       
  1663     {
       
  1664       $strs[] = 'signature=\''.$db->escape($signature).'\'';
       
  1665     }
       
  1666     // User level
       
  1667     if(is_int($user_level))
       
  1668     {
       
  1669       $strs[] = 'user_level='.$user_level;
       
  1670     }
       
  1671     
       
  1672     // Add our generated query to the query string
       
  1673     $q .= implode(',', $strs);
       
  1674     
       
  1675     // One last error check
       
  1676     if(sizeof($strs) < 1) $errors[] = 'An internal error occured building the SQL query, this is a bug';
       
  1677     if(sizeof($errors) > 0) return $errors;
       
  1678     
       
  1679     // Free our temp arrays
       
  1680     unset($strs, $errors);
       
  1681     
       
  1682     // Finalize the query and run it
       
  1683     $q .= ' WHERE user_id='.$user_id.';';
       
  1684     $this->sql($q);
       
  1685     
       
  1686     // We also need to trigger re-activation.
       
  1687     if ( is_string($email) )
       
  1688     {
       
  1689       switch(getConfig('account_activation'))
       
  1690       {
       
  1691         case 'user':
       
  1692         case 'admin':
       
  1693           
       
  1694           if ( $session->user_level >= USER_LEVEL_MOD && getConfig('account_activation') == 'admin' )
       
  1695             // Don't require re-activation by admins for admins
       
  1696             break;
       
  1697           
       
  1698           // retrieve username
       
  1699           if ( !$username )
       
  1700           {
       
  1701             $q = $this->sql('SELECT username FROM '.table_prefix.'users WHERE user_id='.$user_id.';');
       
  1702             if($db->numrows() < 1)
       
  1703             {
       
  1704               $errors[] = 'The username could not be selected.';
       
  1705             }
       
  1706             else
       
  1707             {
       
  1708               $row = $db->fetchrow();
       
  1709               $username = $row['username'];
       
  1710             }
       
  1711           }
       
  1712           if ( !$username )
       
  1713             return $errors;
       
  1714           
       
  1715           // Generate a totally random activation key
       
  1716           $actkey = sha1 ( microtime() . mt_rand() );
       
  1717           $a = $this->send_activation_mail($username, $actkey);
       
  1718           if(!$a)
       
  1719           {
       
  1720             $this->admin_activation_request($username);
       
  1721           }
       
  1722           // Deactivate the account until e-mail is confirmed
       
  1723           $q = $db->sql_query('UPDATE '.table_prefix.'users SET account_active=0,activation_key=\'' . $actkey . '\' WHERE user_id=' . $user_id . ';');
       
  1724           break;
       
  1725       }
       
  1726     }
       
  1727     
       
  1728     // Yay! We're done
       
  1729     return 'success';
       
  1730   }
       
  1731   
       
  1732   #
       
  1733   # Access Control Lists
       
  1734   #
       
  1735   
       
  1736   /**
       
  1737    * Creates a new permission field in memory. If the permissions are set in the database, they are used. Otherwise, $default_perm is used.
       
  1738    * @param string $acl_type An identifier for this field
       
  1739    * @param int $default_perm Whether permission should be granted or not if it's not specified in the ACLs.
       
  1740    * @param string $desc A human readable name for the permission type
       
  1741    * @param array $deps The list of dependencies - this should be an array of ACL types
       
  1742    * @param string $scope Which namespaces this field should apply to. This should be either a pipe-delimited list of namespace IDs or just "All".
       
  1743    */
       
  1744    
       
  1745   function register_acl_type($acl_type, $default_perm = AUTH_DISALLOW, $desc = false, $deps = Array(), $scope = 'All')
       
  1746   {
       
  1747     if(isset($this->acl_types[$acl_type]))
       
  1748       return false;
       
  1749     else
       
  1750     {
       
  1751       if(!$desc)
       
  1752       {
       
  1753         $desc = capitalize_first_letter(str_replace('_', ' ', $acl_type));
       
  1754       }
       
  1755       $this->acl_types[$acl_type] = $default_perm;
       
  1756       $this->acl_descs[$acl_type] = $desc;
       
  1757       $this->acl_deps[$acl_type] = $deps;
       
  1758       $this->acl_scope[$acl_type] = explode('|', $scope);
       
  1759     }
       
  1760     return true;
       
  1761   }
       
  1762   
       
  1763   /**
       
  1764    * Tells us whether permission $type is allowed or not based on the current rules.
       
  1765    * @param string $type The permission identifier ($acl_type passed to sessionManager::register_acl_type())
       
  1766    * @param bool $no_deps If true, disables dependency checking
       
  1767    * @return bool True if allowed, false if denied or if an error occured
       
  1768    */
       
  1769    
       
  1770   function get_permissions($type, $no_deps = false)
       
  1771   {
       
  1772     global $db, $session, $paths, $template, $plugins; // Common objects
       
  1773     if ( isset( $this->perms[$type] ) )
       
  1774     {
       
  1775       if ( $this->perms[$type] == AUTH_DENY )
       
  1776         $ret = false;
       
  1777       else if ( $this->perms[$type] == AUTH_WIKIMODE && $paths->wiki_mode )
       
  1778         $ret = true;
       
  1779       else if ( $this->perms[$type] == AUTH_WIKIMODE && !$paths->wiki_mode )
       
  1780         $ret = false;
       
  1781       else if ( $this->perms[$type] == AUTH_ALLOW )
       
  1782         $ret = true;
       
  1783       else if ( $this->perms[$type] == AUTH_DISALLOW )
       
  1784         $ret = false;
       
  1785     }
       
  1786     else if(isset($this->acl_types[$type]))
       
  1787     {
       
  1788       if ( $this->acl_types[$type] == AUTH_DENY )
       
  1789         $ret = false;
       
  1790       else if ( $this->acl_types[$type] == AUTH_WIKIMODE && $paths->wiki_mode )
       
  1791         $ret = true;
       
  1792       else if ( $this->acl_types[$type] == AUTH_WIKIMODE && !$paths->wiki_mode )
       
  1793         $ret = false;
       
  1794       else if ( $this->acl_types[$type] == AUTH_ALLOW )
       
  1795         $ret = true;
       
  1796       else if ( $this->acl_types[$type] == AUTH_DISALLOW )
       
  1797         $ret = false;
       
  1798     }
       
  1799     else
       
  1800     {
       
  1801       // ACL type is undefined
       
  1802       trigger_error('Unknown access type "' . $type . '"', E_USER_WARNING);
       
  1803       return false; // Be on the safe side and deny access
       
  1804     }
       
  1805     if ( !$no_deps )
       
  1806     {
       
  1807       if ( !$this->acl_check_deps($type) )
       
  1808         return false;
       
  1809     }
       
  1810     return $ret;
       
  1811   }
       
  1812   
       
  1813   /**
       
  1814    * Fetch the permissions that apply to the current user for the page specified. The object you get will have the get_permissions method
       
  1815    * and several other abilities.
       
  1816    * @param string $page_id
       
  1817    * @param string $namespace
       
  1818    * @return object
       
  1819    */
       
  1820    
       
  1821   function fetch_page_acl($page_id, $namespace)
       
  1822   {
       
  1823     global $db, $session, $paths, $template, $plugins; // Common objects
       
  1824     
       
  1825     if ( count ( $this->acl_base_cache ) < 1 )
       
  1826     {
       
  1827       // Permissions table not yet initialized
       
  1828       return false;
       
  1829     }
       
  1830     
       
  1831     //if ( !isset( $paths->pages[$paths->nslist[$namespace] . $page_id] ) )
       
  1832     //{
       
  1833     //  // Page does not exist
       
  1834     //  return false;
       
  1835     //}
       
  1836     
       
  1837     $object = new Session_ACLPageInfo( $page_id, $namespace, $this->acl_types, $this->acl_descs, $this->acl_deps, $this->acl_base_cache );
       
  1838     
       
  1839     return $object;
       
  1840     
       
  1841   }
       
  1842   
       
  1843   /**
       
  1844    * Read all of our permissions from the database and process/apply them. This should be called after the page is determined.
       
  1845    * @access private
       
  1846    */
       
  1847   
       
  1848   function init_permissions()
       
  1849   {
       
  1850     global $db, $session, $paths, $template, $plugins; // Common objects
       
  1851     // Initialize the permissions list with some defaults
       
  1852     $this->perms = $this->acl_types;
       
  1853     $this->acl_defaults_used = $this->perms;
       
  1854     
       
  1855     // Fetch sitewide defaults from the permissions table
       
  1856     $bs = 'SELECT rules FROM '.table_prefix.'acl WHERE page_id IS NULL AND namespace IS NULL AND ( ';
       
  1857     
       
  1858     $q = Array();
       
  1859     $q[] = '( target_type='.ACL_TYPE_USER.' AND target_id='.$this->user_id.' )';
       
  1860     if(count($this->groups) > 0)
       
  1861     {
       
  1862       foreach($this->groups as $g_id => $g_name)
       
  1863       {
       
  1864         $q[] = '( target_type='.ACL_TYPE_GROUP.' AND target_id='.intval($g_id).' )';
       
  1865       }
       
  1866     }
       
  1867     $bs .= implode(' OR ', $q) . ' ) ORDER BY target_type ASC, target_id ASC;';
       
  1868     $q = $this->sql($bs);
       
  1869     if ( $row = $db->fetchrow() )
       
  1870     {
       
  1871       do {
       
  1872         $rules = $this->string_to_perm($row['rules']);
       
  1873         $is_everyone = ( $row['target_type'] == ACL_TYPE_GROUP && $row['target_id'] == 1 );
       
  1874         $this->acl_merge_with_current($rules, $is_everyone);
       
  1875       } while ( $row = $db->fetchrow() );
       
  1876     }
       
  1877     
       
  1878     // Eliminate types that don't apply to this namespace
       
  1879     foreach ( $this->perms AS $i => $perm )
       
  1880     {
       
  1881       if ( !in_array ( $paths->namespace, $this->acl_scope[$i] ) && !in_array('All', $this->acl_scope[$i]) )
       
  1882       {
       
  1883         unset($this->perms[$i]);
       
  1884       }
       
  1885     }
       
  1886     
       
  1887     // Cache the sitewide permissions for later use
       
  1888     $this->acl_base_cache = $this->perms;
       
  1889     
       
  1890     // Build a query to grab ACL info
       
  1891     $bs = 'SELECT rules,target_type,target_id FROM '.table_prefix.'acl WHERE ( ';
       
  1892     $q = Array();
       
  1893     $q[] = '( target_type='.ACL_TYPE_USER.' AND target_id='.$this->user_id.' )';
       
  1894     if(count($this->groups) > 0)
       
  1895     {
       
  1896       foreach($this->groups as $g_id => $g_name)
       
  1897       {
       
  1898         $q[] = '( target_type='.ACL_TYPE_GROUP.' AND target_id='.intval($g_id).' )';
       
  1899       }
       
  1900     }
       
  1901     // The reason we're using an ORDER BY statement here is because ACL_TYPE_GROUP is less than ACL_TYPE_USER, causing the user's individual
       
  1902     // permissions to override group permissions.
       
  1903     $bs .= implode(' OR ', $q) . ' ) AND ( page_id=\''.$db->escape($paths->cpage['urlname_nons']).'\' AND namespace=\''.$db->escape($paths->namespace).'\' )     
       
  1904       ORDER BY target_type ASC, page_id ASC, namespace ASC;';
       
  1905     $q = $this->sql($bs);
       
  1906     if ( $row = $db->fetchrow() )
       
  1907     {
       
  1908       do {
       
  1909         $rules = $this->string_to_perm($row['rules']);
       
  1910         $is_everyone = ( $row['target_type'] == ACL_TYPE_GROUP && $row['target_id'] == 1 );
       
  1911         $this->acl_merge_with_current($rules, $is_everyone);
       
  1912       } while ( $row = $db->fetchrow() );
       
  1913     }
       
  1914     
       
  1915   }
       
  1916   
       
  1917   /**
       
  1918    * Extends the scope of a permission type.
       
  1919    * @param string The name of the permission type
       
  1920    * @param string The namespace(s) that should be covered. This can be either one namespace ID or a pipe-delimited list.
       
  1921    * @param object Optional - the current $paths object, in case we're doing this from the acl_rule_init hook
       
  1922    */
       
  1923    
       
  1924   function acl_extend_scope($perm_type, $namespaces, &$p_in)
       
  1925   {
       
  1926     global $db, $session, $paths, $template, $plugins; // Common objects
       
  1927     $p_obj = ( is_object($p_in) ) ? $p_in : $paths;
       
  1928     $nslist = explode('|', $namespaces);
       
  1929     foreach ( $nslist as $i => $ns )
       
  1930     {
       
  1931       if ( !isset($p_obj->nslist[$ns]) )
       
  1932       {
       
  1933         unset($nslist[$i]);
       
  1934       }
       
  1935       else
       
  1936       {
       
  1937         $this->acl_scope[$perm_type][] = $ns;
       
  1938         if ( isset($this->acl_types[$perm_type]) && !isset($this->perms[$perm_type]) )
       
  1939         {
       
  1940           $this->perms[$perm_type] = $this->acl_types[$perm_type];
       
  1941         }
       
  1942       }
       
  1943     }
       
  1944   }
       
  1945   
       
  1946   /**
       
  1947    * Converts a permissions field into a string for database insertion. Similar in spirit to serialize().
       
  1948    * @param array $perms An associative array with only integers as values
       
  1949    * @return string
       
  1950    */
       
  1951    
       
  1952   function perm_to_string($perms)
       
  1953   {
       
  1954     $s = '';
       
  1955     foreach($perms as $perm => $ac)
       
  1956     {
       
  1957       $s .= "$perm=$ac;";
       
  1958     }
       
  1959     return $s;
       
  1960   }
       
  1961   
       
  1962   /**
       
  1963    * Converts a permissions string back to an array.
       
  1964    * @param string $perms The result from sessionManager::perm_to_string()
       
  1965    * @return array
       
  1966    */
       
  1967    
       
  1968   function string_to_perm($perms)
       
  1969   {
       
  1970     $ret = Array();
       
  1971     preg_match_all('#([a-z0-9_-]+)=([0-9]+);#i', $perms, $matches);
       
  1972     foreach($matches[1] as $i => $t)
       
  1973     {
       
  1974       $ret[$t] = intval($matches[2][$i]);
       
  1975     }
       
  1976     return $ret;
       
  1977   }
       
  1978   
       
  1979   /**
       
  1980    * Merges two ACL arrays. Both parameters should be permission list arrays. The second group takes precedence over the first, but AUTH_DENY always prevails.
       
  1981    * @param array $perm1 The first set of permissions
       
  1982    * @param array $perm2 The second set of permissions
       
  1983    * @return array
       
  1984    */
       
  1985    
       
  1986   function acl_merge($perm1, $perm2)
       
  1987   {
       
  1988     $ret = $perm1;
       
  1989     foreach ( $perm2 as $type => $level )
       
  1990     {
       
  1991       if ( isset( $ret[$type] ) )
       
  1992       {
       
  1993         if ( $ret[$type] != AUTH_DENY )
       
  1994           $ret[$type] = $level;
       
  1995       }
       
  1996       // else
       
  1997       // {
       
  1998       //   $ret[$type] = $level;
       
  1999       // }
       
  2000     }
       
  2001     return $ret;
       
  2002   }
       
  2003   
       
  2004   /**
       
  2005    * Merges the ACL array sent with the current permissions table, deciding precedence based on whether defaults are in effect or not.
       
  2006    * @param array The array to merge into the master ACL list
       
  2007    * @param bool If true, $perm is treated as the "new default"
       
  2008    * @param int 1 if this is a site-wide ACL, 2 if page-specific. Defaults to 2.
       
  2009    */
       
  2010   
       
  2011   function acl_merge_with_current($perm, $is_everyone = false, $scope = 2)
       
  2012   {
       
  2013     foreach ( $this->perms as $i => $p )
       
  2014     {
       
  2015       if ( isset($perm[$i]) )
       
  2016       {
       
  2017         if ( $is_everyone && !$this->acl_defaults_used[$i] )
       
  2018           continue;
       
  2019         // Decide precedence
       
  2020         if ( isset($this->acl_defaults_used[$i]) )
       
  2021         {
       
  2022           //echo "$i: default in use, overriding to: {$perm[$i]}<br />";
       
  2023           // Defaults are in use, override
       
  2024           $this->perms[$i] = $perm[$i];
       
  2025           $this->acl_defaults_used[$i] = ( $is_everyone );
       
  2026         }
       
  2027         else
       
  2028         {
       
  2029           //echo "$i: default NOT in use";
       
  2030           // Defaults are not in use, merge as normal
       
  2031           if ( $this->perms[$i] != AUTH_DENY )
       
  2032           {
       
  2033             //echo ", but overriding";
       
  2034             $this->perms[$i] = $perm[$i];
       
  2035           }
       
  2036           //echo "<br />";
       
  2037         }
       
  2038       }
       
  2039     }
       
  2040   }
       
  2041   
       
  2042   /**
       
  2043    * Merges two ACL arrays. Both parameters should be permission list arrays. The second group takes precedence
       
  2044    * over the first, without exceptions. This is used to merge the hardcoded defaults with admin-specified
       
  2045    * defaults, which take precedence.
       
  2046    * @param array $perm1 The first set of permissions
       
  2047    * @param array $perm2 The second set of permissions
       
  2048    * @return array
       
  2049    */
       
  2050    
       
  2051   function acl_merge_complete($perm1, $perm2)
       
  2052   {
       
  2053     $ret = $perm1;
       
  2054     foreach ( $perm2 as $type => $level )
       
  2055     {
       
  2056       $ret[$type] = $level;
       
  2057     }
       
  2058     return $ret;
       
  2059   }
       
  2060   
       
  2061   /**
       
  2062    * Tell us if the dependencies for a given permission are met.
       
  2063    * @param string The ACL permission ID
       
  2064    * @return bool
       
  2065    */
       
  2066    
       
  2067   function acl_check_deps($type)
       
  2068   {
       
  2069     if(!isset($this->acl_deps[$type])) // This will only happen if the permissions table is hacked or improperly accessed
       
  2070       return true;
       
  2071     if(sizeof($this->acl_deps[$type]) < 1)
       
  2072       return true;
       
  2073     $deps = $this->acl_deps[$type];
       
  2074     while(true)
       
  2075     {
       
  2076       $full_resolved = true;
       
  2077       $j = sizeof($deps);
       
  2078       for ( $i = 0; $i < $j; $i++ )
       
  2079       {
       
  2080         $b = $deps;
       
  2081         $deps = array_merge($deps, $this->acl_deps[$deps[$i]]);
       
  2082         if( $b == $deps )
       
  2083         {
       
  2084           break 2;
       
  2085         }
       
  2086         $j = sizeof($deps);
       
  2087       }
       
  2088     }
       
  2089     //die('<pre>'.print_r($deps, true).'</pre>');
       
  2090     foreach($deps as $d)
       
  2091     {
       
  2092       if ( !$this->get_permissions($d) )
       
  2093       {
       
  2094         return false;
       
  2095       }
       
  2096     }
       
  2097     return true;
       
  2098   }
       
  2099   
       
  2100   /**
       
  2101    * Makes a CAPTCHA code and caches the code in the database
       
  2102    * @param int $len The length of the code, in bytes
       
  2103    * @return string A unique identifier assigned to the code. This hash should be passed to sessionManager::getCaptcha() to retrieve the code.
       
  2104    */
       
  2105   
       
  2106   function make_captcha($len = 7)
       
  2107   {
       
  2108     $chars = array('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9');
       
  2109     $s = '';
       
  2110     for($i=0;$i<$len;$i++) $s .= $chars[mt_rand(0, count($chars)-1)];
       
  2111     $hash = md5(microtime() . mt_rand());
       
  2112     $this->sql('INSERT INTO '.table_prefix.'session_keys(session_key,salt,auth_level,source_ip,user_id) VALUES(\''.$hash.'\', \''.$s.'\', -1, \''.ip2hex($_SERVER['REMOTE_ADDR']).'\', -2);');
       
  2113     return $hash;
       
  2114   }
       
  2115   
       
  2116   /**
       
  2117    * For the given code ID, returns the correct CAPTCHA code, or false on failure
       
  2118    * @param string $hash The unique ID assigned to the code
       
  2119    * @return string The correct confirmation code
       
  2120    */
       
  2121   
       
  2122   function get_captcha($hash)
       
  2123   {
       
  2124     global $db, $session, $paths, $template, $plugins; // Common objects
       
  2125     $s = $this->sql('SELECT salt FROM '.table_prefix.'session_keys WHERE session_key=\''.$db->escape($hash).'\' AND source_ip=\''.ip2hex($_SERVER['REMOTE_ADDR']).'\';');
       
  2126     if($db->numrows() < 1) return false;
       
  2127     $r = $db->fetchrow();
       
  2128     return $r['salt'];
       
  2129   }
       
  2130   
       
  2131   /**
       
  2132    * Deletes all CAPTCHA codes cached in the DB for this user.
       
  2133    */
       
  2134   
       
  2135   function kill_captcha()
       
  2136   {
       
  2137     $this->sql('DELETE FROM '.table_prefix.'session_keys WHERE user_id=-2 AND source_ip=\''.ip2hex($_SERVER['REMOTE_ADDR']).'\';');
       
  2138   }
       
  2139   
       
  2140   /**
       
  2141    * Generates a random password.
       
  2142    * @param int $length Optional - length of password
       
  2143    * @return string
       
  2144    */
       
  2145    
       
  2146   function random_pass($length = 10)
       
  2147   {
       
  2148     $valid_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_+@#%&<>';
       
  2149     $valid_chars = enano_str_split($valid_chars);
       
  2150     $ret = '';
       
  2151     for ( $i = 0; $i < $length; $i++ )
       
  2152     {
       
  2153       $ret .= $valid_chars[mt_rand(0, count($valid_chars)-1)];
       
  2154     }
       
  2155     return $ret;
       
  2156   }
       
  2157   
       
  2158   /**
       
  2159    * Generates some Javascript that calls the AES encryption library.
       
  2160    * @param string The name of the form
       
  2161    * @param string The name of the password field
       
  2162    * @param string The name of the field that switches encryption on or off
       
  2163    * @param string The name of the field that contains the encryption key
       
  2164    * @param string The name of the field that will contain the encrypted password
       
  2165    * @param string The name of the field that handles MD5 challenge data
       
  2166    * @return string
       
  2167    */
       
  2168    
       
  2169   function aes_javascript($form_name, $pw_field, $use_crypt, $crypt_key, $crypt_data, $challenge)
       
  2170   {
       
  2171     $code = '
       
  2172       <script type="text/javascript">
       
  2173         disableJSONExts();
       
  2174           str = \'\';
       
  2175           for(i=0;i<keySizeInBits/4;i++) str+=\'0\';
       
  2176           var key = hexToByteArray(str);
       
  2177           var pt = hexToByteArray(str);
       
  2178           var ct = rijndaelEncrypt(pt, key, \'ECB\');
       
  2179           var ct = byteArrayToHex(ct);
       
  2180           switch(keySizeInBits)
       
  2181           {
       
  2182             case 128:
       
  2183               v = \'66e94bd4ef8a2c3b884cfa59ca342b2e\';
       
  2184               break;
       
  2185             case 192:
       
  2186               v = \'aae06992acbf52a3e8f4a96ec9300bd7aae06992acbf52a3e8f4a96ec9300bd7\';
       
  2187               break;
       
  2188             case 256:
       
  2189               v = \'dc95c078a2408989ad48a21492842087dc95c078a2408989ad48a21492842087\';
       
  2190               break;
       
  2191           }
       
  2192           var testpassed = ' . ( ( isset($_GET['use_crypt']) && $_GET['use_crypt']=='0') ? 'false; // CRYPTO-AUTH DISABLED ON USER REQUEST // ' : '' ) . '( ct == v && md5_vm_test() );
       
  2193           var frm = document.forms.'.$form_name.';
       
  2194           if(testpassed)
       
  2195           {
       
  2196             frm.'.$use_crypt.'.value = \'yes\';
       
  2197             var cryptkey = frm.'.$crypt_key.'.value;
       
  2198             frm.'.$crypt_key.'.value = hex_md5(cryptkey);
       
  2199             cryptkey = hexToByteArray(cryptkey);
       
  2200             if(!cryptkey || ( ( typeof cryptkey == \'string\' || typeof cryptkey == \'object\' ) ) && cryptkey.length != keySizeInBits / 8 )
       
  2201             {
       
  2202               if ( frm._login ) frm._login.disabled = true;
       
  2203               len = ( typeof cryptkey == \'string\' || typeof cryptkey == \'object\' ) ? \'\\nLen: \'+cryptkey.length : \'\';
       
  2204               alert(\'The key is messed up\\nType: \'+typeof(cryptkey)+len);
       
  2205             }
       
  2206           }
       
  2207           if(frm.username) frm.username.focus();
       
  2208           function runEncryption()
       
  2209           {
       
  2210             if(testpassed)
       
  2211             {
       
  2212               pass = frm.'.$pw_field.'.value;
       
  2213               chal = frm.'.$challenge.'.value;
       
  2214               challenge = hex_md5(pass + chal) + chal;
       
  2215               frm.'.$challenge.'.value = challenge;
       
  2216               pass = stringToByteArray(pass);
       
  2217               cryptstring = rijndaelEncrypt(pass, cryptkey, \'ECB\');
       
  2218               if(!cryptstring)
       
  2219               {
       
  2220                 return false;
       
  2221               }
       
  2222               cryptstring = byteArrayToHex(cryptstring);
       
  2223               frm.'.$crypt_data.'.value = cryptstring;
       
  2224               frm.'.$pw_field.'.value = \'\';
       
  2225             }
       
  2226             return false;
       
  2227           }
       
  2228         </script>
       
  2229         ';
       
  2230     return $code;
       
  2231   }
       
  2232   
       
  2233 }
       
  2234 
       
  2235 /**
       
  2236  * Class used to fetch permissions for a specific page. Used internally by SessionManager.
       
  2237  * @package Enano
       
  2238  * @subpackage Session manager
       
  2239  * @license http://www.gnu.org/copyleft/gpl.html
       
  2240  * @access private
       
  2241  */
       
  2242  
       
  2243 class Session_ACLPageInfo {
       
  2244   
       
  2245   /**
       
  2246    * The page ID of this ACL info package
       
  2247    * @var string
       
  2248    */
       
  2249    
       
  2250   var $page_id;
       
  2251   
       
  2252   /**
       
  2253    * The namespace of the page being checked
       
  2254    * @var string
       
  2255    */
       
  2256    
       
  2257   var $namespace;
       
  2258   
       
  2259   /**
       
  2260    * Our list of permission types.
       
  2261    * @access private
       
  2262    * @var array
       
  2263    */
       
  2264    
       
  2265   var $acl_types = Array();
       
  2266   
       
  2267   /**
       
  2268    * The list of descriptions for the permission types
       
  2269    * @var array
       
  2270    */
       
  2271    
       
  2272   var $acl_descs = Array();
       
  2273   
       
  2274   /**
       
  2275    * A list of dependencies for ACL types.
       
  2276    * @var array
       
  2277    */
       
  2278    
       
  2279   var $acl_deps = Array();
       
  2280   
       
  2281   /**
       
  2282    * Our tell-all list of permissions.
       
  2283    * @access private - or, preferably, protected...too bad this has to be PHP4 compatible
       
  2284    * @var array
       
  2285    */
       
  2286    
       
  2287   var $perms = Array();
       
  2288   
       
  2289   /**
       
  2290    * Constructor.
       
  2291    * @param string $page_id The ID of the page to check
       
  2292    * @param string $namespace The namespace of the page to check.
       
  2293    * @param array $acl_types List of ACL types
       
  2294    * @param array $acl_descs List of human-readable descriptions for permissions (associative)
       
  2295    * @param array $acl_deps List of dependencies for permissions. For example, viewing history/diffs depends on the ability to read the page.
       
  2296    * @param array $base What to start with - this is an attempt to reduce the number of SQL queries.
       
  2297    */
       
  2298    
       
  2299   function Session_ACLPageInfo($page_id, $namespace, $acl_types, $acl_descs, $acl_deps, $base)
       
  2300   {
       
  2301     global $db, $session, $paths, $template, $plugins; // Common objects
       
  2302     
       
  2303     $this->perms = $session->acl_merge_complete($acl_types, $base);
       
  2304     $this->acl_deps = $acl_deps;
       
  2305     $this->acl_types = $acl_types;
       
  2306     $this->acl_descs = $acl_descs;
       
  2307     
       
  2308     // Build a query to grab ACL info
       
  2309     $bs = 'SELECT rules FROM '.table_prefix.'acl WHERE ( ';
       
  2310     $q = Array();
       
  2311     $q[] = '( target_type='.ACL_TYPE_USER.' AND target_id='.$session->user_id.' )';
       
  2312     if(count($session->groups) > 0)
       
  2313     {
       
  2314       foreach($session->groups as $g_id => $g_name)
       
  2315       {
       
  2316         $q[] = '( target_type='.ACL_TYPE_GROUP.' AND target_id='.intval($g_id).' )';
       
  2317       }
       
  2318     }
       
  2319     // The reason we're using an ORDER BY statement here is because ACL_TYPE_GROUP is less than ACL_TYPE_USER, causing the user's individual
       
  2320     // permissions to override group permissions.
       
  2321     $bs .= implode(' OR ', $q) . ' ) AND ( page_id=\''.$db->escape($page_id).'\' AND namespace=\''.$db->escape($namespace).'\' )     
       
  2322       ORDER BY target_type ASC, page_id ASC, namespace ASC;';
       
  2323     $q = $session->sql($bs);
       
  2324     if ( $row = $db->fetchrow() )
       
  2325     {
       
  2326       do {
       
  2327         $rules = $session->string_to_perm($row['rules']);
       
  2328         $this->perms = $session->acl_merge($this->perms, $rules);
       
  2329       } while ( $row = $db->fetchrow() );
       
  2330     }
       
  2331     
       
  2332     $this->page_id = $page_id;
       
  2333     $this->namespace = $namespace;
       
  2334   }
       
  2335   
       
  2336   /**
       
  2337    * Tells us whether permission $type is allowed or not based on the current rules.
       
  2338    * @param string $type The permission identifier ($acl_type passed to sessionManager::register_acl_type())
       
  2339    * @param bool $no_deps If true, disables dependency checking
       
  2340    * @return bool True if allowed, false if denied or if an error occured
       
  2341    */
       
  2342    
       
  2343   function get_permissions($type, $no_deps = false)
       
  2344   {
       
  2345     global $db, $session, $paths, $template, $plugins; // Common objects
       
  2346     if ( isset( $this->perms[$type] ) )
       
  2347     {
       
  2348       if ( $this->perms[$type] == AUTH_DENY )
       
  2349         $ret = false;
       
  2350       else if ( $this->perms[$type] == AUTH_WIKIMODE &&
       
  2351         ( isset($paths->pages[$paths->nslist[$this->namespace].$this->page_id]) && 
       
  2352           ( $paths->pages[$paths->nslist[$this->namespace].$this->page_id]['wiki_mode'] == '1' ||
       
  2353             ( $paths->pages[$paths->nslist[$this->namespace].$this->page_id]['wiki_mode'] == '2'
       
  2354               && getConfig('wiki_mode') == '1'
       
  2355           ) ) ) )
       
  2356         $ret = true;
       
  2357       else if ( $this->perms[$type] == AUTH_WIKIMODE && (
       
  2358         !isset($paths->pages[$paths->nslist[$this->namespace].$this->page_id])
       
  2359         || (
       
  2360           isset($paths->pages[$paths->nslist[$this->namespace].$this->page_id]) && (
       
  2361             $paths->pages[$paths->nslist[$this->namespace].$this->page_id]['wiki_mode'] == '0'
       
  2362             || (
       
  2363               $paths->pages[$paths->nslist[$this->namespace].$this->page_id]['wiki_mode'] == '2' && getConfig('wiki_mode') != '1'
       
  2364           ) ) ) ) )
       
  2365         $ret = false;
       
  2366       else if ( $this->perms[$type] == AUTH_ALLOW )
       
  2367         $ret = true;
       
  2368       else if ( $this->perms[$type] == AUTH_DISALLOW )
       
  2369         $ret = false;
       
  2370     }
       
  2371     else if(isset($this->acl_types[$type]))
       
  2372     {
       
  2373       if ( $this->acl_types[$type] == AUTH_DENY )
       
  2374         $ret = false;
       
  2375       else if ( $this->acl_types[$type] == AUTH_WIKIMODE && $paths->wiki_mode )
       
  2376         $ret = true;
       
  2377       else if ( $this->acl_types[$type] == AUTH_WIKIMODE && !$paths->wiki_mode )
       
  2378         $ret = false;
       
  2379       else if ( $this->acl_types[$type] == AUTH_ALLOW )
       
  2380         $ret = true;
       
  2381       else if ( $this->acl_types[$type] == AUTH_DISALLOW )
       
  2382         $ret = false;
       
  2383     }
       
  2384     else
       
  2385     {
       
  2386       // ACL type is undefined
       
  2387       trigger_error('Unknown access type "' . $type . '"', E_USER_WARNING);
       
  2388       return false; // Be on the safe side and deny access
       
  2389     }
       
  2390     if ( !$no_deps )
       
  2391     {
       
  2392       if ( !$this->acl_check_deps($type) )
       
  2393         return false;
       
  2394     }
       
  2395     return $ret;
       
  2396   }
       
  2397   
       
  2398   /**
       
  2399    * Tell us if the dependencies for a given permission are met.
       
  2400    * @param string The ACL permission ID
       
  2401    * @return bool
       
  2402    */
       
  2403    
       
  2404   function acl_check_deps($type)
       
  2405   {
       
  2406     if(!isset($this->acl_deps[$type])) // This will only happen if the permissions table is hacked or improperly accessed
       
  2407       return true;
       
  2408     if(sizeof($this->acl_deps[$type]) < 1)
       
  2409       return true;
       
  2410     $deps = $this->acl_deps[$type];
       
  2411     while(true)
       
  2412     {
       
  2413       $full_resolved = true;
       
  2414       $j = sizeof($deps);
       
  2415       for ( $i = 0; $i < $j; $i++ )
       
  2416       {
       
  2417         $b = $deps;
       
  2418         $deps = array_merge($deps, $this->acl_deps[$deps[$i]]);
       
  2419         if( $b == $deps )
       
  2420         {
       
  2421           break 2;
       
  2422         }
       
  2423         $j = sizeof($deps);
       
  2424       }
       
  2425     }
       
  2426     //die('<pre>'.print_r($deps, true).'</pre>');
       
  2427     foreach($deps as $d)
       
  2428     {
       
  2429       if ( !$this->get_permissions($d) )
       
  2430       {
       
  2431         return false;
       
  2432       }
       
  2433     }
       
  2434     return true;
       
  2435   }
       
  2436   
       
  2437 }
       
  2438 
       
  2439 ?>