1
0
mirror of https://github.com/e107inc/e107.git synced 2025-01-17 20:58:30 +01:00
php-e107/e107_handlers/user_handler.php

563 lines
19 KiB
PHP

<?php
/*
* e107 website system
*
* Copyright (C) 2001-2008 e107 Inc (e107.org)
* Released under the terms and conditions of the
* GNU General Public License (http://www.gnu.org/licenses/gpl.txt)
*
* Handler - user-related functions
*
* $Source: /cvs_backup/e107_0.8/e107_handlers/user_handler.php,v $
* $Revision: 1.9 $
* $Date: 2009-04-23 19:58:28 $
* $Author: e107steved $
*
*/
/*
USER HANDLER CLASS - manages login and various user functions
Vetting routines TODO:
user_sess processing
user_image processing
user_xup processing - nothing special?
*/
if (!defined('e107_INIT')) { exit; }
// Codes for `user_ban` field (not all used ATM)
define('USER_VALIDATED',0);
define('USER_BANNED',1);
define('USER_REGISTERED_NOT_VALIDATED',2);
define('USER_EMAIL_BOUNCED', 3);
define('USER_BOUNCED_RESET', 4);
define('USER_TEMPORARY_ACCOUNT', 5);
define('PASSWORD_E107_MD5',0);
define('PASSWORD_E107_SALT',1);
define('PASSWORD_E107_ID','$E$'); // E107 salted
define('PASSWORD_INVALID', FALSE);
define('PASSWORD_VALID',TRUE);
define ('PASSWORD_DEFAULT_TYPE',PASSWORD_E107_MD5);
//define ('PASSWORD_DEFAULT_TYPE',PASSWORD_E107_SALT);
// Required language file - if not loaded elsewhere, uncomment next line
//include_lan(e_LANGUAGEDIR.e_LANGUAGE.'/lan_user.php');
class UserHandler
{
var $userVettingInfo = array();
var $preferred = PASSWORD_DEFAULT_TYPE; // Preferred password format
var $passwordOpts = 0; // Copy of pref
var $passwordEmail = FALSE; // True if can use email address to log in
var $otherFields = array();
// Constructor
function UserHandler()
{
global $pref;
/*
Table of vetting methods for user data - lists every field whose value could be set manually.
Valid 'vetMethod' values (use comma separated list for multiple vetting):
0 - Null method
1 - Check for duplicates
2 - Check against $pref['signup_disallow_text']
Index is the destination field name. If the source index name is different, specify 'srcName' in the array.
Possible processing options:
'dbClean' - 'sanitising' method for final value:
- 'toDB' - passes final value through $tp->toDB()
- 'intval' - converts to an integer
- 'image' - checks image for size
- 'avatar' - checks an image in the avatars directory
'stripTags' - strips HTML tags from the value (not an error if there are some)
'minLength' - minimum length (in utf-8 characters) for the string
'maxLength' - minimum length (in utf-8 characters) for the string
'longTrim' - if set, and the string exceeds maxLength, its trimmed
'enablePref' - value is processed only if the named $pref evaluates to true; otherwise any input is discarded without error
*/
$this->userVettingInfo = array(
'user_name' => array('niceName'=> LAN_USER_01, 'fieldType' => 'string', 'vetMethod' => '1,2', 'vetParam' => 'signup_disallow_text', 'srcName' => 'username', 'stripTags' => TRUE, 'stripChars' => '/&nbsp;|\#|\=|\$/', fixedBlock => 'anonymous', 'minLength' => 2, 'maxLength' => varset($pref['displayname_maxlength'],15)), // Display name
'user_loginname' => array('niceName'=> LAN_USER_02, 'fieldType' => 'string', 'vetMethod' => '1', 'vetParam' => '', 'srcName' => 'loginname', 'stripTags' => TRUE, 'stripChars' => '/&nbsp;|\#|\=|\$/', 'minLength' => 2, 'maxLength' => varset($pref['loginname_maxlength'],30)), // User name
'user_login' => array('niceName'=> LAN_USER_03, 'fieldType' => 'string', 'vetMethod' => '0', 'vetParam' => '', 'srcName' => 'realname', 'dbClean' => 'toDB'), // Real name (no real vetting)
'user_customtitle' => array('niceName'=> LAN_USER_04, 'fieldType' => 'string', 'vetMethod' => '0', 'vetParam' => '', 'srcName' => 'customtitle', 'dbClean' => 'toDB', 'enablePref' => 'signup_option_customtitle'), // No real vetting
'user_password' => array('niceName'=> LAN_USER_05, 'fieldType' => 'string', 'vetMethod' => '0', 'vetParam' => '', 'srcName' => 'password1', 'dataType' => 2, 'minLength' => varset($pref['signup_pass_len'],1)),
'user_sess' => array('niceName'=> LAN_USER_06, 'fieldType' => 'string', 'vetMethod' => '0', 'vetParam' => '', 'stripChars' => "#\"|'|(|)#", 'dbClean' => 'image', 'imagePath' => e_FILE.'public/avatars/', 'maxHeight' => varset($pref['im_height'], 100), 'maxWidth' => varset($pref['im_width'], 120)), // Photo
'user_image' => array('niceName'=> LAN_USER_07, 'fieldType' => 'string', 'vetMethod' => '0', 'vetParam' => '', 'srcName' => 'image', 'stripChars' => "#\"|'|(|)#", 'dbClean' => 'avatar', 'maxHeight' => varset($pref['im_height'], 100), 'maxWidth' => varset($pref['im_width'], 120)), // Avatar
'user_email' => array('niceName'=> LAN_USER_08, 'fieldType' => 'string', 'vetMethod' => '1,3', 'vetParam' => '', 'srcName' => 'email', 'dbClean' => 'toDB'),
'user_signature' => array('niceName'=> LAN_USER_09, 'fieldType' => 'string', 'vetMethod' => '0', 'vetParam' => '', 'srcName' => 'signature', 'dbClean' => 'toDB'),
'user_hideemail' => array('niceName'=> LAN_USER_10, 'fieldType' => 'int', 'vetMethod' => '0', 'vetParam' => '', 'srcName' => 'hideemail', 'dbClean' => 'intval'),
'user_xup' => array('niceName'=> LAN_USER_11, 'fieldType' => 'string', 'vetMethod' => '0', 'vetParam' => '', 'srcName' => 'user_xup', 'dbClean' => 'toDB'),
'user_class' => array('niceName'=> LAN_USER_12, 'fieldType' => 'string', 'vetMethod' => '0', 'vetParam' => '', 'srcName' => 'class', 'dataType' => '1')
);
$this->otherFields = array(
'user_join' => LAN_USER_14,
'user_lastvisit' => LAN_USER_15,
'user_currentvisit' => LAN_USER_16,
'user_comments' => LAN_USER_17,
'user_ip' => LAN_USER_18,
'user_ban' => LAN_USER_19,
'user_prefs' => LAN_USER_20,
'user_visits' => LAN_USER_21,
'user_admin' => LAN_USER_22,
'user_perms' => LAN_USER_23,
'user_pwchange' => LAN_USER_24
// user_chats int(10) unsigned NOT NULL default '0',
);
$this->otherFieldTypes = array(
'user_join' => 'int',
'user_lastvisit' => 'int',
'user_currentvisit' => 'int',
'user_comments' => 'int',
'user_ip' => 'string',
'user_ban' => 'int',
'user_prefs' => 'string',
'user_visits' => 'int',
'user_admin' => 'int',
'user_perms' => 'string',
'user_pwchange' => 'int'
);
$this->passwordOpts = varset($pref['passwordEncoding'],0);
$this->passwordEmail = varset($pref['allowEmailLogin'],FALSE);
switch ($this->passwordOpts)
{
case 1 :
case 2 :
$this->preferred = PASSWORD_E107_SALT;
break;
case 0 :
default :
$this->preferred = PASSWORD_E107_MD5;
$this->passwordOpts = 0; // In case it got set to some stupid value
break;
}
return FALSE;
}
// Given plaintext password and login name, generate password string to store in DB
function HashPassword($password, $login_name, $force='')
{
if ($force == '') $force = $this->preferred;
switch ($force)
{
case PASSWORD_E107_MD5 :
return md5($password);
case PASSWORD_E107_SALT :
return PASSWORD_E107_ID.md5(md5($password).$login_name);
break;
}
return FALSE;
}
// Verify existing plaintext password against a stored hash value (which defines the encoding format and any 'salt')
// Return PASSWORD_INVALID if invalid password
// Return PASSWORD_VALID if valid password
// Return a new hash to store if valid password but non-preferred encoding
function CheckPassword($password, $login_name, $stored_hash)
{
if (strlen(trim($password)) == 0) return PASSWORD_INVALID;
if (($this->passwordOpts <= 1) && (strlen($stored_hash) == 32))
{ // Its simple md5 encoding
if (md5($password) !== $stored_hash) return PASSWORD_INVALID;
if ($this->preferred == PASSWORD_E107_MD5) return PASSWORD_VALID;
return $this->HashPassword($password); // Valid password, but non-preferred encoding; return the new hash
}
// Allow the salted password even if disabled - for those that do try to go back!
// if (($this->passwordOpts >= 1) && (strlen($stored_hash) == 35) && (substr($stored_hash,0,3) == PASSWORD_E107_ID))
if ((strlen($stored_hash) == 35) && (substr($stored_hash,0,3) == PASSWORD_E107_ID))
{ // Its the standard E107 salted hash
$hash = $this->HashPassword($password, $login_name, PASSWORD_E107_SALT);
if ($hash === FALSE) return PASSWORD_INVALID;
return ($hash == $stored_hash) ? PASSWORD_VALID : PASSWORD_INVALID;
}
return PASSWORD_INVALID;
}
// Verifies a standard response to a CHAP challenge
function CheckCHAP($challenge, $response, $login_name, $stored_hash )
{
if (strlen($challenge) != 40) return PASSWORD_INVALID;
if (strlen($response) != 32) return PASSWORD_INVALID;
$valid_ret = PASSWORD_VALID;
if (strlen($stored_hash) == 32)
{ // Its simple md5 password storage
$stored_hash = PASSWORD_E107_ID.md5($stored_hash.$login_name); // Convert to the salted format always used by CHAP
if ($this->passwordOpts != PASSWORD_E107_MD5) $valid_ret = $stored_response;
}
$testval = md5(substr($stored_hash,strlen(PASSWORD_E107_ID)).$challenge);
if ($testval == $response) return $valid_ret;
return PASSWORD_INVALID;
}
// Checks whether the user has to validate a user setting change by entering password (basically, if that field affects the
// stored password value)
// Returns TRUE if change required, FALSE otherwise
function isPasswordRequired($fieldName)
{
if ($this->preferred == PASSWORD_E107_MD5) return FALSE;
switch ($fieldName)
{
case 'user_email' :
return $this->passwordEmail;
case 'user_loginname' :
return TRUE;
}
return FALSE;
}
// Determines whether its necessary to store a separate password for email address validation
function needEmailPassword()
{
if ($this->preferred == PASSWORD_E107_MD5) return FALSE;
if ($this->passwordEmail) return TRUE;
return FALSE;
}
// Checks whether the password value can be converted to the current default
// Returns TRUE if conversion possible.
// Returns FALSE if conversion not possible, or not needed
function canConvert($password)
{
if ($this->preferred == PASSWORD_E107_MD5) return FALSE;
if (strlen($password) == 32) return TRUE; // Can convert from md5 to salted
return FALSE;
}
// Given md5-encoded password and login name, generate password string to store in DB
function ConvertPassword($password, $login_name)
{
if ($this->canConvert($password) === FALSE) return $password;
return PASSWORD_E107_ID.md5($password.$login_name);
}
// Generates a random user login name according to some pattern.
// Checked for uniqueness.
function generateUserLogin($pattern, $seed='')
{
$ul_sql = new db;
if (strlen($pattern) < 6) $pattern = '##....';
do
{
$newname = $this->generateRandomString($pattern, $seed);
} while ($ul_sql->db_Select('user','user_id',"`user_loginname`='{$newname}'"));
return $newname;
}
// Generates a random string - for user login name, password etc, according to some pattern.
// Checked for uniqueness.
// Pattern format:
// # - an alpha character
// . - a numeric character
// * - an alphanumeric character
// ^ - next character from seed
// alphanumerics are included 'as is'
function generateRandomString($pattern, $seed='')
{
if (strlen($pattern) < 6) $pattern = '##....';
$newname = '';
$seed_ptr = 0; // Next character of seed (if used)
for ($i = 0; $i < strlen($pattern); $i++)
{
$c = $pattern[$i];
switch ($c)
{
case '#' : // Alpha only (upper and lower case)
do
{
$t = chr(rand(65,122));
} while (!ctype_alpha($t));
$newname .= $t;
break;
case '.' : // Numeric only
do
{
$t = chr(rand(48,57));
} while (!ctype_digit($t));
$newname .= $t;
break;
case '*' : // Alphanumeric
do
{
$t = chr(rand(48,122));
} while (!ctype_alnum($t));
$newname .= $t;
break;
case '^' : // Next character from seed
if ($seed_ptr < strlen($seed))
{
$newname .= $seed[$seed_ptr];
$seed_ptr++;
}
break;
default :
if (ctype_alnum($c)) $newname .= $c;
// (else just ignore other characters in pattern)
}
}
return $newname;
}
// Split up an email address to check for banned domains.
// Return false if invalid address. Otherwise returns a set of values to check
function make_email_query($email, $fieldname = 'banlist_ip')
{
global $tp;
$tmp = strtolower($tp -> toDB(trim(substr($email, strrpos($email, "@")+1)))); // Pull out the domain name
if ($tmp == '') return FALSE;
if (strpos($tmp,'.') === FALSE) return FALSE;
$em = array_reverse(explode('.',$tmp));
$line = '';
$out = array();
foreach ($em as $e)
{
$line = '.'.$e.$line;
$out[] = '`'.$fieldname."`='*{$line}'";
}
return implode(' OR ',$out);
}
function makeUserCookie($lode,$autologin = FALSE)
{
global $pref;
$cookieval = $lode['user_id'].".".md5($lode['user_password']); // (Use extra md5 on cookie value to obscure hashed value for password)
if ($pref['user_tracking'] == "session")
{
$_SESSION[$pref['cookie_name']] = $cookieval;
}
else
{
if ($autologin == 1)
{ // Cookie valid for up to 30 days
cookie($pref['cookie_name'], $cookieval, (time() + 3600 * 24 * 30));
}
else
{
cookie($pref['cookie_name'], $cookieval);
}
}
}
// Generate an array of all the basic classes a user belongs to
// if $asArray TRUE, returns results in an array; else as a comma-separated string
// If $incInherited is TRUE, includes inherited classes
function addCommonClasses($userData, $asArray = FALSE, $incInherited = FALSE)
{
if ($incInherited)
{
$classList = array();
global $e_userclass;
if (!isset($e_userclass) && !is_object($e_userclass))
{
require_once(e_HANDLER."userclass_class.php");
$e_userclass = new user_class;
}
$classList = $e_userclass->get_all_user_classes($var['user_class']);
}
else
{
if ($userData['user_class'] != '') $classList = explode(',',$userData['user_class']);
}
foreach (array(e_UC_MEMBER, e_UC_READONLY, e_UC_PUBLIC) as $c)
{
if (!in_array($c,$classList))
{
$classList[] = $c;
}
}
if ((varset($userData['user_admin'],0) == 1) && strlen($userData['user_perms']))
{
$classList[] = e_UC_ADMIN;
if (strpos($userData['user_perms'],'0') === 0)
{
$classList[] = e_UC_MAINADMIN;
}
}
if ($asArray) return $classList;
return implode(',',$classList);
}
// Return an array of descriptive names for each field in the user DB. If $all is false, just returns the modifiable ones. Else returns all
function getNiceNames($all = FALSE)
{
// $ret = array('user_id' => LAN_USER_13);
foreach ($this->userVettingInfo as $k => $v)
{
$ret[$k] = $v['niceName'];
}
if ($all)
{
$ret = array_merge($ret, $this->otherFields);
}
return $ret;
}
//===================================================
// User Field validation
//===================================================
/* $_POST field names:
DB signup usersettings quick add function
------------------------------------------------------------------------------
user_id - user_id - Unique user ID
user_name name$ username username Display name
user_loginname loginname loginname loginname User name (login name)
user_customtitle - customtitle - Custom title
user_password password1 password1 password1 Password (prior to encoding)
password2 password2 password1 (Check password field)
user_sess * - Photo (file on server)
user_email email email email Email address
email_confirm
user_signature signature signature - User signature
user_image image image* - Avatar (may be external URL or file on server)
user_hideemail hideemail hideemail - Flag to hide user's email address
user_login realname realname realname User Real name
user_xup xupexist$ user_xup - XUP file link
user_class class class userclass User class (array on form)
user_loginname may be auto-generated
* avatar (user_image) and photo (user_sess) may be uploaded files
$changed to match the majority vote
Following fields auto-filled in code as required:
user_join
user_lastvisit
user_currentvisit
user_chats
user_comments
user_forums
user_ip
user_ban
user_prefs
user_viewed
user_visits
user_admin
user_perms
user_pwchange
*/
// Function does validation specific to user data. Updates the $targetData array as appropriate.
// Returns TRUE if nothing updated; FALSE if errors found (only checks data previously passed as good)
function userValidation(&$targetData)
{
global $e107, $pref;
$u_sql = new db;
$ret = TRUE;
if (isset($targetData['data']['user_email']))
{
$v = trim($targetData['data']['user_email']); // Always check email address if its entered
if ($v == '')
{
$errMsg = ERR_MISSING_VALUE;
}
elseif (!check_email($v))
{
$errMsg = ERR_INVALID_EMAIL;
}
elseif ($u_sql->db_Count('user', '(*)', "WHERE `user_email`='".$v."' AND `user_ban`=1 "))
{
$errMsg = ERR_BANNED_USER;
}
else
{ // See if email address banned
$wc = $this->make_email_query($v); // Generate the query for the ban list
if ($wc) { $wc = "`banlist_ip`='{$v}' OR ".$wc; }
if (($wc === FALSE) || !$e107->check_ban($wc, FALSE, TRUE))
{
echo "Email banned<br />";
$errMsg = ERR_BANNED_EMAIL;
}
}
if ($errMsg)
{
unset($targetData['data']['user_email']); // Remove the valid entry
}
}
else
{
if (!isset($targetData['errors']['user_email']) && !varset($pref['disable_emailcheck'],FALSE))
{ // We may have already picked up an error on the email address - or it may be allowed to be empty
$errMsg = ERR_MISSING_VALUE;
}
}
if ($errMsg)
{ // Update the error
$targetData['errors']['user_email'] = $errMsg;
$targetData['failed']['user_email'] = $v;
$ret = FALSE;
}
return $ret;
}
// Given an array of user data intended to be written to the DB, adds empty strings (or other default value) for any field which doesn't have a default in the SQL definition.
// (Avoids problems with MySQL in STRICT mode.).
// Returns TRUE if additions made, FALSE if no change.
function addNonDefaulted(&$userInfo)
{
$nonDefaulted = array('user_signature' => '', 'user_prefs' => '', 'user_class' => '', 'user_perms' => '');
$ret = FALSE;
foreach ($nonDefaulted as $k => $v)
{
if (!isset($userInfo[$k]))
{
$userInfo[$k] = $v;
$ret = TRUE;
}
}
return $ret;
}
// Delete time-expired partial registrations from the user DB, clean up user_extended table
function deleteExpired($force = FALSE)
{
global $pref, $sql;
$temp1 = 0;
if (isset($pref['del_unv']) && $pref['del_unv'] && $pref['user_reg_veri'] != 2)
{
$threshold= intval(time() - ($pref['del_unv'] * 60));
if (($temp1 = $sql->db_Delete('user', 'user_ban = 2 AND user_join < '.$threshold)) > 0) { $force = TRUE; }
}
if ($force)
{ // Remove 'orphaned' extended user field records
$sql->db_Select_gen("DELETE `#user_extended` FROM `#user_extended` LEFT JOIN `#user` ON `#user_extended`.`user_extended_id`=`#user`.`user_id`
WHERE `#user`.`user_id` IS NULL");
}
return $temp1;
}
}
?>