Merge branch 'password_plugin' of https://github.com/johndoh/roundcubemail into johndoh-password_plugin

pull/6517/head
Aleksander Machniak 6 years ago
commit 75d6687cac

@ -26,6 +26,7 @@
},
"suggest": {
"pear/net_ldap2": "~2.2.0 required for connecting to LDAP",
"kolab/net_ldap3": "~1.0.6 required for connecting to LDAP"
"kolab/net_ldap3": "~1.0.6 required for connecting to LDAP",
"mkopinsky/zxcvbn-php": "^4.4.2 required for Zxcvbn password strength driver"
}
}

@ -61,12 +61,15 @@
2. Drivers
----------
2.1. Password Change Drivers
----------------------------
Password plugin supports many password change mechanisms which are
handled by included drivers. Just pass driver name in 'password_driver' option.
2.1. Database (sql)
-------------------
2.1.1. Database (sql)
---------------------
You can specify which database to connect by 'password_db_dsn' option and
what SQL query to execute by 'password_query'. See config.inc.php.dist file for
@ -119,8 +122,8 @@
UPDATE users SET password=MD5(%p) WHERE username=%u AND password=MD5(%o) LIMIT 1
2.2. Cyrus/SASL (sasl)
----------------------
2.1.2. Cyrus/SASL (sasl)
------------------------
Cyrus SASL database authentication allows your Cyrus+Roundcube
installation to host mail users without requiring a Unix Shell account!
@ -170,21 +173,21 @@
This could save you some headaches if you are the paranoid type.
2.3. Poppassd/Courierpassd (poppassd)
-------------------------------------
2.1.3. Poppassd/Courierpassd (poppassd)
---------------------------------------
You can specify which host to connect to via 'password_pop_host' and
what port via 'password_pop_port'. See config.inc.php.dist file for more info.
2.4. LDAP (ldap)
----------------
2.1.4. LDAP (ldap)
------------------
See config.inc.php.dist file. Requires PEAR::Net_LDAP2 package.
2.5. DirectAdmin Control Panel (directadmin)
--------------------------------------------
2.1.5. DirectAdmin Control Panel (directadmin)
----------------------------------------------
You can specify which host to connect to via 'password_directadmin_host' (don't
forget to use tcp:// or ssl://) and what port via 'password_direactadmin_port'.
@ -193,8 +196,8 @@
See config.inc.php.dist file for more info.
2.6. cPanel
-----------
2.1.6. cPanel
-------------
cPanel offers various APIs. The `cpanel` driver is configured with and admin
account. It can change user's passwords without access to the current password.
@ -204,8 +207,8 @@
an admin account. See 2.6.2.
2.6.1. cPanel WHM (cpanel)
--------------------------
2.1.6.1. cPanel WHM (cpanel)
----------------------------
Install cPanel XMLAPI Client Class into Roundcube program/lib directory
or any other place in PHP include path. You can get the class from
@ -215,8 +218,8 @@
See config.inc.php.dist file for more info.
2.6.2. cPanel Webmail (cpanel_webmail)
--------------------------------------
2.1.6.2. cPanel Webmail (cpanel_webmail)
----------------------------------------
Specify the host to connect to via 'password_webmail_cpanel_host'. This driver
comes with a minimal UAPI implementation and does not use the external xmlapi
@ -225,38 +228,38 @@
See config.inc.php.dist file for more info.
2.7. XIMSS/Communigate (ximms)
------------------------------
2.1.7. XIMSS/Communigate (ximms)
--------------------------------
You can specify which host and port to connect to via 'password_ximss_host'
and 'password_ximss_port'. See config.inc.php.dist file for more info.
2.8. Virtualmin (virtualmin)
----------------------------
2.1.8. Virtualmin (virtualmin)
------------------------------
As in sasl driver this one allows to change password using shell
utility called "virtualmin". See helpers/chgvirtualminpasswd.c for
installation instructions. Requires virtualmin >= 4.09.
2.9. hMailServer (hmail)
------------------------
2.1.9. hMailServer (hmail)
--------------------------
Requires PHP COM (Windows only). For access to hMail server on remote host
you'll need to define 'hmailserver_remote_dcom' and 'hmailserver_server'.
See config.inc.php.dist file for more info.
2.10. PAM (pam)
---------------
2.1.10. PAM (pam)
-----------------
This driver is for changing passwords of shell users authenticated with PAM.
Requires PECL's PAM exitension to be installed (http://pecl.php.net/package/PAM).
2.11. Chpasswd (chpasswd)
-------------------------
2.1.11. Chpasswd (chpasswd)
---------------------------
Driver that adds functionality to change the systems user password via
the 'chpasswd' command. See config.inc.php.dist file.
@ -265,8 +268,8 @@
to uids >= 1000 and can deny requests based on a blacklist.
2.12. LDAP - no PEAR (ldap_simple)
-----------------------------------
2.1.12. LDAP - no PEAR (ldap_simple)
-------------------------------------
It's rewritten ldap driver that doesn't require the Net_LDAP2 PEAR extension.
It uses directly PHP's ldap module functions instead (as Roundcube does).
@ -280,29 +283,29 @@
why the 'force replace' is always used).
2.13. XMail (xmail)
-----------------------------------
2.1.13. XMail (xmail)
----------------------
Driver for XMail (www.xmailserver.org). See config.inc.php.dist file
for configuration description.
2.14. Pw (pw_usermod)
-----------------------------------
2.1.14. Pw (pw_usermod)
------------------------
Driver to change the systems user password via the 'pw usermod' command.
See config.inc.php.dist file for configuration description.
2.15. domainFACTORY (domainfactory)
-----------------------------------
2.1.15. domainFACTORY (domainfactory)
-------------------------------------
Driver for the hosting provider domainFACTORY (www.df.eu).
No configuration options.
2.16. DBMail (dbmail)
-----------------------------------
2.1.16. DBMail (dbmail)
------------------------
Driver that adds functionality to change the users DBMail password.
It only works with dbmail-users on the same host where Roundcube runs
@ -313,22 +316,22 @@
Note: DBMail users can also use sql driver.
2.17. Expect (expect)
-----------------------------------
2.1.17. Expect (expect)
------------------------
Driver to change user password via the 'expect' command.
See config.inc.php.dist file for configuration description.
2.18. Samba (smb)
-----------------------------------
2.1.18. Samba (smb)
--------------------
Driver to change Samba user password via the 'smbpasswd' command.
See config.inc.php.dist file for configuration description.
2.19. Vpopmail daemon (vpopmaild)
-----------------------------------
2.1.19. Vpopmail daemon (vpopmaild)
-------------------------------------
Driver for the daemon of vpopmail. Vpopmail is used with qmail to
enable virtual users that are saved in a database and not in /etc/passwd.
@ -341,8 +344,8 @@
connection to vpopmaild (You may want to set it higher on busy servers).
2.20. Plesk (Plesk RPC-API)
---------------------------
2.1.20. Plesk (Plesk RPC-API)
-----------------------------
Driver for changing Passwords via Plesk RPC-API. This Driver also works with
Parallels Plesk Automation (PPA).
@ -356,30 +359,67 @@
Set the RPC-Path in $config['password_plesk_rpc_path']. Normally this is: enterprise/control/agent.php.
2.21. Kpasswd
-----------------------------------
2.1.21. Kpasswd
---------------
Driver to change the password in Kerberos environments via the 'kpasswd' command.
See config.inc.php.dist file for configuration description.
2.22. Modoboa
-----------------------------------
2.1.22. Modoboa
---------------
Driver to change the password in Modoboa servers.
See config.inc.php.dist file for configuration description.
2.2. Password Strength Drivers
------------------------------
Password plugin supports many password strength checking mechanisms which are
handled by included drivers. Just pass driver name in 'password_strength_driver' option.
2.2.1. Zxcvbn
-------------
Driver to use the Zxcvbn library to check password strength. Requires zxcvbn-php library.
Set $config['password_zxcvbn_min_score'] to define minimum acceptable password strength score.
3. Driver API
-------------
Driver file (<driver_name>.php) must define rcube_<driver_name>_password class
with public save() method that has two arguments. First - current password, second - new password.
Driver file (<driver_name>.php) must define rcube_<driver_name>_password class. Drivers should
provide one or both of a public save() or check_strength() method.
All password changing drivers (used in config `password_driver` - the password driver) must have
a save() method. The same driver can also contain a check_strength() method or a separate driver
containing this method can be used in `password_strength_driver` (the strength driver). To enable
strength checks ensure `password_check_strength` is set to true.
The save() method, used for changing the password has two arguments:
First - current password, second - new password.
This method should return PASSWORD_SUCCESS on success or any of PASSWORD_CONNECT_ERROR,
PASSWORD_CRYPT_ERROR, PASSWORD_ERROR when driver was unable to change password.
Extended result (as a hash-array with 'message' and 'code' items) can be returned
too. See existing drivers in drivers/ directory for examples.
Optionally a password driver can contain a compare() method which has three arguments:
First - current password, second - test password, third - compare type.
Compare type: PASSWORD_COMPARE_CURRENT - when comparing the test password with current password.
PASSWORD_COMPARE_NEW - when comparing the current password with the test password.
For PASSWORD_COMPARE_CURRENT it should return error text if user entered and real current password
DO NOT MATCH. For PASSWORD_COMPARE_NEW it should return error text if user entered and real current
password DO MATCH. Else it should return null (no error).
The check_strength() method, used for checking password strength has one argument: new password.
This method should return null on success or error message text on failure.
Optionally a strength driver can contain a strength_rules() method. This has no arguments as returns
a string, or array of strings explaining the password strength rules.
4. Sudo setup
-------------

