class_passwordRecovery.inc 20.27 KiB
<?php
/*
  This code is part of FusionDirectory (http://www.fusiondirectory.org/)
  Copyright (C) 2003-2010  Cajus Pollmeier
  Copyright (C) 2011-2018  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
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.
  This program 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 General Public License for more details.
  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
require_once("../include/php_setup.inc");
require_once("functions.inc");
require_once("variables.inc");
/* base class for passwordRecovery and such classes handling requests on their own */
class standAlonePage {
  var $directory;
  var $activated;
  protected $interactive;
  /* Constructor */
  function __construct ($interactive = TRUE)
    global $config, $ssl, $ui;
    $this->interactive = $interactive;
    if ($this->interactive) {
      /* Destroy old session if exists.
          Else you will get your old session back, if you not logged out correctly. */
      session::destroy();
      session::start();
      /* Reset errors */
      reset_errors();
      $config = $this->loadConfig();
      /* If SSL is forced, just forward to the SSL enabled site */
      if (($config->get_cfg_value("forcessl") == "TRUE") && ($ssl != '')) {
        header ("Location: $ssl");
        exit;
      $this->setupSmarty();
      $smarty = get_smarty();
      /* Generate server list */
      $servers = [];
      foreach ($config->data['LOCATIONS'] as $key => $ignored) {
        $servers[$key] = $key;
      $smarty->assign("show_directory_chooser", FALSE);
      if (isset($_POST['server'])) {
        $this->directory = validate($_POST['server']);
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
} elseif (isset($_GET['directory']) && isset($servers[$_GET['directory']])) { $this->directory = validate($_GET['directory']); } else { $this->directory = $config->data['MAIN']['DEFAULT']; if (!isset($servers[$this->directory])) { $this->directory = key($servers); } if (count($servers) > 1) { $smarty->assign("show_directory_chooser", TRUE); $smarty->assign("server_options", $servers); $smarty->assign("server_id", $this->directory); } } /* Set config to selected one */ $config->set_current($this->directory); session::set('config', $config); } $this->activated = $this->readLdapConfig(); if (!$this->activated) { /* Password recovery has been disabled */ return; } if ($this->interactive) { Language::init(); if (session::is_set('plist')) { session::un_set('plist'); } $ui = new fake_userinfo(); load_plist(); $ssl = $this->checkForSSL(); static::securityHeaders(); } } function loadConfig () { global $BASE_DIR; /* Check if CONFIG_FILE is accessible */ if (!is_readable(CONFIG_DIR."/".CONFIG_FILE)) { msg_dialog::display(_("Fatal error"), sprintf(_("FusionDirectory configuration %s/%s is not readable. Aborted."), CONFIG_DIR, CONFIG_FILE), FATAL_ERROR_DIALOG); exit(); } /* Parse configuration file */ $config = new config(CONFIG_DIR."/".CONFIG_FILE, $BASE_DIR); session::set('DEBUGLEVEL', $config->get_cfg_value("debuglevel")); @DEBUG(DEBUG_CONFIG, __LINE__, __FUNCTION__, __FILE__, $config->data, "config"); return $config; } function setupSmarty () { global $config; $smarty = get_smarty(); /* Set template compile directory */ $smarty->compile_dir = $config->get_cfg_value("templateCompileDirectory", SPOOL_DIR); /* Check for compile directory */ if (!(is_dir($smarty->compile_dir) && is_writable($smarty->compile_dir))) {
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
msg_dialog::display(_("Configuration error"), sprintf(_("Directory '%s' specified as compile directory is not accessible!"), $smarty->compile_dir), FATAL_ERROR_DIALOG); exit(); } /* Check for old files in compile directory */ clean_smarty_compile_dir($smarty->compile_dir); $smarty->assign('date', gmdate('D, d M Y H:i:s')); $smarty->assign('params', ''); $smarty->assign('message', ''); $smarty->assign('changed', FALSE); $smarty->assign('revision', FD_VERSION); $smarty->assign('year', date('Y')); } function checkForSSL () { global $config; $smarty = get_smarty(); /* Check for SSL connection */ $ssl = ''; $smarty->assign('ssl', ''); if (!sslOn()) { $ssl = sslUrl(); /* If SSL is forced, just forward to the SSL enabled site */ if ($config->get_cfg_value('forcessl') == 'TRUE') { header("Location: $ssl"); exit; } elseif ($config->get_cfg_value('warnssl') == 'TRUE') { /* Display SSL mode warning? */ $smarty->assign ('ssl', sprintf(_('Warning: <a href="%s">Session is not encrypted!</a>'), $ssl)); } } return $ssl; } function getPageURL () { $protocol = 'http'; if (isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on')) { $protocol .= 's'; } $port = '80'; if (!empty($_SERVER['HTTP_X_FORWARDED_HOST'])) { $host = $_SERVER['HTTP_X_FORWARDED_HOST']; if (isset($_SERVER['HTTP_X_FORWARDED_PORT'])) { $port = $_SERVER['HTTP_X_FORWARDED_PORT']; } if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) { $protocol = $_SERVER['HTTP_X_FORWARDED_PROTO']; } } else { $host = $_SERVER['SERVER_NAME']; $port = $_SERVER['SERVER_PORT']; } $pageURL = $protocol.'://'; $pageURL .= $host; if ($port != '80') { $pageURL .= ':'.$port; } if (empty($_SERVER['PATH_INFO'])) { $pageURL .= $_SERVER['PHP_SELF']; } else {
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
$pageURL .= $_SERVER['PATH_INFO']; } return $pageURL; } function encodeParams ($keys) { $params = ''; foreach ($keys as $key) { $params .= "&amp;$key=".urlencode($this->$key); } $params = preg_replace('/^&amp;/', '?', $params); return $params; } static function securityHeaders () { header('X-XSS-Protection: 1; mode=block'); header('X-Content-Type-Options: nosniff'); header('X-Frame-Options: deny'); } static function generateRandomHash () { /* Generate a very long random value */ $len = 56; $base = 'ABCDEFGHKLMNOPQRSTWXYZabcdefghjkmnpqrstwxyz123456789'; $max = strlen($base) - 1; $randomhash = ''; while (strlen($randomhash) < $len + 1) { $randomhash .= $base[random_int(0, $max)]; } return $randomhash; } } class passwordRecovery extends standAlonePage { protected $loginAttribute; protected $login; var $message = []; var $email_address; var $step = 1; /* Some Configuration variable */ /* Salt needed to mask the uniq id in the ldap */ var $salt; /* Delay allowed for the user to change his password (minutes) */ var $delay_allowed; /* Sender */ var $from_mail; var $mail_body; var $mail_subject; var $mail2_body; var $mail2_subject; var $usealternates; /* Constructor */ function __construct ($interactive = TRUE) { parent::__construct($interactive); if (isset($_GET['email_address']) && ($_GET['email_address'] != '')) { $this->email_address = validate($_GET['email_address']);
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
} elseif (isset($_POST['email_address'])) { $this->email_address = validate($_POST['email_address']); } /* Check for selected user... */ if (isset($_GET['login']) && $_GET['login'] != '') { $this->login = validate($_GET['login']); } elseif (isset($_POST['login'])) { $this->login = validate($_POST['login']); } else { $this->login = ''; } } function execute () { if (!$this->activated) { return; } /* Got a formular answer, validate and try to log in */ if ($_SERVER['REQUEST_METHOD'] == 'POST') { if (session::is_set('_LAST_PAGE_REQUEST')) { session::set('_LAST_PAGE_REQUEST', time()); } if (isset($_POST['change'])) { $this->step4(); } elseif (isset($_POST['apply'])) { if ($_POST['email_address'] == '') { $this->message[] = msgPool::required(_('Email address')); return; } $this->email_address = $_POST['email_address']; $this->step2(); if ($this->step == 2) { /* No errors */ $this->step3(); } } } elseif ($_SERVER['REQUEST_METHOD'] == 'GET') { if (isset($_GET['uniq'])) { $this->step4(); } } } function displayPWchanger () { global $error_collector, $error_collector_mailto; /* Do we need to show error messages? */ if (count($this->message) != 0) { /* Show error message and continue editing */ msg_dialog::displayChecks($this->message); } @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $this->step, "Step"); $smarty = get_smarty(); $smarty->assign("PHPSESSID", session_id()); if (session::is_set('errors')) { $smarty->assign("errors", session::get('errors')); } if ($error_collector != "") { $smarty->assign("php_errors", preg_replace("/%BUGBODY%/", $error_collector_mailto, $error_collector)."</div>"); } else { $smarty->assign("php_errors", ""); } $smarty->assign('msg_dialogs', msg_dialog::get_dialogs());
351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
$smarty->assign('usePrototype', 'FALSE'); $smarty->append('js_files', 'include/pwdStrength.js'); $smarty->append('css_files', get_template_path('login.css')); $lang = session::get('lang'); $smarty->assign('lang', preg_replace('/_.*$/', '', $lang)); $smarty->assign('rtl', Language::isRTL($lang)); $smarty->assign('title', _('Password recovery')); $smarty->display(get_template_path('headers.tpl')); $smarty->assign('version', FD_VERSION); $smarty->assign('step', $this->step); $smarty->assign('delay_allowed', $this->delay_allowed); $smarty->assign('activated', $this->activated); $smarty->assign('email_address', $this->email_address); $smarty->display(get_template_path('recovery.tpl')); exit(); } /* Check that password recovery is activated, read config in ldap * Returns a boolean saying if password recovery is activated */ function readLdapConfig () { global $config; $this->salt = $config->get_cfg_value('passwordRecoverySalt'); $this->delay_allowed = $config->get_cfg_value('passwordRecoveryValidity'); $this->mail_subject = $config->get_cfg_value('passwordRecoveryMailSubject'); $this->mail_body = $config->get_cfg_value('passwordRecoveryMailBody'); $this->mail2_subject = $config->get_cfg_value('passwordRecoveryMail2Subject'); $this->mail2_body = $config->get_cfg_value('passwordRecoveryMail2Body'); $this->from_mail = $config->get_cfg_value('passwordRecoveryEmail'); $this->usealternates = $config->get_cfg_value('passwordRecoveryUseAlternate'); $this->loginAttribute = $config->get_cfg_value('passwordRecoveryLoginAttribute', 'uid'); @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $config->get_cfg_value ('passwordRecoveryActivated'), "passwordRecoveryActivated"); return($config->get_cfg_value('passwordRecoveryActivated') == "TRUE"); } function storeToken ($temp_password) { global $config; /* Store it in ldap with the salt */ $salt_temp_password = $this->salt.$temp_password.$this->salt; $sha1_temp_password = "{SHA}".base64_encode(pack("H*", sha1($salt_temp_password))); $ldap = $config->get_ldap_link(); // Check if token branch is here $token = get_ou('recoveryTokenRDN').get_ou('fusiondirectoryRDN').$config->current['BASE']; $ldap->cat($token, ['dn']); if (!$ldap->count()) { /* It's not, let's create it */ $ldap->cd($config->current['BASE']); $ldap->create_missing_trees($token); if (!$ldap->success()) { return msgPool::ldaperror($ldap->get_error(), $token, LDAP_MOD, get_class()); } fusiondirectory_log("Created token branch ".$token); } $dn = 'ou='.$this->login.','.$token; $ldap->cat($dn, ['dn']); $add = ($ldap->count() == 0); /* We store the token and its validity due date */ $attrs = [
421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
'objectClass' => ['organizationalUnit'], 'ou' => $this->login, 'userPassword' => $sha1_temp_password, 'description' => time() + $this->delay_allowed * 60, ]; $ldap->cd($dn); if ($add) { $ldap->add($attrs); } else { $ldap->modify($attrs); } if (!$ldap->success()) { return msgPool::ldaperror($ldap->get_error(), $dn, LDAP_ADD, get_class()); } return ""; /* Everything went well */ } function checkToken ($token) { global $config; $salt_token = $this->salt.$token.$this->salt; $sha1_token = "{SHA}".base64_encode(pack("H*", sha1($salt_token))); /* Retrieve hash from the ldap */ $ldap = $config->get_ldap_link(); $token = get_ou('recoveryTokenRDN').get_ou('fusiondirectoryRDN').$config->current['BASE']; $dn = 'ou='.$this->login.','.$token; $ldap->cat($dn); $attrs = $ldap->fetch(); $ldap_token = $attrs['userPassword'][0]; $last_time_recovery = $attrs['description'][0]; /* Return TRUE if the token match and is still valid */ return ($last_time_recovery >= time()) && ($ldap_token == $sha1_token); } function getUserDn () { global $config; /* Retrieve dn from the ldap */ $ldap = $config->get_ldap_link(); $objectClasses = ['gosaMailAccount']; if (class_available('personalInfo') && ($config->get_cfg_value('privateEmailPasswordRecovery', 'FALSE') == 'TRUE')) { $objectClasses[] = 'fdPersonalInfo'; } if (class_available('supannAccount') && ($config->get_cfg_value('supannPasswordRecovery', 'TRUE') == 'TRUE')) { $objectClasses[] = 'supannPerson'; } $filter = '(&(|(objectClass='.join(')(objectClass=', $objectClasses).'))('.$this->loginAttribute.'='.ldap_escape_f($this->login).'))'; $ldap->cd($config->current['BASE']); $ldap->search($filter, ['dn']); if ($ldap->count() < 1) { $this->message[] = sprintf(_('Did not find an account with login "%s"'), htmlentities($this->login, ENT_COMPAT, 'UTF-8')); return; } elseif ($ldap->count() > 1) { $this->message[] = sprintf(_('Found multiple accounts with login "%s"'), htmlentities($this->login, ENT_COMPAT, 'UTF-8')); return; } $attrs = $ldap->fetch(); return $attrs['dn'];
491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
} /* Find the login of for the given email address */ function step2 () { global $config; /* Search login corresponding to the mail */ $address_escaped = ldap_escape_f($this->email_address); if ($this->usealternates) { $filter = '(&(objectClass=gosaMailAccount)(|(mail='.$address_escaped.')(gosaMailAlternateAddress='.$address_escaped.')))'; } else { $filter = '(&(objectClass=gosaMailAccount)(mail='.$address_escaped.'))'; } if (class_available('personalInfo') && ($config->get_cfg_value('privateEmailPasswordRecovery', 'FALSE') == 'TRUE')) { $filter = '(|'.$filter.'(&(objectClass=fdPersonalInfo)(fdPrivateMail='.$address_escaped.')))'; } if (class_available('supannAccount') && ($config->get_cfg_value('supannPasswordRecovery', 'TRUE') == 'TRUE')) { $filter = '(|'.$filter.'(&(objectClass=supannPerson)(supannMailPerso='.$address_escaped.')))'; } $ldap = $config->get_ldap_link(); $ldap->cd($config->current['BASE']); $ldap->search($filter, ['dn', 'userPassword', $this->loginAttribute]); /* Only one ldap node should be found */ if ($ldap->count() < 1) { $this->message[] = sprintf(_('There is no account using email "%s"'), htmlentities($this->email_address, ENT_COMPAT, 'UTF-8')); return; } elseif ($ldap->count() > 1) { $this->message[] = sprintf(_('There are several accounts using email "%s"'), htmlentities($this->email_address, ENT_COMPAT, 'UTF-8')); return; } $attrs = $ldap->fetch(); $method = passwordMethod::get_method($attrs['userPassword'][0], $attrs['dn']); if (is_object($method) && $method->is_locked($attrs['dn'])) { $this->message[] = sprintf(_('The user using email "%s" is locked. Please contact your administrator.'), htmlentities($this->email_address, ENT_COMPAT, 'UTF-8')); return; } $this->login = $attrs[$this->loginAttribute][0]; $this->step = 2; if ($this->interactive) { $smarty = get_smarty(); $smarty->assign('login', $this->login); $smarty->assign('email_address', $this->email_address); $params = $this->encodeParams(['login', 'directory', 'email_address']); $smarty->assign('params', $params); } return $attrs['dn']; } function generateAndStoreToken () { $activatecode = static::generateRandomHash(); $error = $this->storeToken($activatecode); if (!empty($error)) { $this->message[] = $error; return FALSE; } return $activatecode; } /* generate a token and send it by email */
561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
function step3 () { /* Send a mail, save information in session and create a very random unique id */ $token = $this->generateAndStoreToken(); if ($token === FALSE) { return; } $reinit_link = $this->getPageURL(); $reinit_link .= '?uniq='.urlencode($token); $reinit_link .= '&login='.urlencode($this->login); $reinit_link .= '&email_address='.urlencode($this->email_address); @DEBUG(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $reinit_link, 'Setting link to'); /* Send the mail */ $mail_body = sprintf($this->mail_body, $this->login, $reinit_link); if (mail_utf8($this->email_address, FALSE, $this->from_mail, $this->mail_subject, $mail_body)) { $this->step = 3; } else { $this->message[] = _('Contact your administrator, there was a problem with mail server'); } $smarty = get_smarty(); $smarty->assign('login', $this->login); } /* check if the given token is the good one */ function step4 () { $uniq_id_from_mail = validate($_GET['uniq']); if (!$this->checkToken($uniq_id_from_mail)) { $this->message[] = _("This token is invalid"); return; } $smarty = get_smarty(); $smarty->assign('uniq', $uniq_id_from_mail); $this->uniq = $uniq_id_from_mail; $this->step = 4; $smarty->assign('login', $this->login); $params = $this->encodeParams(['login', 'directory', 'email_address', 'uniq']); $smarty->assign('params', $params); if (isset($_POST['change'])) { $this->step5(); } } function changeUserPassword ($new_password, $new_password_repeated) { $dn = $this->getUserDn(); if (!$dn) { return FALSE; } $userTabs = objects::open($dn, 'user'); $userTab = $userTabs->getBaseObject(); $userTab->userPassword = [ '', $new_password, $new_password_repeated, $userTab->userPassword, $userTab->attributesAccess['userPassword']->isLocked() ];
631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666
/* Is there any problem with entered passwords? */ $userTabs->save_object(); $errors = $userTabs->save(); if (!empty($errors)) { $this->message = $errors; return; } fusiondirectory_log('User '.$this->login.' password has been changed'); return TRUE; } /* change the password and send confirmation email */ function step5 () { $success = $this->changeUserPassword($_POST['new_password'], $_POST['new_password_repeated']); if (!$success) { return; } /* Send the mail */ $mail_body = sprintf($this->mail2_body, $this->login); if (mail_utf8($this->email_address, FALSE, $this->from_mail, $this->mail2_subject, $mail_body)) { $smarty = get_smarty(); $this->step = 5; $smarty->assign('changed', TRUE); } else { $this->message[] = _('There was a problem with mail server, confirmation email not sent'); } } } ?>