includes/sessions.php~
changeset 1 fe660c52c48f
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         redirect($url, 'Login sucessful', 'Please wait while you are transferred to the Password Reset form.');
       
   540         exit;
       
   541       }
       
   542     }
       
   543     
       
   544     if($row['old_encryption'] == 1)
       
   545     {
       
   546       // The user's password is stored using the obsolete and insecure MD5 algorithm, so we'll update the field with the new password
       
   547       if(md5($password) == $row['password'])
       
   548       {
       
   549         $pass_stashed = $aes->encrypt($password, $this->private_key, ENC_HEX);
       
   550         $this->sql('UPDATE '.table_prefix.'users SET password=\''.$pass_stashed.'\',old_encryption=0 WHERE user_id='.$row['user_id'].';');
       
   551         $success = true;
       
   552       }
       
   553     }
       
   554     else
       
   555     {
       
   556       // 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
       
   557       $real_pass = $aes->decrypt(hexdecode($row['password']), $this->private_key, ENC_BINARY);
       
   558       if($password == $real_pass)
       
   559       {
       
   560         // Yay! We passed AES authentication, now do an MD5 challenge check to make sure we weren't spoofed
       
   561         $chal = substr($challenge, 0, 32);
       
   562         $salt = substr($challenge, 32, 32);
       
   563         $correct_challenge = md5( $real_pass . $salt );
       
   564         if($chal == $correct_challenge)
       
   565           $success = true;
       
   566       }
       
   567     }
       
   568     if($success)
       
   569     {
       
   570       if($level > $row['user_level'])
       
   571         return 'You are not authorized for this level of access.';
       
   572       
       
   573       $sess = $this->register_session(intval($row['user_id']), $username, $password, $level);
       
   574       if($sess)
       
   575       {
       
   576         $this->username = $username;
       
   577         $this->user_id = intval($row['user_id']);
       
   578         $this->theme = $row['theme'];
       
   579         $this->style = $row['style'];
       
   580         
       
   581         if($level > USER_LEVEL_MEMBER)
       
   582           $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) . ')');
       
   583         else
       
   584           $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']).'\')');
       
   585         
       
   586         $code = $plugins->setHook('login_success');
       
   587         foreach ( $code as $cmd )
       
   588         {
       
   589           eval($cmd);
       
   590         }
       
   591         return 'success';
       
   592       }
       
   593       else
       
   594         return 'Your login credentials were correct, but an internal error occurred while registering the session key in the database.';
       
   595     }
       
   596     else
       
   597     {
       
   598       if($level > USER_LEVEL_MEMBER)
       
   599         $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) . ')');
       
   600       else
       
   601         $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']).'\')');
       
   602         
       
   603       return 'The username and/or password is incorrect.';
       
   604     }
       
   605   }
       
   606   
       
   607   /**
       
   608    * Attempts to login without using crypto stuff, mainly for use when the other side doesn't like Javascript
       
   609    * 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
       
   610    * Technically it still uses crypto, but it only decrypts the password already stored, which is (obviously) required for authentication
       
   611    * @param string $username The username
       
   612    * @param string $password The password -OR- the MD5 hash of the password if $already_md5ed is true
       
   613    * @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.
       
   614    * @param int $level The privilege level we're authenticating for, defaults to 0
       
   615    */
       
   616   
       
   617   function login_without_crypto($username, $password, $already_md5ed = false, $level = USER_LEVEL_MEMBER)
       
   618   {
       
   619     global $db, $session, $paths, $template, $plugins; // Common objects
       
   620     
       
   621     $pass_hashed = ( $already_md5ed ) ? $password : md5($password);
       
   622     
       
   623     // Perhaps we're upgrading Enano?
       
   624     if($this->compat)
       
   625     {
       
   626       return $this->login_compat($username, $pass_hashed, $level);
       
   627     }
       
   628     
       
   629     // Instanciate the Rijndael encryption object
       
   630     $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
       
   631     
       
   632     // Initialize our success switch
       
   633     $success = false;
       
   634     
       
   635     // Retrieve the real password from the database
       
   636     $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)).'\';');
       
   637     if($db->numrows() < 1)
       
   638       return 'The username and/or password is incorrect.';
       
   639     $row = $db->fetchrow();
       
   640     
       
   641     // Check to see if we're logging in using a temporary password
       
   642     
       
   643     if((intval($row['temp_password_time']) + 3600*24) > time() )
       
   644     {
       
   645       $temp_pass = $aes->decrypt( $row['temp_password'], $this->private_key, ENC_HEX );
       
   646       if( md5($temp_pass) == $pass_hashed )
       
   647       {
       
   648         header('Location: ' . makeUrlComplete('Special', 'PasswordReset/stage2/' . $row['user_id'] . '/' . $row['temp_password']) );
       
   649         exit;
       
   650       }
       
   651     }
       
   652     
       
   653     if($row['old_encryption'] == 1)
       
   654     {
       
   655       // The user's password is stored using the obsolete and insecure MD5 algorithm - we'll update the field with the new password
       
   656       if($pass_hashed == $row['password'] && !$already_md5ed)
       
   657       {
       
   658         $pass_stashed = $aes->encrypt($password, $this->private_key, ENC_HEX);
       
   659         $this->sql('UPDATE '.table_prefix.'users SET password=\''.$pass_stashed.'\',old_encryption=0 WHERE user_id='.$row['user_id'].';');
       
   660         $success = true;
       
   661       }
       
   662       elseif($pass_hashed == $row['password'] && $already_md5ed)
       
   663       {
       
   664         // We don't have the real password so don't bother with encrypting it, just call it success and get out of here
       
   665         $success = true;
       
   666       }
       
   667     }
       
   668     else
       
   669     {
       
   670       // 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
       
   671       $real_pass = $aes->decrypt($row['password'], $this->private_key);
       
   672       if($pass_hashed == md5($real_pass))
       
   673       {
       
   674         $success = true;
       
   675       }
       
   676     }
       
   677     if($success)
       
   678     {
       
   679       if((int)$level > (int)$row['user_level'])
       
   680         return 'You are not authorized for this level of access.';
       
   681       $sess = $this->register_session(intval($row['user_id']), $username, $real_pass, $level);
       
   682       if($sess)
       
   683       {
       
   684         if($level > USER_LEVEL_MEMBER)
       
   685           $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) . ')');
       
   686         else
       
   687           $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']).'\')');
       
   688         
       
   689         $code = $plugins->setHook('login_success');
       
   690         foreach ( $code as $cmd )
       
   691         {
       
   692           eval($cmd);
       
   693         }
       
   694         return 'success';
       
   695       }
       
   696       else
       
   697         return 'Your login credentials were correct, but an internal error occured while registering the session key in the database.';
       
   698     }
       
   699     else
       
   700     {
       
   701       if($level > USER_LEVEL_MEMBER)
       
   702         $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) . ')');
       
   703       else
       
   704         $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']).'\')');
       
   705         
       
   706       return 'The username and/or password is incorrect.';
       
   707     }
       
   708   }
       
   709   
       
   710   /**
       
   711    * Attempts to log in using the old table structure and algorithm.
       
   712    * @param string $username
       
   713    * @param string $password This should be an MD5 hash
       
   714    * @return string 'success' if successful, or error message on failure
       
   715    */
       
   716   
       
   717   function login_compat($username, $password, $level = 0)
       
   718   {
       
   719     global $db, $session, $paths, $template, $plugins; // Common objects
       
   720     $pass_hashed =& $password;
       
   721     $this->sql('SELECT password,user_id,user_level FROM '.table_prefix.'users WHERE username=\''.$this->prepare_text($username).'\';');
       
   722     if($db->numrows() < 1)
       
   723       return 'The username and/or password is incorrect.';
       
   724     $row = $db->fetchrow();
       
   725     if($row['password'] == $password)
       
   726     {
       
   727       if((int)$level > (int)$row['user_level'])
       
   728         return 'You are not authorized for this level of access.';
       
   729       $sess = $this->register_session_compat(intval($row['user_id']), $username, $password, $level);
       
   730       if($sess)
       
   731         return 'success';
       
   732       else
       
   733         return 'Your login credentials were correct, but an internal error occured while registering the session key in the database.';
       
   734     }
       
   735     else
       
   736     {
       
   737       return 'The username and/or password is incorrect.';
       
   738     }
       
   739   }
       
   740   
       
   741   /**
       
   742    * Registers a session key in the database. This function *ASSUMES* that the username and password have already been validated!
       
   743    * Basically the session key is a base64-encoded cookie (encrypted with the site's private key) that says "u=[username];p=[sha1 of password]"
       
   744    * @param int $user_id
       
   745    * @param string $username
       
   746    * @param string $password
       
   747    * @param int $level The level of access to grant, defaults to USER_LEVEL_MEMBER
       
   748    * @return bool
       
   749    */
       
   750    
       
   751   function register_session($user_id, $username, $password, $level = USER_LEVEL_MEMBER)
       
   752   {
       
   753     $salt = md5(microtime() . mt_rand());
       
   754     $passha1 = sha1($password);
       
   755     $session_key = "u=$username;p=$passha1;s=$salt";
       
   756     $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
       
   757     $session_key = $aes->encrypt($session_key, $this->private_key, ENC_HEX);
       
   758     if($level > USER_LEVEL_MEMBER)
       
   759     {
       
   760       $hexkey = strrev($session_key);
       
   761       $this->sid_super = $hexkey;
       
   762       $_GET['auth'] = $hexkey;
       
   763     }
       
   764     else
       
   765     {
       
   766       setcookie( 'sid', $session_key, time()+315360000, scriptPath.'/' );
       
   767       $_COOKIE['sid'] = $session_key;
       
   768     }
       
   769     $keyhash = md5($session_key);
       
   770     $ip = ip2hex($_SERVER['REMOTE_ADDR']);
       
   771     if(!$ip)
       
   772       die('$session->register_session: Remote-Addr was spoofed');
       
   773     $time = time();
       
   774     if(!is_int($user_id))
       
   775       die('Somehow an SQL injection attempt crawled into our session registrar! (1)');
       
   776     if(!is_int($level))
       
   777       die('Somehow an SQL injection attempt crawled into our session registrar! (2)');
       
   778     
       
   779     $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.');');
       
   780     return true;
       
   781   }
       
   782   
       
   783   /**
       
   784    * Identical to register_session in nature, but uses the old login/table structure. DO NOT use this.
       
   785    * @see sessionManager::register_session()
       
   786    * @access private
       
   787    */
       
   788   
       
   789   function register_session_compat($user_id, $username, $password, $level = 0)
       
   790   {
       
   791     $salt = md5(microtime() . mt_rand());
       
   792     $thekey = md5($password . $salt);
       
   793     if($level > 0)
       
   794     {
       
   795       $this->sid_super = $thekey;
       
   796     }
       
   797     else
       
   798     {
       
   799       setcookie( 'sid', $thekey, time()+315360000, scriptPath.'/' );
       
   800       $_COOKIE['sid'] = $thekey;
       
   801     }
       
   802     $ip = ip2hex($_SERVER['REMOTE_ADDR']);
       
   803     if(!$ip)
       
   804       die('$session->register_session: Remote-Addr was spoofed');
       
   805     $time = time();
       
   806     if(!is_int($user_id))
       
   807       die('Somehow an SQL injection attempt crawled into our session registrar! (1)');
       
   808     if(!is_int($level))
       
   809       die('Somehow an SQL injection attempt crawled into our session registrar! (2)');
       
   810     $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.');');
       
   811     return true;
       
   812   }
       
   813   
       
   814   /**
       
   815    * Creates/restores a guest session
       
   816    * @todo implement real session management for guests
       
   817    */
       
   818    
       
   819   function register_guest_session()
       
   820   {
       
   821     global $db, $session, $paths, $template, $plugins; // Common objects
       
   822     $this->username = $_SERVER['REMOTE_ADDR'];
       
   823     $this->user_level = USER_LEVEL_GUEST;
       
   824     if($this->compat || defined('IN_ENANO_INSTALL'))
       
   825     {
       
   826       $this->theme = 'oxygen';
       
   827       $this->style = 'bleu';
       
   828     }
       
   829     else
       
   830     {
       
   831       $this->theme = ( isset($_GET['theme']) && isset($template->named_theme_list[$_GET['theme']])) ? $_GET['theme'] : $template->default_theme;
       
   832       $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);
       
   833     }
       
   834     $this->user_id = 1;
       
   835   }
       
   836   
       
   837   /**
       
   838    * Validates a session key, and returns the userdata associated with the key or false
       
   839    * @param string $key The session key to validate
       
   840    * @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.
       
   841    */
       
   842    
       
   843   function validate_session($key)
       
   844   {
       
   845     global $db, $session, $paths, $template, $plugins; // Common objects
       
   846     $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE, true);
       
   847     $decrypted_key = $aes->decrypt($key, $this->private_key, ENC_HEX);
       
   848     
       
   849     if ( !$decrypted_key )
       
   850     {
       
   851       die_semicritical('AES encryption error', '<p>Something went wrong during the AES decryption process.</p><pre>'.print_r($decrypted_key, true).'</pre>');
       
   852     }
       
   853     
       
   854     $n = preg_match('/^u='.$this->valid_username.';p=([A-Fa-f0-9]+?);s=([A-Fa-f0-9]+?)$/', $decrypted_key, $keydata);
       
   855     if($n < 1)
       
   856     {
       
   857       // echo '(debug) $session->validate_session: Key does not match regex<br />Decrypted key: '.$decrypted_key;
       
   858       return false;
       
   859     }
       
   860     $keyhash = md5($key);
       
   861     $salt = $db->escape($keydata[3]);
       
   862     $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
       
   863                            LEFT JOIN '.table_prefix.'users AS u
       
   864                              ON ( u.user_id=k.user_id )
       
   865                            LEFT JOIN '.table_prefix.'users_extra AS x
       
   866                              ON ( u.user_id=x.user_id OR x.user_id IS NULL )
       
   867                            LEFT JOIN '.table_prefix.'privmsgs AS p
       
   868                              ON ( p.message_to=u.username AND p.message_read=0 )
       
   869                            WHERE k.session_key=\''.$keyhash.'\'
       
   870                              AND k.salt=\''.$salt.'\'
       
   871                            GROUP BY u.user_id;');
       
   872     if($db->numrows() < 1)
       
   873     {
       
   874       // echo '(debug) $session->validate_session: Key was not found in database<br />';
       
   875       return false;
       
   876     }
       
   877     $row = $db->fetchrow();
       
   878     $row['user_id'] =& $row['uid'];
       
   879     $ip = ip2hex($_SERVER['REMOTE_ADDR']);
       
   880     if($row['auth_level'] > $row['user_level'])
       
   881     {
       
   882       // Failed authorization check
       
   883       // echo '(debug) $session->validate_session: access to this auth level denied<br />';
       
   884       return false;
       
   885     }
       
   886     if($ip != $row['source_ip'])
       
   887     {
       
   888       // Failed IP address check
       
   889       // echo '(debug) $session->validate_session: IP address mismatch<br />';
       
   890       return false;
       
   891     }
       
   892     
       
   893     // Do the password validation
       
   894     $real_pass = $aes->decrypt($row['password'], $this->private_key, ENC_HEX);
       
   895     
       
   896     //die('<pre>'.print_r($keydata, true).'</pre>');
       
   897     if(sha1($real_pass) != $keydata[2])
       
   898     {
       
   899       // Failed password check
       
   900       // 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];
       
   901       return false;
       
   902     }
       
   903     
       
   904     $time_now = time();
       
   905     $time_key = $row['time'] + 900;
       
   906     if($time_now > $time_key && $row['auth_level'] > USER_LEVEL_MEMBER)
       
   907     {
       
   908       // Session timed out
       
   909       // echo '(debug) $session->validate_session: super session timed out<br />';
       
   910       $this->sw_timed_out = true;
       
   911       return false;
       
   912     }
       
   913     
       
   914     // If this is an elevated-access session key, update the time
       
   915     if( $row['auth_level'] > USER_LEVEL_MEMBER )
       
   916     {
       
   917       $this->sql('UPDATE '.table_prefix.'session_keys SET time='.time().' WHERE session_key=\''.$keyhash.'\';');
       
   918     }
       
   919     
       
   920     $row['password'] = md5($real_pass);
       
   921     return $row;
       
   922   }
       
   923   
       
   924   /**
       
   925    * Validates a session key, and returns the userdata associated with the key or false. Optimized for compatibility with the old MD5-based auth system.
       
   926    * @param string $key The session key to validate
       
   927    * @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.
       
   928    */
       
   929    
       
   930   function compat_validate_session($key)
       
   931   {
       
   932     global $db, $session, $paths, $template, $plugins; // Common objects
       
   933     $key = $db->escape($key);
       
   934     
       
   935     $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
       
   936                            LEFT JOIN '.table_prefix.'users AS u
       
   937                              ON u.user_id=k.user_id
       
   938                            WHERE k.session_key=\''.$key.'\';');
       
   939     if($db->numrows() < 1)
       
   940     {
       
   941       // echo '(debug) $session->validate_session: Key '.$key.' was not found in database<br />';
       
   942       return false;
       
   943     }
       
   944     $row = $db->fetchrow();
       
   945     $ip = ip2hex($_SERVER['REMOTE_ADDR']);
       
   946     if($row['auth_level'] > $row['user_level'])
       
   947     {
       
   948       // Failed authorization check
       
   949       // echo '(debug) $session->validate_session: user not authorized for this access level';
       
   950       return false;
       
   951     }
       
   952     if($ip != $row['source_ip'])
       
   953     {
       
   954       // Failed IP address check
       
   955       // echo '(debug) $session->validate_session: IP address mismatch; IP in table: '.$row['source_ip'].'; reported IP: '.$ip.'';
       
   956       return false;
       
   957     }
       
   958     
       
   959     // Do the password validation
       
   960     $real_key = md5($row['password'] . $row['salt']);
       
   961     
       
   962     //die('<pre>'.print_r($keydata, true).'</pre>');
       
   963     if($real_key != $key)
       
   964     {
       
   965       // Failed password check
       
   966       // echo '(debug) $session->validate_session: supplied password is wrong<br />Real key: '.$real_key.'<br />User key: '.$key;
       
   967       return false;
       
   968     }
       
   969     
       
   970     $time_now = time();
       
   971     $time_key = $row['time'] + 900;
       
   972     if($time_now > $time_key && $row['auth_level'] >= 1)
       
   973     {
       
   974       $this->sw_timed_out = true;
       
   975       // Session timed out
       
   976       // echo '(debug) $session->validate_session: super session timed out<br />';
       
   977       return false;
       
   978     }
       
   979     
       
   980     return $row;
       
   981   }
       
   982    
       
   983   /**
       
   984    * Demotes us to one less than the specified auth level. AKA destroys elevated authentication and/or logs out the user, depending on $level
       
   985    * @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
       
   986    * @return string 'success' if successful, or error on failure
       
   987    */
       
   988    
       
   989   function logout($level = USER_LEVEL_MEMBER)
       
   990   {
       
   991     global $db, $session, $paths, $template, $plugins; // Common objects
       
   992     $ou = $this->username;
       
   993     $oid = $this->user_id;
       
   994     if($level > USER_LEVEL_CHPREF)
       
   995     {
       
   996       $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
       
   997       if(!$this->user_logged_in || $this->auth_level < USER_LEVEL_MOD) return 'success';
       
   998       // Destroy elevated privileges
       
   999       $keyhash = md5(strrev($this->sid_super));
       
  1000       $this->sql('DELETE FROM '.table_prefix.'session_keys WHERE session_key=\''.$keyhash.'\' AND user_id=\'' . $this->user_id . '\';');
       
  1001       $this->sid_super = false;
       
  1002       $this->auth_level = USER_LEVEL_MEMBER;
       
  1003     }
       
  1004     else
       
  1005     {
       
  1006       if($this->user_logged_in)
       
  1007       {
       
  1008         // Completely destroy our session
       
  1009         if($this->auth_level > USER_LEVEL_CHPREF)
       
  1010         {
       
  1011           $this->logout(USER_LEVEL_ADMIN);
       
  1012         }
       
  1013         $this->sql('DELETE FROM '.table_prefix.'session_keys WHERE session_key=\''.md5($this->sid).'\';');
       
  1014         setcookie( 'sid', '', time()-(3600*24), scriptPath.'/' );
       
  1015       }
       
  1016     }
       
  1017     $code = $plugins->setHook('logout_success'); // , Array('level'=>$level,'old_username'=>$ou,'old_user_id'=>$oid));
       
  1018     foreach ( $code as $cmd )
       
  1019     {
       
  1020       eval($cmd);
       
  1021     }
       
  1022     return 'success';
       
  1023   }
       
  1024   
       
  1025   # Miscellaneous stuff
       
  1026   
       
  1027   /**
       
  1028    * Appends the high-privilege session key to the URL if we are authorized to do high-privilege stuff
       
  1029    * @param string $url The URL to add session data to
       
  1030    * @return string
       
  1031    */
       
  1032   
       
  1033   function append_sid($url)
       
  1034   {
       
  1035     $sep = ( strstr($url, '?') ) ? '&' : '?';
       
  1036     if ( $this->sid_super )
       
  1037     {
       
  1038       $url = $url . $sep . 'auth=' . urlencode($this->sid_super);
       
  1039       // echo($this->sid_super.'<br/>');
       
  1040     }
       
  1041     return $url;
       
  1042   }
       
  1043   
       
  1044   /**
       
  1045    * Grabs the user's password MD5
       
  1046    * @return string, or bool false if access denied
       
  1047    */
       
  1048    
       
  1049   function grab_password_hash()
       
  1050   {
       
  1051     if(!$this->password_hash) return false;
       
  1052     return $this->password_hash;
       
  1053   }
       
  1054   
       
  1055   /**
       
  1056    * Destroys the user's password MD5 in memory
       
  1057    */
       
  1058   
       
  1059   function disallow_password_grab()
       
  1060   {
       
  1061     $this->password_hash = false;
       
  1062     return false;
       
  1063   }
       
  1064   
       
  1065   /**
       
  1066    * Generates an AES key and stashes it in the database
       
  1067    * @return string Hex-encoded AES key
       
  1068    */
       
  1069    
       
  1070   function rijndael_genkey()
       
  1071   {
       
  1072     $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
       
  1073     $key = $aes->gen_readymade_key();
       
  1074     $keys = getConfig('login_key_cache');
       
  1075     if(is_string($keys))
       
  1076       $keys .= $key;
       
  1077     else
       
  1078       $keys = $key;
       
  1079     setConfig('login_key_cache', $keys);
       
  1080     return $key;
       
  1081   }
       
  1082   
       
  1083   /**
       
  1084    * Generate a totally random 128-bit value for MD5 challenges
       
  1085    * @return string
       
  1086    */
       
  1087    
       
  1088   function dss_rand()
       
  1089   {
       
  1090     $aes = new AESCrypt();
       
  1091     $random = $aes->randkey(128);
       
  1092     unset($aes);
       
  1093     return md5(microtime() . $random);
       
  1094   }
       
  1095   
       
  1096   /**
       
  1097    * Fetch a cached login public key using the MD5sum as an identifier. Each key can only be fetched once before it is destroyed.
       
  1098    * @param string $md5 The MD5 sum of the key
       
  1099    * @return string, or bool false on failure
       
  1100    */
       
  1101    
       
  1102   function fetch_public_key($md5)
       
  1103   {
       
  1104     $keys = getConfig('login_key_cache');
       
  1105     $keys = enano_str_split($keys, AES_BITS / 4);
       
  1106     
       
  1107     foreach($keys as $i => $k)
       
  1108     {
       
  1109       if(md5($k) == $md5)
       
  1110       {
       
  1111         unset($keys[$i]);
       
  1112         if(count($keys) > 0)
       
  1113         {
       
  1114           if ( strlen(getConfig('login_key_cache') ) > 64000 )
       
  1115           {
       
  1116             // This should only need to be done once every month or so for an average-size site
       
  1117             setConfig('login_key_cache', '');
       
  1118           }
       
  1119           else
       
  1120           {
       
  1121             $keys = implode('', array_values($keys));
       
  1122             setConfig('login_key_cache', $keys);
       
  1123           }
       
  1124         }
       
  1125         else
       
  1126         {
       
  1127           setConfig('login_key_cache', '');
       
  1128         }
       
  1129         return $k;
       
  1130       }
       
  1131     }
       
  1132     // Couldn't find the key...
       
  1133     return false;
       
  1134   }
       
  1135   
       
  1136   /**
       
  1137    * Adds a user to a group.
       
  1138    * @param int User ID
       
  1139    * @param int Group ID
       
  1140    * @param bool Group moderator - defaults to false
       
  1141    * @return bool True on success, false on failure
       
  1142    */
       
  1143   
       
  1144   function add_user_to_group($user_id, $group_id, $is_mod = false)
       
  1145   {
       
  1146     global $db, $session, $paths, $template, $plugins; // Common objects
       
  1147     
       
  1148     // Validation
       
  1149     if ( !is_int($user_id) || !is_int($group_id) || !is_bool($is_mod) )
       
  1150       return false;
       
  1151     if ( $user_id < 1 || $group_id < 1 )
       
  1152       return false;
       
  1153     
       
  1154     $mod_switch = ( $is_mod ) ? '1' : '0';
       
  1155     $q = $this->sql('SELECT member_id,is_mod FROM '.table_prefix.'group_members WHERE user_id=' . $user_id . ' AND group_id=' . $group_id . ';');
       
  1156     if ( !$q )
       
  1157       $db->_die();
       
  1158     if ( $db->numrows() < 1 )
       
  1159     {
       
  1160       // User is not in group
       
  1161       $this->sql('INSERT INTO '.table_prefix.'group_members(user_id,group_id,is_mod) VALUES(' . $user_id . ', ' . $group_id . ', ' . $mod_switch . ');');
       
  1162       return true;
       
  1163     }
       
  1164     else
       
  1165     {
       
  1166       $row = $db->fetchrow();
       
  1167       // Update modship status
       
  1168       if ( strval($row['is_mod']) == $mod_switch )
       
  1169       {
       
  1170         // Modship unchanged
       
  1171         return true;
       
  1172       }
       
  1173       else
       
  1174       {
       
  1175         // Modship changed
       
  1176         $this->sql('UPDATE '.table_prefix.'group_members SET is_mod=' . $mod_switch . ' WHERE member_id=' . $row['member_id'] . ';');
       
  1177         return true;
       
  1178       }
       
  1179     }
       
  1180     return false;
       
  1181   }
       
  1182   
       
  1183   /**
       
  1184    * Removes a user from a group.
       
  1185    * @param int User ID
       
  1186    * @param int Group ID
       
  1187    * @return bool True on success, false on failure
       
  1188    * @todo put a little more error checking in...
       
  1189    */
       
  1190   
       
  1191   function remove_user_from_group($user_id, $group_id)
       
  1192   {
       
  1193     if ( !is_int($user_id) || !is_int($group_id) )
       
  1194       return false;
       
  1195     $this->sql('DELETE FROM '.table_prefix."group_members WHERE user_id=$user_id AND group_id=$group_id;");
       
  1196     return true;
       
  1197   }
       
  1198   
       
  1199   /**
       
  1200    * Checks the banlist to ensure that we're an allowed user. Doesn't return anything because it dies if the user is banned.
       
  1201    */
       
  1202    
       
  1203   function check_banlist()
       
  1204   {
       
  1205     global $db, $session, $paths, $template, $plugins; // Common objects
       
  1206     if($this->compat)
       
  1207       $q = $this->sql('SELECT ban_id,ban_type,ban_value,is_regex FROM '.table_prefix.'banlist ORDER BY ban_type;');
       
  1208     else
       
  1209       $q = $this->sql('SELECT ban_id,ban_type,ban_value,is_regex,reason FROM '.table_prefix.'banlist ORDER BY ban_type;');
       
  1210     if(!$q) $db->_die('The banlist data could not be selected.');
       
  1211     $banned = false;
       
  1212     while($row = $db->fetchrow())
       
  1213     {
       
  1214       if($this->compat)
       
  1215         $row['reason'] = 'None available - session manager is in compatibility mode';
       
  1216       switch($row['ban_type'])
       
  1217       {
       
  1218       case BAN_IP:
       
  1219         if(intval($row['is_regex'])==1) {
       
  1220           if(preg_match('#'.$row['ban_value'].'#i', $_SERVER['REMOTE_ADDR']))
       
  1221           {
       
  1222             $banned = true;
       
  1223             $reason = $row['reason'];
       
  1224           }
       
  1225         }
       
  1226         else {
       
  1227           if($row['ban_value']==$_SERVER['REMOTE_ADDR']) { $banned = true; $reason = $row['reason']; }
       
  1228         }
       
  1229         break;
       
  1230       case BAN_USER:
       
  1231         if(intval($row['is_regex'])==1) {
       
  1232           if(preg_match('#'.$row['ban_value'].'#i', $this->username))
       
  1233           {
       
  1234             $banned = true;
       
  1235             $reason = $row['reason'];
       
  1236           }
       
  1237         }
       
  1238         else {
       
  1239           if($row['ban_value']==$this->username) { $banned = true; $reason = $row['reason']; }
       
  1240         }
       
  1241         break;
       
  1242       case BAN_EMAIL:
       
  1243         if(intval($row['is_regex'])==1) {
       
  1244           if(preg_match('#'.$row['ban_value'].'#i', $this->email))
       
  1245           {
       
  1246             $banned = true;
       
  1247             $reason = $row['reason'];
       
  1248           }
       
  1249         }
       
  1250         else {
       
  1251           if($row['ban_value']==$this->email) { $banned = true; $reason = $row['reason']; }
       
  1252         }
       
  1253         break;
       
  1254       default:
       
  1255         die('Ban error: rule "'.$row['ban_value'].'" has an invalid type ('.$row['ban_type'].')');
       
  1256       }
       
  1257     }
       
  1258     if($banned && $paths->get_pageid_from_url() != $paths->nslist['Special'].'CSS')
       
  1259     {
       
  1260       // This guy is banned - kill the session, kill the database connection, bail out, and be pretty about it
       
  1261       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>');
       
  1262       exit;
       
  1263     }
       
  1264   }
       
  1265   
       
  1266   # Registration
       
  1267   
       
  1268   /**
       
  1269    * Registers a user. This does not perform any type of login.
       
  1270    * @param string $username
       
  1271    * @param string $password This should be unencrypted.
       
  1272    * @param string $email
       
  1273    * @param string $real_name Optional, defaults to ''.
       
  1274    */
       
  1275    
       
  1276   function create_user($username, $password, $email, $real_name = '') {
       
  1277     global $db, $session, $paths, $template, $plugins; // Common objects
       
  1278     
       
  1279     // Initialize AES
       
  1280     $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
       
  1281     
       
  1282     if(!preg_match('#^'.$this->valid_username.'$#', $username)) return 'The username you chose contains invalid characters.';
       
  1283     $username = $this->prepare_text($username);
       
  1284     $email = $this->prepare_text($email);
       
  1285     $real_name = $this->prepare_text($real_name);
       
  1286     $password = $aes->encrypt($password, $this->private_key, ENC_HEX);
       
  1287     
       
  1288     $nameclause = ( $real_name != '' ) ? ' OR real_name=\''.$real_name.'\'' : '';
       
  1289     $q = $this->sql('SELECT * FROM '.table_prefix.'users WHERE lcase(username)=\''.strtolower($username).'\' OR email=\''.$email.'\''.$nameclause.';');
       
  1290     if($db->numrows() > 0) {
       
  1291       $r = 'The ';
       
  1292       $i=0;
       
  1293       $row = $db->fetchrow();
       
  1294       // Wow! An error checker that actually speaks English with the properest grammar! :-P
       
  1295       if($row['username'] == $username) { $r .= 'username'; $i++; }
       
  1296       if($row['email'] == $email) { if($i) $r.=', '; $r .= 'e-mail address'; $i++; }
       
  1297       if($row['real_name'] == $real_name && $real_name != '') { if($i) $r.=', and '; $r .= 'real name'; $i++; }
       
  1298       $r .= ' that you entered ';
       
  1299       $r .= ( $i == 1 ) ? 'is' : 'are';
       
  1300       $r .= ' already in use by another user.';
       
  1301       return $r;
       
  1302     }
       
  1303     
       
  1304     // Require the account to be activated?
       
  1305     switch(getConfig('account_activation'))
       
  1306     {
       
  1307       case 'none':
       
  1308       default:
       
  1309         $active = '1';
       
  1310         break;
       
  1311       case 'user':
       
  1312         $active = '0';
       
  1313         break;
       
  1314       case 'admin':
       
  1315         $active = '0';
       
  1316         break;
       
  1317     }
       
  1318     
       
  1319     // Generate a totally random activation key
       
  1320     $actkey = sha1 ( microtime() . mt_rand() );
       
  1321 
       
  1322     // We good, create the user    
       
  1323     $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.' )');
       
  1324     
       
  1325     // Require the account to be activated?
       
  1326     switch(getConfig('account_activation'))
       
  1327     {
       
  1328       case 'none':
       
  1329       default:
       
  1330         break;
       
  1331       case 'user':
       
  1332         $a = $this->send_activation_mail($username);
       
  1333         if(!$a)
       
  1334         {
       
  1335           $this->admin_activation_request($username);
       
  1336           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;
       
  1337         }
       
  1338         break;
       
  1339       case 'admin':
       
  1340         $this->admin_activation_request($username);
       
  1341         break;
       
  1342     }
       
  1343     
       
  1344     // Leave some data behind for the hook
       
  1345     $code = $plugins->setHook('user_registered'); // , Array('username'=>$username));
       
  1346     foreach ( $code as $cmd )
       
  1347     {
       
  1348       eval($cmd);
       
  1349     }
       
  1350     
       
  1351     // $this->register_session($username, $password);
       
  1352     return 'success';
       
  1353   }
       
  1354   
       
  1355   /**
       
  1356    * Attempts to send an e-mail to the specified user with activation instructions.
       
  1357    * @param string $u The usernamd of the user requesting activation
       
  1358    * @return bool true on success, false on failure
       
  1359    */
       
  1360    
       
  1361   function send_activation_mail($u, $actkey = false)
       
  1362   {
       
  1363     global $db, $session, $paths, $template, $plugins; // Common objects
       
  1364     $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;');
       
  1365     $un = $db->fetchrow();
       
  1366     $admin_user = $un['username'];
       
  1367     $q = $this->sql('SELECT username,activation_key,account_active,email FROM '.table_prefix.'users WHERE username=\''.$db->escape($u).'\';');
       
  1368     $r = $db->fetchrow();
       
  1369     if ( empty($r['email']) )
       
  1370       $db->_die('BUG: $session->send_activation_mail(): no e-mail address in row');
       
  1371     $message = 'Dear '.$u.',
       
  1372 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:
       
  1373     
       
  1374 ';
       
  1375     if(isset($_SERVER['HTTPS'])) $prot = 'https';
       
  1376     else $prot = 'http';                                                                           
       
  1377     if($_SERVER['SERVER_PORT'] == '80') $p = '';
       
  1378     else $p = ':'.$_SERVER['SERVER_PORT'];
       
  1379     $sidbak = false;
       
  1380     if($this->sid_super)
       
  1381       $sidbak = $this->sid_super;
       
  1382     $this->sid_super = false;
       
  1383     $aklink = makeUrlNS('Special', 'ActivateAccount/'.str_replace(' ', '_', $u).'/'. ( ( is_string($actkey) ) ? $actkey : $r['activation_key'] ) );
       
  1384     if($sidbak)
       
  1385       $this->sid_super = $sidbak;
       
  1386     unset($sidbak);
       
  1387     $message .= "$prot://".$_SERVER['HTTP_HOST'].$p.$aklink;
       
  1388       $message .= "\n\nSincerely yours, \n$admin_user and the ".$_SERVER['HTTP_HOST']." administration team";
       
  1389     error_reporting(E_ALL);
       
  1390     dc_dump($r, 'session: about to send activation e-mail to '.$r['email']);
       
  1391     if(getConfig('smtp_enabled') == '1')
       
  1392     {
       
  1393       $result = smtp_send_email($r['email'], getConfig('site_name').' website account activation', preg_replace("#(?<!\r)\n#s", "\n", $message), getConfig('contact_email'));
       
  1394       if($result == 'success') $result = true;
       
  1395       else { echo $result; $result = false; }
       
  1396     } else {
       
  1397       $result = mail($r['email'], getConfig('site_name').' website account activation', preg_replace("#(?<!\r)\n#s", "\n", $message), 'From: '.getConfig('contact_email'));
       
  1398     }
       
  1399     return $result;
       
  1400   }
       
  1401   
       
  1402   /**
       
  1403    * Sends an e-mail to a user so they can reset their password.
       
  1404    * @param int $user The user ID, or username if it's a string
       
  1405    * @return bool true on success, false on failure
       
  1406    */
       
  1407    
       
  1408   function mail_password_reset($user)
       
  1409   {
       
  1410     global $db, $session, $paths, $template, $plugins; // Common objects
       
  1411     if(is_int($user))
       
  1412     {
       
  1413       $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
       
  1414     }
       
  1415     elseif(is_string($user))
       
  1416     {
       
  1417       $q = $this->sql('SELECT user_id,username,email FROM '.table_prefix.'users WHERE username=\''.$db->escape($user).'\';');
       
  1418     }
       
  1419     else
       
  1420     {
       
  1421       return false;
       
  1422     }
       
  1423     
       
  1424     $row = $db->fetchrow();
       
  1425     $temp_pass = $this->random_pass();
       
  1426     
       
  1427     $this->register_temp_password($row['user_id'], $temp_pass);
       
  1428     
       
  1429     $site_name = getConfig('site_name');
       
  1430     
       
  1431     $message = "Dear {$row['username']},
       
  1432     
       
  1433 Someone (hopefully you) on the {$site_name} website requested that a new password be created.
       
  1434 
       
  1435 The request was sent from the IP address {$_SERVER['REMOTE_ADDR']}.
       
  1436 
       
  1437 If you did not request the new password, then you do not need to do anything; the password will be invalidated after 24 hours.
       
  1438 
       
  1439 If you did request this password, then please log in using the password shown below:
       
  1440 
       
  1441   Password: {$temp_pass}
       
  1442   
       
  1443 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.
       
  1444 
       
  1445 Sincerely yours,
       
  1446 The {$site_name} administration team
       
  1447 ";
       
  1448     
       
  1449     if(getConfig('smtp_enabled') == '1')
       
  1450     {
       
  1451       $result = smtp_send_email($row['email'], getConfig('site_name').' password reset', preg_replace("#(?<!\r)\n#s", "\n", $message), getConfig('contact_email'));
       
  1452       if($result == 'success')
       
  1453       {
       
  1454         $result = true;
       
  1455       }
       
  1456       else
       
  1457       {
       
  1458         echo '<p>'.$result.'</p>';
       
  1459         $result = false;
       
  1460       }
       
  1461     } else {
       
  1462       $result = mail($row['email'], getConfig('site_name').' password reset', preg_replace("#(?<!\r)\n#s", "\n", $message), 'From: '.getConfig('contact_email'));
       
  1463     }
       
  1464     return $result;
       
  1465   }
       
  1466   
       
  1467   /**
       
  1468    * Sets the temporary password for the specified user to whatever is specified.
       
  1469    * @param int $user_id
       
  1470    * @param string $password
       
  1471    * @return bool
       
  1472    */
       
  1473    
       
  1474   function register_temp_password($user_id, $password)
       
  1475   {
       
  1476     $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
       
  1477     $temp_pass = $aes->encrypt($password, $this->private_key, ENC_HEX);
       
  1478     $this->sql('UPDATE '.table_prefix.'users SET temp_password=\'' . $temp_pass . '\',temp_password_time='.time().' WHERE user_id='.intval($user_id).';');
       
  1479   }
       
  1480   
       
  1481   /**
       
  1482    * Sends a request to the admin panel to have the username $u activated.
       
  1483    * @param string $u The username of the user requesting activation
       
  1484    */
       
  1485   
       
  1486   function admin_activation_request($u)
       
  1487   {
       
  1488     global $db;
       
  1489     $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).'\');');
       
  1490   }
       
  1491   
       
  1492   /**
       
  1493    * Activates a user account. If the action fails, a report is sent to the admin.
       
  1494    * @param string $user The username of the user requesting activation
       
  1495    * @param string $key The activation key
       
  1496    */
       
  1497   
       
  1498   function activate_account($user, $key)
       
  1499   {
       
  1500     global $db, $session, $paths, $template, $plugins; // Common objects
       
  1501     $this->sql('UPDATE '.table_prefix.'users SET account_active=1 WHERE username=\''.$db->escape($user).'\' AND activation_key=\''.$db->escape($key).'\';');
       
  1502     $r = mysql_affected_rows();
       
  1503     if ( $r > 0 )
       
  1504     {
       
  1505       $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'].'\')');
       
  1506     }
       
  1507     else
       
  1508     {
       
  1509       $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'].'\')');
       
  1510     }
       
  1511     return $r;
       
  1512   }
       
  1513   
       
  1514   /**
       
  1515    * For a given user level identifier (USER_LEVEL_*), returns a string describing that user level.
       
  1516    * @param int User level
       
  1517    * @return string
       
  1518    */
       
  1519   
       
  1520   function userlevel_to_string($user_level)
       
  1521   {
       
  1522     switch ( $user_level )
       
  1523     {
       
  1524       case USER_LEVEL_GUEST:
       
  1525         return 'Low - guest privileges';
       
  1526       case USER_LEVEL_MEMBER:
       
  1527         return 'Standard - normal member level';
       
  1528       case USER_LEVEL_CHPREF:
       
  1529         return 'Medium - user can change his/her own e-mail address and password';
       
  1530       case USER_LEVEL_MOD:
       
  1531         return 'High - moderator privileges';
       
  1532       case USER_LEVEL_ADMIN:
       
  1533         return 'Highest - administrative privileges';
       
  1534       default:
       
  1535         return "Unknown ($user_level)";
       
  1536     }
       
  1537   }
       
  1538   
       
  1539   /**
       
  1540    * 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.
       
  1541    * @param int $user_id The user ID of the user to update - this cannot be changed
       
  1542    * @param string $username The new username
       
  1543    * @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
       
  1544    * @param string $password The new password
       
  1545    * @param string $email The new e-mail address
       
  1546    * @param string $realname The new real name
       
  1547    * @param string $signature The updated forum/comment signature
       
  1548    * @param int $user_level The updated user level
       
  1549    * @return string 'success' if successful, or array of error strings on failure
       
  1550    */
       
  1551    
       
  1552   function update_user($user_id, $username = false, $old_pass = false, $password = false, $email = false, $realname = false, $signature = false, $user_level = false)
       
  1553   {
       
  1554     global $db, $session, $paths, $template, $plugins; // Common objects
       
  1555     
       
  1556     // Create some arrays
       
  1557     
       
  1558     $errors = Array(); // Used to hold error strings
       
  1559     $strs = Array();   // Sub-query statements
       
  1560     
       
  1561     // Scan the user ID for problems
       
  1562     if(intval($user_id) < 1) $errors[] = 'SQL injection attempt';
       
  1563     
       
  1564     // Instanciate the AES encryption class
       
  1565     $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE);
       
  1566     
       
  1567     // If all of our input vars are false, then we've effectively done our job so get out of here
       
  1568     if($username === false && $password === false && $email === false && $realname === false && $signature === false && $user_level === false)
       
  1569     {
       
  1570    // echo 'debug: $session->update_user(): success (no changes requested)';
       
  1571       return 'success';
       
  1572     }
       
  1573     
       
  1574     // Initialize our authentication check
       
  1575     $authed = false;
       
  1576     
       
  1577     // Verify the inputted password
       
  1578     if(is_string($old_pass))
       
  1579     {
       
  1580       $q = $this->sql('SELECT password FROM '.table_prefix.'users WHERE user_id='.$user_id.';');
       
  1581       if($db->numrows() < 1)
       
  1582       {
       
  1583         $errors[] = 'The password data could not be selected for verification.';
       
  1584       }
       
  1585       else
       
  1586       {
       
  1587         $row = $db->fetchrow();
       
  1588         $real = $aes->decrypt($row['password'], $this->private_key, ENC_HEX);
       
  1589         if($real == $old_pass)
       
  1590           $authed = true;
       
  1591       }
       
  1592     }
       
  1593     
       
  1594     elseif(is_array($old_pass))
       
  1595     {
       
  1596       $old_pass = $aes->decrypt($old_pass[0], $old_pass[1]);
       
  1597       $q = $this->sql('SELECT password FROM '.table_prefix.'users WHERE user_id='.$user_id.';');
       
  1598       if($db->numrows() < 1)
       
  1599       {
       
  1600         $errors[] = 'The password data could not be selected for verification.';
       
  1601       }
       
  1602       else
       
  1603       {
       
  1604         $row = $db->fetchrow();
       
  1605         $real = $aes->decrypt($row['password'], $this->private_key, ENC_HEX);
       
  1606         if($real == $old_pass)
       
  1607           $authed = true;
       
  1608       }
       
  1609     }
       
  1610     
       
  1611     // Initialize our query
       
  1612     $q = 'UPDATE '.table_prefix.'users SET ';
       
  1613     
       
  1614     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
       
  1615     {
       
  1616       // Username
       
  1617       if(is_string($username))
       
  1618       {
       
  1619         // Check the username for problems
       
  1620         if(!preg_match('#^'.$this->valid_username.'$#', $username))
       
  1621           $errors[] = 'The username you entered contains invalid characters.';
       
  1622         $strs[] = 'username=\''.$db->escape($username).'\'';
       
  1623       }
       
  1624       // Password
       
  1625       if(is_string($password) && strlen($password) >= 6)
       
  1626       {
       
  1627         // Password needs to be encrypted before being stashed
       
  1628         $encpass = $aes->encrypt($password, $this->private_key, ENC_HEX);
       
  1629         if(!$encpass)
       
  1630           $errors[] = 'The password could not be encrypted due to an internal error.';
       
  1631         $strs[] = 'password=\''.$encpass.'\'';
       
  1632       }
       
  1633       // E-mail addy
       
  1634       if(is_string($email))
       
  1635       {
       
  1636         // I didn't write this regex.
       
  1637         if(!preg_match('/^(?:[\w\d]+\.?)+@(?:(?:[\w\d]\-?)+\.)+\w{2,4}$/', $email))
       
  1638           $errors[] = 'The e-mail address you entered is invalid.';
       
  1639         $strs[] = 'email=\''.$db->escape($email).'\'';
       
  1640       }
       
  1641     }
       
  1642     // Real name
       
  1643     if(is_string($realname))
       
  1644     {
       
  1645       $strs[] = 'real_name=\''.$db->escape($realname).'\'';
       
  1646     }
       
  1647     // Forum/comment signature
       
  1648     if(is_string($signature))
       
  1649     {
       
  1650       $strs[] = 'signature=\''.$db->escape($signature).'\'';
       
  1651     }
       
  1652     // User level
       
  1653     if(is_int($user_level))
       
  1654     {
       
  1655       $strs[] = 'user_level='.$user_level;
       
  1656     }
       
  1657     
       
  1658     // Add our generated query to the query string
       
  1659     $q .= implode(',', $strs);
       
  1660     
       
  1661     // One last error check
       
  1662     if(sizeof($strs) < 1) $errors[] = 'An internal error occured building the SQL query, this is a bug';
       
  1663     if(sizeof($errors) > 0) return $errors;
       
  1664     
       
  1665     // Free our temp arrays
       
  1666     unset($strs, $errors);
       
  1667     
       
  1668     // Finalize the query and run it
       
  1669     $q .= ' WHERE user_id='.$user_id.';';
       
  1670     $this->sql($q);
       
  1671     
       
  1672     // We also need to trigger re-activation.
       
  1673     if ( is_string($email) )
       
  1674     {
       
  1675       switch(getConfig('account_activation'))
       
  1676       {
       
  1677         case 'user':
       
  1678         case 'admin':
       
  1679           
       
  1680           if ( $session->user_level >= USER_LEVEL_MOD && getConfig('account_activation') == 'admin' )
       
  1681             // Don't require re-activation by admins for admins
       
  1682             break;
       
  1683           
       
  1684           // retrieve username
       
  1685           if ( !$username )
       
  1686           {
       
  1687             $q = $this->sql('SELECT username FROM '.table_prefix.'users WHERE user_id='.$user_id.';');
       
  1688             if($db->numrows() < 1)
       
  1689             {
       
  1690               $errors[] = 'The username could not be selected.';
       
  1691             }
       
  1692             else
       
  1693             {
       
  1694               $row = $db->fetchrow();
       
  1695               $username = $row['username'];
       
  1696             }
       
  1697           }
       
  1698           if ( !$username )
       
  1699             return $errors;
       
  1700           
       
  1701           // Generate a totally random activation key
       
  1702           $actkey = sha1 ( microtime() . mt_rand() );
       
  1703           $a = $this->send_activation_mail($username, $actkey);
       
  1704           if(!$a)
       
  1705           {
       
  1706             $this->admin_activation_request($username);
       
  1707           }
       
  1708           // Deactivate the account until e-mail is confirmed
       
  1709           $q = $db->sql_query('UPDATE '.table_prefix.'users SET account_active=0,activation_key=\'' . $actkey . '\' WHERE user_id=' . $user_id . ';');
       
  1710           break;
       
  1711       }
       
  1712     }
       
  1713     
       
  1714     // Yay! We're done
       
  1715     return 'success';
       
  1716   }
       
  1717   
       
  1718   #
       
  1719   # Access Control Lists
       
  1720   #
       
  1721   
       
  1722   /**
       
  1723    * Creates a new permission field in memory. If the permissions are set in the database, they are used. Otherwise, $default_perm is used.
       
  1724    * @param string $acl_type An identifier for this field
       
  1725    * @param int $default_perm Whether permission should be granted or not if it's not specified in the ACLs.
       
  1726    * @param string $desc A human readable name for the permission type
       
  1727    * @param array $deps The list of dependencies - this should be an array of ACL types
       
  1728    * @param string $scope Which namespaces this field should apply to. This should be either a pipe-delimited list of namespace IDs or just "All".
       
  1729    */
       
  1730    
       
  1731   function register_acl_type($acl_type, $default_perm = AUTH_DISALLOW, $desc = false, $deps = Array(), $scope = 'All')
       
  1732   {
       
  1733     if(isset($this->acl_types[$acl_type]))
       
  1734       return false;
       
  1735     else
       
  1736     {
       
  1737       if(!$desc)
       
  1738       {
       
  1739         $desc = capitalize_first_letter(str_replace('_', ' ', $acl_type));
       
  1740       }
       
  1741       $this->acl_types[$acl_type] = $default_perm;
       
  1742       $this->acl_descs[$acl_type] = $desc;
       
  1743       $this->acl_deps[$acl_type] = $deps;
       
  1744       $this->acl_scope[$acl_type] = explode('|', $scope);
       
  1745     }
       
  1746     return true;
       
  1747   }
       
  1748   
       
  1749   /**
       
  1750    * Tells us whether permission $type is allowed or not based on the current rules.
       
  1751    * @param string $type The permission identifier ($acl_type passed to sessionManager::register_acl_type())
       
  1752    * @param bool $no_deps If true, disables dependency checking
       
  1753    * @return bool True if allowed, false if denied or if an error occured
       
  1754    */
       
  1755    
       
  1756   function get_permissions($type, $no_deps = false)
       
  1757   {
       
  1758     global $db, $session, $paths, $template, $plugins; // Common objects
       
  1759     if ( isset( $this->perms[$type] ) )
       
  1760     {
       
  1761       if ( $this->perms[$type] == AUTH_DENY )
       
  1762         $ret = false;
       
  1763       else if ( $this->perms[$type] == AUTH_WIKIMODE && $paths->wiki_mode )
       
  1764         $ret = true;
       
  1765       else if ( $this->perms[$type] == AUTH_WIKIMODE && !$paths->wiki_mode )
       
  1766         $ret = false;
       
  1767       else if ( $this->perms[$type] == AUTH_ALLOW )
       
  1768         $ret = true;
       
  1769       else if ( $this->perms[$type] == AUTH_DISALLOW )
       
  1770         $ret = false;
       
  1771     }
       
  1772     else if(isset($this->acl_types[$type]))
       
  1773     {
       
  1774       if ( $this->acl_types[$type] == AUTH_DENY )
       
  1775         $ret = false;
       
  1776       else if ( $this->acl_types[$type] == AUTH_WIKIMODE && $paths->wiki_mode )
       
  1777         $ret = true;
       
  1778       else if ( $this->acl_types[$type] == AUTH_WIKIMODE && !$paths->wiki_mode )
       
  1779         $ret = false;
       
  1780       else if ( $this->acl_types[$type] == AUTH_ALLOW )
       
  1781         $ret = true;
       
  1782       else if ( $this->acl_types[$type] == AUTH_DISALLOW )
       
  1783         $ret = false;
       
  1784     }
       
  1785     else
       
  1786     {
       
  1787       // ACL type is undefined
       
  1788       trigger_error('Unknown access type "' . $type . '"', E_USER_WARNING);
       
  1789       return false; // Be on the safe side and deny access
       
  1790     }
       
  1791     if ( !$no_deps )
       
  1792     {
       
  1793       if ( !$this->acl_check_deps($type) )
       
  1794         return false;
       
  1795     }
       
  1796     return $ret;
       
  1797   }
       
  1798   
       
  1799   /**
       
  1800    * Fetch the permissions that apply to the current user for the page specified. The object you get will have the get_permissions method
       
  1801    * and several other abilities.
       
  1802    * @param string $page_id
       
  1803    * @param string $namespace
       
  1804    * @return object
       
  1805    */
       
  1806    
       
  1807   function fetch_page_acl($page_id, $namespace)
       
  1808   {
       
  1809     global $db, $session, $paths, $template, $plugins; // Common objects
       
  1810     
       
  1811     if ( count ( $this->acl_base_cache ) < 1 )
       
  1812     {
       
  1813       // Permissions table not yet initialized
       
  1814       return false;
       
  1815     }
       
  1816     
       
  1817     //if ( !isset( $paths->pages[$paths->nslist[$namespace] . $page_id] ) )
       
  1818     //{
       
  1819     //  // Page does not exist
       
  1820     //  return false;
       
  1821     //}
       
  1822     
       
  1823     $object = new Session_ACLPageInfo( $page_id, $namespace, $this->acl_types, $this->acl_descs, $this->acl_deps, $this->acl_base_cache );
       
  1824     
       
  1825     return $object;
       
  1826     
       
  1827   }
       
  1828   
       
  1829   /**
       
  1830    * Read all of our permissions from the database and process/apply them. This should be called after the page is determined.
       
  1831    * @access private
       
  1832    */
       
  1833   
       
  1834   function init_permissions()
       
  1835   {
       
  1836     global $db, $session, $paths, $template, $plugins; // Common objects
       
  1837     // Initialize the permissions list with some defaults
       
  1838     $this->perms = $this->acl_types;
       
  1839     $this->acl_defaults_used = $this->perms;
       
  1840     
       
  1841     // Fetch sitewide defaults from the permissions table
       
  1842     $bs = 'SELECT rules FROM '.table_prefix.'acl WHERE page_id IS NULL AND namespace IS NULL AND ( ';
       
  1843     
       
  1844     $q = Array();
       
  1845     $q[] = '( target_type='.ACL_TYPE_USER.' AND target_id='.$this->user_id.' )';
       
  1846     if(count($this->groups) > 0)
       
  1847     {
       
  1848       foreach($this->groups as $g_id => $g_name)
       
  1849       {
       
  1850         $q[] = '( target_type='.ACL_TYPE_GROUP.' AND target_id='.intval($g_id).' )';
       
  1851       }
       
  1852     }
       
  1853     $bs .= implode(' OR ', $q) . ' ) ORDER BY target_type ASC, target_id ASC;';
       
  1854     $q = $this->sql($bs);
       
  1855     if ( $row = $db->fetchrow() )
       
  1856     {
       
  1857       do {
       
  1858         $rules = $this->string_to_perm($row['rules']);
       
  1859         $is_everyone = ( $row['target_type'] == ACL_TYPE_GROUP && $row['target_id'] == 1 );
       
  1860         $this->acl_merge_with_current($rules, $is_everyone);
       
  1861       } while ( $row = $db->fetchrow() );
       
  1862     }
       
  1863     
       
  1864     // Eliminate types that don't apply to this namespace
       
  1865     foreach ( $this->perms AS $i => $perm )
       
  1866     {
       
  1867       if ( !in_array ( $paths->namespace, $this->acl_scope[$i] ) && !in_array('All', $this->acl_scope[$i]) )
       
  1868       {
       
  1869         unset($this->perms[$i]);
       
  1870       }
       
  1871     }
       
  1872     
       
  1873     // Cache the sitewide permissions for later use
       
  1874     $this->acl_base_cache = $this->perms;
       
  1875     
       
  1876     // Build a query to grab ACL info
       
  1877     $bs = 'SELECT rules,target_type,target_id FROM '.table_prefix.'acl WHERE ( ';
       
  1878     $q = Array();
       
  1879     $q[] = '( target_type='.ACL_TYPE_USER.' AND target_id='.$this->user_id.' )';
       
  1880     if(count($this->groups) > 0)
       
  1881     {
       
  1882       foreach($this->groups as $g_id => $g_name)
       
  1883       {
       
  1884         $q[] = '( target_type='.ACL_TYPE_GROUP.' AND target_id='.intval($g_id).' )';
       
  1885       }
       
  1886     }
       
  1887     // 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
       
  1888     // permissions to override group permissions.
       
  1889     $bs .= implode(' OR ', $q) . ' ) AND ( page_id=\''.$db->escape($paths->cpage['urlname_nons']).'\' AND namespace=\''.$db->escape($paths->namespace).'\' )     
       
  1890       ORDER BY target_type ASC, page_id ASC, namespace ASC;';
       
  1891     $q = $this->sql($bs);
       
  1892     if ( $row = $db->fetchrow() )
       
  1893     {
       
  1894       do {
       
  1895         $rules = $this->string_to_perm($row['rules']);
       
  1896         $is_everyone = ( $row['target_type'] == ACL_TYPE_GROUP && $row['target_id'] == 1 );
       
  1897         $this->acl_merge_with_current($rules, $is_everyone);
       
  1898       } while ( $row = $db->fetchrow() );
       
  1899     }
       
  1900     
       
  1901   }
       
  1902   
       
  1903   /**
       
  1904    * Extends the scope of a permission type.
       
  1905    * @param string The name of the permission type
       
  1906    * @param string The namespace(s) that should be covered. This can be either one namespace ID or a pipe-delimited list.
       
  1907    * @param object Optional - the current $paths object, in case we're doing this from the acl_rule_init hook
       
  1908    */
       
  1909    
       
  1910   function acl_extend_scope($perm_type, $namespaces, &$p_in)
       
  1911   {
       
  1912     global $db, $session, $paths, $template, $plugins; // Common objects
       
  1913     $p_obj = ( is_object($p_in) ) ? $p_in : $paths;
       
  1914     $nslist = explode('|', $namespaces);
       
  1915     foreach ( $nslist as $i => $ns )
       
  1916     {
       
  1917       if ( !isset($p_obj->nslist[$ns]) )
       
  1918       {
       
  1919         unset($nslist[$i]);
       
  1920       }
       
  1921       else
       
  1922       {
       
  1923         $this->acl_scope[$perm_type][] = $ns;
       
  1924         if ( isset($this->acl_types[$perm_type]) && !isset($this->perms[$perm_type]) )
       
  1925         {
       
  1926           $this->perms[$perm_type] = $this->acl_types[$perm_type];
       
  1927         }
       
  1928       }
       
  1929     }
       
  1930   }
       
  1931   
       
  1932   /**
       
  1933    * Converts a permissions field into a string for database insertion. Similar in spirit to serialize().
       
  1934    * @param array $perms An associative array with only integers as values
       
  1935    * @return string
       
  1936    */
       
  1937    
       
  1938   function perm_to_string($perms)
       
  1939   {
       
  1940     $s = '';
       
  1941     foreach($perms as $perm => $ac)
       
  1942     {
       
  1943       $s .= "$perm=$ac;";
       
  1944     }
       
  1945     return $s;
       
  1946   }
       
  1947   
       
  1948   /**
       
  1949    * Converts a permissions string back to an array.
       
  1950    * @param string $perms The result from sessionManager::perm_to_string()
       
  1951    * @return array
       
  1952    */
       
  1953    
       
  1954   function string_to_perm($perms)
       
  1955   {
       
  1956     $ret = Array();
       
  1957     preg_match_all('#([a-z0-9_-]+)=([0-9]+);#i', $perms, $matches);
       
  1958     foreach($matches[1] as $i => $t)
       
  1959     {
       
  1960       $ret[$t] = intval($matches[2][$i]);
       
  1961     }
       
  1962     return $ret;
       
  1963   }
       
  1964   
       
  1965   /**
       
  1966    * Merges two ACL arrays. Both parameters should be permission list arrays. The second group takes precedence over the first, but AUTH_DENY always prevails.
       
  1967    * @param array $perm1 The first set of permissions
       
  1968    * @param array $perm2 The second set of permissions
       
  1969    * @return array
       
  1970    */
       
  1971    
       
  1972   function acl_merge($perm1, $perm2)
       
  1973   {
       
  1974     $ret = $perm1;
       
  1975     foreach ( $perm2 as $type => $level )
       
  1976     {
       
  1977       if ( isset( $ret[$type] ) )
       
  1978       {
       
  1979         if ( $ret[$type] != AUTH_DENY )
       
  1980           $ret[$type] = $level;
       
  1981       }
       
  1982       // else
       
  1983       // {
       
  1984       //   $ret[$type] = $level;
       
  1985       // }
       
  1986     }
       
  1987     return $ret;
       
  1988   }
       
  1989   
       
  1990   /**
       
  1991    * Merges the ACL array sent with the current permissions table, deciding precedence based on whether defaults are in effect or not.
       
  1992    * @param array The array to merge into the master ACL list
       
  1993    * @param bool If true, $perm is treated as the "new default"
       
  1994    * @param int 1 if this is a site-wide ACL, 2 if page-specific. Defaults to 2.
       
  1995    */
       
  1996   
       
  1997   function acl_merge_with_current($perm, $is_everyone = false, $scope = 2)
       
  1998   {
       
  1999     foreach ( $this->perms as $i => $p )
       
  2000     {
       
  2001       if ( isset($perm[$i]) )
       
  2002       {
       
  2003         if ( $is_everyone && !$this->acl_defaults_used[$i] )
       
  2004           continue;
       
  2005         // Decide precedence
       
  2006         if ( isset($this->acl_defaults_used[$i]) )
       
  2007         {
       
  2008           //echo "$i: default in use, overriding to: {$perm[$i]}<br />";
       
  2009           // Defaults are in use, override
       
  2010           $this->perms[$i] = $perm[$i];
       
  2011           $this->acl_defaults_used[$i] = ( $is_everyone );
       
  2012         }
       
  2013         else
       
  2014         {
       
  2015           //echo "$i: default NOT in use";
       
  2016           // Defaults are not in use, merge as normal
       
  2017           if ( $this->perms[$i] != AUTH_DENY )
       
  2018           {
       
  2019             //echo ", but overriding";
       
  2020             $this->perms[$i] = $perm[$i];
       
  2021           }
       
  2022           //echo "<br />";
       
  2023         }
       
  2024       }
       
  2025     }
       
  2026   }
       
  2027   
       
  2028   /**
       
  2029    * Merges two ACL arrays. Both parameters should be permission list arrays. The second group takes precedence
       
  2030    * over the first, without exceptions. This is used to merge the hardcoded defaults with admin-specified
       
  2031    * defaults, which take precedence.
       
  2032    * @param array $perm1 The first set of permissions
       
  2033    * @param array $perm2 The second set of permissions
       
  2034    * @return array
       
  2035    */
       
  2036    
       
  2037   function acl_merge_complete($perm1, $perm2)
       
  2038   {
       
  2039     $ret = $perm1;
       
  2040     foreach ( $perm2 as $type => $level )
       
  2041     {
       
  2042       $ret[$type] = $level;
       
  2043     }
       
  2044     return $ret;
       
  2045   }
       
  2046   
       
  2047   /**
       
  2048    * Tell us if the dependencies for a given permission are met.
       
  2049    * @param string The ACL permission ID
       
  2050    * @return bool
       
  2051    */
       
  2052    
       
  2053   function acl_check_deps($type)
       
  2054   {
       
  2055     if(!isset($this->acl_deps[$type])) // This will only happen if the permissions table is hacked or improperly accessed
       
  2056       return true;
       
  2057     if(sizeof($this->acl_deps[$type]) < 1)
       
  2058       return true;
       
  2059     $deps = $this->acl_deps[$type];
       
  2060     while(true)
       
  2061     {
       
  2062       $full_resolved = true;
       
  2063       $j = sizeof($deps);
       
  2064       for ( $i = 0; $i < $j; $i++ )
       
  2065       {
       
  2066         $b = $deps;
       
  2067         $deps = array_merge($deps, $this->acl_deps[$deps[$i]]);
       
  2068         if( $b == $deps )
       
  2069         {
       
  2070           break 2;
       
  2071         }
       
  2072         $j = sizeof($deps);
       
  2073       }
       
  2074     }
       
  2075     //die('<pre>'.print_r($deps, true).'</pre>');
       
  2076     foreach($deps as $d)
       
  2077     {
       
  2078       if ( !$this->get_permissions($d) )
       
  2079       {
       
  2080         return false;
       
  2081       }
       
  2082     }
       
  2083     return true;
       
  2084   }
       
  2085   
       
  2086   /**
       
  2087    * Makes a CAPTCHA code and caches the code in the database
       
  2088    * @param int $len The length of the code, in bytes
       
  2089    * @return string A unique identifier assigned to the code. This hash should be passed to sessionManager::getCaptcha() to retrieve the code.
       
  2090    */
       
  2091   
       
  2092   function make_captcha($len = 7)
       
  2093   {
       
  2094     $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');
       
  2095     $s = '';
       
  2096     for($i=0;$i<$len;$i++) $s .= $chars[mt_rand(0, count($chars)-1)];
       
  2097     $hash = md5(microtime() . mt_rand());
       
  2098     $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);');
       
  2099     return $hash;
       
  2100   }
       
  2101   
       
  2102   /**
       
  2103    * For the given code ID, returns the correct CAPTCHA code, or false on failure
       
  2104    * @param string $hash The unique ID assigned to the code
       
  2105    * @return string The correct confirmation code
       
  2106    */
       
  2107   
       
  2108   function get_captcha($hash)
       
  2109   {
       
  2110     global $db, $session, $paths, $template, $plugins; // Common objects
       
  2111     $s = $this->sql('SELECT salt FROM '.table_prefix.'session_keys WHERE session_key=\''.$db->escape($hash).'\' AND source_ip=\''.ip2hex($_SERVER['REMOTE_ADDR']).'\';');
       
  2112     if($db->numrows() < 1) return false;
       
  2113     $r = $db->fetchrow();
       
  2114     return $r['salt'];
       
  2115   }
       
  2116   
       
  2117   /**
       
  2118    * Deletes all CAPTCHA codes cached in the DB for this user.
       
  2119    */
       
  2120   
       
  2121   function kill_captcha()
       
  2122   {
       
  2123     $this->sql('DELETE FROM '.table_prefix.'session_keys WHERE user_id=-2 AND source_ip=\''.ip2hex($_SERVER['REMOTE_ADDR']).'\';');
       
  2124   }
       
  2125   
       
  2126   /**
       
  2127    * Generates a random password.
       
  2128    * @param int $length Optional - length of password
       
  2129    * @return string
       
  2130    */
       
  2131    
       
  2132   function random_pass($length = 10)
       
  2133   {
       
  2134     $valid_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_+@#%&<>';
       
  2135     $valid_chars = enano_str_split($valid_chars);
       
  2136     $ret = '';
       
  2137     for ( $i = 0; $i < $length; $i++ )
       
  2138     {
       
  2139       $ret .= $valid_chars[mt_rand(0, count($valid_chars)-1)];
       
  2140     }
       
  2141     return $ret;
       
  2142   }
       
  2143   
       
  2144   /**
       
  2145    * Generates some Javascript that calls the AES encryption library.
       
  2146    * @param string The name of the form
       
  2147    * @param string The name of the password field
       
  2148    * @param string The name of the field that switches encryption on or off
       
  2149    * @param string The name of the field that contains the encryption key
       
  2150    * @param string The name of the field that will contain the encrypted password
       
  2151    * @param string The name of the field that handles MD5 challenge data
       
  2152    * @return string
       
  2153    */
       
  2154    
       
  2155   function aes_javascript($form_name, $pw_field, $use_crypt, $crypt_key, $crypt_data, $challenge)
       
  2156   {
       
  2157     $code = '
       
  2158       <script type="text/javascript">
       
  2159         disableJSONExts();
       
  2160           str = \'\';
       
  2161           for(i=0;i<keySizeInBits/4;i++) str+=\'0\';
       
  2162           var key = hexToByteArray(str);
       
  2163           var pt = hexToByteArray(str);
       
  2164           var ct = rijndaelEncrypt(pt, key, \'ECB\');
       
  2165           var ct = byteArrayToHex(ct);
       
  2166           switch(keySizeInBits)
       
  2167           {
       
  2168             case 128:
       
  2169               v = \'66e94bd4ef8a2c3b884cfa59ca342b2e\';
       
  2170               break;
       
  2171             case 192:
       
  2172               v = \'aae06992acbf52a3e8f4a96ec9300bd7aae06992acbf52a3e8f4a96ec9300bd7\';
       
  2173               break;
       
  2174             case 256:
       
  2175               v = \'dc95c078a2408989ad48a21492842087dc95c078a2408989ad48a21492842087\';
       
  2176               break;
       
  2177           }
       
  2178           var testpassed = ' . ( ( isset($_GET['use_crypt']) && $_GET['use_crypt']=='0') ? 'false; // CRYPTO-AUTH DISABLED ON USER REQUEST // ' : '' ) . '( ct == v && md5_vm_test() );
       
  2179           var frm = document.forms.'.$form_name.';
       
  2180           if(testpassed)
       
  2181           {
       
  2182             frm.'.$use_crypt.'.value = \'yes\';
       
  2183             var cryptkey = frm.'.$crypt_key.'.value;
       
  2184             frm.'.$crypt_key.'.value = hex_md5(cryptkey);
       
  2185             cryptkey = hexToByteArray(cryptkey);
       
  2186             if(!cryptkey || ( ( typeof cryptkey == \'string\' || typeof cryptkey == \'object\' ) ) && cryptkey.length != keySizeInBits / 8 )
       
  2187             {
       
  2188               if ( frm._login ) frm._login.disabled = true;
       
  2189               len = ( typeof cryptkey == \'string\' || typeof cryptkey == \'object\' ) ? \'\\nLen: \'+cryptkey.length : \'\';
       
  2190               alert(\'The key is messed up\\nType: \'+typeof(cryptkey)+len);
       
  2191             }
       
  2192           }
       
  2193           if(frm.username) frm.username.focus();
       
  2194           function runEncryption()
       
  2195           {
       
  2196             if(testpassed)
       
  2197             {
       
  2198               pass = frm.'.$pw_field.'.value;
       
  2199               chal = frm.'.$challenge.'.value;
       
  2200               challenge = hex_md5(pass + chal) + chal;
       
  2201               frm.'.$challenge.'.value = challenge;
       
  2202               pass = stringToByteArray(pass);
       
  2203               cryptstring = rijndaelEncrypt(pass, cryptkey, \'ECB\');
       
  2204               if(!cryptstring)
       
  2205               {
       
  2206                 return false;
       
  2207               }
       
  2208               cryptstring = byteArrayToHex(cryptstring);
       
  2209               frm.'.$crypt_data.'.value = cryptstring;
       
  2210               frm.'.$pw_field.'.value = \'\';
       
  2211             }
       
  2212             return false;
       
  2213           }
       
  2214         </script>
       
  2215         ';
       
  2216     return $code;
       
  2217   }
       
  2218   
       
  2219 }
       
  2220 
       
  2221 /**
       
  2222  * Class used to fetch permissions for a specific page. Used internally by SessionManager.
       
  2223  * @package Enano
       
  2224  * @subpackage Session manager
       
  2225  * @license http://www.gnu.org/copyleft/gpl.html
       
  2226  * @access private
       
  2227  */
       
  2228  
       
  2229 class Session_ACLPageInfo {
       
  2230   
       
  2231   /**
       
  2232    * The page ID of this ACL info package
       
  2233    * @var string
       
  2234    */
       
  2235    
       
  2236   var $page_id;
       
  2237   
       
  2238   /**
       
  2239    * The namespace of the page being checked
       
  2240    * @var string
       
  2241    */
       
  2242    
       
  2243   var $namespace;
       
  2244   
       
  2245   /**
       
  2246    * Our list of permission types.
       
  2247    * @access private
       
  2248    * @var array
       
  2249    */
       
  2250    
       
  2251   var $acl_types = Array();
       
  2252   
       
  2253   /**
       
  2254    * The list of descriptions for the permission types
       
  2255    * @var array
       
  2256    */
       
  2257    
       
  2258   var $acl_descs = Array();
       
  2259   
       
  2260   /**
       
  2261    * A list of dependencies for ACL types.
       
  2262    * @var array
       
  2263    */
       
  2264    
       
  2265   var $acl_deps = Array();
       
  2266   
       
  2267   /**
       
  2268    * Our tell-all list of permissions.
       
  2269    * @access private - or, preferably, protected...too bad this has to be PHP4 compatible
       
  2270    * @var array
       
  2271    */
       
  2272    
       
  2273   var $perms = Array();
       
  2274   
       
  2275   /**
       
  2276    * Constructor.
       
  2277    * @param string $page_id The ID of the page to check
       
  2278    * @param string $namespace The namespace of the page to check.
       
  2279    * @param array $acl_types List of ACL types
       
  2280    * @param array $acl_descs List of human-readable descriptions for permissions (associative)
       
  2281    * @param array $acl_deps List of dependencies for permissions. For example, viewing history/diffs depends on the ability to read the page.
       
  2282    * @param array $base What to start with - this is an attempt to reduce the number of SQL queries.
       
  2283    */
       
  2284    
       
  2285   function Session_ACLPageInfo($page_id, $namespace, $acl_types, $acl_descs, $acl_deps, $base)
       
  2286   {
       
  2287     global $db, $session, $paths, $template, $plugins; // Common objects
       
  2288     
       
  2289     $this->perms = $session->acl_merge_complete($acl_types, $base);
       
  2290     $this->acl_deps = $acl_deps;
       
  2291     $this->acl_types = $acl_types;
       
  2292     $this->acl_descs = $acl_descs;
       
  2293     
       
  2294     // Build a query to grab ACL info
       
  2295     $bs = 'SELECT rules FROM '.table_prefix.'acl WHERE ( ';
       
  2296     $q = Array();
       
  2297     $q[] = '( target_type='.ACL_TYPE_USER.' AND target_id='.$session->user_id.' )';
       
  2298     if(count($session->groups) > 0)
       
  2299     {
       
  2300       foreach($session->groups as $g_id => $g_name)
       
  2301       {
       
  2302         $q[] = '( target_type='.ACL_TYPE_GROUP.' AND target_id='.intval($g_id).' )';
       
  2303       }
       
  2304     }
       
  2305     // 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
       
  2306     // permissions to override group permissions.
       
  2307     $bs .= implode(' OR ', $q) . ' ) AND ( page_id=\''.$db->escape($page_id).'\' AND namespace=\''.$db->escape($namespace).'\' )     
       
  2308       ORDER BY target_type ASC, page_id ASC, namespace ASC;';
       
  2309     $q = $session->sql($bs);
       
  2310     if ( $row = $db->fetchrow() )
       
  2311     {
       
  2312       do {
       
  2313         $rules = $session->string_to_perm($row['rules']);
       
  2314         $this->perms = $session->acl_merge($this->perms, $rules);
       
  2315       } while ( $row = $db->fetchrow() );
       
  2316     }
       
  2317     
       
  2318     $this->page_id = $page_id;
       
  2319     $this->namespace = $namespace;
       
  2320   }
       
  2321   
       
  2322   /**
       
  2323    * Tells us whether permission $type is allowed or not based on the current rules.
       
  2324    * @param string $type The permission identifier ($acl_type passed to sessionManager::register_acl_type())
       
  2325    * @param bool $no_deps If true, disables dependency checking
       
  2326    * @return bool True if allowed, false if denied or if an error occured
       
  2327    */
       
  2328    
       
  2329   function get_permissions($type, $no_deps = false)
       
  2330   {
       
  2331     global $db, $session, $paths, $template, $plugins; // Common objects
       
  2332     if ( isset( $this->perms[$type] ) )
       
  2333     {
       
  2334       if ( $this->perms[$type] == AUTH_DENY )
       
  2335         $ret = false;
       
  2336       else if ( $this->perms[$type] == AUTH_WIKIMODE &&
       
  2337         ( isset($paths->pages[$paths->nslist[$this->namespace].$this->page_id]) && 
       
  2338           ( $paths->pages[$paths->nslist[$this->namespace].$this->page_id]['wiki_mode'] == '1' ||
       
  2339             ( $paths->pages[$paths->nslist[$this->namespace].$this->page_id]['wiki_mode'] == '2'
       
  2340               && getConfig('wiki_mode') == '1'
       
  2341           ) ) ) )
       
  2342         $ret = true;
       
  2343       else if ( $this->perms[$type] == AUTH_WIKIMODE && (
       
  2344         !isset($paths->pages[$paths->nslist[$this->namespace].$this->page_id])
       
  2345         || (
       
  2346           isset($paths->pages[$paths->nslist[$this->namespace].$this->page_id]) && (
       
  2347             $paths->pages[$paths->nslist[$this->namespace].$this->page_id]['wiki_mode'] == '0'
       
  2348             || (
       
  2349               $paths->pages[$paths->nslist[$this->namespace].$this->page_id]['wiki_mode'] == '2' && getConfig('wiki_mode') != '1'
       
  2350           ) ) ) ) )
       
  2351         $ret = false;
       
  2352       else if ( $this->perms[$type] == AUTH_ALLOW )
       
  2353         $ret = true;
       
  2354       else if ( $this->perms[$type] == AUTH_DISALLOW )
       
  2355         $ret = false;
       
  2356     }
       
  2357     else if(isset($this->acl_types[$type]))
       
  2358     {
       
  2359       if ( $this->acl_types[$type] == AUTH_DENY )
       
  2360         $ret = false;
       
  2361       else if ( $this->acl_types[$type] == AUTH_WIKIMODE && $paths->wiki_mode )
       
  2362         $ret = true;
       
  2363       else if ( $this->acl_types[$type] == AUTH_WIKIMODE && !$paths->wiki_mode )
       
  2364         $ret = false;
       
  2365       else if ( $this->acl_types[$type] == AUTH_ALLOW )
       
  2366         $ret = true;
       
  2367       else if ( $this->acl_types[$type] == AUTH_DISALLOW )
       
  2368         $ret = false;
       
  2369     }
       
  2370     else
       
  2371     {
       
  2372       // ACL type is undefined
       
  2373       trigger_error('Unknown access type "' . $type . '"', E_USER_WARNING);
       
  2374       return false; // Be on the safe side and deny access
       
  2375     }
       
  2376     if ( !$no_deps )
       
  2377     {
       
  2378       if ( !$this->acl_check_deps($type) )
       
  2379         return false;
       
  2380     }
       
  2381     return $ret;
       
  2382   }
       
  2383   
       
  2384   /**
       
  2385    * Tell us if the dependencies for a given permission are met.
       
  2386    * @param string The ACL permission ID
       
  2387    * @return bool
       
  2388    */
       
  2389    
       
  2390   function acl_check_deps($type)
       
  2391   {
       
  2392     if(!isset($this->acl_deps[$type])) // This will only happen if the permissions table is hacked or improperly accessed
       
  2393       return true;
       
  2394     if(sizeof($this->acl_deps[$type]) < 1)
       
  2395       return true;
       
  2396     $deps = $this->acl_deps[$type];
       
  2397     while(true)
       
  2398     {
       
  2399       $full_resolved = true;
       
  2400       $j = sizeof($deps);
       
  2401       for ( $i = 0; $i < $j; $i++ )
       
  2402       {
       
  2403         $b = $deps;
       
  2404         $deps = array_merge($deps, $this->acl_deps[$deps[$i]]);
       
  2405         if( $b == $deps )
       
  2406         {
       
  2407           break 2;
       
  2408         }
       
  2409         $j = sizeof($deps);
       
  2410       }
       
  2411     }
       
  2412     //die('<pre>'.print_r($deps, true).'</pre>');
       
  2413     foreach($deps as $d)
       
  2414     {
       
  2415       if ( !$this->get_permissions($d) )
       
  2416       {
       
  2417         return false;
       
  2418       }
       
  2419     }
       
  2420     return true;
       
  2421   }
       
  2422   
       
  2423 }
       
  2424 
       
  2425 ?>