@ -6,6 +6,11 @@
// See README file for list of supported driver names.
$config['password_driver'] = 'sql';
// A driver to use for checking password strength. Default: null.
// Set password_check_strength to true to enable
// See README file for list of supported driver names.
$config['password_strength_driver'] = null;
// Determine whether current password is required to change password.
// Default: false.
$config['password_confirm_current'] = true;
@ -16,7 +21,7 @@ $config['password_minimum_length'] = 0;
// Require the new password to contain a letter and punctuation character
// Change to false to remove this check.
$config['password_require_nonalpha'] = false;
$config['password_check_strength'] = false;
// Enables logging of password changes into logs/password
$config['password_log'] = false;
@ -483,7 +488,14 @@ $config['password_plesk_rpc_path'] = 'enterprise/control/agent.php';
// Command to use
$config['password_kpasswd_cmd'] = '/usr/bin/kpasswd';
// Modoboa Driver options
// ---------------------
// put token number from Modoboa server
$config['password_modoboa_api_token'] = '';
// Zxcvbn Strength Driver options
// ------------------------------
// minimum Zxcvbn score required for new passwords (0 = weak, 4 = very strong, 3 = default)
$config['password_zxcvbn_min_score'] = 3;

@ -0,0 +1,57 @@
<?php
/**
* Zxcvb Password Strength Driver
*
* Driver to check password strength using Zxcvbn-PHP
*
* @version 0.1
* @author Philip Weir
*
* Copyright (C) 2018 Philip Weir
*
* 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 3 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, see http://www.gnu.org/licenses/.
*/
use ZxcvbnPhp\Zxcvbn;
class rcube_zxcvbn_password
{
function strength_rules()
{
$rcmail = rcmail::get_instance();
$rules = array();
$rules[] = $rcmail->gettext('password.passwordweak');
$rules[] = $rcmail->gettext('password.passwordnoseq');
$rules[] = $rcmail->gettext('password.passwordnocommon');
return $rules;
}
function check_strength($passwd)
{
$rcmail = rcmail::get_instance();
$zxcvbn = new Zxcvbn();
$strength = $zxcvbn->passwordStrength($passwd);
$result = null;
if ($strength['score'] < $rcmail->config->get('password_zxcvbn_min_score', 3)) {
$reason = $strength['feedback']['warning'];
$result = $rcmail->gettext(array('name' => 'password.passwordweakreason', 'vars' => array('reason' => $reason)));
}
return $result;
}
}

