diff --git a/plugins/postfixadmin-change-password/ChangePasswordPostfixAdminDriver.php b/plugins/postfixadmin-change-password/ChangePasswordPostfixAdminDriver.php new file mode 100644 index 000000000..80ece64f5 --- /dev/null +++ b/plugins/postfixadmin-change-password/ChangePasswordPostfixAdminDriver.php @@ -0,0 +1,246 @@ +sHost = $sHost; + return $this; + } + + /** + * @param int $iPort + * + * @return \ChangePasswordPostfixAdminDriver + */ + public function SetPort($iPort) + { + $this->iPort = (int) $iPort; + return $this; + } + + /** + * @param string $sDatabase + * + * @return \ChangePasswordPostfixAdminDriver + */ + public function SetDatabase($sDatabase) + { + $this->sDatabase = $sDatabase; + return $this; + } + + /** + * @param string $sUser + * + * @return \ChangePasswordPostfixAdminDriver + */ + public function SetUser($sUser) + { + $this->sUser = $sUser; + return $this; + } + + /** + * @param string $sPassword + * + * @return \ChangePasswordPostfixAdminDriver + */ + public function SetPassword($sPassword) + { + $this->sPassword = $sPassword; + return $this; + } + + /** + * @param string $sEncrypt + * + * @return \ChangePasswordPostfixAdminDriver + */ + public function SetEncrypt($sEncrypt) + { + $this->sEncrypt = $sEncrypt; + return $this; + } + + /** + * @param string $sAllowedEmails + * + * @return \ChangePasswordPostfixAdminDriver + */ + public function SetAllowedEmails($sAllowedEmails) + { + $this->sAllowedEmails = $sAllowedEmails; + return $this; + } + + /** + * @param \MailSo\Log\Logger $oLogger + * + * @return \ChangePasswordPostfixAdminDriver + */ + 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\Account $oAccount + * @param string $sPrevPassword + * @param string $sNewPassword + * + * @return bool + */ + public function ChangePassword(\RainLoop\Account $oAccount, $sPrevPassword, $sNewPassword) + { + if ($this->oLogger) + { + $this->oLogger->Write('Postfix: Try to change password for '.$oAccount->Email()); + } + + $bResult = false; + + if (0 < \strlen($sNewPassword)) + { + try + { + $sDsn = 'mysql:host='.$this->sHost.';port='.$this->iPort.';dbname='.$this->sDatabase; + + $oPdo = new \PDO($sDsn, $this->sUser, $this->sPassword); + $oPdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + + $sUpdatePassword = $this->cryptPassword($sNewPassword, $oPdo); + if (0 < \strlen($sUpdatePassword)) + { + $oStmt = $oPdo->prepare('UPDATE mailbox SET password = ? WHERE username = ?'); + $bResult = (bool) $oStmt->execute(array($sUpdatePassword, $oAccount->Email())); + } + else + { + if ($this->oLogger) + { + $this->oLogger->Write('Postfix: Encyted password is ematy', + \MailSo\Log\Enumerations\Type::ERROR); + } + } + + $oPdo = null; + } + catch (\Exception $oException) + { + if ($this->oLogger) + { + $this->oLogger->WriteException($oException); + } + } + } + + return $bResult; + } + + /** + * @param string $sPassword + * @param \PDO $oPdo + * + * @return string + */ + private function cryptPassword($sPassword, $oPdo) + { + $sResult = ''; + switch ($this->sEncrypt) + { + default: + case 'cleartext': + $sResult = $sPassword; + break; + + case 'md5crypt': + include_once __DIR__.'/md5crypt.php'; + $sResult = md5crypt($sPassword); + break; + + case 'md5': + $sResult = md5($sPassword); + break; + + case 'system': + $sResult = crypt($sPassword); + break; + + case 'mysql_encrypt': + $oStmt = $oPdo->prepare('SELECT ENCRYPT(?) AS encpass'); + if ($oStmt->execute(array($sPassword))) + { + $aFetchResult = $oStmt->fetchAll(\PDO::FETCH_ASSOC); + if (\is_array($aFetchResult) && isset($aFetchResult[0]['encpass'])) + { + $sResult = $aFetchResult[0]['encpass']; + } + } + break; + } + + return $sResult; + } +} diff --git a/plugins/postfixadmin-change-password/LICENSE b/plugins/postfixadmin-change-password/LICENSE new file mode 100644 index 000000000..4a4ca8d81 --- /dev/null +++ b/plugins/postfixadmin-change-password/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014 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/postfixadmin-change-password/README b/plugins/postfixadmin-change-password/README new file mode 100644 index 000000000..342f4d4b5 --- /dev/null +++ b/plugins/postfixadmin-change-password/README @@ -0,0 +1 @@ +Plugin that adds functionality to change the email account password (PostfixAdmin). \ No newline at end of file diff --git a/plugins/postfixadmin-change-password/VERSION b/plugins/postfixadmin-change-password/VERSION new file mode 100644 index 000000000..b123147e2 --- /dev/null +++ b/plugins/postfixadmin-change-password/VERSION @@ -0,0 +1 @@ +1.1 \ No newline at end of file diff --git a/plugins/postfixadmin-change-password/index.php b/plugins/postfixadmin-change-password/index.php new file mode 100644 index 000000000..bf3fee467 --- /dev/null +++ b/plugins/postfixadmin-change-password/index.php @@ -0,0 +1,86 @@ +addHook('main.fabrica', 'MainFabrica'); + } + + /** + * @return string + */ + public function Supported() + { + if (!extension_loaded('pdo') || !class_exists('PDO')) + { + return 'The PHP exention PDO (mysql) must be installed to use this plugin'; + } + + $aDrivers = \PDO::getAvailableDrivers(); + if (!is_array($aDrivers) || !in_array('mysql', $aDrivers)) + { + return 'The PHP exention PDO (mysql) must be installed to use this plugin'; + } + + return ''; + } + + /** + * @param string $sName + * @param mixed $oProvider + */ + public function MainFabrica($sName, &$oProvider) + { + switch ($sName) + { + case 'change-password': + + include_once __DIR__.'/ChangePasswordPostfixAdminDriver.php'; + + $oProvider = new ChangePasswordPostfixAdminDriver(); + + $oProvider + ->SetHost($this->Config()->Get('plugin', 'host', '')) + ->SetPort((int) $this->Config()->Get('plugin', 'port', 3306)) + ->SetDatabase($this->Config()->Get('plugin', 'database', '')) + ->SetUser($this->Config()->Get('plugin', 'user', '')) + ->SetPassword($this->Config()->Get('plugin', 'password', '')) + ->SetEncrypt($this->Config()->Get('plugin', 'encrypt', '')) + ->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('host')->SetLabel('MySQL Host') + ->SetDefaultValue('127.0.0.1'), + \RainLoop\Plugins\Property::NewInstance('port')->SetLabel('MySQL Port') + ->SetType(\RainLoop\Enumerations\PluginPropertyType::INT) + ->SetDefaultValue(3306), + \RainLoop\Plugins\Property::NewInstance('database')->SetLabel('MySQL Database') + ->SetDefaultValue('postfixadmin'), + \RainLoop\Plugins\Property::NewInstance('user')->SetLabel('MySQL User') + ->SetDefaultValue('postfixadmin'), + \RainLoop\Plugins\Property::NewInstance('password')->SetLabel('MySQL Password') + ->SetType(\RainLoop\Enumerations\PluginPropertyType::PASSWORD) + ->SetDefaultValue(''), + \RainLoop\Plugins\Property::NewInstance('encrypt')->SetLabel('Encrypt') + ->SetType(\RainLoop\Enumerations\PluginPropertyType::SELECTION) + ->SetDefaultValue(array('md5crypt', 'md5', 'system', 'cleartext', 'mysql_encrypt')) + ->SetDescription('In what way do you want the passwords to be crypted ?'), + \RainLoop\Plugins\Property::NewInstance('allowed_emails')->SetLabel('Allowed emails') + ->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING_TEXT) + ->SetDescription('Allowed emails, space as delimiter, wildcard supported. Example: user1@domain1.net user2@domain1.net *@domain2.net') + ->SetDefaultValue('*') + ); + } +} diff --git a/plugins/postfixadmin-change-password/md5crypt.php b/plugins/postfixadmin-change-password/md5crypt.php new file mode 100644 index 000000000..13f878eca --- /dev/null +++ b/plugins/postfixadmin-change-password/md5crypt.php @@ -0,0 +1,139 @@ + 0; $i -= 16) + { + if ($i > 16) + { + $ctx .= substr($final,0,16); + } + else + { + $ctx .= substr($final,0,$i); + } + } + + $i = strlen($pw); + + while ($i > 0) + { + if ($i & 1) + { + $ctx .= chr(0); + } + else + { + $ctx .= $pw[0]; + } + + $i = $i >> 1; + } + + $final = hex2bin(md5($ctx)); + + for ($i=0; $i<1000; $i++) + { + $ctx1 = ""; + if ($i & 1) + { + $ctx1 .= $pw; + } + else + { + $ctx1 .= substr($final,0,16); + } + if ($i % 3) + { + $ctx1 .= $salt; + } + if ($i % 7) + { + $ctx1 .= $pw; + } + if ($i & 1) + { + $ctx1 .= substr($final, 0, 16); + } + else + { + $ctx1 .= $pw; + } + + $final = hex2bin(md5($ctx1)); + } + + $passwd = ""; + $passwd .= to64(((ord($final[0]) << 16) | (ord($final[6]) << 8) | (ord($final[12]))), 4); + $passwd .= to64(((ord($final[1]) << 16) | (ord($final[7]) << 8) | (ord($final[13]))), 4); + $passwd .= to64(((ord($final[2]) << 16) | (ord($final[8]) << 8) | (ord($final[14]))), 4); + $passwd .= to64(((ord($final[3]) << 16) | (ord($final[9]) << 8) | (ord($final[15]))), 4); + $passwd .= to64(((ord($final[4]) << 16) | (ord($final[10]) << 8) | (ord($final[5]))), 4); + $passwd .= to64(ord($final[11]), 2); + + return $magic.$salt.'$'.$passwd; +} + +function create_salt() +{ + srand((double) microtime() * 1000000); + return substr(md5(rand(0,9999999)), 0, 8); +} + +// PHP around 5.3.8 includes hex2bin as native function - http://php.net/hex2bin +if (!function_exists('hex2bin')) +{ + function hex2bin($str) + { + $len = strlen($str); + $nstr = ""; + for ($i = 0; $i < $len; $i += 2) + { + $num = sscanf(substr($str, $i, 2), "%x"); + $nstr .= chr($num[0]); + } + + return $nstr; + } +} + +function to64($v, $n) +{ + $ITOA64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + $ret = ""; + + while (($n - 1) >= 0) + { + $n--; + $ret .= $ITOA64[$v & 0x3f]; + $v = $v >> 6; + } + + return $ret; +} \ No newline at end of file