diff --git a/html/main.php b/html/main.php index 8b467d5dba0d42c1c949497a384a48f67af4e6a2..df5cd95a6c81a61d59f0337c60804a2ab4c3a806 100644 --- a/html/main.php +++ b/html/main.php @@ -106,35 +106,33 @@ if (session::global_is_set('plugin_index')) { $plist->gen_menu(); -/* check if we are using account expiration */ -$smarty->assign("hideMenus", FALSE); -if ($config->get_cfg_value("handleExpiredAccounts") == "TRUE") { - $expired = $ui->expired_status(); - if (($expired == POSIX_WARN_ABOUT_EXPIRATION) && !session::is_set('POSIX_WARN_ABOUT_EXPIRATION__DONE')) { - @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $expired, 'This user account ('.$ui->uid.') is about to expire'); - - // The users password is about to xpire soon, display a warning message. - logging::log('security', 'fusiondirectory', '', [], 'password for user "'.$ui->uid.'" is about to expire'); - msg_dialog::display(_('Password change'), _('Your password is about to expire, please change your password!'), INFO_DIALOG); - session::set('POSIX_WARN_ABOUT_EXPIRATION__DONE', TRUE); - } elseif ($expired == POSIX_FORCE_PASSWORD_CHANGE) { - @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $expired, "This user account expired"); - - // The password is expired, we are now going to enforce a new one from the user. - - // Hide the FusionDirectory menus to avoid leaving the enforced password change dialog. - $smarty->assign("hideMenus", TRUE); - $plug = (isset($_GET['plug'])) ? $_GET['plug'] : NULL; - - // Search for the 'user' class and set its id as active plug. - foreach ($plist->dirlist as $key => $value) { - if ($value == 'user') { - if (!isset($_GET['plug']) || ($_GET['plug'] != $key)) { - $_GET['plug'] = $key; - msg_dialog::display(_('Warning'), _('Your password has expired, please set a new one.'), WARNING_DIALOG); - } - break; +$smarty->assign('hideMenus', FALSE); +/* check user expiration status */ +$expired = $ui->expired_status(); +if (($expired == POSIX_WARN_ABOUT_EXPIRATION) && !session::is_set('POSIX_WARN_ABOUT_EXPIRATION__DONE')) { + @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $expired, 'This user account ('.$ui->uid.') is about to expire'); + + // The users password is about to expire soon, display a warning message. + logging::log('security', 'fusiondirectory', '', [], 'password for user "'.$ui->uid.'" is about to expire'); + msg_dialog::display(_('Password change'), _('Your password is about to expire, please change your password!'), INFO_DIALOG); + session::set('POSIX_WARN_ABOUT_EXPIRATION__DONE', TRUE); +} elseif ($expired == POSIX_FORCE_PASSWORD_CHANGE) { + @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $expired, 'This user account expired'); + + // The password is expired, we are now going to enforce a new one from the user. + + // Hide the FusionDirectory menus to avoid leaving the enforced password change dialog. + $smarty->assign('hideMenus', TRUE); + $plug = (isset($_GET['plug'])) ? $_GET['plug'] : NULL; + + // Search for the 'user' class and set its id as active plug. + foreach ($plist->dirlist as $key => $value) { + if ($value == 'user') { + if (!isset($_GET['plug']) || ($_GET['plug'] != $key)) { + $_GET['plug'] = $key; + msg_dialog::display(_('Warning'), _('Your password has expired, please set a new one.'), WARNING_DIALOG); } + break; } } } diff --git a/include/class_userinfo.inc b/include/class_userinfo.inc index 5579feb9e237c867bc49128b7db781cc5ed34f3c..f6f92859d077f86d8acba54909d120ec731980f8 100644 --- a/include/class_userinfo.inc +++ b/include/class_userinfo.inc @@ -1,8 +1,9 @@ <?php /* This code is part of FusionDirectory (http://www.fusiondirectory.org/) + Copyright (C) 2003-2010 Cajus Pollmeier - Copyright (C) 2011-2016 FusionDirectory + Copyright (C) 2011-2019 FusionDirectory This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,6 +25,13 @@ * Source code for the class userinfo */ +/* Define shadow states */ +define('POSIX_ACCOUNT_EXPIRED', 1); +define('POSIX_WARN_ABOUT_EXPIRATION', 2); +define('POSIX_FORCE_PASSWORD_CHANGE', 4); +define('POSIX_DISALLOW_PASSWORD_CHANGE', 8); +define('PPOLICY_ACCOUNT_EXPIRED', 16); + /*! * \brief Class userinfo * This class contains all informations and functions @@ -890,12 +898,26 @@ class userinfo function expired_status () { global $config; + // Skip this for the admin account, we do not want to lock him out. if ($this->is_user_admin()) { return 0; } $ldap = $config->get_ldap_link(); + + if (class_available('ppolicyAccount')) { + $ldap->cd($config->current['BASE']); + $ldap->search('(objectClass=*)', [], 'one'); + if (!$ldap->success()) { + return PPOLICY_ACCOUNT_EXPIRED; + } + } + + if ($config->get_cfg_value('handleExpiredAccounts') != 'TRUE') { + return 0; + } + $ldap->cd($config->current['BASE']); $ldap->cat($this->dn); $attrs = $ldap->fetch(); @@ -1078,4 +1100,114 @@ class userinfo { return preg_replace('/[\/\-,.#:;]/', '_', $name); } + + /*! + * \brief Get user from LDAP directory + * + * Search the user by login or other fields authorized by the configuration + * + * \param string $username The username or email to check + * + * \return userinfo instance on SUCCESS, FALSE if not found, string error on error + */ + public static function getLdapUser (string $username) + { + global $config; + + /* look through the entire ldap */ + $ldap = $config->get_ldap_link(); + if (!$ldap->success()) { + msg_dialog::display(_('LDAP error'), + msgPool::ldaperror($ldap->get_error(), '', LDAP_AUTH), + FATAL_ERROR_DIALOG); + exit(); + } + + $allowed_attributes = ['uid','mail']; + $verify_attr = []; + $tmp = explode(',', $config->get_cfg_value('loginAttribute')); + foreach ($tmp as $attr) { + if (in_array($attr, $allowed_attributes)) { + $verify_attr[] = $attr; + } + } + + if (count($verify_attr) == 0) { + $verify_attr = ['uid']; + } + $tmp = $verify_attr; + $tmp[] = 'uid'; + $filter = ''; + foreach ($verify_attr as $attr) { + $filter .= '('.$attr.'='.$username.')'; + } + $filter = '(&(|'.$filter.')(objectClass=inetOrgPerson))'; + $ldap->cd($config->current['BASE']); + $ldap->search($filter, $tmp); + + /* get results, only a count of 1 is valid */ + if ($ldap->count() == 0) { + /* user not found */ + return FALSE; + } elseif ($ldap->count() != 1) { + /* found more than one matching id */ + return _('Login (uid) is not unique inside the LDAP tree. Please contact your administrator.'); + } + + /* LDAP schema is not case sensitive. Perform additional check. */ + $attrs = $ldap->fetch(); + $success = FALSE; + foreach ($verify_attr as $attr) { + if (isset($attrs[$attr][0]) && $attrs[$attr][0] == $username) { + $success = TRUE; + } + } + $ldap->disconnect(); + if (!$success) { + return FALSE; + } + + return new userinfo($attrs['dn']); + } + + /*! + * \brief Verify user login against LDAP directory + * + * Checks if the specified username is in the LDAP and verifies if the + * password is correct by binding to the LDAP with the given credentials. + * + * \param string $username The username to check + * + * \param string $password The password to check + * + * \return TRUE on SUCCESS, NULL or FALSE on error + */ + public static function loginUser (string $username, string $password) + { + global $config; + + $ui = static::getLdapUser($username); + + if ($ui === FALSE) { + return NULL; + } elseif (is_string($ui)) { + msg_dialog::display(_('Internal error'), $ui, FATAL_ERROR_DIALOG); + return NULL; + } + + /* password check, bind as user with supplied password */ + $ldapObj = new LDAP($ui->dn, $password, $config->current['SERVER'], + isset($config->current['LDAPFOLLOWREFERRALS']) && ($config->current['LDAPFOLLOWREFERRALS'] == 'TRUE'), + isset($config->current['LDAPTLS']) && ($config->current['LDAPTLS'] == 'TRUE') + ); + $ldap = new ldapMultiplexer($ldapObj); + if (!$ldap->success()) { + return NULL; + } + + /* Username is set, load subtreeACL's now */ + $ui->loadACL(); + + return $ui; + } } diff --git a/include/functions.inc b/include/functions.inc index 192949fef551ca3139ddf48b7d5e35cdd5f7b3ff..7c2dcfb116212ac54102f8dadd40b2c4931ec4b4 100644 --- a/include/functions.inc +++ b/include/functions.inc @@ -45,12 +45,6 @@ define('DEBUG_SI', 256); /*! Debug level for communication with Argonaut * define('DEBUG_MAIL', 512); /*! Debug level for all about mail (mailAccounts, imap, sieve etc.) */ define('DEBUG_FAI', 1024); /* FAI (incomplete) */ -/* Define shadow states */ -define('POSIX_ACCOUNT_EXPIRED', 1); -define('POSIX_WARN_ABOUT_EXPIRATION', 2); -define('POSIX_FORCE_PASSWORD_CHANGE', 4); -define('POSIX_DISALLOW_PASSWORD_CHANGE', 8); - /* Rewrite german 'umlauts' and spanish 'accents' to get better results */ $REWRITE = [ "ä" => "ae", @@ -394,130 +388,6 @@ function ldap_init ($server, $base, $binddn = '', $pass = '') return $ldap; } -/*! - * \brief Get user from LDAP directory - * - * Search the user by login or other fields authorized by the configuration - * - * \param string $username The username or email to check - * - * \return userinfo instance on SUCCESS, FALSE if not found, string error on error - */ -function ldap_get_user ($username) -{ - global $config; - - /* look through the entire ldap */ - $ldap = $config->get_ldap_link(); - if (!$ldap->success()) { - msg_dialog::display(_('LDAP error'), - msgPool::ldaperror($ldap->get_error(), '', LDAP_AUTH), - FATAL_ERROR_DIALOG); - exit(); - } - - $allowed_attributes = ['uid','mail']; - $verify_attr = []; - $tmp = explode(',', $config->get_cfg_value('loginAttribute')); - foreach ($tmp as $attr) { - if (in_array($attr, $allowed_attributes)) { - $verify_attr[] = $attr; - } - } - - if (count($verify_attr) == 0) { - $verify_attr = ['uid']; - } - $tmp = $verify_attr; - $tmp[] = 'uid'; - $filter = ''; - foreach ($verify_attr as $attr) { - $filter .= '('.$attr.'='.$username.')'; - } - $filter = '(&(|'.$filter.')(objectClass=inetOrgPerson))'; - $ldap->cd($config->current['BASE']); - $ldap->search($filter, $tmp); - - /* get results, only a count of 1 is valid */ - if ($ldap->count() == 0) { - /* user not found */ - return FALSE; - } elseif ($ldap->count() != 1) { - /* found more than one matching id */ - return _('Login (uid) is not unique inside the LDAP tree. Please contact your administrator.'); - } - - /* LDAP schema is not case sensitive. Perform additional check. */ - $attrs = $ldap->fetch(); - $success = FALSE; - foreach ($verify_attr as $attr) { - if (isset($attrs[$attr][0]) && $attrs[$attr][0] == $username) { - $success = TRUE; - } - } - $ldap->disconnect(); - if (!$success) { - return FALSE; - } - - return new userinfo($attrs['dn']); -} - -/*! - * \brief Verify user login against LDAP directory - * - * Checks if the specified username is in the LDAP and verifies if the - * password is correct by binding to the LDAP with the given credentials. - * - * \param string $username The username to check - * - * \param string $password The password to check - * - * \return TRUE on SUCCESS, NULL or FALSE on error - */ -function ldap_login_user ($username, $password) -{ - global $config; - - $ui = ldap_get_user($username); - - if ($ui === FALSE) { - return NULL; - } elseif (is_string($ui)) { - msg_dialog::display(_('Internal error'), $ui, FATAL_ERROR_DIALOG); - return NULL; - } - - /* password check, bind as user with supplied password */ - $ldapObj = new LDAP($ui->dn, $password, $config->current['SERVER'], - isset($config->current['LDAPFOLLOWREFERRALS']) && - $config->current['LDAPFOLLOWREFERRALS'] == 'TRUE', - isset($config->current['LDAPTLS']) - && $config->current['LDAPTLS'] == 'TRUE' - ); - $ldap = new ldapMultiplexer($ldapObj); - if (!$ldap->success()) { - return NULL; - } - if (class_available('ppolicyAccount')) { - $ldap->cd($config->current['BASE']); - $ldap->search('(objectClass=*)', [], 'one'); - if (!$ldap->success()) { - msg_dialog::display( - _('Authentication error'), - _('It seems your user password has expired. Please use <a href="recovery.php">password recovery</a> to change it.'), - ERROR_DIALOG - ); - return NULL; - } - } - - /* Username is set, load subtreeACL's now */ - $ui->loadACL(); - - return $ui; -} - /*! * \brief Add a lock for object(s) * diff --git a/include/login/class_LoginCAS.inc b/include/login/class_LoginCAS.inc index 3846c70f838f7c51ee4eb362106eb5e8bfee9f11..98a8386288cedb7f92401ec7568637c2b5bd9279 100644 --- a/include/login/class_LoginCAS.inc +++ b/include/login/class_LoginCAS.inc @@ -59,7 +59,7 @@ class LoginCAS extends LoginMethod phpCAS::forceAuthentication(); static::$username = phpCAS::getUser(); - $ui = ldap_get_user(static::$username); + $ui = userinfo::getLdapUser(static::$username); if ($ui === FALSE) { msg_dialog::display( diff --git a/include/login/class_LoginHTTPHeader.inc b/include/login/class_LoginHTTPHeader.inc index d7f6d64e974b31b5585a35af38bb0e196a7f7c80..6b45ec330b73e34695ccc56b28a37590242d3ca0 100644 --- a/include/login/class_LoginHTTPHeader.inc +++ b/include/login/class_LoginHTTPHeader.inc @@ -55,7 +55,7 @@ class LoginHTTPHeader extends LoginMethod exit(); } - $ui = ldap_get_user(static::$username); + $ui = userinfo::getLdapUser(static::$username); if ($ui === FALSE) { msg_dialog::display( diff --git a/include/login/class_LoginMethod.inc b/include/login/class_LoginMethod.inc index 14f10965c81f3362501511928a4cc32ffe61ece6..18a86edec6ffb2f0d405f9214a8a80523aaa1958 100644 --- a/include/login/class_LoginMethod.inc +++ b/include/login/class_LoginMethod.inc @@ -95,7 +95,7 @@ class LoginMethod { global $ui, $config, $message, $smarty; /* Login as user, initialize user ACL's */ - $ui = ldap_login_user(static::$username, static::$password); + $ui = userinfo::loginUser(static::$username, static::$password); if ($ui === NULL) { if (isset($_SERVER['REMOTE_ADDR'])) { logging::log('security', 'login', '', [], 'Authentication failed for user "'.static::$username.'" [from '.$_SERVER['REMOTE_ADDR'].']'); @@ -132,16 +132,26 @@ class LoginMethod $config->checkLdapConfig(); /* Check account expiration */ - if ($config->get_cfg_value('handleExpiredAccounts') == 'TRUE') { - $expired = $ui->expired_status(); + $expired = $ui->expired_status(); + if ($expired == PPOLICY_ACCOUNT_EXPIRED) { + msg_dialog::display( + _('Authentication error'), + _('It seems your user password has expired. Please use <a href="recovery.php">password recovery</a> to change it.'), + ERROR_DIALOG + ); + logging::log('security', 'login', '', [], 'Account for user "'.static::$username.'" has expired (ppolicy)'); + $message = _('Password expired'); + $smarty->assign('focusfield', 'username'); + return FALSE; + } - if ($expired == POSIX_ACCOUNT_EXPIRED) { - logging::log('security', 'login', '', [], 'Account for user "'.static::$username.'" has expired'); - $message = _('Account locked. Please contact your system administrator!'); - $smarty->assign('focusfield', 'username'); - return FALSE; - } + if ($expired == POSIX_ACCOUNT_EXPIRED) { + logging::log('security', 'login', '', [], 'Account for user "'.static::$username.'" has expired'); + $message = _('Account locked. Please contact your system administrator!'); + $smarty->assign('focusfield', 'username'); + return FALSE; } + return TRUE; } diff --git a/include/php_setup.inc b/include/php_setup.inc index 4256e1551976f4468550716435b751b95a56eeff..2738df3d1983ff398edbcaf6b21089cca3dcc252 100644 --- a/include/php_setup.inc +++ b/include/php_setup.inc @@ -29,11 +29,11 @@ require_once("variables.inc"); function html_trace ($errstr = "") { static $hideArgs = [ - 'ldap_init' => [3], - 'ldap_login_user' => [1], - 'change_password' => [1], - 'cred_decrypt' => [0,1], - 'LDAP/__construct' => [1], + 'ldap_init' => [3], + 'userinfo/loginUser' => [1], + 'change_password' => [1], + 'cred_decrypt' => [0,1], + 'LDAP/__construct' => [1], ]; if (!function_exists('debug_backtrace')) { return ['', '']; diff --git a/plugins/personal/generic/class_user.inc b/plugins/personal/generic/class_user.inc index 0332c69374df5e7df899470034fdb916c2f1a3e2..b751ed3973ca21cd00a4df0bc7647e76a27d8b7a 100644 --- a/plugins/personal/generic/class_user.inc +++ b/plugins/personal/generic/class_user.inc @@ -397,23 +397,12 @@ class user extends simplePlugin $addAttrs['passwordClear'] = $this->attributesAccess['userPassword']->getClear(); } - static function reportPasswordProblems ($user, $new_password, $repeated_password, $current_password = NULL) + static function fetchPpolicy (string $userdn): array { - global $config, $ui; - - /* Should we check different characters in new password */ - $check_differ = ($config->get_cfg_value('passwordMinDiffer') != ''); - $differ = $config->get_cfg_value('passwordMinDiffer', 0); - if ($current_password === NULL) { - $check_differ = FALSE; - } - - /* Enable length check ? */ - $check_length = ($config->get_cfg_value('passwordMinLength') != ''); - $length = $config->get_cfg_value('passwordMinLength', 0); + global $config; $ldap = $config->get_ldap_link(); - $ldap->cat($user, ['pwdPolicySubentry', 'pwdHistory', 'pwdChangedTime', 'userPassword']); + $ldap->cat($userdn, ['pwdPolicySubentry', 'pwdHistory', 'pwdChangedTime', 'userPassword']); $attrs = $ldap->fetch(); $ppolicydn = ''; if (isset($attrs['pwdPolicySubentry'][0])) { @@ -424,12 +413,41 @@ class user extends simplePlugin $ppolicydn = 'cn='.$ppolicydn.','.get_ou('ppolicyRDN').$config->current['BASE']; } } + + $policy = NULL; if (!empty($ppolicydn)) { $ldap->cat($ppolicydn, ['pwdAllowUserChange', 'pwdMinLength', 'pwdMinAge', 'pwdSafeModify']); $policy = $ldap->fetch(); if (!$policy) { - return sprintf(_('Ppolicy "%s" could not be found in the LDAP!'), $ppolicydn); + throw new NonExistingLdapNodeException(sprintf(_('Ppolicy "%s" could not be found in the LDAP!'), $ppolicydn)); } + } + + return [$policy, $attrs]; + } + + static function reportPasswordProblems ($user, $new_password, $repeated_password, $current_password = NULL) + { + global $config, $ui; + + /* Should we check different characters in new password */ + $check_differ = ($config->get_cfg_value('passwordMinDiffer') != ''); + $differ = $config->get_cfg_value('passwordMinDiffer', 0); + if ($current_password === NULL) { + $check_differ = FALSE; + } + + /* Enable length check ? */ + $check_length = ($config->get_cfg_value('passwordMinLength') != ''); + $length = $config->get_cfg_value('passwordMinLength', 0); + + try { + list($policy, $attrs) = static::fetchPpolicy($user); + } catch (NonExistingLdapNodeException $e) { + return $e->getMessage(); + } + + if (isset($policy)) { if (isset($policy['pwdAllowUserChange'][0]) && ($policy['pwdAllowUserChange'][0] == 'FALSE') && ($ui->dn == $user)) { return _('You are not allowed to change your own password'); }