@ -32,6 +32,9 @@ $messages['connecterror'] = 'Could not save new password. Connection error.';
$messages['internalerror'] = 'Could not save new password.';
$messages['passwordshort'] = 'Password must be at least $length characters long.';
$messages['passwordweak'] = 'Password must include at least one number and one punctuation character.';
$messages['passwordweakreason'] = 'Password too weak. $reason';
$messages['passwordnoseq'] = 'Password should not be a sequence like 123456 or QWERTY.';
$messages['passwordnocommon'] = 'Password should not be a common word or name.';
$messages['passwordforbidden'] = 'Password contains forbidden characters.';
$messages['firstloginchange'] = 'This is your first login. Please change your password.';
$messages['disablednotice'] = 'The system is currently under maintenance and password change is not possible at the moment. Everything should be back to normal soon. We apologize for any inconvenience.';

@ -26,6 +26,8 @@ define('PASSWORD_ERROR', 2);
define('PASSWORD_CONNECT_ERROR', 3);
define('PASSWORD_IN_HISTORY', 4);
define('PASSWORD_CONSTRAINT_VIOLATION', 5);
define('PASSWORD_COMPARE_CURRENT', 6);
define('PASSWORD_COMPARE_NEW', 7);
define('PASSWORD_SUCCESS', 0);
/**
@ -46,12 +48,17 @@ class password extends rcube_plugin
public $noajax = true;
private $newuser = false;
private $drivers = array();
function init()
{
$rcmail = rcmail::get_instance();
$this->load_config();
// update deprecated password_require_nonalpha option removed 20181007
if ($rcmail->config->get('password_check_strength') === null) {
$rcmail->config->set('password_check_strength', $rcmail->config->get('password_require_nonalpha'));
}
if ($rcmail->task == 'settings') {
if (!$this->check_host_login_exceptions()) {
@ -127,7 +134,8 @@ class password extends rcube_plugin
$form_disabled = $rcmail->config->get('password_disabled');
$confirm = $rcmail->config->get('password_confirm_current');
$required_length = intval($rcmail->config->get('password_minimum_length'));
$check_strength = $rcmail->config->get('password_require_nonalpha');
$check_strength = $rcmail->config->get('password_check_strength');
$force_save = $rcmail->config->get('password_force_save');
if (($confirm && !isset($_POST['_curpasswd'])) || !isset($_POST['_newpasswd']) || !strlen($_POST['_newpasswd'])) {
$rcmail->output->command('display_message', $this->gettext('nopassword'), 'error');
@ -161,19 +169,19 @@ class password extends rcube_plugin
else if ($conpwd != $newpwd) {
$rcmail->output->command('display_message', $this->gettext('passwordinconsistency'), 'error');
}
else if ($confirm && $sespwd != $curpwd) {
$rcmail->output->command('display_message', $this->gettext('passwordincorrect'), 'error');
else if ($confirm && ($res = $this->_compare($sespwd, $curpwd, PASSWORD_COMPARE_CURRENT))) {
$rcmail->output->command('display_message', $res, 'error');
}
else if ($required_length && strlen($newpwd) < $required_length) {
$rcmail->output->command('display_message', $this->gettext(
array('name' => 'passwordshort', 'vars' => array('length' => $required_length))), 'error');
}
else if ($check_strength && (!preg_match("/[0-9]/", $newpwd) || !preg_match("/[^A-Za-z0-9]/", $newpwd))) {
$rcmail->output->command('display_message', $this->gettext('passwordweak'), 'error');
else if ($check_strength && ($res = $this->_check_strength($newpwd))) {
$rcmail->output->command('display_message', $res, 'error');
}
// password is the same as the old one, warn user, return error
else if ($sespwd == $newpwd && !$rcmail->config->get('password_force_save')) {
$rcmail->output->command('display_message', $this->gettext('samepasswd'), 'error');
else if (!$force_save && ($res = $this->_compare($sespwd, $newpwd, PASSWORD_COMPARE_NEW))) {
$rcmail->output->command('display_message', $res, 'error');
}
// try to save the password
else if (!($res = $this->_save($curpwd, $newpwd))) {
@ -268,14 +276,16 @@ class password extends rcube_plugin
$required_length = intval($rcmail->config->get('password_minimum_length'));
if ($required_length > 0) {
$rules .= html::tag('li', array('id' => 'required-length'), $this->gettext(array(
$rules .= html::tag('li', array('class' => 'required-length'), $this->gettext(array(
'name' => 'passwordshort',
'vars' => array('length' => $required_length)
)));
}
if ($rcmail->config->get('password_require_nonalpha')) {
$rules .= html::tag('li', array('id' => 'require-nonalpha'), $this->gettext('passwordweak'));
if ($rcmail->config->get('password_check_strength') && ($msgs = $this->_strength_rules())) {
foreach ($msgs as $msg) {
$rules .= html::tag('li', array('class' => 'strength-rule'), $msg);
}
}
if (!empty($rules)) {
@ -312,37 +322,76 @@ class password extends rcube_plugin
. $form_buttons);
}
private function _save($curpass, $passwd)
private function _compare($curpwd, $newpwd, $type)
{
$config = rcmail::get_instance()->config;
$driver = $config->get('password_driver', 'sql');
$class = "rcube_{$driver}_password";
$file = $this->home . "/drivers/$driver.php";
$result = null;
if (!file_exists($file)) {
rcube::raise_error(array(
'code' => 600,
'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Password plugin: Unable to open driver file ($file)"
), true, false);
if (!($driver = $this->_load_driver()))
return $this->gettext('internalerror');
if (method_exists($driver, 'compare')) {
$result = $driver->compare($curpwd, $newpwd, $type);
}
else {
switch ($type) {
case PASSWORD_COMPARE_CURRENT:
$result = $curpwd != $newpwd ? $this->gettext('passwordincorrect') : null;
break;
case PASSWORD_COMPARE_NEW:
$result = $curpwd == $newpwd ? $this->gettext('samepasswd') : null;
break;
default:
$result = $this->gettext('internalerror');
}
}
include_once $file;
return $result;
}
if (!class_exists($class, false) || !method_exists($class, 'save')) {
rcube::raise_error(array(
'code' => 600,
'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Password plugin: Broken driver $driver"
), true, false);
private function _strength_rules()
{
$result = null;
if (!($driver = $this->_load_driver('strength')))
return $this->gettext('internalerror');
if (method_exists($driver, 'strength_rules')) {
$result = $driver->strength_rules();
}
else {
$result = $this->gettext('passwordweak');
}
if (!is_array($result)) {
$result = array($result);
}
return $result;
}
private function _check_strength($passwd)
{
$result = null;
if (!($driver = $this->_load_driver('strength')))
return $this->gettext('internalerror');
$object = new $class;
$result = $object->save($curpass, $passwd, self::username());
if (method_exists($driver, 'check_strength')) {
$result = $driver->check_strength($passwd);
}
else {
$result = (!preg_match("/[0-9]/", $passwd) || !preg_match("/[^A-Za-z0-9]/", $passwd)) ? $this->gettext('passwordweak') : null;
}
return $result;
}
private function _save($curpass, $passwd)
{
if (!($driver = $this->_load_driver()))
return $this->gettext('internalerror');
$result = $driver->save($curpass, $passwd, self::username());
$message = '';
if (is_array($result)) {
@ -377,6 +426,46 @@ class password extends rcube_plugin
return $reason;
}
private function _load_driver($type = 'password')
{
if (!($type && $driver = rcmail::get_instance()->config->get('password_' . $type . '_driver'))) {
$driver = rcmail::get_instance()->config->get('password_driver', 'sql');
}
if (!$this->drivers[$type]) {
$class = "rcube_{$driver}_password";
$file = $this->home . "/drivers/$driver.php";
if (!file_exists($file)) {
rcube::raise_error(array(
'code' => 600,
'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Password plugin: Unable to open driver file ($file)"
), true, false);
return false;
}
include_once $file;
if (!class_exists($class, false) || (!method_exists($class, 'save') && !method_exists($class, 'check_strength'))) {
rcube::raise_error(array(
'code' => 600,
'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Password plugin: Broken driver $driver"
), true, false);
return false;
}
$this->drivers[$type] = new $class;
return $this->drivers[$type];
}
else {
return $this->drivers[$type];
}
}
function user_create($args)
{
$this->newuser = true;

Loading…
Cancel
Save