From 3522f76d379f8eda9a78ffc912d81ef7ea02b6ea Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Thu, 28 Apr 2022 23:51:41 +0200 Subject: [PATCH] Added Poppassd for #351 --- plugins/change-password/drivers/poppassd.php | 207 +++++++++++++++++++ plugins/change-password/index.php | 4 +- 2 files changed, 209 insertions(+), 2 deletions(-) create mode 100644 plugins/change-password/drivers/poppassd.php diff --git a/plugins/change-password/drivers/poppassd.php b/plugins/change-password/drivers/poppassd.php new file mode 100644 index 000000000..d94951637 --- /dev/null +++ b/plugins/change-password/drivers/poppassd.php @@ -0,0 +1,207 @@ +oConfig = $oConfig; + $this->oLogger = $oLogger; + } + + public static function isSupported() : bool + { + return true; + } + + public static function configMapping() : array + { + return array( + \RainLoop\Plugins\Property::NewInstance('poppassd_host')->SetLabel('POPPASSD Host') + ->SetDefaultValue('127.0.0.1'), + \RainLoop\Plugins\Property::NewInstance('poppassd_port')->SetLabel('POPPASSD Port') + ->SetType(\RainLoop\Enumerations\PluginPropertyType::INT) + ->SetDefaultValue(106), + \RainLoop\Plugins\Property::NewInstance('poppassd_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('*') + ); + } + + public function ChangePassword(\RainLoop\Model\Account $oAccount, string $sPrevPassword, string $sNewPassword) : bool + { + if (!\RainLoop\Plugins\Helper::ValidateWildcardValues($oAccount->Email(), $this->oConfig->Get('plugin', 'poppassd_allowed_emails', ''))) { + return false; + } + + try + { + $oPoppassdClient = new PoppassdClient(); + if ($this->oLogger) { + $oPoppassdClient->SetLogger($this->oLogger); + } + $oPoppassdClient->Connect( + $this->oConfig->Get('plugin', 'poppassd_host', ''), + (int) $this->oConfig->Get('plugin', 'poppassd_port', 106) + ); + $oPoppassdClient->Login($oAccount->Login(), $sPrevPassword) + ->NewPass($sNewPassword) + ->Disconnect() + ; + + return true; + } + catch (\Throwable $oException) + { + } + + return false; + } +} + +class PoppassdClient extends \MailSo\Net\NetClient +{ + private + $bIsLoggined = false, + $iRequestTime = 0; + + public function Connect(string $sServerName, int $iPort, + int $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT, + bool $bVerifySsl = false, bool $bAllowSelfSigned = true, + string $sClientCert = '') : void + { + $this->iRequestTime = \microtime(true); + parent::Connect($sServerName, $iPort, $iSecurityType, $bVerifySsl, $bAllowSelfSigned, $sClientCert); + $this->validateResponse(); + } + + public function Login(string $sLogin, string $sPassword) : self + { + if ($this->bIsLoggined) { + $this->writeLogException( + new \RuntimeException('Already authenticated for this session'), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + $sLogin = \trim($sLogin); + $sPassword = $sPassword; + + try + { + $this->sendRequestWithCheck('user', $sLogin, true); + $this->sendRequestWithCheck('pass', $sPassword, true); + } + catch (\Throwable $oException) + { + $this->writeLogException($oException, \MailSo\Log\Enumerations\Type::NOTICE, true); + } + + $this->bIsLoggined = true; + + return $this; + } + + public function Logout() : void + { + if ($this->bIsLoggined) { + $this->sendRequestWithCheck('quit'); + } + $this->bIsLoggined = false; + } + + public function NewPass(string $sNewPassword) : self + { + if ($this->bIsLoggined) { + $this->sendRequestWithCheck('newpass', $sNewPassword); + } else { + $this->writeLogException( + new \RuntimeException('Required login'), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + return $this; + } + + private function secureRequestParams($sCommand, $sAddToCommand) : ?string + { + if (\strlen($sAddToCommand)) { + switch (\strtolower($sCommand)) + { + case 'pass': + case 'newpass': + return '********'; + } + } + + return null; + } + + private function sendRequest(string $sCommand, string $sAddToCommand = '') : self + { + if (!\strlen(\trim($sCommand))) { + $this->writeLogException( + new \MailSo\Base\Exceptions\InvalidArgumentException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + $this->IsConnected(true); + + $sCommand = \trim($sCommand); + $sRealCommand = $sCommand . (\strlen($sAddToCommand) ? ' '.$sAddToCommand : ''); + + $sFakeCommand = ''; + $sFakeAddToCommand = $this->secureRequestParams($sCommand, $sAddToCommand); + if (\strlen($sFakeAddToCommand)) { + $sFakeCommand = $sCommand . ' ' . $sFakeAddToCommand; + } + + $this->iRequestTime = \microtime(true); + $this->sendRaw($sRealCommand, true, $sFakeCommand); + + return $this; + } + + private function sendRequestWithCheck(string $sCommand, string $sAddToCommand = '', bool $bAuthRequestValidate = false) : self + { + $this->sendRequest($sCommand, $sAddToCommand); + $this->validateResponse($bAuthRequestValidate); + + return $this; + } + + private function validateResponse(bool $bAuthRequestValidate = false) : self + { + $this->getNextBuffer(); + + $bResult = \preg_match($bAuthRequestValidate ? '/^[23]\d\d/' : '/^2\d\d/', trim($this->sResponseBuffer)); + + if (!$bResult) { + // POP3 validation hack + $bResult = '+OK ' === \substr(\trim($this->sResponseBuffer), 0, 4); + } + + if (!$bResult) { + $this->writeLogException( + new \MailSo\Base\Exceptions\Exception(), + \MailSo\Log\Enumerations\Type::WARNING, true); + } + + $this->writeLog((\microtime(true) - $this->iRequestTime), + \MailSo\Log\Enumerations\Type::TIME); + + return $this; + } + + function getLogName() : string + { + return 'POPPASSD'; + } +} diff --git a/plugins/change-password/index.php b/plugins/change-password/index.php index 4fc0eb724..9f38fdd3f 100644 --- a/plugins/change-password/index.php +++ b/plugins/change-password/index.php @@ -6,8 +6,8 @@ class ChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin { const NAME = 'Change Password', - VERSION = '2.13.1', - RELEASE = '2022-03-10', + VERSION = '2.13.2', + RELEASE = '2022-04-28', REQUIRED = '2.12.0', CATEGORY = 'Security', DESCRIPTION = 'Extension to allow users to change their passwords';