diff --git a/include/password-methods/class_password-methods-clear.inc b/include/password-methods/class_password-methods-clear.inc
index d84ff16b9467e07e2fd038599e96f741c431c3b0..70d13d1c10538157028035f704526be66cbb2071 100644
--- a/include/password-methods/class_password-methods-clear.inc
+++ b/include/password-methods/class_password-methods-clear.inc
@@ -57,7 +57,7 @@ class passwordMethodClear extends passwordMethod
    *
    * \param string $pwd Password
    */
-  function generate_hash($pwd)
+  function generate_hash($pwd, $locked = FALSE)
   {
     return $pwd;
   }
diff --git a/include/password-methods/class_password-methods-crypt.inc b/include/password-methods/class_password-methods-crypt.inc
index 0e44832dd4d4cb2f3483a4801580fe5ee142b168..5e76216e557aff3d48637a493f8d522c3fa55e3c 100644
--- a/include/password-methods/class_password-methods-crypt.inc
+++ b/include/password-methods/class_password-methods-crypt.inc
@@ -54,7 +54,7 @@ class passwordMethodCrypt extends passwordMethod
    *
    * \param string $pwd Password
    */
-  function generate_hash($pwd)
+  function generate_hash($pwd, $locked = FALSE)
   {
     if ($this->hash == "crypt/standard-des") {
       $salt = "";
@@ -102,7 +102,7 @@ class passwordMethodCrypt extends passwordMethod
       $salt .= "\$";
     }
 
-    return "{CRYPT}".crypt($pwd, $salt);
+    return '{CRYPT}'.($locked ? '!' : '').crypt($pwd, $salt);
   }
 
   function checkPassword($pwd, $hash)
@@ -151,7 +151,7 @@ class passwordMethodCrypt extends passwordMethod
    *
    * \param string $password_hash
    */
