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');
       }