From fb32a710da86e8d4f55ac0fab250e127ce9d319e Mon Sep 17 00:00:00 2001 From: Hardware Date: Sat, 3 Feb 2018 21:34:59 +0100 Subject: [PATCH] feat(): add postfixadmin-change-password plugin #3 --- README.md | 1 + rootfs/usr/local/bin/run.sh | 8 + .../ChangePasswordPostfixAdminDriver.php | 301 ++++++++++++++++++ .../postfixadmin-change-password/index.php | 95 ++++++ 4 files changed, 405 insertions(+) create mode 100644 rootfs/usr/local/include/postfixadmin-change-password/ChangePasswordPostfixAdminDriver.php create mode 100644 rootfs/usr/local/include/postfixadmin-change-password/index.php diff --git a/README.md b/README.md index 86ae48b..02131bb 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Rainloop is a simple, modern & fast web-based client. More details on the [offic - Latest Rainloop **Community Edition** (stable) - Contacts (DB) : sqlite, or mysql (server not built-in) - With Nginx and PHP7 +- Postfixadmin-change-password plugin ### Build-time variables diff --git a/rootfs/usr/local/bin/run.sh b/rootfs/usr/local/bin/run.sh index 765d1b6..35fdbd4 100644 --- a/rootfs/usr/local/bin/run.sh +++ b/rootfs/usr/local/bin/run.sh @@ -3,6 +3,14 @@ # Set attachment size limit sed -i "s//$UPLOAD_MAX_SIZE/g" /etc/php7/php-fpm.conf /etc/nginx/nginx.conf +# Remove postfixadmin-change-password plugin if exist +if [ -d "/rainloop/data/_data_/_default_/plugins/postfixadmin-change-password" ]; then + rm -rf /rainloop/data/_data_/_default_/plugins/postfixadmin-change-password +fi + +# Add postfixadmin-change-password plugin +cp -r /usr/local/include/postfixadmin-change-password /rainloop/data/_data_/_default_/plugins/ + # Fix permissions chown -R $UID:$GID /rainloop/data /services /var/log /var/lib/nginx diff --git a/rootfs/usr/local/include/postfixadmin-change-password/ChangePasswordPostfixAdminDriver.php b/rootfs/usr/local/include/postfixadmin-change-password/ChangePasswordPostfixAdminDriver.php new file mode 100644 index 0000000..bf4249a --- /dev/null +++ b/rootfs/usr/local/include/postfixadmin-change-password/ChangePasswordPostfixAdminDriver.php @@ -0,0 +1,301 @@ +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 $sTable + * + * @return \ChangePasswordPostfixAdminDriver + */ + public function SetTable($sTable) + { + $this->sTable = $sTable; + return $this; + } + + /** + * @param string $sUsercol + * + * @return \ChangePasswordPostfixAdminDriver + */ + public function SetUserColumn($sUsercol) + { + $this->sUsercol = $sUsercol; + return $this; + } + + /** + * @param string $sPasscol + * + * @return \ChangePasswordPostfixAdminDriver + */ + public function SetPasswordColumn($sPasscol) + { + $this->sPasscol = $sPasscol; + 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\Model\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) + { + if ($this->oLogger) + { + $this->oLogger->Write('Postfix: Try to change password for '.$oAccount->Email()); + } + + unset($sPrevPassword); + + $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 {$this->sTable} SET {$this->sPasscol} = ? WHERE {$this->sUsercol} = ?"); + $bResult = (bool) $oStmt->execute(array($sUpdatePassword, $oAccount->Email())); + } + else + { + if ($this->oLogger) + { + $this->oLogger->Write('Postfix: Encrypted password is empty', + \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 = ''; + $sSalt = substr(str_shuffle('./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'), 0, 16); + switch (strtolower($this->sEncrypt)) + { + default: + case 'plain': + case 'cleartext': + $sResult = '{PLAIN}' . $sPassword; + break; + + case 'md5': + $sResult = '{PLAIN-MD5}' . md5($sPassword); + break; + + case 'system': + $sResult = '{CRYPT}' . crypt($sPassword); + break; + + case 'sha256-crypt': + $sResult = '{SHA256-CRYPT}' . crypt($sPassword,'$5$'.$sSalt); + break; + + case 'sha512-crypt': + $sResult = '{SHA512-CRYPT}' . crypt($sPassword,'$6$'.$sSalt); + 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/rootfs/usr/local/include/postfixadmin-change-password/index.php b/rootfs/usr/local/include/postfixadmin-change-password/index.php new file mode 100644 index 0000000..aec05f4 --- /dev/null +++ b/rootfs/usr/local/include/postfixadmin-change-password/index.php @@ -0,0 +1,95 @@ +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', '')) + ->SetTable($this->Config()->Get('plugin', 'table', '')) + ->SetUserColumn($this->Config()->Get('plugin', 'usercol', '')) + ->SetPasswordColumn($this->Config()->Get('plugin', 'passcol', '')) + ->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('mariadb'), + \RainLoop\Plugins\Property::NewInstance('port')->SetLabel('MySQL Port') + ->SetType(\RainLoop\Enumerations\PluginPropertyType::INT) + ->SetDefaultValue(3306), + \RainLoop\Plugins\Property::NewInstance('database')->SetLabel('MySQL Database') + ->SetDefaultValue('postfix'), + \RainLoop\Plugins\Property::NewInstance('table')->SetLabel('MySQL table') + ->SetDefaultValue('mailbox'), + \RainLoop\Plugins\Property::NewInstance('usercol')->SetLabel('MySQL username column') + ->SetDefaultValue('username'), + \RainLoop\Plugins\Property::NewInstance('passcol')->SetLabel('MySQL password column') + ->SetDefaultValue('password'), + \RainLoop\Plugins\Property::NewInstance('user')->SetLabel('MySQL User') + ->SetDefaultValue('postfix'), + \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('SHA512-CRYPT','SHA256-CRYPT','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('*') + ); + } +}