diff --git a/plugins/password/config.inc.php.dist b/plugins/password/config.inc.php.dist index 3e4431712..004debb52 100644 --- a/plugins/password/config.inc.php.dist +++ b/plugins/password/config.inc.php.dist @@ -271,6 +271,34 @@ $config['password_ldap_samba_pwattr'] = ''; // Whenever the password is changed, the attribute will be updated if set $config['password_ldap_samba_lchattr'] = ''; +// LDAP PPolicy Driver options +// ----------------------------------- + +// LDAP Change password command - filename of the perl script +// Example: 'change_ldap_pass.pl' +$config['password_ldap_ppolicy_cmd'] = 'change_ldap_pass.pl'; + +// LDAP URI +// Example: 'ldap://ldap.example.com/ ldaps://ldap2.example.com:636/' +$config['password_ldap_ppolicy_uri'] = 'ldap://localhost/ ; + +// LDAP base name (root directory) +// Exemple: 'dc=exemple,dc=com' +$config['password_ldap_ppolicy_basedn'] = 'dc=example,dc=com'; + +$config['password_ldap_ppolicy_searchDN'] = 'cn=someuser,dc=example,dc=com'; + +$config['password_ldap_ppolicy_searchPW'] = 'secret'; + +// LDAP search filter +// Example: '(uid=%login)' +// Example: '(&(objectClass=posixAccount)(uid=%login))' +$config['password_ldap_ppolicy_search_filter'] = '(uid=%login)'; + +// CA Certificate file if in URI is LDAPS connection +$config['password_ldap_ppolicy_cafile'] = '/etc/ssl/cacert.crt'; + + // DirectAdmin Driver options // -------------------------- diff --git a/plugins/password/drivers/ldap_ppolicy.php b/plugins/password/drivers/ldap_ppolicy.php new file mode 100644 index 000000000..667be2f4f --- /dev/null +++ b/plugins/password/drivers/ldap_ppolicy.php @@ -0,0 +1,100 @@ + + * + + */ + +class rcube_ldap_ppolicy_password +{ + public function save($currpass, $newpass) + { + $rcmail = rcmail::get_instance(); + $this->debug = $rcmail->config->get('ldap_debug'); + + $cmd = $rcmail->config->get('password_ldap_ppolicy_cmd'); + $uri = $rcmail->config->get('password_ldap_ppolicy_uri'); + $baseDN = $rcmail->config->get('password_ldap_ppolicy_basedn'); + $filter = $rcmail->config->get('password_ldap_ppolicy_search_filter'); + $bindDN = $rcmail->config->get('password_ldap_ppolicy_searchDN'); + $bindPW = $rcmail->config->get('password_ldap_ppolicy_searchPW'); + $cafile = $rcmail->config->get('password_ldap_ppolicy_cafile'); + + $log_dir = $rcmail->config->get('log_dir'); + + if (empty($log_dir)) { + $log_dir = RCUBE_INSTALL_PATH . 'logs'; + } + + // try to open specific log file for writing + $logfile = $log_dir.'/password_ldap_ppolicy.err'; + + $descriptorspec = array( + 0 => array("pipe", "r"), // stdin is a pipe that the child will read from + 1 => array("pipe", "w"), // stdout is a pipe that the child will write to + 2 => array("file", $logfile, "a") // stderr is a file to write to + ); + + $cmd = 'plugins/password/helpers/'. $cmd; + $this->_debug("parameters:\ncmd:$cmd\nuri:$uri\nbaseDN:$baseDN\nfilter:$filter"); + $process = proc_open($cmd, $descriptorspec, $pipes); + + if (is_resource($process)) { + // $pipes now looks like this: + // 0 => writeable handle connected to child stdin + // 1 => readable handle connected to child stdout + // Any error output will be appended to /tmp/error-output.txt + + fwrite($pipes[0], $uri."\n"); + fwrite($pipes[0], $baseDN."\n"); + fwrite($pipes[0], $filter."\n"); + fwrite($pipes[0], $bindDN."\n"); + fwrite($pipes[0], $bindPW."\n"); + fwrite($pipes[0], $_SESSION['username']."\n"); + fwrite($pipes[0], $currpass."\n"); + fwrite($pipes[0], $newpass."\n"); + fwrite($pipes[0], $cafile); + fclose($pipes[0]); + + $result = stream_get_contents($pipes[1]); + fclose($pipes[1]); + + $this->_debug('Result:'.$result); + + switch ($result) { + case "OK": + return PASSWORD_SUCCESS; + case "Password is in history of old passwords": + return PASSWORD_IN_HISTORY; + + case "Cannot connect to any server": + return PASSWORD_CONNECT_ERROR; + default: + rcube::raise_error(array( + 'code' => 600, + 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => $result + ), true, false); + } + + return PASSWORD_ERROR; + } + } + + private function _debug($str) + { + if ($this->debug) { + rcube::write_log('password_ldap_ppolicy', $str); + } + } + +} diff --git a/plugins/password/helpers/change_ldap_pass.pl b/plugins/password/helpers/change_ldap_pass.pl new file mode 100644 index 000000000..85c19ca76 --- /dev/null +++ b/plugins/password/helpers/change_ldap_pass.pl @@ -0,0 +1,89 @@ +#!/usr/bin/perl +=pod +Script to change the LDAP password using the set_password method +to proper setting the password policy attributes +author: Zbigniew Szmyd (zbigniew.szmyd@linseco.pl) +version 1.0 2016-02-22 +=cut + +use Net::LDAP; +use Net::LDAP::Extension::SetPassword; +use URI; +use utf8; +binmode(STDOUT, ':utf8'); + +my %PAR = (); +if (my $param = shift @ARGV){ + print "Password change in LDAP\n\n"; + print "Run script without any parameter and pass the following data:\n"; + print "URI\nbaseDN\nFilter\nbindDN\nbindPW\nLogin\nuserPass\nnewPass\nCAfile\n"; + exit; +} + +foreach my $param ('uri','base','filter','binddn','bindpw','user','pass','new_pass','ca'){ + $PAR{$param} = <>; + $PAR{$param} =~ s/\r|\n//g; +} + +my @servers = split (/\s+/, $PAR{'uri'}); +my $active_server = 0; + +my $ldap; +while ((my $serwer = shift @servers) && !($active_server)){ + my $ldap_uri = URI->new($serwer); + if ($ldap_uri->secure){ + $ldap = Net::LDAP->new($ldap_uri->as_string, + version => 3, + verify => 'require', + sslversion => 'tlsv1', + cafile => $PAR{'ca'}); + } else { + $ldap = Net::LDAP->new($ldap_uri->as_string, version => 3); + } + $active_server = 1 if ($ldap); +} + +if ($active_server){ + my $mesg = $ldap->bind( $PAR{'binddn'}, password => $PAR{'bindpw'} ); + if ($mesg->code != 0){ + print "Cannot login: ". $mesg->error; + } else { + # Wyszukanie usera wg filtra + $PAR{'filter'} =~ s/\%login/$PAR{'user'}/; + my @search_args = (base => $PAR{'base'}, + scope => 'sub', + filter => $PAR{'filter'}, + attrs => ['1.1'], + ); + my $result = $ldap->search( @search_args ); + if ($result->code){ + print $result->error; + } else { + my $count = $result->count; + if ($count == 1){ + my @users = $result->entries; + my $dn = $users[0]->dn(); + $result = $ldap->bind($dn, password => $PAR{'pass'}); + if ($result->code){ + print $result->error; + } else { + $result = $ldap->set_password( + newpasswd => $PAR{'new_pass'}, + ); + if ($result->code){ + print $result->error; + } else { + print "OK"; + } + } + } else { + print "User not found in LDAP\n" if $count == 0; + print "Found $count users\n"; + } + + } + } + $ldap->unbind(); +} else { + print "Cannot connect to any server"; +} diff --git a/plugins/password/localization/en_GB.inc b/plugins/password/localization/en_GB.inc index 2120f8f3b..644bb7dd6 100644 --- a/plugins/password/localization/en_GB.inc +++ b/plugins/password/localization/en_GB.inc @@ -31,3 +31,4 @@ $messages['passwordweak'] = 'Password must include at least one number and one s $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 apologise for any inconvenience.'; +$messages['passwdinhistory'] = 'This password has already been used previously'; \ No newline at end of file diff --git a/plugins/password/localization/en_US.inc b/plugins/password/localization/en_US.inc index d115b4d28..f7e659892 100644 --- a/plugins/password/localization/en_US.inc +++ b/plugins/password/localization/en_US.inc @@ -35,3 +35,4 @@ $messages['passwordweak'] = 'Password must include at least one number and one p $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.'; +$messages['passwinhistory'] = 'This password has already been used previously'; \ No newline at end of file diff --git a/plugins/password/localization/pl_PL.inc b/plugins/password/localization/pl_PL.inc index d9c5cf1ac..a70eb6e4f 100644 --- a/plugins/password/localization/pl_PL.inc +++ b/plugins/password/localization/pl_PL.inc @@ -31,3 +31,4 @@ $messages['passwordweak'] = 'Hasło musi zawierać co najmniej jedną cyfrę i z $messages['passwordforbidden'] = 'Hasło zawiera niedozwolone znaki.'; $messages['firstloginchange'] = 'To jest twoje pierwsze logowanie. Proszę zmień hasło.'; $messages['disablednotice'] = 'System jest w trakcie konserwacji i zmiana hasła w tym momencie nie jest możliwa. Wszystko powinno wrócić do normy w niedługim czasie. Przepraszamy za wszelkie niedogodności'; +$messages['passwdinhistory'] = 'To hasło było już użyte poprzednio.'; \ No newline at end of file diff --git a/plugins/password/password.php b/plugins/password/password.php index 7f30b65c3..673d626c4 100644 --- a/plugins/password/password.php +++ b/plugins/password/password.php @@ -25,6 +25,7 @@ define('PASSWORD_CRYPT_ERROR', 1); define('PASSWORD_ERROR', 2); define('PASSWORD_CONNECT_ERROR', 3); +define('PASSWORD_IN_HISTORY', 4); define('PASSWORD_SUCCESS', 0); /** @@ -333,6 +334,9 @@ class password extends rcube_plugin case PASSWORD_CONNECT_ERROR: $reason = $this->gettext('connecterror'); break; + case PASSWORD_IN_HISTORY: + $reason = $this->gettext('passwdinhistory'); + break; case PASSWORD_ERROR: default: $reason = $this->gettext('internalerror');