diff --git a/plugins/ldap-change-password/ChangePasswordLdapDriver.php b/plugins/ldap-change-password/ChangePasswordLdapDriver.php new file mode 100644 index 000000000..5b266f289 --- /dev/null +++ b/plugins/ldap-change-password/ChangePasswordLdapDriver.php @@ -0,0 +1,214 @@ +sHostName = $sHostName; + $this->sUserDnFormat = $sUserDnFormat; + $this->sPasswordField = $sPasswordField; + $this->sPasswordEncType = $sPasswordEncType; + + return $this; + } + + /** + * @param string $sAllowedEmails + * + * @return \ChangePasswordLdapDriver + */ + public function SetAllowedEmails($sAllowedEmails) + { + $this->sAllowedEmails = $sAllowedEmails; + + return $this; + } + + /** + * @param \MailSo\Log\Logger $oLogger + * + * @return \ChangePasswordLdapDriver + */ + public function SetLogger($oLogger) + { + if ($oLogger instanceof \MailSo\Log\Logger) + { + $this->oLogger = $oLogger; + } + + return $this; + } + + /** + * @param \RainLoop\Account $oAccount + * + * @return bool + */ + public function PasswordChangePossibility($oAccount) + { + return $oAccount && $oAccount->Email() && + \RainLoop\Plugins\Helper::ValidateWildcardValues($oAccount->Email(), $this->sAllowedEmails); + } + + /** + * @param \RainLoop\Model\Account $oAccount + * @param string $sPrevPassword + * @param string $sNewPassword + * + * @return bool + */ + public function ChangePassword(\RainLoop\Account $oAccount, $sPrevPassword, $sNewPassword) + { + $bResult = false; + + try + { + $sDomain = \MailSo\Base\Utils::GetDomainFromEmail($oAccount->Email()); + $sUserDn = \strtr($this->sUserDnFormat, array( + '{domain}' => $sDomain, + '{domain:dc}' => 'dc='.\strtr($sDomain, array('.' => ',dc=')), + '{email}' => $oAccount->Email(), + '{email:user}' => \MailSo\Base\Utils::GetAccountNameFromEmail($oAccount->Email()), + '{email:domain}' => $sDomain, + '{login}' => $oAccount->Login(), + '{imap:login}' => $oAccount->Login(), + '{imap:host}' => $oAccount->DomainIncHost(), + '{imap:port}' => $oAccount->DomainIncPort() + )); + + $oCon = @\ldap_connect($this->sHostName); + if ($oCon) + { + @\ldap_set_option($oCon, LDAP_OPT_PROTOCOL_VERSION, 3); + + if (!@\ldap_bind($oCon, $sUserDn, $sPrevPassword)) + { + if ($this->oLogger) + { + $sError = $oCon ? @\ldap_error($oCon) : ''; + $iErrno = $oCon ? @\ldap_errno($oCon) : 0; + + $this->oLogger->Write('ldap_bind error: '.$sError.' ('.$iErrno.')', + \MailSo\Log\Enumerations\Type::WARNING, 'LDAP'); + } + + return false; + } + } + + $sEncodedNewPassword = $sNewPassword; + switch (\strtolower($this->sPasswordEncType)) + { + case 'sha': + switch (true) + { + default: + case \function_exists('sha1'): + $sEncodedNewPassword = '{SHA}'.\base64_encode(\pack('H*', \sha1($sNewPassword))); + break; + case \function_exists('hash'): + $sEncodedNewPassword = '{SHA}'.\base64_encode(\hash('sha1', $sNewPassword, true)); + break; + case \function_exists('mhash') && defined('MHASH_SHA1'): + $sEncodedNewPassword = '{SHA}'.\base64_encode(\mhash(MHASH_SHA1, $sNewPassword)); + break; + } + break; + case 'md5': + $sEncodedNewPassword = '{MD5}'.\base64_encode(\pack('H*', \md5($sNewPassword))); + break; + case 'crypt': + $sEncodedNewPassword = '{CRYPT}'.\crypt($sNewPassword, $this->getSalt(2)); + break; + } + + $aEntry = array(); + $aEntry[$this->sPasswordField] = (string) $sEncodedNewPassword; + + if (!!@\ldap_modify($oCon, $sUserDn, $aEntry)) + { + $bResult = true; + } + else + { + if ($this->oLogger) + { + $sError = $oCon ? @\ldap_error($oCon) : ''; + $iErrno = $oCon ? @\ldap_errno($oCon) : 0; + + $this->oLogger->Write('ldap_modify error: '.$sError.' ('.$iErrno.')', + \MailSo\Log\Enumerations\Type::WARNING, 'LDAP'); + } + } + } + catch (\Exception $oException) + { + if ($this->oLogger) + { + $this->oLogger->WriteException($oException, + \MailSo\Log\Enumerations\Type::WARNING, 'LDAP'); + } + + $bResult = false; + } + + return $bResult; + } + + /** + * @param int $iLength + * + * @return string + */ + private function getSalt($iLength) + { + $sChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $iCharsLength = \strlen($sChars); + + $sResult = ''; + while (\strlen($sResult) < $iLength) + { + $sResult .= \substr($sChars, \rand() % $iCharsLength, 1); + } + + return $sResult; + } +} diff --git a/plugins/ldap-change-password/LICENSE b/plugins/ldap-change-password/LICENSE new file mode 100644 index 000000000..4aed64b3a --- /dev/null +++ b/plugins/ldap-change-password/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2015 RainLoop Team + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/plugins/ldap-change-password/README b/plugins/ldap-change-password/README new file mode 100644 index 000000000..53c1abf7f --- /dev/null +++ b/plugins/ldap-change-password/README @@ -0,0 +1 @@ +Plugin that adds functionality to change the email account password (LDAP Password). diff --git a/plugins/ldap-change-password/VERSION b/plugins/ldap-change-password/VERSION new file mode 100644 index 000000000..9f8e9b69a --- /dev/null +++ b/plugins/ldap-change-password/VERSION @@ -0,0 +1 @@ +1.0 \ No newline at end of file diff --git a/plugins/ldap-change-password/index.php b/plugins/ldap-change-password/index.php new file mode 100644 index 000000000..639a6ed03 --- /dev/null +++ b/plugins/ldap-change-password/index.php @@ -0,0 +1,75 @@ +addHook('main.fabrica', 'MainFabrica'); + } + + /** + * @return string + */ + public function Supported() + { + if (!\function_exists('ldap_connect')) + { + return 'The LDAP PHP exention must be installed to use this plugin'; + } + + return ''; + } + + /** + * @param string $sName + * @param mixed $oProvider + */ + public function MainFabrica($sName, &$oProvider) + { + switch ($sName) + { + case 'change-password': + + $sHostName = \trim($this->Config()->Get('plugin', 'hostname', '')); + $sUserDnFormat = \trim($this->Config()->Get('plugin', 'user_dn_format', '')); + $sPasswordField = \trim($this->Config()->Get('plugin', 'password_field', '')); + $sPasswordEncType = \trim($this->Config()->Get('plugin', 'password_enc_type', '')); + + if (!empty($sHostName) && !empty($sUserDnFormat) && !empty($sPasswordField) && !empty($sPasswordEncType)) + { + include_once __DIR__.'/ChangePasswordLdapDriver.php'; + + $oProvider = new \ChangePasswordLdapDriver(); + + $oProvider + ->SetConfig($sHostName, $sUserDnFormat, $sPasswordField, $sPasswordEncType) + ->SetAllowedEmails(\strtolower(\trim($this->Config()->Get('plugin', 'allowed_emails', '')))) + ->SetLogger($this->Manager()->Actions()->Logger()) + ; + } + break; + } + } + + /** + * @return array + */ + public function configMapping() + { + return array( + \RainLoop\Plugins\Property::NewInstance('hostname')->SetLabel('LDAP hostname') + ->SetDefaultValue('127.0.0.1'), + \RainLoop\Plugins\Property::NewInstance('user_dn_format')->SetLabel('User DN format') + ->SetDescription('LDAP user dn format. Supported tokens: {email}, {login}, {domain}, {domain:dc}, {imap:login}, {imap:host}, {imap:port}') + ->SetDefaultValue('uid={imap:login},ou=Users,{domain:dc}'), + \RainLoop\Plugins\Property::NewInstance('password_field')->SetLabel('Password field') + ->SetDefaultValue('userPassword'), + \RainLoop\Plugins\Property::NewInstance('password_enc_type')->SetLabel('Encryption type') + ->SetType(\RainLoop\Enumerations\PluginPropertyType::SELECTION) + ->SetDefaultValue(array('SHA', 'MD5', 'Crypt', 'Clear')), + \RainLoop\Plugins\Property::NewInstance('allowed_emails')->SetLabel('Allowed emails') + ->SetDescription('Allowed emails, space as delimiter, wildcard supported. Example: user1@domain1.net user2@domain1.net *@domain2.net') + ->SetDefaultValue('*') + ); + } +}