-  static function _extract_method($classname, $password_hash)
+  static function _extract_method($password_hash)
   {
     if (!preg_match('/^{crypt}/i', $password_hash)) {
       return "";
diff --git a/include/password-methods/class_password-methods-empty.inc b/include/password-methods/class_password-methods-empty.inc
index 14f43e4e07ead6743c9dbdd448c4f144affb5135..8fc8a7b4b619eb6f7286c452e43faaa01e612e85 100644
--- a/include/password-methods/class_password-methods-empty.inc
+++ b/include/password-methods/class_password-methods-empty.inc
@@ -30,10 +30,12 @@
  */
 class passwordMethodEmpty extends passwordMethod
 {
-  protected $lockable = FALSE;
+  protected $lockable   = TRUE;
 
   public $hash = 'empty';
 
+  const LOCKVALUE  = '{CRYPT}!';
+
   /*!
    * \brief passwordMethodClear Constructor
    */
@@ -56,9 +58,9 @@ class passwordMethodEmpty extends passwordMethod
    *
    * \param string $pwd Password
    */
-  function generate_hash($pwd)
+  function generate_hash($pwd, $locked = FALSE)
   {
-    return '';
+    return ($locked ? static::LOCKVALUE : '');
   }
 
   /*!
@@ -78,5 +80,14 @@ class passwordMethodEmpty extends passwordMethod
   {
     return FALSE;
   }
+
+  static function _extract_method($password_hash)
+  {
+    if (empty($password_hash) || ($password_hash == static::LOCKVALUE)) {
+      return static::get_hash_name();
+    }
+
+    return '';
+  }
 }
 ?>
diff --git a/include/password-methods/class_password-methods-md5.inc b/include/password-methods/class_password-methods-md5.inc
index 127a5178c2ccfbab8428470209670429b1f12273..835a8adcd7adc9b6ebe0a60c0335fddbbc318fbe 100644
--- a/include/password-methods/class_password-methods-md5.inc
+++ b/include/password-methods/class_password-methods-md5.inc
@@ -54,9 +54,9 @@ class passwordMethodMd5 extends passwordMethod
    *
    * \param string $pwd Password
    */
-  function generate_hash($pwd)
+  function generate_hash($pwd, $locked = FALSE)
   {
-    return  '{MD5}'.base64_encode( pack('H*', md5($pwd)));
+    return  '{MD5}'.($locked ? '!' : '').base64_encode( pack('H*', md5($pwd)));
   }
 
   /*!
diff --git a/include/password-methods/class_password-methods-sasl.inc b/include/password-methods/class_password-methods-sasl.inc
index a170602d1f685894fc8b8015db93a3b2ffdc1490..064e7154b29880f56d7c3f4d1997a236dba71fd6 100644
--- a/include/password-methods/class_password-methods-sasl.inc
+++ b/include/password-methods/class_password-methods-sasl.inc
@@ -85,16 +85,16 @@ class passwordMethodsasl extends passwordMethod
    *
    * \param string $pwd Password
    */
-  function generate_hash($pwd)
+  function generate_hash($pwd, $locked = FALSE)
   {
     if (empty($this->exop)) {
       if (empty($this->realm)) {
         msg_dialog::display(_('Error'), _('You need to fill saslRealm or saslExop in the configuration screen in order to use SASL'), ERROR_DIALOG);
       }
-      return '{SASL}'.$this->uid.'@'.$this->realm;
+      return '{SASL}'.($locked ? '!' : '').$this->uid.'@'.$this->realm;
     } else {
       // may not be the uid, see saslExop option
-      return '{SASL}'.$this->uid;
+      return '{SASL}'.($locked ? '!' : '').$this->uid;
     }
   }
 
diff --git a/include/password-methods/class_password-methods-sha.inc b/include/password-methods/class_password-methods-sha.inc
index 95f3dd3d6e5567a6380312d3fd19e37d4b46ffba..8e05c7b745e8bda8556ffd10a57f050175206e2a 100644
--- a/include/password-methods/class_password-methods-sha.inc
+++ b/include/password-methods/class_password-methods-sha.inc
@@ -53,12 +53,12 @@ class passwordMethodsha extends passwordMethod
    *
    * \param string $password Password
    */
-  function generate_hash($password)
+  function generate_hash($password, $locked = FALSE)
   {
     if (function_exists('sha1')) {
-      $hash = '{SHA}' . base64_encode(pack('H*', sha1($password)));
+      $hash = '{SHA}'.($locked ? '!' : '').base64_encode(pack('H*', sha1($password)));
     } elseif (function_exists('mhash')) {
-      $hash = '{SHA}' . base64_encode(mHash(MHASH_SHA1, $password));
+      $hash = '{SHA}'.($locked ? '!' : '').base64_encode(mHash(MHASH_SHA1, $password));
     } else {
       msg_dialog::display(_('Configuration error'), msgPool::missingext('mhash'), ERROR_DIALOG);
       return FALSE;
diff --git a/include/password-methods/class_password-methods-smd5.inc b/include/password-methods/class_password-methods-smd5.inc
index 8f4246255a71c2b4a125f99ed6d27598f25dc86c..655bb75a2f76c88c29ef147ee0f99ede98f394c0 100644
--- a/include/password-methods/class_password-methods-smd5.inc
+++ b/include/password-methods/class_password-methods-smd5.inc
@@ -53,11 +53,11 @@ class passwordMethodsmd5 extends passwordMethod
    *
    * \param string $pwd Password
    */
-  function generate_hash($pwd)
+  function generate_hash($pwd, $locked = FALSE)
   {
     $salt0  = substr(pack('h*', md5(random_int(0, PHP_INT_MAX))), 0, 8);
     $salt   = substr(pack('H*', md5($salt0 . $pwd)), 0, 4);
-    return '{SMD5}'.base64_encode(pack('H*', md5($pwd . $salt)) . $salt);
+    return '{SMD5}'.($locked ? '!' : '').base64_encode(pack('H*', md5($pwd . $salt)) . $salt);
   }
 
   function checkPassword($pwd, $hash)
diff --git a/include/password-methods/class_password-methods-ssha.inc b/include/password-methods/class_password-methods-ssha.inc
index 92081f1be45ee5a05efd904a7c554642708b01d1..c0d287b55e6eefd6361792349ad01158bb5eb8ef 100644
--- a/include/password-methods/class_password-methods-ssha.inc
+++ b/include/password-methods/class_password-methods-ssha.inc
@@ -53,15 +53,15 @@ class passwordMethodssha extends passwordMethod
    *
    * \param string $pwd Password
    */
-  function generate_hash($pwd)
+  function generate_hash($pwd, $locked = FALSE)
   {
     if (function_exists('sha1')) {
       $salt = substr(pack('h*', md5(random_int(0, PHP_INT_MAX))), 0, 8);
       $salt = substr(pack('H*', sha1($salt.$pwd)), 0, 4);
-      $pwd  = '{SSHA}'.base64_encode(pack('H*', sha1($pwd.$salt)).$salt);
+      $pwd  = '{SSHA}'.($locked ? '!' : '').base64_encode(pack('H*', sha1($pwd.$salt)).$salt);
     } elseif (function_exists('mhash')) {
       $salt = mhash_keygen_s2k(MHASH_SHA1, $pwd, substr(pack('h*', md5(random_int(0, PHP_INT_MAX))), 0, 8), 4);
-      $pwd  = '{SSHA}'.base64_encode(mhash(MHASH_SHA1, $pwd.$salt).$salt);
+      $pwd  = '{SSHA}'.($locked ? '!' : '').base64_encode(mhash(MHASH_SHA1, $pwd.$salt).$salt);
     } else {
       msg_dialog::display(_('Configuration error'), msgPool::missingext('mhash'), ERROR_DIALOG);
       return FALSE;
diff --git a/include/password-methods/class_password-methods.inc b/include/password-methods/class_password-methods.inc
index 0582f523f180fd711d0f0fa8454a30abf4228a2e..5ad44c626c19c34d368cbc3b4244f419079371c9 100644
--- a/include/password-methods/class_password-methods.inc
+++ b/include/password-methods/class_password-methods.inc
@@ -77,7 +77,7 @@ class passwordMethod
    *
    * \param string $dn The DN
    */
-  function is_locked($dn = "")
+  function is_locked($dn = '', $pwd = '')
   {
     global $config;
     if (!$this->lockable) {
@@ -85,7 +85,6 @@ class passwordMethod
     }
 
     /* Get current password hash */
-    $pwd = "";
     if (!empty($dn)) {
       $ldap = $config->get_ldap_link();
       $ldap->cd($config->current['BASE']);
@@ -147,56 +146,60 @@ class passwordMethod
       }
     }
 
-    /* We can only lock/unlock non-empty passwords */
-    if (!empty($pwd)) {
-      /* Check if this entry is already locked. */
-      if (!preg_match("/^[^\}]*+\}!/", $pwd)) {
-        if ($mode == 'UNLOCK') {
-          return TRUE;
-        }
-      } elseif ($mode == 'LOCK') {
+    /* Check if this entry is already locked. */
+    if (!preg_match("/^[^\}]*+\}!/", $pwd)) {
+      if ($mode == 'UNLOCK') {
         return TRUE;
       }
+    } elseif ($mode == 'LOCK') {
+      return TRUE;
+    }
 
-      // (Un)lock the samba account
-      $modify = lock_samba_account($mode, $attrs);
+    // (Un)lock the samba account
+    $modify = lock_samba_account($mode, $attrs);
 
-      // (Un)lock SSH keys
-      lock_ssh_account($mode, $attrs, $modify);
+    // (Un)lock SSH keys
+    lock_ssh_account($mode, $attrs, $modify);
 
-      // Call pre hooks
-      $userClass = new user($dn);
-      $errors = $userClass->callHook('PRE'.$mode, array(), $ret);
-      if (!empty($errors)) {
-        msg_dialog::displayChecks($errors);
-        return FALSE;
-      }
+    // Call pre hooks
+    $userClass = new user($dn);
+    $errors = $userClass->callHook('PRE'.$mode, array(), $ret);
+    if (!empty($errors)) {
+      msg_dialog::displayChecks($errors);
+      return FALSE;
+    }
 
-      // (Un)lock the account by modifying the password hash.
-      if ($mode == 'LOCK') {
-        /* Lock entry */
+    // (Un)lock the account by modifying the password hash.
+    if ($mode == 'LOCK') {
+      /* Lock entry */
+      if (empty($pwd)) {
+        $pwd = passwordMethodEmpty::LOCKVALUE;
+      } else {
         $pwd = preg_replace("/(^[^\}]+\})(.*$)/",   "\\1!\\2",  $pwd);
+      }
+    } else {
+      /* Unlock entry */
+      if ($pwd == passwordMethodEmpty::LOCKVALUE) {
+        $pwd = '';
       } else {
-        /* Unlock entry */
         $pwd = preg_replace("/(^[^\}]+\})!(.*$)/",  "\\1\\2",   $pwd);
       }
-      $modify['userPassword'] = $pwd;
-      $ldap->cd($dn);
-      $ldap->modify($modify);
-
-      // Call the password post-lock hook, if defined.
-      if ($ldap->success()) {
-        $userClass = new user($dn);
-        $errors = $userClass->callHook('POST'.$mode, array(), $ret);
-        if (!empty($errors)) {
-          msg_dialog::displayChecks($errors);
-        }
-      } else {
-        msg_dialog::display(_('LDAP error'), msgPool::ldaperror($ldap->get_error(), $dn, LDAP_MOD), LDAP_ERROR);
+    }
+    $modify['userPassword'] = $pwd;
+    $ldap->cd($dn);
+    $ldap->modify($modify);
+
+    // Call the password post-lock hook, if defined.
+    if ($ldap->success()) {
+      $userClass = new user($dn);
+      $errors = $userClass->callHook('POST'.$mode, array(), $ret);
+      if (!empty($errors)) {
+        msg_dialog::displayChecks($errors);
       }
-      return $ldap->success();
+    } else {
+      msg_dialog::display(_('LDAP error'), msgPool::ldaperror($ldap->get_error(), $dn, LDAP_MOD), LDAP_ERROR);
     }
-    return FALSE;
+    return $ldap->success();
   }
 
 
@@ -299,14 +302,15 @@ class passwordMethod
   {
     $methods = passwordMethod::get_available_methods();
 
-    if (empty($password_hash) && passwordMethodEmpty::is_available()) {
+    if (isset($methods['class']['passwordMethodEmpty']) && (passwordMethodEmpty::_extract_method($password_hash) != '')) {
+      /* Test empty method first as it gets priority */
       $method = new passwordMethodEmpty($dn);
       return $method;
     }
 
     foreach ($methods['class'] as $class) {
-      $method = $class::_extract_method($class, $password_hash);
-      if ($method != "") {
+      $method = $class::_extract_method($password_hash);
+      if ($method != '') {
         $test = new $class($dn);
         $test->set_hash($method);
         return $test;
@@ -324,9 +328,9 @@ class passwordMethod
    *
    * \param string $password_hash
    */
-  static function _extract_method($classname, $password_hash)
+  static function _extract_method($password_hash)
   {
-    $hash = $classname::get_hash_name();
+    $hash = static::get_hash_name();
     if (preg_match("/^\{$hash\}/i", $password_hash)) {
       return $hash;
     }
diff --git a/include/simpleplugin/attributes/class_BooleanAttribute.inc b/include/simpleplugin/attributes/class_BooleanAttribute.inc
index 0ba929c9790f8b279768a93888631c02590e31d5..c1b1bba5a0d8a899e78f7579ab2052f155c8538a 100644
--- a/include/simpleplugin/attributes/class_BooleanAttribute.inc
+++ b/include/simpleplugin/attributes/class_BooleanAttribute.inc
@@ -25,6 +25,7 @@ class BooleanAttribute extends Attribute
 {
   public $trueValue;
   public $falseValue;
+  protected $templatable = TRUE;
 
   /*! \brief The constructor of BooleanAttribute
    *
@@ -44,6 +45,11 @@ class BooleanAttribute extends Attribute
     $this->falseValue = $falseValue;
   }
 
+  function setTemplatable($bool)
+  {
+    $this->templatable = $bool;
+  }
+
   function inputValue ($value)
   {
     return ($value == $this->trueValue);
@@ -92,7 +98,7 @@ class BooleanAttribute extends Attribute
 
   function renderTemplateInput ()
   {
-    if (!$this->submitForm && empty($this->managedAttributes)) {
+    if (!$this->submitForm && empty($this->managedAttributes) && $this->templatable) {
       /* Allow to set to %askme% if we are not (de)activating other fields */
 
       $id = $this->getHtmlId();
diff --git a/plugins/admin/users/class_userManagement.inc b/plugins/admin/users/class_userManagement.inc
index 2ad4f6b44cfadb00e9881da93643ff1ad79b155b..02ad2d1eebac3133bade6538ae17362703f4e96e 100644
--- a/plugins/admin/users/class_userManagement.inc
+++ b/plugins/admin/users/class_userManagement.inc
@@ -37,6 +37,9 @@ class LockAction extends Action
   {
     if (isset($entry['userPassword']) && preg_match('/^\{[^\}]/', $entry['userPassword'])) {
       return (preg_match('/^[^\}]*+\}!/', $entry['userPassword']) === 1);
+    } elseif ((strtolower($entry->type) == 'user') && !isset($entry['userPassword'])) {
+      /* Empty lockable password */
+      return FALSE;
     }
     return NULL;
   }
@@ -170,9 +173,6 @@ class userManagement extends management
     foreach ($allowed as $dn) {
       // We can't lock empty passwords.
       $entry = $this->listing->getEntry($dn);
-      if (!isset($entry['userPassword'])) {
-        continue;
-      }
 
       // Detect the password method and try to lock/unlock.
       $pwd      = $entry['userPassword'];
diff --git a/plugins/personal/generic/class_UserPasswordAttribute.inc b/plugins/personal/generic/class_UserPasswordAttribute.inc
new file mode 100644
index 0000000000000000000000000000000000000000..220db59333cb28f39854fd9a6481871bb869312c
--- /dev/null
+++ b/plugins/personal/generic/class_UserPasswordAttribute.inc
@@ -0,0 +1,252 @@
+<?php
+/*
+  This code is part of FusionDirectory (http://www.fusiondirectory.org/)
+  Copyright (C) 2013-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.
+*/
+
+/* Handle a password and its hash method */
+class UserPasswordAttribute extends CompositeAttribute
+{
+  protected $needPassword;
+  protected $previousMethod;
+
+  function __construct ($label, $description, $ldapName, $required = FALSE, $defaultValue = "", $acl = "")
+  {
+    $temp = passwordMethod::get_available_methods();
+
+    /* Create password methods array */
+    $pwd_methods = array();
+    $this->needPassword = array();
+    foreach ($temp['name'] as $id => $name) {
+      $this->needPassword[$name] = $temp[$id]['object']->need_password();
+      $pwd_methods[$name] = $name;
+      if (!empty($temp[$id]['desc'])) {
+        $pwd_methods[$name] .= " (".$temp[$id]['desc'].")";
+      }
+    }
+
+    parent::__construct (
+      $description, $ldapName,
+      array(
+        new SelectAttribute(
+          _('Password method'), _('Password hash method to use'),
+          $ldapName.'_pwstorage', TRUE,
+          array_keys($pwd_methods), '', array_values($pwd_methods)
+        ),
+        new PasswordAttribute(
+          _('Password'), _('Password (Leave empty if you do not wish to change it)'),
+          $ldapName.'_password', $required
+        ),
+        new PasswordAttribute(
+          _('Password again'), _('Same password as above, to avoid errors'),
+          $ldapName.'_password2', $required
+        ),
+        new HiddenAttribute(
+          $ldapName.'_hash'
+        ),
+        new BooleanAttribute(
+          /* Label/Description will only be visible for templates */
+          _('Locked'), _('Whether accounts created with this template will be locked'),
+          $ldapName.'_locked', FALSE,
+          FALSE
+        )
+      ),
+      '', '', $acl, $label
+    );
+    $this->attributes[0]->setSubmitForm(TRUE);
+    $this->attributes[4]->setTemplatable(FALSE);
+  }
+
+  public function setParent(&$plugin)
+  {
+    global $config;
+    parent::setParent($plugin);
+    if (is_object($this->plugin)) {
+      $hash = $config->get_cfg_value('passwordDefaultHash', 'ssha');
+      $this->attributes[0]->setDefaultValue($hash);
+      if ($config->get_cfg_value('forcePasswordDefaultHash', 'FALSE') == 'TRUE') {
+        $this->attributes[0]->setValue($hash);
+        $this->attributes[0]->setDisabled(TRUE);
+      }
+      if (!$this->plugin->is_template) {
+        $this->attributes[4]->setVisible(FALSE);
+      }
+      $this->checkIfMethodNeedsPassword();
+    }
+  }
+
+  /* We need to handle method select disabling manually */
+  function renderAttribute(array &$attributes, $readOnly)
+  {
+    global $config;
+    if ($this->visible) {
+      if ($this->linearRendering) {
+        parent::renderAttribute($attributes, $readOnly);
+      } else {
+        foreach ($this->attributes as $key => &$attribute) {
+          if (is_object($this->plugin) && $this->plugin->is_template && ($key == 2)) {
+            /* Do not display confirmation field in template mode */
+            continue;
+          }
+          if (($key == 0) && ($config->get_cfg_value('forcePasswordDefaultHash', 'FALSE') == 'TRUE')) {
+            $attribute->setDisabled(TRUE);
+          } else {
+            $attribute->setDisabled($this->disabled);
+          }
+          $attribute->renderAttribute($attributes, $readOnly);
+        }
+        unset($attribute);
+      }
+    }
+  }
+
+  /*! \brief Loads this attribute value from the attrs array
+   */
+  protected function loadAttrValue (array $attrs)
+  {
+    if (isset($attrs[$this->getLdapName()])) {
+      $this->setValue($this->inputValue($attrs[$this->getLdapName()][0]));
+    } elseif ($this->plugin->initially_was_account) {
+      $this->setValue($this->inputValue(''));
+    } else {
+      $this->attributes[0]->resetToDefault();
+      $this->checkIfMethodNeedsPassword();
+    }
+  }
+
+  function setValue ($value)
+  {
+    if (!is_array($value)) {
+      $value = $this->inputValue($value);
+    }
+    reset($value);
+    $key = key($value);
+    if ($this->attributes[0]->isDisabled() || ($value[$key] == '')) {
+      $value[$key] = $this->attributes[0]->getValue();
+    }
+    parent::setValue($value);
+    $this->checkIfMethodNeedsPassword();
+  }
+
+  function applyPostValue ()
+  {
+    parent::applyPostValue();
+    $this->checkIfMethodNeedsPassword();
+  }
+
+  function checkIfMethodNeedsPassword()
+  {
+    $method = $this->attributes[0]->getValue();
+    if ($method != $this->previousMethod) {
+      if (isset($this->needPassword[$method]) && $this->needPassword[$method]) {
+        $hashEmpty = ($this->attributes[3]->getValue() == '');
+        $this->attributes[1]->setVisible(TRUE);
+        $this->attributes[1]->setRequired($hashEmpty);
+        $this->attributes[2]->setVisible(TRUE);
+        $this->attributes[2]->setRequired($hashEmpty);
+      } else {
+        $this->attributes[1]->setRequired(FALSE);
+        $this->attributes[1]->setVisible(FALSE);
+        $this->attributes[1]->setValue('');
+        $this->attributes[2]->setRequired(FALSE);
+        $this->attributes[2]->setVisible(FALSE);
+        $this->attributes[2]->setValue('');
+      }
+    }
+    $this->previousMethod = $method;
+  }
+
+  function readValues($value)
+  {
+    return $this->readUserPasswordValues($value, $this->plugin->is_template);
+  }
+
+  function readUserPasswordValues($value, $istemplate)
+  {
+    global $config;
+    $pw_storage = $config->get_cfg_value('passwordDefaultHash', 'ssha');
+    $locked     = FALSE;
+    $password   = '';
+    if ($istemplate && !empty($value)) {
+      if ($value == '%askme%') {
+        return array('%askme%', '', '', $value, $locked);
+      }
+      list($value, $password) = explode('|', $value, 2);
+    }
+    if (preg_match ('/^{[^}]+}/', $value)) {
+      $tmp = passwordMethod::get_method($value);
+      if (is_object($tmp)) {
+        $pw_storage = $tmp->get_hash();
+        $locked     = $tmp->is_locked('', $value);
+      }
+    } elseif ($value != '') {
+      $pw_storage = 'clear';
+    } elseif ($this->plugin->initially_was_account) {
+      $pw_storage = 'empty';
+    }
+    return array($pw_storage, $password, $password, $value, $locked);
+  }
+
+  function writeValues(array $values)
+  {
+    if ($this->plugin->is_template && ($values[0] == '%askme%')) {
+      return '%askme%';
+    }
+    if (!$this->plugin->is_template && ($this->needPassword[$values[0]] || ($values[0] == 'empty')) && ($values[1] == '')) {
+      return $values[3];
+    }
+    $temp = passwordMethod::get_available_methods();
+    if (!isset($temp[$values[0]])) {
+      trigger_error('Unknown password method '.$values[0]);
+      return $values[3];
+    }
+    $test = new $temp[$values[0]]($this->plugin->dn, $this->plugin);
+    $test->set_hash($values[0]);
+    if ($this->plugin->is_template) {
+      return $test->generate_hash($values[1], $values[4]).'|'.$values[1];
+    } else {
+      return $test->generate_hash($values[1]);
+    }
+  }
+
+  function check()
+  {
+    $method = $this->attributes[0]->getValue();
+    $error = parent::check();
+    if (!empty($error)) {
+      return $error;
+    }
+    if (($this->attributes[1]->getValue() != '') || ($this->attributes[2]->getValue() != '')) {
+      return user::reportPasswordProblems($this->plugin->dn, $this->attributes[1]->getValue(), $this->attributes[2]->getValue());
+    }
+  }
+
+  function getMethod()
+  {
+    return $this->attributes[0]->getValue();
+  }
+
+  function getClear()
+  {
+    return $this->attributes[1]->getValue();
+  }
+
+  function isLocked()
+  {
+    return $this->attributes[4]->getValue();
+  }
+}
diff --git a/plugins/personal/generic/class_user.inc b/plugins/personal/generic/class_user.inc
index 52f0812f304819ba6bade0d738178012a7512cd9..c32c1c0991c6610ec535c77a239d58b636db74be 100644
--- a/plugins/personal/generic/class_user.inc
+++ b/plugins/personal/generic/class_user.inc
@@ -1,7 +1,7 @@
 <?php
 /*
   This code is part of FusionDirectory (http://www.fusiondirectory.org/)
-  Copyright (C) 2013-2016  FusionDirectory
+  Copyright (C) 2013-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
@@ -18,231 +18,6 @@
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
 */
 
-/* Handle a password and its hash method */
-class UserPasswordAttribute extends CompositeAttribute
-{
-  protected $needPassword;
-  protected $previousMethod;
-
-  function __construct ($label, $description, $ldapName, $required = FALSE, $defaultValue = "", $acl = "")
-  {
-    $temp = passwordMethod::get_available_methods();
-
-    /* Create password methods array */
-    $pwd_methods = array();
-    $this->needPassword = array();
-    foreach ($temp['name'] as $id => $name) {
-      $this->needPassword[$name] = $temp[$id]['object']->need_password();
-      $pwd_methods[$name] = $name;
-      if (!empty($temp[$id]['desc'])) {
-        $pwd_methods[$name] .= " (".$temp[$id]['desc'].")";
-      }
-    }
-
-    parent::__construct (
-      $description, $ldapName,
-      array(
-        new SelectAttribute(
-          _('Password method'), _('Password hash method to use'),
-          $ldapName.'_pwstorage', TRUE,
-          array_keys($pwd_methods), '', array_values($pwd_methods)
-        ),
-        new PasswordAttribute(
-          _('Password'), _('Password (Leave empty if you do not wish to change it)'),
-          $ldapName.'_password', $required
-        ),
-        new PasswordAttribute(
-          _('Password again'), _('Same password as above, to avoid errors'),
-          $ldapName.'_password2', $required
-        ),
-        new HiddenAttribute(
-          $ldapName.'_hash'
-        ),
-        new HiddenAttribute(
-          $ldapName.'_locked', FALSE,
-          FALSE
-        )
-      ),
-      '', '', $acl, $label
-    );
-    $this->attributes[0]->setSubmitForm(TRUE);
-  }
-
-  public function setParent(&$plugin)
-  {
-    global $config;
-    parent::setParent($plugin);
-    if (is_object($this->plugin)) {
-      $hash = $config->get_cfg_value('passwordDefaultHash', 'ssha');
-      $this->attributes[0]->setDefaultValue($hash);
-      if ($config->get_cfg_value('forcePasswordDefaultHash', 'FALSE') == 'TRUE') {
-        $this->attributes[0]->setValue($hash);
-        $this->attributes[0]->setDisabled(TRUE);
-      }
-      $this->checkIfMethodNeedsPassword();
-    }
-  }
-
-  /* We need to handle method select disabling manually */
-  function renderAttribute(array &$attributes, $readOnly)
-  {
-    global $config;
-    if ($this->visible) {
-      if ($this->linearRendering) {
-        parent::renderAttribute($attributes, $readOnly);
-      } else {
-        foreach ($this->attributes as $key => &$attribute) {
-          if (is_object($this->plugin) && $this->plugin->is_template && ($key == 2)) {
-            /* Do not display confirmation field in template mode */
-            continue;
-          }
-          if (($key == 0) && ($config->get_cfg_value('forcePasswordDefaultHash', 'FALSE') == 'TRUE')) {
-            $attribute->setDisabled(TRUE);
-          } else {
-            $attribute->setDisabled($this->disabled);
-          }
-          $attribute->renderAttribute($attributes, $readOnly);
-        }
-        unset($attribute);
-      }
-    }
-  }
-
-  /*! \brief Loads this attribute value from the attrs array
-   */
-  protected function loadAttrValue (array $attrs)
-  {
-    if (isset($attrs[$this->getLdapName()])) {
-      $this->setValue($this->inputValue($attrs[$this->getLdapName()][0]));
-    } elseif ($this->plugin->initially_was_account) {
-      $this->setValue($this->inputValue(''));
-    } else {
-      $this->attributes[0]->resetToDefault();
-      $this->checkIfMethodNeedsPassword();
-    }
-  }
-
-  function setValue ($value)
-  {
-    if (!is_array($value)) {
-      $value = $this->inputValue($value);
-    }
-    reset($value);
-    $key = key($value);
-    if ($this->attributes[0]->isDisabled() || ($value[$key] == '')) {
-      $value[$key] = $this->attributes[0]->getValue();
-    }
-    parent::setValue($value);
-    $this->checkIfMethodNeedsPassword();
-  }
-
-  function applyPostValue ()
-  {
-    parent::applyPostValue();
-    $this->checkIfMethodNeedsPassword();
-  }
-
-  function checkIfMethodNeedsPassword()
-  {
-    $method = $this->attributes[0]->getValue();
-    if ($method != $this->previousMethod) {
-      if (isset($this->needPassword[$method]) && $this->needPassword[$method]) {
-        $hashEmpty = ($this->attributes[3]->getValue() == '');
-        $this->attributes[1]->setVisible(TRUE);
-        $this->attributes[1]->setRequired($hashEmpty);
-        $this->attributes[2]->setVisible(TRUE);
-        $this->attributes[2]->setRequired($hashEmpty);
-      } else {
-        $this->attributes[1]->setRequired(FALSE);
-        $this->attributes[1]->setVisible(FALSE);
-        $this->attributes[1]->setValue('');
-        $this->attributes[2]->setRequired(FALSE);
-        $this->attributes[2]->setVisible(FALSE);
-        $this->attributes[2]->setValue('');
-      }
-    }
-    $this->previousMethod = $method;
-  }
-
-  function readValues($value)
-  {
-    global $config;
-    $pw_storage = $config->get_cfg_value('passwordDefaultHash', 'ssha');
-    $locked     = FALSE;
-    $password   = '';
-    if ($this->plugin->is_template && !empty($value)) {
-      if ($value == '%askme%') {
-        return array('%askme%', '', '', $value, $locked);
-      }
-      list($value, $password) = explode('|', $value, 2);
-    }
-    if (preg_match ('/^{[^}]+}/', $value)) {
-      $tmp = passwordMethod::get_method($value);
-      if (is_object($tmp)) {
-        $pw_storage = $tmp->get_hash();
-        $locked     = $tmp->is_locked($this->plugin->dn);
-        if ($this->plugin->is_template) {
-          $value = $tmp->generate_hash($password);
-        }
-      }
-    } elseif ($value != '') {
-      $pw_storage = 'clear';
-    } elseif ($this->plugin->initially_was_account) {
-      $pw_storage = 'empty';
-    }
-    return array($pw_storage, $password, $password, $value, $locked);
-  }
-
-  function writeValues(array $values)
-  {
-    if ($this->plugin->is_template && ($values[0] == '%askme%')) {
-      return '%askme%';
-    }
-    if (!$this->plugin->is_template && $this->needPassword[$values[0]] && ($values[1] == '')) {
-      return $values[3];
-    }
-    $temp = passwordMethod::get_available_methods();
-    if (!isset($temp[$values[0]])) {
-      trigger_error('Unknown password method '.$values[0]);
-      return $values[3];
-    }
-    $test = new $temp[$values[0]]($this->plugin->dn, $this->plugin);
-    $test->set_hash($values[0]);
-    if ($this->plugin->is_template) {
-      return $test->generate_hash($values[1]).'|'.$values[1];
-    } else {
-      return $test->generate_hash($values[1]);
-    }
-  }
-
-  function check()
-  {
-    $method = $this->attributes[0]->getValue();
-    $error = parent::check();
-    if (!empty($error)) {
-      return $error;
-    }
-    if (($this->attributes[1]->getValue() != '') || ($this->attributes[2]->getValue() != '')) {
-      return user::reportPasswordProblems($this->plugin->dn, $this->attributes[1]->getValue(), $this->attributes[2]->getValue());
-    }
-  }
-
-  function getMethod()
-  {
-    return $this->attributes[0]->getValue();
-  }
-
-  function getClear()
-  {
-    return $this->attributes[1]->getValue();
-  }
-
-  function isLocked()
-  {
-    return $this->attributes[4]->getValue();
-  }
-}
-
 class PostalAddressAttribute extends TextAreaAttribute
 {
   function inputValue ($ldapValue)
@@ -609,22 +384,9 @@ class user extends simplePlugin
     if ($this->uid != '') {
       $skip[] = 'uid';
     }
-    parent::adapt_from_template($attrs, $skip);
+    parent::adapt_from_template($attrs, array_merge($skip, array('userPassword')));
     if (isset($this->attrs['userPassword']) && !in_array('userPassword', $skip)) {
-      list($hash,$password) = explode('|', $this->attrs['userPassword'][0], 2);
-      if (preg_match ('/^{[^}]+}/', $hash)) {
-        $tmp = passwordMethod::get_method($hash);
-        if (is_object($tmp)) {
-          $hash = $tmp->generate_hash($password);
-        }
-      }
-      $this->userPassword = array(
-        '',
-        $password,
-        $password,
-        $hash,
-        $this->attributesAccess['userPassword']->isLocked()
-      );
+      $this->userPassword = $this->attributesAccess['userPassword']->readUserPasswordValues($this->attrs['userPassword'][0], TRUE);
     }
   }