From 4f94d5837db5731bb14a750a4f00542be826738e Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Sat, 15 Mar 2008 17:27:36 +0000 Subject: [PATCH] use of global db object and new headers, made all methods of the auth class static --- phpgwapi/inc/class.auth.inc.php | 879 +++++++++++++-------------- phpgwapi/inc/class.auth_ldap.inc.php | 279 ++++----- phpgwapi/inc/class.auth_sql.inc.php | 320 +++++----- 3 files changed, 732 insertions(+), 746 deletions(-) diff --git a/phpgwapi/inc/class.auth.inc.php b/phpgwapi/inc/class.auth.inc.php index 0e8393e1ad..a4d20fc739 100644 --- a/phpgwapi/inc/class.auth.inc.php +++ b/phpgwapi/inc/class.auth.inc.php @@ -1,459 +1,458 @@ * - * Copyright (C) 2004 Miles Lott * - * Many functions based on code from Frank Thomas * - * which can be seen at http://www.thomas-alfeld.de/frank/ * - * Other functions from class.common.inc.php originally from phpGroupWare * - * ------------------------------------------------------------------------ * - * This library is free software; you can redistribute it and/or modify it * - * under the terms of the GNU Lesser General Public License as published by * - * the Free Software Foundation; either version 2.1 of the License, * - * or any later version. * - * This library is distributed in the hope that it will be useful, but * - * WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * - * See the GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public License * - * along with this library; if not, write to the Free Software Foundation, * - * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * - \**************************************************************************/ +/** + * eGroupWare API - Authentication baseclass + * + * @link http://www.egroupware.org + * @author Miles Lott + * @copyright 2004 by Miles Lott + * @license http://opensource.org/licenses/lgpl-license.php LGPL - GNU Lesser General Public License + * @package api + * @subpackage authentication + * @version $Id$ + */ - /* $Id$ */ +if(empty($GLOBALS['egw_info']['server']['auth_type'])) +{ + $GLOBALS['egw_info']['server']['auth_type'] = 'sql'; +} +include(EGW_API_INC.'/class.auth_'.$GLOBALS['egw_info']['server']['auth_type'].'.inc.php'); - if(empty($GLOBALS['egw_info']['server']['auth_type'])) +/** + * eGroupWare API - Authentication baseclass, password auth and crypt functions + * + * Many functions based on code from Frank Thomas + * which can be seen at http://www.thomas-alfeld.de/frank/ + * + * Other functions from class.common.inc.php originally from phpGroupWare + */ +class auth extends auth_ +{ + static $error; + + /** + * return a random string of size $size + * + * @param $size int-size of random string to return + */ + static function randomstring($size) { - $GLOBALS['egw_info']['server']['auth_type'] = 'sql'; + $s = ''; + $random_char = array( + '0','1','2','3','4','5','6','7','8','9','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','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' + ); + + for ($i=0; $i<$size; $i++) + { + $s .= $random_char[mt_rand(1,61)]; + } + return $s; } - include(EGW_API_INC.'/class.auth_'.$GLOBALS['egw_info']['server']['auth_type'].'.inc.php'); - class auth extends auth_ + /** + * encrypt password + * + * uses the encryption type set in setup and calls the appropriate encryption functions + * + * @param $password password to encrypt + */ + function encrypt_password($password,$sql=False) { - var $seeded = False; - var $error = ''; - - /** - * return a random string of size $size - * - * @param $size int-size of random string to return - */ - function randomstring($size) + if($sql) { - $s = ''; - $random_char = array( - '0','1','2','3','4','5','6','7','8','9','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','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' - ); - - for ($i=0; $i<$size; $i++) - { - $s .= $random_char[mt_rand(1,61)]; - } - return $s; + return self::encrypt_sql($password); } + return self::encrypt_ldap($password); + } - /** - * encrypt password - * - * uses the encryption type set in setup and calls the appropriate encryption functions - * - * @param $password password to encrypt - */ - function encrypt_password($password,$sql=False) + /** + * compares an encrypted password + * + * encryption type set in setup and calls the appropriate encryption functions + * + * @param $cleartext cleartext password + * @param $encrypted encrypted password, can have a {hash} prefix, which overrides $type + * @param $type type of encryption + * @param $username used as optional key of encryption for md5_hmac + */ + static function compare_password($cleartext,$encrypted,$type,$username='') + { + // allow to specify the hash type to prefix the hash, to easy migrate passwords from ldap + $saved_enc = $encrypted; + if (preg_match('/^\\{([a-z_5]+)\\}(.+)$/i',$encrypted,$matches)) { - if($sql) + $type = strtolower($matches[1]); + $encrypted = $matches[2]; + + switch($type) // some hashs are specially "packed" in ldap { - return $this->encrypt_sql($password); + case 'md5': + $encrypted = implode('',unpack('H*',base64_decode($encrypted))); + break; + case 'plain': + case 'crypt': + // nothing to do + break; + default: + $encrypted = $saved_enc; + // ToDo: the others ... } - return $this->encrypt_ldap($password); } - - /** - * compares an encrypted password - * - * encryption type set in setup and calls the appropriate encryption functions - * - * @param $cleartext cleartext password - * @param $encrypted encrypted password, can have a {hash} prefix, which overrides $type - * @param $type type of encryption - * @param $username used as optional key of encryption for md5_hmac - */ - function compare_password($cleartext,$encrypted,$type,$username='') + switch($type) { - // allow to specify the hash type to prefix the hash, to easy migrate passwords from ldap - $saved_enc = $encrypted; - if (preg_match('/^\\{([a-z_5]+)\\}(.+)$/i',$encrypted,$matches)) - { - $type = strtolower($matches[1]); - $encrypted = $matches[2]; - - switch($type) // some hashs are specially "packed" in ldap + case 'plain': + if(strcmp($cleartext,$encrypted) == 0) { - case 'md5': - $encrypted = implode('',unpack('H*',base64_decode($encrypted))); - break; - case 'plain': - case 'crypt': - // nothing to do - break; - default: - $encrypted = $saved_enc; - // ToDo: the others ... + return True; } - } - switch($type) - { - case 'plain': - if(strcmp($cleartext,$encrypted) == 0) - { - return True; - } - return False; - case 'smd5': - return $this->smd5_compare($cleartext,$encrypted); - case 'sha': - return $this->sha_compare($cleartext,$encrypted); - case 'ssha': - return $this->ssha_compare($cleartext,$encrypted); - case 'crypt': - case 'md5_crypt': - case 'blowfish_crypt': - case 'ext_crypt': - return $this->crypt_compare($cleartext,$encrypted,$type); - case 'md5_hmac': - return $this->md5_hmac_compare($cleartext,$encrypted,$username); - case 'md5': - default: - return strcmp(md5($cleartext),$encrypted) == 0 ? true : false; - } - } - - /** - * encrypt password for ldap - * - * uses the encryption type set in setup and calls the appropriate encryption functions - * - * @param $password password to encrypt - */ - function encrypt_ldap($password) - { - $type = strtolower($GLOBALS['egw_info']['server']['ldap_encryption_type']); - $salt = ''; - switch($type) - { - default: // eg. setup >> config never saved - case 'des': - $salt = $this->randomstring(2); - $_password = crypt($password, $salt); - $e_password = '{crypt}'.$_password; - break; - case 'md5': - /* New method taken from the openldap-software list as recommended by - * Kervin L. Pierre" - */ - $e_password = '{md5}' . base64_encode(pack("H*",md5($password))); - break; - case 'smd5': - if(!function_exists('mhash')) - { - return False; - } - $salt = $this->randomstring(8); - $hash = mhash(MHASH_MD5, $password . $salt); - $e_password = '{SMD5}' . base64_encode($hash . $salt); - break; - case 'sha': - if(!function_exists('mhash')) - { - return False; - } - $e_password = '{SHA}' . base64_encode(mhash(MHASH_SHA1, $password)); - break; - case 'ssha': - if(!function_exists('mhash')) - { - return False; - } - $salt = $this->randomstring(8); - $hash = mhash(MHASH_SHA1, $password . $salt); - $e_password = '{SSHA}' . base64_encode($hash . $salt); - break; - case 'plain': - // if plain no type is prepended - $e_password =$password; - break; - } - return $e_password; - } - - /** - * Create an ldap hash from an sql hash - * - * @param string $hash - */ - function hash_sql2ldap($hash) - { - switch(strtolower($GLOBALS['egw_info']['server']['sql_encryption_type'])) - { - case '': // not set sql_encryption_type - case 'md5': - $hash = '{md5}' . base64_encode(pack("H*",$hash)); - break; - case 'crypt': - $hash = '{crypt}' . $hash; - break; - case 'plain': - $saved_h = $hash; - if (preg_match('/^\\{([a-z_5]+)\\}(.+)$/i',$hash,$matches)) - { - $hash= $matches[2]; - } else { - $hash = $saved_h; - } - break; - } - return $hash; - } - - /** - * Create a password for storage in the accounts table - * - * @param string $password - * @return string hash - */ - function encrypt_sql($password) - { - /* Grab configured type, or default to md5() (old method) */ - $type = @$GLOBALS['egw_info']['server']['sql_encryption_type'] - ? strtolower($GLOBALS['egw_info']['server']['sql_encryption_type']) - : 'md5'; - switch($type) - { - case 'plain': - // since md5 is the default, type plain must be prepended, for eGroupware to understand - return '{PLAIN}'.$password; - case 'crypt': - if(@defined('CRYPT_STD_DES') && CRYPT_STD_DES == 1) - { - $salt = $this->randomstring(2); - return crypt($password,$salt); - } - $this->error = 'no std crypt'; - break; - case 'blowfish_crypt': - if(@defined('CRYPT_BLOWFISH') && CRYPT_BLOWFISH == 1) - { - $salt = '$2$' . $this->randomstring(13); - return crypt($password,$salt); - } - $this->error = 'no blowfish crypt'; - break; - case 'md5_crypt': - if(@defined('CRYPT_MD5') && CRYPT_MD5 == 1) - { - $salt = '$1$' . $this->randomstring(9); - return crypt($password,$salt); - } - $this->error = 'no md5 crypt'; - break; - case 'ext_crypt': - if(@defined('CRYPT_EXT_DES') && CRYPT_EXT_DES == 1) - { - $salt = $this->randomstring(9); - return crypt($password,$salt); - } - $this->error = 'no ext crypt'; - break; - case 'smd5': - if(!function_exists('mhash')) - { - return False; - } - $salt = $this->randomstring(8); - $hash = mhash(MHASH_MD5, $password . $salt); - return '{SMD5}' . base64_encode($hash . $salt); - case 'sha': - if(!function_exists('mhash')) - { - $this->error = 'no sha'; - return False; - } - return '{SHA}' . base64_encode(mhash(MHASH_SHA1,$password)); - case 'ssha': - if(!function_exists('mhash')) - { - $this->error = 'no ssha'; - return False; - } - $salt = $this->randomstring(8); - $hash = mhash(MHASH_SHA1, $password . $salt); - return '{SSHA}' . base64_encode($hash . $salt); - case 'md5': - default: - /* This is the old standard for password storage in SQL */ - return md5($password); - } - $this->error = $this->error ? $this->error : 'no valid encryption available'; - return False; - } - - /** - * Checks if a given password is "safe" - * - * @param string $login - * @abstract atm a simple check in length, #digits, #uppercase and #lowercase - * could be made more safe using e.g. pecl library cracklib - * but as pecl dosn't run on any platform and isn't GPL'd - * i haven't implemented it yet - * Windows compatible check is: 7 char lenth, 1 Up, 1 Low, 1 Num and 1 Special - * @author cornelius weiss - * @return mixed false if password is considered "safe" or a string $message if "unsafe" - */ - function crackcheck($passwd) - { - if (!preg_match('/.{'. ($noc=7). ',}/',$passwd)) - { - $message = lang('Password must have at least %1 characters',$noc). '
'; - } - if(!preg_match('/(.*\d.*){'. ($non=1). ',}/',$passwd)) - { - $message .= lang('Password must contain at least %1 numbers',$non). '
'; - } - if(!preg_match('/(.*[[:upper:]].*){'. ($nou=1). ',}/',$passwd)) - { - $message .= lang('Password must contain at least %1 uppercase letters',$nou). '
'; - } - if(!preg_match('/(.*[[:lower:]].*){'. ($nol=1). ',}/',$passwd)) - { - $message .= lang('Password must contain at least %1 lowercase letters',$nol). '
'; - } - if(!preg_match('/(.*[\\!"#$%&\'()*+,-.\/:;<=>?@\[\]\^_ {|}~`].*){'. ($nol=1). ',}/',$passwd)) - { - $message .= lang('Password must contain at least %1 special characters',$nol). '
'; - } - return $message ? $message : false; - } - - /** - * compare SMD5-encrypted passwords for authentication - * - * @param string $form_val user input value for comparison - * @param string $db_val stored value (from database) - * @return boolean True on successful comparison - */ - function smd5_compare($form_val,$db_val) - { - /* Start with the first char after {SMD5} */ - $hash = base64_decode(substr($db_val,6)); - - /* SMD5 hashes are 16 bytes long */ - $orig_hash = substr($hash, 0, 16); - $salt = substr($hash, 16); - - $new_hash = mhash(MHASH_MD5,$form_val . $salt); - //echo '
DB: ' . base64_encode($orig_hash) . '
FORM: ' . base64_encode($new_hash); - - if(strcmp($orig_hash,$new_hash) == 0) - { - return True; - } - return False; - } - - /** - * compare SHA-encrypted passwords for authentication - * - * @param string $form_val user input value for comparison - * @param string $db_val stored value (from database) - * @return boolean True on successful comparison - */ - function sha_compare($form_val,$db_val) - { - /* Start with the first char after {SHA} */ - $hash = base64_decode(substr($db_val,5)); - $new_hash = mhash(MHASH_SHA1,$form_val); - //echo '
DB: ' . base64_encode($orig_hash) . '
FORM: ' . base64_encode($new_hash); - - if(strcmp($hash,$new_hash) == 0) - { - return True; - } - return False; - } - - /** - * compare SSHA-encrypted passwords for authentication - * - * @param string $form_val user input value for comparison - * @param string $db_val stored value (from database) - * @return boolean True on successful comparison - */ - function ssha_compare($form_val,$db_val) - { - /* Start with the first char after {SSHA} */ - $hash = base64_decode(substr($db_val, 6)); - - // SHA-1 hashes are 160 bits long - $orig_hash = substr($hash, 0, 20); - $salt = substr($hash, 20); - $new_hash = mhash(MHASH_SHA1, $form_val . $salt); - - if(strcmp($orig_hash,$new_hash) == 0) - { - return True; - } - return False; - } - - /** - * compare crypted passwords for authentication whether des,ext_des,md5, or blowfish crypt - * - * @param string $form_val user input value for comparison - * @param string $db_val stored value (from database) - * @param string $type crypt() type - * @return boolean True on successful comparison - */ - function crypt_compare($form_val,$db_val,$type) - { - $saltlen = array( - 'blowfish_crypt' => 16, - 'md5_crypt' => 12, - 'ext_crypt' => 9, - 'crypt' => 2 - ); - - // PHP's crypt(): salt + hash - // notice: "The encryption type is triggered by the salt argument." - $salt = substr($db_val, 0, (int)$saltlen[$type]); - $new_hash = crypt($form_val, $salt); - - if(strcmp($db_val,$new_hash) == 0) - { - return True; - } - return False; - } - - /** - * compare md5_hmac-encrypted passwords for authentication (see RFC2104) - * - * @param string $form_val user input value for comparison - * @param string $db_val stored value (from database) - * @param string $key key for md5_hmac-encryption (username for imported smf users) - * @return boolean True on successful comparison - */ - function md5_hmac_compare($form_val,$db_val,$key) - { - $key = str_pad(strlen($key) <= 64 ? $key : pack('H*', md5($key)), 64, chr(0x00)); - $md5_hmac = md5(($key ^ str_repeat(chr(0x5c), 64)) . pack('H*', md5(($key ^ str_repeat(chr(0x36), 64)). $form_val))); - if(strcmp($md5_hmac,$db_val) == 0) - { - return True; - } - return False; + return False; + case 'smd5': + return self::smd5_compare($cleartext,$encrypted); + case 'sha': + return self::sha_compare($cleartext,$encrypted); + case 'ssha': + return self::ssha_compare($cleartext,$encrypted); + case 'crypt': + case 'md5_crypt': + case 'blowfish_crypt': + case 'ext_crypt': + return self::crypt_compare($cleartext,$encrypted,$type); + case 'md5_hmac': + return self::md5_hmac_compare($cleartext,$encrypted,$username); + case 'md5': + default: + return strcmp(md5($cleartext),$encrypted) == 0 ? true : false; } } -?> + + /** + * encrypt password for ldap + * + * uses the encryption type set in setup and calls the appropriate encryption functions + * + * @param $password password to encrypt + */ + static function encrypt_ldap($password) + { + $type = strtolower($GLOBALS['egw_info']['server']['ldap_encryption_type']); + $salt = ''; + switch($type) + { + default: // eg. setup >> config never saved + case 'des': + $salt = self::randomstring(2); + $_password = crypt($password, $salt); + $e_password = '{crypt}'.$_password; + break; + case 'md5': + /* New method taken from the openldap-software list as recommended by + * Kervin L. Pierre" + */ + $e_password = '{md5}' . base64_encode(pack("H*",md5($password))); + break; + case 'smd5': + if(!function_exists('mhash')) + { + return False; + } + $salt = self::randomstring(8); + $hash = mhash(MHASH_MD5, $password . $salt); + $e_password = '{SMD5}' . base64_encode($hash . $salt); + break; + case 'sha': + if(!function_exists('mhash')) + { + return False; + } + $e_password = '{SHA}' . base64_encode(mhash(MHASH_SHA1, $password)); + break; + case 'ssha': + if(!function_exists('mhash')) + { + return False; + } + $salt = self::randomstring(8); + $hash = mhash(MHASH_SHA1, $password . $salt); + $e_password = '{SSHA}' . base64_encode($hash . $salt); + break; + case 'plain': + // if plain no type is prepended + $e_password =$password; + break; + } + return $e_password; + } + + /** + * Create an ldap hash from an sql hash + * + * @param string $hash + */ + static function hash_sql2ldap($hash) + { + switch(strtolower($GLOBALS['egw_info']['server']['sql_encryption_type'])) + { + case '': // not set sql_encryption_type + case 'md5': + $hash = '{md5}' . base64_encode(pack("H*",$hash)); + break; + case 'crypt': + $hash = '{crypt}' . $hash; + break; + case 'plain': + $saved_h = $hash; + if (preg_match('/^\\{([a-z_5]+)\\}(.+)$/i',$hash,$matches)) + { + $hash= $matches[2]; + } else { + $hash = $saved_h; + } + break; + } + return $hash; + } + + /** + * Create a password for storage in the accounts table + * + * @param string $password + * @return string hash + */ + static function encrypt_sql($password) + { + /* Grab configured type, or default to md5() (old method) */ + $type = @$GLOBALS['egw_info']['server']['sql_encryption_type'] + ? strtolower($GLOBALS['egw_info']['server']['sql_encryption_type']) + : 'md5'; + + switch($type) + { + case 'plain': + // since md5 is the default, type plain must be prepended, for eGroupware to understand + return '{PLAIN}'.$password; + case 'crypt': + if(@defined('CRYPT_STD_DES') && CRYPT_STD_DES == 1) + { + $salt = self::randomstring(2); + return crypt($password,$salt); + } + self::$error = 'no std crypt'; + break; + case 'blowfish_crypt': + if(@defined('CRYPT_BLOWFISH') && CRYPT_BLOWFISH == 1) + { + $salt = '$2$' . self::randomstring(13); + return crypt($password,$salt); + } + self::$error = 'no blowfish crypt'; + break; + case 'md5_crypt': + if(@defined('CRYPT_MD5') && CRYPT_MD5 == 1) + { + $salt = '$1$' . self::randomstring(9); + return crypt($password,$salt); + } + self::$error = 'no md5 crypt'; + break; + case 'ext_crypt': + if(@defined('CRYPT_EXT_DES') && CRYPT_EXT_DES == 1) + { + $salt = self::randomstring(9); + return crypt($password,$salt); + } + self::$error = 'no ext crypt'; + break; + case 'smd5': + if(!function_exists('mhash')) + { + return False; + } + $salt = self::randomstring(8); + $hash = mhash(MHASH_MD5, $password . $salt); + return '{SMD5}' . base64_encode($hash . $salt); + case 'sha': + if(!function_exists('mhash')) + { + self::$error = 'no sha'; + return False; + } + return '{SHA}' . base64_encode(mhash(MHASH_SHA1,$password)); + case 'ssha': + if(!function_exists('mhash')) + { + self::$error = 'no ssha'; + return False; + } + $salt = self::randomstring(8); + $hash = mhash(MHASH_SHA1, $password . $salt); + return '{SSHA}' . base64_encode($hash . $salt); + case 'md5': + default: + /* This is the old standard for password storage in SQL */ + return md5($password); + } + if (!self::$error) + { + self::$error = 'no valid encryption available'; + } + return False; + } + + /** + * Checks if a given password is "safe" + * + * @param string $login + * @abstract atm a simple check in length, #digits, #uppercase and #lowercase + * could be made more safe using e.g. pecl library cracklib + * but as pecl dosn't run on any platform and isn't GPL'd + * i haven't implemented it yet + * Windows compatible check is: 7 char lenth, 1 Up, 1 Low, 1 Num and 1 Special + * @author cornelius weiss + * @return mixed false if password is considered "safe" or a string $message if "unsafe" + */ + static function crackcheck($passwd) + { + if (!preg_match('/.{'. ($noc=7). ',}/',$passwd)) + { + $message = lang('Password must have at least %1 characters',$noc). '
'; + } + if(!preg_match('/(.*\d.*){'. ($non=1). ',}/',$passwd)) + { + $message .= lang('Password must contain at least %1 numbers',$non). '
'; + } + if(!preg_match('/(.*[[:upper:]].*){'. ($nou=1). ',}/',$passwd)) + { + $message .= lang('Password must contain at least %1 uppercase letters',$nou). '
'; + } + if(!preg_match('/(.*[[:lower:]].*){'. ($nol=1). ',}/',$passwd)) + { + $message .= lang('Password must contain at least %1 lowercase letters',$nol). '
'; + } + if(!preg_match('/(.*[\\!"#$%&\'()*+,-.\/:;<=>?@\[\]\^_ {|}~`].*){'. ($nol=1). ',}/',$passwd)) + { + $message .= lang('Password must contain at least %1 special characters',$nol). '
'; + } + return $message ? $message : false; + } + + /** + * compare SMD5-encrypted passwords for authentication + * + * @param string $form_val user input value for comparison + * @param string $db_val stored value (from database) + * @return boolean True on successful comparison + */ + static function smd5_compare($form_val,$db_val) + { + /* Start with the first char after {SMD5} */ + $hash = base64_decode(substr($db_val,6)); + + /* SMD5 hashes are 16 bytes long */ + $orig_hash = substr($hash, 0, 16); + $salt = substr($hash, 16); + + $new_hash = mhash(MHASH_MD5,$form_val . $salt); + //echo '
DB: ' . base64_encode($orig_hash) . '
FORM: ' . base64_encode($new_hash); + + if(strcmp($orig_hash,$new_hash) == 0) + { + return True; + } + return False; + } + + /** + * compare SHA-encrypted passwords for authentication + * + * @param string $form_val user input value for comparison + * @param string $db_val stored value (from database) + * @return boolean True on successful comparison + */ + static function sha_compare($form_val,$db_val) + { + /* Start with the first char after {SHA} */ + $hash = base64_decode(substr($db_val,5)); + $new_hash = mhash(MHASH_SHA1,$form_val); + //echo '
DB: ' . base64_encode($orig_hash) . '
FORM: ' . base64_encode($new_hash); + + if(strcmp($hash,$new_hash) == 0) + { + return True; + } + return False; + } + + /** + * compare SSHA-encrypted passwords for authentication + * + * @param string $form_val user input value for comparison + * @param string $db_val stored value (from database) + * @return boolean True on successful comparison + */ + static function ssha_compare($form_val,$db_val) + { + /* Start with the first char after {SSHA} */ + $hash = base64_decode(substr($db_val, 6)); + + // SHA-1 hashes are 160 bits long + $orig_hash = substr($hash, 0, 20); + $salt = substr($hash, 20); + $new_hash = mhash(MHASH_SHA1, $form_val . $salt); + + if(strcmp($orig_hash,$new_hash) == 0) + { + return True; + } + return False; + } + + /** + * compare crypted passwords for authentication whether des,ext_des,md5, or blowfish crypt + * + * @param string $form_val user input value for comparison + * @param string $db_val stored value (from database) + * @param string $type crypt() type + * @return boolean True on successful comparison + */ + static function crypt_compare($form_val,$db_val,$type) + { + $saltlen = array( + 'blowfish_crypt' => 16, + 'md5_crypt' => 12, + 'ext_crypt' => 9, + 'crypt' => 2 + ); + + // PHP's crypt(): salt + hash + // notice: "The encryption type is triggered by the salt argument." + $salt = substr($db_val, 0, (int)$saltlen[$type]); + $new_hash = crypt($form_val, $salt); + + if(strcmp($db_val,$new_hash) == 0) + { + return True; + } + return False; + } + + /** + * compare md5_hmac-encrypted passwords for authentication (see RFC2104) + * + * @param string $form_val user input value for comparison + * @param string $db_val stored value (from database) + * @param string $key key for md5_hmac-encryption (username for imported smf users) + * @return boolean True on successful comparison + */ + static function md5_hmac_compare($form_val,$db_val,$key) + { + $key = str_pad(strlen($key) <= 64 ? $key : pack('H*', md5($key)), 64, chr(0x00)); + $md5_hmac = md5(($key ^ str_repeat(chr(0x5c), 64)) . pack('H*', md5(($key ^ str_repeat(chr(0x36), 64)). $form_val))); + if(strcmp($md5_hmac,$db_val) == 0) + { + return True; + } + return False; + } +} diff --git a/phpgwapi/inc/class.auth_ldap.inc.php b/phpgwapi/inc/class.auth_ldap.inc.php index ffdd845940..cc02c0b33c 100644 --- a/phpgwapi/inc/class.auth_ldap.inc.php +++ b/phpgwapi/inc/class.auth_ldap.inc.php @@ -1,164 +1,155 @@ * - * and Joseph Engo * - * Authentication based on LDAP Server * - * Copyright (C) 2000, 2001 Joseph Engo * - * Copyright (C) 2002, 2003 Lars Kneschke * - * ------------------------------------------------------------------------ * - * This library is part of the eGroupWare API * - * http://www.egroupware.org/api * - * ------------------------------------------------------------------------ * - * This library is free software; you can redistribute it and/or modify it * - * under the terms of the GNU Lesser General Public License as published by * - * the Free Software Foundation; either version 2.1 of the License, * - * or any later version. * - * This library is distributed in the hope that it will be useful, but * - * WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * - * See the GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public License * - * along with this library; if not, write to the Free Software Foundation, * - * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * - \**************************************************************************/ +/** + * eGroupWare API - LDAP Authentication + * + * @link http://www.egroupware.org + * @author Lars Kneschke + * @author Joseph Engo + * Copyright (C) 2000, 2001 Joseph Engo + * Copyright (C) 2002, 2003 Lars Kneschke + * @license http://opensource.org/licenses/lgpl-license.php LGPL - GNU Lesser General Public License + * @package api + * @subpackage authentication + * @version $Id$ + */ - /* $Id$ */ +/** + * Authentication agains a LDAP Server + */ +class auth_ +{ + var $previous_login = -1; - class auth_ + /** + * authentication against LDAP + * + * @param string $username username of account to authenticate + * @param string $passwd corresponding password + * @return boolean true if successful authenticated, false otherwise + */ + function authenticate($username, $passwd) { - var $previous_login = -1; + // allow non-ascii in username & password + $username = $GLOBALS['egw']->translation->convert($username,$GLOBALS['egw']->translation->charset(),'utf-8'); + $passwd = $GLOBALS['egw']->translation->convert($passwd,$GLOBALS['egw']->translation->charset(),'utf-8'); - /** - * authentication against LDAP - * - * @param string $username username of account to authenticate - * @param string $passwd corresponding password - * @return boolean true if successful authenticated, false otherwise - */ - function authenticate($username, $passwd) + if(!$ldap = $GLOBALS['egw']->common->ldapConnect()) { - // allow non-ascii in username & password - $username = $GLOBALS['egw']->translation->convert($username,$GLOBALS['egw']->translation->charset(),'utf-8'); - $passwd = $GLOBALS['egw']->translation->convert($passwd,$GLOBALS['egw']->translation->charset(),'utf-8'); - - if(!$ldap = $GLOBALS['egw']->common->ldapConnect()) - { - $GLOBALS['egw']->log->message('F-Abort, Failed connecting to LDAP server for authenication, execution stopped'); - $GLOBALS['egw']->log->commit(); - return False; - } - - /* Login with the LDAP Admin. User to find the User DN. */ - if(!@ldap_bind($ldap, $GLOBALS['egw_info']['server']['ldap_root_dn'], $GLOBALS['egw_info']['server']['ldap_root_pw'])) - { - return False; - } - /* find the dn for this uid, the uid is not always in the dn */ - $attributes = array('uid','dn','givenName','sn','mail','uidNumber','shadowExpire'); - - $filter = $GLOBALS['egw_info']['server']['ldap_search_filter'] ? $GLOBALS['egw_info']['server']['ldap_search_filter'] : '(uid=%user)'; - $filter = str_replace(array('%user','%domain'),array(ldap::quote($username),$GLOBALS['egw_info']['user']['domain']),$filter); - - if ($GLOBALS['egw_info']['server']['account_repository'] == 'ldap') - { - $filter = "(&$filter(objectclass=posixaccount))"; - } - $sri = ldap_search($ldap, $GLOBALS['egw_info']['server']['ldap_context'], $filter, $attributes); - $allValues = ldap_get_entries($ldap, $sri); - - if ($allValues['count'] > 0) - { - if ($GLOBALS['egw_info']['server']['case_sensitive_username'] == true && - $allValues[0]['uid'][0] != $username) - { - return false; - } - if ($GLOBALS['egw_info']['server']['account_repository'] == 'ldap' && - isset($allValues[0]['shadowexpire']) && $allValues[0]['shadowexpire'][0]*24*3600 < time()) - { - return false; // account is expired - } - $userDN = $allValues[0]['dn']; - - // try to bind as the user with user suplied password - // only if a non-empty password given, in case anonymous search is enabled - if (!empty($passwd) && @ldap_bind($ldap, $userDN, $passwd)) - { - if ($GLOBALS['egw_info']['server']['account_repository'] != 'ldap') - { - if (!$account->account_id && $GLOBALS['egw_info']['server']['auto_create_acct']) - { - // create a global array with all availible info about that account - $GLOBALS['auto_create_acct'] = array(); - foreach(array( - 'givenname' => 'firstname', - 'sn' => 'lastname', - 'uidnumber' => 'account_id', - 'mail' => 'email', - ) as $ldap_name => $acct_name) - { - $GLOBALS['auto_create_acct'][$acct_name] = - $GLOBALS['egw']->translation->convert($allValues[0][$ldap_name][0],'utf-8'); - } - return True; - } - return ($id = $GLOBALS['egw']->accounts->name2id($username,'account_lid','u')) && - $GLOBALS['egw']->accounts->id2name($id,'account_status') == 'A'; - } - return True; - } - } - // dn not found or password wrong + $GLOBALS['egw']->log->message('F-Abort, Failed connecting to LDAP server for authenication, execution stopped'); + $GLOBALS['egw']->log->commit(); return False; } - /** - * changes password in LDAP - * - * If $old_passwd is given, the password change is done binded as user and NOT with the - * "root" dn given in the configurations. - * - * @param string $old_passwd must be cleartext or empty to not to be checked - * @param string $new_passwd must be cleartext - * @param int $account_id account id of user whose passwd should be changed - * @return boolean true if password successful changed, false otherwise - */ - function change_password($old_passwd, $new_passwd, $account_id=0) + /* Login with the LDAP Admin. User to find the User DN. */ + if(!@ldap_bind($ldap, $GLOBALS['egw_info']['server']['ldap_root_dn'], $GLOBALS['egw_info']['server']['ldap_root_pw'])) { - if (!$account_id) - { - $username = $GLOBALS['egw_info']['user']['account_lid']; - } - else - { - $username = $GLOBALS['egw']->translation->convert($GLOBALS['egw']->accounts->id2name($account_id), - $GLOBALS['egw']->translation->charset(),'utf-8'); - } - //echo "

auth_ldap::change_password('$old_password','$new_passwd',$account_id) username='$username'

\n"; + return False; + } + /* find the dn for this uid, the uid is not always in the dn */ + $attributes = array('uid','dn','givenName','sn','mail','uidNumber','shadowExpire'); - $filter = $GLOBALS['egw_info']['server']['ldap_search_filter'] ? $GLOBALS['egw_info']['server']['ldap_search_filter'] : '(uid=%user)'; - $filter = str_replace(array('%user','%domain'),array($username,$GLOBALS['egw_info']['user']['domain']),$filter); + $filter = $GLOBALS['egw_info']['server']['ldap_search_filter'] ? $GLOBALS['egw_info']['server']['ldap_search_filter'] : '(uid=%user)'; + $filter = str_replace(array('%user','%domain'),array(ldap::quote($username),$GLOBALS['egw_info']['user']['domain']),$filter); - $ds = $GLOBALS['egw']->common->ldapConnect(); - $sri = ldap_search($ds, $GLOBALS['egw_info']['server']['ldap_context'], $filter); - $allValues = ldap_get_entries($ds, $sri); - - $entry['userpassword'] = $this->encrypt_password($new_passwd); - $dn = $allValues[0]['dn']; + if ($GLOBALS['egw_info']['server']['account_repository'] == 'ldap') + { + $filter = "(&$filter(objectclass=posixaccount))"; + } + $sri = ldap_search($ldap, $GLOBALS['egw_info']['server']['ldap_context'], $filter, $attributes); + $allValues = ldap_get_entries($ldap, $sri); - if($old_passwd) // if old password given (not called by admin) --> bind as that user to change the pw - { - $ds = $GLOBALS['egw']->common->ldapConnect('',$dn,$old_passwd); - } - if (!@ldap_modify($ds, $dn, $entry)) + if ($allValues['count'] > 0) + { + if ($GLOBALS['egw_info']['server']['case_sensitive_username'] == true && + $allValues[0]['uid'][0] != $username) { return false; } - if($old_passwd) // if old password given (not called by admin) update the password in the session + if ($GLOBALS['egw_info']['server']['account_repository'] == 'ldap' && + isset($allValues[0]['shadowexpire']) && $allValues[0]['shadowexpire'][0]*24*3600 < time()) { - $GLOBALS['egw']->session->appsession('password','phpgwapi',$new_passwd); + return false; // account is expired + } + $userDN = $allValues[0]['dn']; + + // try to bind as the user with user suplied password + // only if a non-empty password given, in case anonymous search is enabled + if (!empty($passwd) && @ldap_bind($ldap, $userDN, $passwd)) + { + if ($GLOBALS['egw_info']['server']['account_repository'] != 'ldap') + { + if ($GLOBALS['egw_info']['server']['auto_create_acct']) + { + // create a global array with all availible info about that account + $GLOBALS['auto_create_acct'] = array(); + foreach(array( + 'givenname' => 'firstname', + 'sn' => 'lastname', + 'uidnumber' => 'account_id', + 'mail' => 'email', + ) as $ldap_name => $acct_name) + { + $GLOBALS['auto_create_acct'][$acct_name] = + $GLOBALS['egw']->translation->convert($allValues[0][$ldap_name][0],'utf-8'); + } + return True; + } + return ($id = $GLOBALS['egw']->accounts->name2id($username,'account_lid','u')) && + $GLOBALS['egw']->accounts->id2name($id,'account_status') == 'A'; + } + return True; } - return $entry['userpassword']; } + // dn not found or password wrong + return False; } + + /** + * changes password in LDAP + * + * If $old_passwd is given, the password change is done binded as user and NOT with the + * "root" dn given in the configurations. + * + * @param string $old_passwd must be cleartext or empty to not to be checked + * @param string $new_passwd must be cleartext + * @param int $account_id account id of user whose passwd should be changed + * @return boolean true if password successful changed, false otherwise + */ + function change_password($old_passwd, $new_passwd, $account_id=0) + { + if (!$account_id) + { + $username = $GLOBALS['egw_info']['user']['account_lid']; + } + else + { + $username = $GLOBALS['egw']->translation->convert($GLOBALS['egw']->accounts->id2name($account_id), + $GLOBALS['egw']->translation->charset(),'utf-8'); + } + //echo "

auth_ldap::change_password('$old_password','$new_passwd',$account_id) username='$username'

\n"; + + $filter = $GLOBALS['egw_info']['server']['ldap_search_filter'] ? $GLOBALS['egw_info']['server']['ldap_search_filter'] : '(uid=%user)'; + $filter = str_replace(array('%user','%domain'),array($username,$GLOBALS['egw_info']['user']['domain']),$filter); + + $ds = $GLOBALS['egw']->common->ldapConnect(); + $sri = ldap_search($ds, $GLOBALS['egw_info']['server']['ldap_context'], $filter); + $allValues = ldap_get_entries($ds, $sri); + + $entry['userpassword'] = auth::encrypt_password($new_passwd); + $dn = $allValues[0]['dn']; + + if($old_passwd) // if old password given (not called by admin) --> bind as that user to change the pw + { + $ds = $GLOBALS['egw']->common->ldapConnect('',$dn,$old_passwd); + } + if (!@ldap_modify($ds, $dn, $entry)) + { + return false; + } + if($old_passwd) // if old password given (not called by admin) update the password in the session + { + $GLOBALS['egw']->session->appsession('password','phpgwapi',$new_passwd); + } + return $entry['userpassword']; + } +} diff --git a/phpgwapi/inc/class.auth_sql.inc.php b/phpgwapi/inc/class.auth_sql.inc.php index e0aea2f4cc..8719405da6 100644 --- a/phpgwapi/inc/class.auth_sql.inc.php +++ b/phpgwapi/inc/class.auth_sql.inc.php @@ -1,175 +1,171 @@ * - * and Joseph Engo * - * Encryption types other than md5() added by * - * Miles Lott based on code from * - * http://www.thomas-alfeld.de/frank/ * - * massive code cleanup and * - * added password migration by * - * Cornelius Weiss + * @author Joseph Engo + * Copyright (C) 2000, 2001 Dan Kuykendall + * @license http://opensource.org/licenses/lgpl-license.php LGPL - GNU Lesser General Public License + * @package api + * @subpackage authentication + * @version $Id$ + */ - class auth_ +/** + * eGroupWare API - Authentication based on SQL table of accounts + * + * Encryption types other than md5() added by Miles Lott + * based on code from http://www.thomas-alfeld.de/frank/ + * + * Massive code cleanup and added password migration by Cornelius Weiss db = $GLOBALS['egw']->db; - function auth_() + $this->type = @$GLOBALS['egw_info']['server']['sql_encryption_type'] ? + strtolower($GLOBALS['egw_info']['server']['sql_encryption_type']) : 'md5'; + } + + /** + * password authentication against password stored in sql datababse + * + * @param string $username username of account to authenticate + * @param string $passwd corresponding password + * @param string $passwd_type='text' 'text' for cleartext passwords (default) + * @return boolean true if successful authenticated, false otherwise + */ + function authenticate($username, $passwd, $passwd_type='text') + { + /* normal web form login */ + if($passwd_type == 'text') { - $this->db = clone($GLOBALS['egw']->db); - $this->db->set_app('phpgwapi'); - $this->table = 'egw_accounts'; - - $this->type = @$GLOBALS['egw_info']['server']['sql_encryption_type'] ? - strtolower($GLOBALS['egw_info']['server']['sql_encryption_type']) : 'md5'; - } - - /** - * password authentication against password stored in sql datababse - * - * @param string $username username of account to authenticate - * @param string $passwd corresponding password - * @param string $passwd_type='text' 'text' for cleartext passwords (default) - * @return boolean true if successful authenticated, false otherwise - */ - function authenticate($username, $passwd, $passwd_type='text') - { - /* normal web form login */ - if($passwd_type == 'text') - { - $this->db->select($this->table,'account_lid,account_pwd,account_lastlogin',array( - 'account_lid' => $username, - 'account_type' => 'u', - 'account_status' => 'A' - ),__LINE__,__FILE__); - - if(!$this->db->next_record() || !$this->db->f('account_pwd') || - $GLOBALS['egw_info']['server']['case_sensitive_username'] && $this->db->f('account_lid') != $username) - { - return false; - } - if(!$this->compare_password($passwd,$this->db->f('account_pwd'),$this->type,strtolower($username))) - { - $match = false; - // do we have to migrate an old password ? - if($GLOBALS['egw_info']['server']['pwd_migration_allowed'] && !empty($GLOBALS['egw_info']['server']['pwd_migration_types'])) - { - foreach(explode(',', $GLOBALS['egw_info']['server']['pwd_migration_types']) as $type) - { - if($this->compare_password($passwd,$this->db->f('account_pwd'),$type,strtolower($username))) - { - $account_id = $GLOBALS['egw_info']['user']['account_id']; - $encrypted_passwd = $this->encrypt_sql($passwd); - $this->_update_passwd($encrypted_passwd,$passwd,$account_id); - $match = true; - break; - } - } - } - if (!$match) return false; - } - } - /* Auth via crypted password. NOTE: mail needs cleartext password to authenticate against mailserver! */ - else - { - $this->db->select($this->table,'account_lid,account_lastlogin',array( - 'account_lid' => $username, - 'account_type' => 'u', - 'account_status' => 'A', - 'account_pwd' => $passwd, - ),__LINE__,__FILE__); - - if(!$this->db->next_record() || - $GLOBALS['egw_info']['server']['case_sensitive_username'] && $this->db->f('account_lid') != $username) - { - return false; - } - } - // if this point is reached, auth was successfull - $this->previous_login = $this->db->f('account_lastlogin'); - - return true; - } - - /** - * changes password in sql datababse - * - * @param string $old_passwd must be cleartext - * @param string $new_passwd must be cleartext - * @param int $account_id account id of user whose passwd should be changed - * @return boolean true if password successful changed, false otherwise - */ - function change_password($old_passwd, $new_passwd, $account_id=0) - { - $admin = True; - // Don't allow password changes for other accounts when using XML-RPC - if(!$account_id || $GLOBALS['egw_info']['flags']['currentapp'] == 'login') - { - $admin = False; - $account_id = $GLOBALS['egw_info']['user']['account_id']; - } - - $this->db->select($this->table,'account_pwd',array( - 'account_id' => $account_id, + if (!($row = $this->db->select($this->table,'account_lid,account_pwd,account_lastlogin',array( + 'account_lid' => $username, 'account_type' => 'u', - 'account_status' => 'A', - ),__LINE__,__FILE__); - - if(!$this->db->next_record()) return false; // account not found - - /* Check the old_passwd to make sure this is legal */ - if(!$admin && !$this->compare_password($old_passwd,$this->db->f('account_pwd'),$this->type,strtolower($username))) + 'account_status' => 'A' + ),__LINE__,__FILE__)->fetch()) || empty($row['account_pwd']) || + $GLOBALS['egw_info']['server']['case_sensitive_username'] && $row['account_lid'] != $username) { return false; } - - /* old password ok, or admin called the function from the admin application (no old passwd available).*/ - return $this->_update_passwd($this->encrypt_sql($new_passwd),$new_passwd,$account_id,$admin); - } - - /** - * changes password in sql datababse - * - * @internal - * @param string $encrypted_passwd - * @param string $new_passwd cleartext - * @param int $account_id account id of user whose passwd should be changed - * @param boolean $admin=false called by admin, if not update password in the session - * @return boolean true if password successful changed, false otherwise - */ - function _update_passwd($encrypted_passwd,$new_passwd,$account_id,$admin=false) - { - $this->db->update($this->table,array( - 'account_pwd' => $encrypted_passwd, - 'account_lastpwd_change' => time(), - ),array( - 'account_id' => $account_id, - ),__LINE__,__FILE__); - - if(!$this->db->affected_rows()) return false; - - if(!$admin) + if(!auth::compare_password($passwd,$row['account_pwd'],$this->type,strtolower($username))) { - $GLOBALS['egw']->session->appsession('password','phpgwapi',$new_passwd); + $match = false; + // do we have to migrate an old password ? + if($GLOBALS['egw_info']['server']['pwd_migration_allowed'] && !empty($GLOBALS['egw_info']['server']['pwd_migration_types'])) + { + foreach(explode(',', $GLOBALS['egw_info']['server']['pwd_migration_types']) as $type) + { + if(auth::compare_password($passwd,$row['account_pwd'],$type,strtolower($username))) + { + $account_id = $GLOBALS['egw_info']['user']['account_id']; + $encrypted_passwd = $this->encrypt_sql($passwd); + $this->_update_passwd($encrypted_passwd,$passwd,$account_id); + $match = true; + break; + } + } + } + if (!$match) + { + return false; + } } - return $encrypted_passwd; } + /* Auth via crypted password. NOTE: mail needs cleartext password to authenticate against mailserver! */ + else + { + if (!($row = $this->db->select($this->table,'account_lid,account_lastlogin',array( + 'account_lid' => $username, + 'account_type' => 'u', + 'account_status' => 'A', + 'account_pwd' => $passwd, + ),__LINE__,__FILE__)->fetch()) || + $GLOBALS['egw_info']['server']['case_sensitive_username'] && $row['account_lid'] != $username) + { + return false; + } + } + // if this point is reached, auth was successfull + $this->previous_login = $row['account_lastlogin']; + + return true; } + + /** + * changes password in sql datababse + * + * @param string $old_passwd must be cleartext + * @param string $new_passwd must be cleartext + * @param int $account_id account id of user whose passwd should be changed + * @return boolean true if password successful changed, false otherwise + */ + function change_password($old_passwd, $new_passwd, $account_id=0) + { + $admin = True; + // Don't allow password changes for other accounts when using XML-RPC + if(!$account_id || $GLOBALS['egw_info']['flags']['currentapp'] == 'login') + { + $admin = False; + $account_id = $GLOBALS['egw_info']['user']['account_id']; + } + + if (($pw = $this->db->select($this->table,'account_pwd',array( + 'account_id' => $account_id, + 'account_type' => 'u', + 'account_status' => 'A', + ),__LINE__,__FILE__)->fetchSingle()) === false) + { + return false; // account not found + } + // Check the old_passwd to make sure this is legal + if(!$admin && !auth::compare_password($old_passwd,$pw,$this->type,strtolower($username))) + { + return false; + } + + // old password ok, or admin called the function from the admin application (no old passwd available). + return $this->_update_passwd($this->encrypt_sql($new_passwd),$new_passwd,$account_id,$admin); + } + + /** + * changes password in sql datababse + * + * @internal + * @param string $encrypted_passwd + * @param string $new_passwd cleartext + * @param int $account_id account id of user whose passwd should be changed + * @param boolean $admin=false called by admin, if not update password in the session + * @return boolean true if password successful changed, false otherwise + */ + function _update_passwd($encrypted_passwd,$new_passwd,$account_id,$admin=false) + { + $this->db->update($this->table,array( + 'account_pwd' => $encrypted_passwd, + 'account_lastpwd_change' => time(), + ),array( + 'account_id' => $account_id, + ),__LINE__,__FILE__); + + if(!$this->db->affected_rows()) return false; + + if(!$admin) + { + $GLOBALS['egw']->session->appsession('password','phpgwapi',$new_passwd); + } + return $encrypted_passwd; + } +}