diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Sieve/ManageSieveClient.php b/rainloop/v/0.0.0/app/libraries/MailSo/Sieve/ManageSieveClient.php index 92f889e9a..cd4e17982 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Sieve/ManageSieveClient.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Sieve/ManageSieveClient.php @@ -1,638 +1,653 @@ -bIsLoggined = false; - $this->iRequestTime = 0; - $this->aCapa = array(); - $this->aModules = array(); - } - - /** - * @return \MailSo\Sieve\ManageSieveClient - */ - public static function NewInstance() - { - return new self(); - } - - /** - * @param string $sCapa - * - * @return bool - */ - public function IsSupported($sCapa) - { - return isset($this->aCapa[\strtoupper($sCapa)]); - } - - /** - * @param string $sModule - * - * @return bool - */ - public function IsModuleSupported($sModule) - { - return $this->IsSupported('SIEVE') && \in_array(\strtolower(\trim($sModule)), $this->aModules); - } - - /** - * @return array - */ - public function Modules() - { - return $this->aModules; - } - - /** - * @param string $sAuth - * - * @return bool - */ - public function IsAuthSupported($sAuth) - { - return $this->IsSupported('SASL') && \in_array(\strtoupper($sAuth), $this->aAuth); - } - - /** - * @param string $sServerName - * @param int $iPort - * @param int $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT - * @param bool $bVerifySsl = false - * @param bool $bAllowSelfSigned = true - * - * @return \MailSo\Sieve\ManageSieveClient - * - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - * @throws \MailSo\Sieve\Exceptions\ResponseException - */ - public function Connect($sServerName, $iPort, - $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT, - $bVerifySsl = false, $bAllowSelfSigned = true) - { - $this->iRequestTime = \microtime(true); - - parent::Connect($sServerName, $iPort, $iSecurityType, $bVerifySsl, $bAllowSelfSigned); - - $mResponse = $this->parseResponse(); - $this->validateResponse($mResponse); - $this->parseStartupResponse($mResponse); - - if (\MailSo\Net\Enumerations\ConnectionSecurityType::UseStartTLS( - $this->IsSupported('STARTTLS'), $this->iSecurityType)) - { - $this->sendRequestWithCheck('STARTTLS'); - $this->EnableCrypto(); - - $mResponse = $this->parseResponse(); - $this->validateResponse($mResponse); - $this->parseStartupResponse($mResponse); - } - else if (\MailSo\Net\Enumerations\ConnectionSecurityType::STARTTLS === $this->iSecurityType) - { - $this->writeLogException( - new \MailSo\Net\Exceptions\SocketUnsuppoterdSecureConnectionException('STARTTLS is not supported'), - \MailSo\Log\Enumerations\Type::ERROR, true); - } - - return $this; - } - - /** - * @param string $sLogin - * @param string $sPassword - * @param string $sLoginAuthKey = '' - * - * @return \MailSo\Sieve\ManageSieveClient - * - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - * @throws \MailSo\Sieve\Exceptions\LoginException - */ - public function Login($sLogin, $sPassword, $sLoginAuthKey = '') - { - if (!\MailSo\Base\Validator::NotEmptyString($sLogin, true) || - !\MailSo\Base\Validator::NotEmptyString($sPassword, true)) - { - $this->writeLogException( - new \MailSo\Base\Exceptions\InvalidArgumentException(), - \MailSo\Log\Enumerations\Type::ERROR, true); - } - - if ($this->IsSupported('SASL')) - { - $bAuth = false; - try - { - if ($this->IsAuthSupported('PLAIN')) - { - $sAuth = \base64_encode($sLoginAuthKey."\0".$sLogin."\0".$sPassword); - - $this->sendRequest('AUTHENTICATE "PLAIN" "'.$sAuth.'"'); - - $mResponse = $this->parseResponse(); - $this->validateResponse($mResponse); - $this->parseStartupResponse($mResponse); - $bAuth = true; - } - else if ($this->IsAuthSupported('LOGIN')) - { - $sLogin = \base64_encode($sLogin); - $sPassword = \base64_encode($sPassword); - - $this->sendRequest('AUTHENTICATE "LOGIN"'); - $this->sendRequest('{'.\strlen($sLogin).'+}'); - $this->sendRequest($sLogin); - $this->sendRequest('{'.\strlen($sPassword).'+}'); - $this->sendRequest($sPassword); - - $mResponse = $this->parseResponse(); - $this->validateResponse($mResponse); - $this->parseStartupResponse($mResponse); - $bAuth = true; - } - } - catch (\MailSo\Sieve\Exceptions\NegativeResponseException $oException) - { - $this->writeLogException( - new \MailSo\Sieve\Exceptions\LoginBadCredentialsException( - $oException->GetResponses(), '', 0, $oException), - \MailSo\Log\Enumerations\Type::ERROR, true); - } - - if (!$bAuth) - { - $this->writeLogException( - new \MailSo\Sieve\Exceptions\LoginBadMethodException(), - \MailSo\Log\Enumerations\Type::ERROR, true); - } - } - else - { - $this->writeLogException( - new \MailSo\Sieve\Exceptions\LoginException(), - \MailSo\Log\Enumerations\Type::ERROR, true); - } - - $this->bIsLoggined = true; - - return $this; - } - - /** - * @return \MailSo\Sieve\ManageSieveClient - * - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Sieve\Exceptions\NegativeResponseException - */ - public function Logout() - { - if ($this->bIsLoggined) - { - $this->sendRequestWithCheck('LOGOUT'); - $this->bIsLoggined = false; - } - - return $this; - } - - /** - * @return array - * - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Sieve\Exceptions\NegativeResponseException - */ - public function ListScripts() - { - $this->sendRequest('LISTSCRIPTS'); - $mResponse = $this->parseResponse(); - $this->validateResponse($mResponse); - - $aResult = array(); - if (\is_array($mResponse)) - { - foreach ($mResponse as $sLine) - { - $aTokens = $this->parseLine($sLine); - if (false === $aTokens) - { - continue; - } - - $aResult[$aTokens[0]] = 'ACTIVE' === substr($sLine, -6); - } - } - - return $aResult; - } - - /** - * @return array - * - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Sieve\Exceptions\NegativeResponseException - */ - public function Capability() - { - $this->sendRequest('CAPABILITY'); - $mResponse = $this->parseResponse(); - $this->validateResponse($mResponse); - $this->parseStartupResponse($mResponse); - - return $this->aCapa; - } - - /** - * @return \MailSo\Sieve\ManageSieveClient - * - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Sieve\Exceptions\NegativeResponseException - */ - public function Noop() - { - $this->sendRequestWithCheck('NOOP'); - - return $this; - } - - /** - * @param string $sScriptName - * - * @return string - * - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Sieve\Exceptions\NegativeResponseException - */ - public function GetScript($sScriptName) - { - $this->sendRequest('GETSCRIPT "'.$sScriptName.'"'); - $mResponse = $this->parseResponse(); - $this->validateResponse($mResponse); - - $sScript = ''; - if (\is_array($mResponse) && 0 < \count($mResponse)) - { - if ('{' === $mResponse[0]{0}) - { - \array_shift($mResponse); - } - - if (\in_array(\substr($mResponse[\count($mResponse) - 1], 0, 2), array('OK', 'NO'))) - { - \array_pop($mResponse); - } - - $sScript = \implode("\n", $mResponse); - } - - return $sScript; - } - - /** - * @param string $sScriptName - * @param string $sScriptSource - * - * @return \MailSo\Sieve\ManageSieveClient - * - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Sieve\Exceptions\NegativeResponseException - */ - public function PutScript($sScriptName, $sScriptSource) - { - $this->sendRequest('PUTSCRIPT "'.$sScriptName.'" {'.\strlen($sScriptSource).'+}'); - $this->sendRequestWithCheck($sScriptSource); - - return $this; - } - - /** - * @param string $sScriptSource - * - * @return \MailSo\Sieve\ManageSieveClient - * - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Sieve\Exceptions\NegativeResponseException - */ - public function CheckScript($sScriptSource) - { - $this->sendRequest('CHECKSCRIPT {'.\strlen($sScriptSource).'+}'); - $this->sendRequestWithCheck($sScriptSource); - - return $this; - } - - /** - * @param string $sScriptName - * - * @return \MailSo\Sieve\ManageSieveClient - * - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Sieve\Exceptions\NegativeResponseException - */ - public function SetActiveScript($sScriptName) - { - $this->sendRequestWithCheck('SETACTIVE "'.$sScriptName.'"'); - - return $this; - } - - /** - * @param string $sScriptName - * - * @return \MailSo\Sieve\ManageSieveClient - * - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Sieve\Exceptions\NegativeResponseException - */ - public function DeleteScript($sScriptName) - { - $this->sendRequestWithCheck('DELETESCRIPT "'.$sScriptName.'"'); - - return $this; - } - - /** - * @return string - * - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Sieve\Exceptions\NegativeResponseException - */ - public function GetActiveScriptName() - { - $aList = $this->ListScripts(); - if (\is_array($aList) && 0 < \count($aList)) - { - foreach ($aList as $sName => $bIsActive) - { - if ($bIsActive) - { - return $sName; - } - } - } - - return ''; - } - - /** - * @param string $sScriptName - * - * @return bool - * - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Sieve\Exceptions\NegativeResponseException - */ - public function IsActiveScript($sScriptName) - { - return $sScriptName === $this->GetActiveScriptName(); - } - - /** - * @param string $sLine - * @return array|false - */ - private function parseLine($sLine) - { - if (false === $sLine || null === $sLine || \in_array(\substr($sLine, 0, 2), array('OK', 'NO'))) - { - return false; - } - - $iStart = -1; - $iIndex = 0; - $aResult = false; - - for ($iPos = 0; $iPos < \strlen($sLine); $iPos++) - { - if ('"' === $sLine[$iPos] && '\\' !== $sLine[$iPos]) - { - if (-1 === $iStart) - { - $iStart = $iPos; - } - else - { - $aResult = \is_array($aResult) ? $aResult : array(); - $aResult[$iIndex++] = \substr($sLine, $iStart + 1, $iPos - $iStart - 1); - $iStart = -1; - } - } - } - - return \is_array($aResult) && isset($aResult[0]) ? $aResult : false; - } - - /** - * @param string $mResponse - * - * @return void - * - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - * @throws \MailSo\Net\Exceptions\Exception - */ - private function parseStartupResponse($mResponse) - { - foreach ($mResponse as $sLine) - { - $aTokens = $this->parseLine($sLine); - - if (false === $aTokens || !isset($aTokens[0]) || - \in_array(\substr($sLine, 0, 2), array('OK', 'NO'))) - { - continue; - } - - $sToken = \strtoupper($aTokens[0]); - $this->aCapa[$sToken] = isset($aTokens[1]) ? $aTokens[1] : ''; - - if (isset($aTokens[1])) - { - switch ($sToken) { - case 'SASL': - $this->aAuth = \explode(' ', \strtoupper($aTokens[1])); - break; - case 'SIEVE': - $this->aModules = \explode(' ', \strtolower($aTokens[1])); - break; - } - } - } - } - - /** - * @param string $sRequest - * - * @return void - * - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - * @throws \MailSo\Net\Exceptions\Exception - */ - private function sendRequest($sRequest) - { - if (!\MailSo\Base\Validator::NotEmptyString($sRequest, true)) - { - $this->writeLogException( - new \MailSo\Base\Exceptions\InvalidArgumentException(), - \MailSo\Log\Enumerations\Type::ERROR, true); - } - - $this->IsConnected(true); - - $this->sendRaw($sRequest); - } - - /** - * @param string $sRequest - * - * @return void - * - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Sieve\Exceptions\NegativeResponseException - */ - private function sendRequestWithCheck($sRequest) - { - $this->sendRequest($sRequest); - $this->validateResponse($this->parseResponse()); - } - - /** - * @param string $sLine - * - * @return string - */ - private function convertEndOfLine($sLine) - { - $sLine = \trim($sLine); - if ('}' === \substr($sLine, -1)) - { - $iPos = \strrpos($sLine, '{'); - if (false !== $iPos) - { - $sSunLine = \substr($sLine, $iPos + 1, -1); - if (\is_numeric($sSunLine) && 0 < (int) $sSunLine) - { - $iLen = (int) $sSunLine; - - $this->getNextBuffer($iLen, true); - - if (\strlen($this->sResponseBuffer) === $iLen) - { - $sLine = \trim(\substr_replace($sLine, $this->sResponseBuffer, $iPos)); - } - } - } - } - - return $sLine; - } - - /** - * @return array|bool - */ - private function parseResponse() - { - $this->iRequestTime = \microtime(true); - - $aResult = array(); - do - { - $this->getNextBuffer(); - - $sLine = $this->sResponseBuffer; - if (false === $sLine) - { - break; - } - else if (\in_array(\substr($sLine, 0, 2), array('OK', 'NO'))) - { - $aResult[] = $this->convertEndOfLine($sLine); - break; - } - else - { - $aResult[] = $this->convertEndOfLine($sLine); - } - } - while (true); - - $this->writeLog((\microtime(true) - $this->iRequestTime), - \MailSo\Log\Enumerations\Type::TIME); - - return $aResult; - } - - /** - * @throws \MailSo\Sieve\Exceptions\NegativeResponseException - */ - private function validateResponse($aResponse) - { - if (!\is_array($aResponse) || 0 === \count($aResponse) || - 'OK' !== \substr($aResponse[\count($aResponse) - 1], 0, 2)) - { - $this->writeLogException( - new \MailSo\Sieve\Exceptions\NegativeResponseException($aResponse), - \MailSo\Log\Enumerations\Type::WARNING, true); - } - } - - /** - * @return string - */ - protected function getLogName() - { - return 'SIEVE'; - } - - /** - * @param \MailSo\Log\Logger $oLogger - * - * @return \MailSo\Sieve\ManageSieveClient - * - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - */ - public function SetLogger($oLogger) - { - parent::SetLogger($oLogger); - - return $this; - } -} +bIsLoggined = false; + $this->iRequestTime = 0; + $this->aCapa = array(); + $this->aModules = array(); + + $this->__USE_INITIAL_AUTH_PLAIN_COMMAND = true; + } + + /** + * @return \MailSo\Sieve\ManageSieveClient + */ + public static function NewInstance() + { + return new self(); + } + + /** + * @param string $sCapa + * + * @return bool + */ + public function IsSupported($sCapa) + { + return isset($this->aCapa[\strtoupper($sCapa)]); + } + + /** + * @param string $sModule + * + * @return bool + */ + public function IsModuleSupported($sModule) + { + return $this->IsSupported('SIEVE') && \in_array(\strtolower(\trim($sModule)), $this->aModules); + } + + /** + * @return array + */ + public function Modules() + { + return $this->aModules; + } + + /** + * @param string $sAuth + * + * @return bool + */ + public function IsAuthSupported($sAuth) + { + return $this->IsSupported('SASL') && \in_array(\strtoupper($sAuth), $this->aAuth); + } + + /** + * @param string $sServerName + * @param int $iPort + * @param int $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT + * @param bool $bVerifySsl = false + * @param bool $bAllowSelfSigned = true + * + * @return \MailSo\Sieve\ManageSieveClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Sieve\Exceptions\ResponseException + */ + public function Connect($sServerName, $iPort, + $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT, + $bVerifySsl = false, $bAllowSelfSigned = true) + { + $this->iRequestTime = \microtime(true); + + parent::Connect($sServerName, $iPort, $iSecurityType, $bVerifySsl, $bAllowSelfSigned); + + $mResponse = $this->parseResponse(); + $this->validateResponse($mResponse); + $this->parseStartupResponse($mResponse); + + if (\MailSo\Net\Enumerations\ConnectionSecurityType::UseStartTLS( + $this->IsSupported('STARTTLS'), $this->iSecurityType)) + { + $this->sendRequestWithCheck('STARTTLS'); + $this->EnableCrypto(); + + $mResponse = $this->parseResponse(); + $this->validateResponse($mResponse); + $this->parseStartupResponse($mResponse); + } + else if (\MailSo\Net\Enumerations\ConnectionSecurityType::STARTTLS === $this->iSecurityType) + { + $this->writeLogException( + new \MailSo\Net\Exceptions\SocketUnsuppoterdSecureConnectionException('STARTTLS is not supported'), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + return $this; + } + + /** + * @param string $sLogin + * @param string $sPassword + * @param string $sLoginAuthKey = '' + * + * @return \MailSo\Sieve\ManageSieveClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Sieve\Exceptions\LoginException + */ + public function Login($sLogin, $sPassword, $sLoginAuthKey = '') + { + if (!\MailSo\Base\Validator::NotEmptyString($sLogin, true) || + !\MailSo\Base\Validator::NotEmptyString($sPassword, true)) + { + $this->writeLogException( + new \MailSo\Base\Exceptions\InvalidArgumentException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + if ($this->IsSupported('SASL')) + { + $bAuth = false; + try + { + if ($this->IsAuthSupported('PLAIN')) + { + $sAuth = \base64_encode($sLoginAuthKey."\0".$sLogin."\0".$sPassword); + + if ($this->__USE_INITIAL_AUTH_PLAIN_COMMAND) + { + $this->sendRequest('AUTHENTICATE "PLAIN" "'.$sAuth.'"'); + } + else + { + $this->sendRequest('AUTHENTICATE "PLAIN" {'.\strlen($sAuth).'+}'); + $this->sendRequest($sAuth); + } + + $mResponse = $this->parseResponse(); + $this->validateResponse($mResponse); + $this->parseStartupResponse($mResponse); + $bAuth = true; + } + else if ($this->IsAuthSupported('LOGIN')) + { + $sLogin = \base64_encode($sLogin); + $sPassword = \base64_encode($sPassword); + + $this->sendRequest('AUTHENTICATE "LOGIN"'); + $this->sendRequest('{'.\strlen($sLogin).'+}'); + $this->sendRequest($sLogin); + $this->sendRequest('{'.\strlen($sPassword).'+}'); + $this->sendRequest($sPassword); + + $mResponse = $this->parseResponse(); + $this->validateResponse($mResponse); + $this->parseStartupResponse($mResponse); + $bAuth = true; + } + } + catch (\MailSo\Sieve\Exceptions\NegativeResponseException $oException) + { + $this->writeLogException( + new \MailSo\Sieve\Exceptions\LoginBadCredentialsException( + $oException->GetResponses(), '', 0, $oException), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + if (!$bAuth) + { + $this->writeLogException( + new \MailSo\Sieve\Exceptions\LoginBadMethodException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + } + else + { + $this->writeLogException( + new \MailSo\Sieve\Exceptions\LoginException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + $this->bIsLoggined = true; + + return $this; + } + + /** + * @return \MailSo\Sieve\ManageSieveClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Sieve\Exceptions\NegativeResponseException + */ + public function Logout() + { + if ($this->bIsLoggined) + { + $this->sendRequestWithCheck('LOGOUT'); + $this->bIsLoggined = false; + } + + return $this; + } + + /** + * @return array + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Sieve\Exceptions\NegativeResponseException + */ + public function ListScripts() + { + $this->sendRequest('LISTSCRIPTS'); + $mResponse = $this->parseResponse(); + $this->validateResponse($mResponse); + + $aResult = array(); + if (\is_array($mResponse)) + { + foreach ($mResponse as $sLine) + { + $aTokens = $this->parseLine($sLine); + if (false === $aTokens) + { + continue; + } + + $aResult[$aTokens[0]] = 'ACTIVE' === substr($sLine, -6); + } + } + + return $aResult; + } + + /** + * @return array + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Sieve\Exceptions\NegativeResponseException + */ + public function Capability() + { + $this->sendRequest('CAPABILITY'); + $mResponse = $this->parseResponse(); + $this->validateResponse($mResponse); + $this->parseStartupResponse($mResponse); + + return $this->aCapa; + } + + /** + * @return \MailSo\Sieve\ManageSieveClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Sieve\Exceptions\NegativeResponseException + */ + public function Noop() + { + $this->sendRequestWithCheck('NOOP'); + + return $this; + } + + /** + * @param string $sScriptName + * + * @return string + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Sieve\Exceptions\NegativeResponseException + */ + public function GetScript($sScriptName) + { + $this->sendRequest('GETSCRIPT "'.$sScriptName.'"'); + $mResponse = $this->parseResponse(); + $this->validateResponse($mResponse); + + $sScript = ''; + if (\is_array($mResponse) && 0 < \count($mResponse)) + { + if ('{' === $mResponse[0]{0}) + { + \array_shift($mResponse); + } + + if (\in_array(\substr($mResponse[\count($mResponse) - 1], 0, 2), array('OK', 'NO'))) + { + \array_pop($mResponse); + } + + $sScript = \implode("\n", $mResponse); + } + + return $sScript; + } + + /** + * @param string $sScriptName + * @param string $sScriptSource + * + * @return \MailSo\Sieve\ManageSieveClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Sieve\Exceptions\NegativeResponseException + */ + public function PutScript($sScriptName, $sScriptSource) + { + $this->sendRequest('PUTSCRIPT "'.$sScriptName.'" {'.\strlen($sScriptSource).'+}'); + $this->sendRequestWithCheck($sScriptSource); + + return $this; + } + + /** + * @param string $sScriptSource + * + * @return \MailSo\Sieve\ManageSieveClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Sieve\Exceptions\NegativeResponseException + */ + public function CheckScript($sScriptSource) + { + $this->sendRequest('CHECKSCRIPT {'.\strlen($sScriptSource).'+}'); + $this->sendRequestWithCheck($sScriptSource); + + return $this; + } + + /** + * @param string $sScriptName + * + * @return \MailSo\Sieve\ManageSieveClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Sieve\Exceptions\NegativeResponseException + */ + public function SetActiveScript($sScriptName) + { + $this->sendRequestWithCheck('SETACTIVE "'.$sScriptName.'"'); + + return $this; + } + + /** + * @param string $sScriptName + * + * @return \MailSo\Sieve\ManageSieveClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Sieve\Exceptions\NegativeResponseException + */ + public function DeleteScript($sScriptName) + { + $this->sendRequestWithCheck('DELETESCRIPT "'.$sScriptName.'"'); + + return $this; + } + + /** + * @return string + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Sieve\Exceptions\NegativeResponseException + */ + public function GetActiveScriptName() + { + $aList = $this->ListScripts(); + if (\is_array($aList) && 0 < \count($aList)) + { + foreach ($aList as $sName => $bIsActive) + { + if ($bIsActive) + { + return $sName; + } + } + } + + return ''; + } + + /** + * @param string $sScriptName + * + * @return bool + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Sieve\Exceptions\NegativeResponseException + */ + public function IsActiveScript($sScriptName) + { + return $sScriptName === $this->GetActiveScriptName(); + } + + /** + * @param string $sLine + * @return array|false + */ + private function parseLine($sLine) + { + if (false === $sLine || null === $sLine || \in_array(\substr($sLine, 0, 2), array('OK', 'NO'))) + { + return false; + } + + $iStart = -1; + $iIndex = 0; + $aResult = false; + + for ($iPos = 0; $iPos < \strlen($sLine); $iPos++) + { + if ('"' === $sLine[$iPos] && '\\' !== $sLine[$iPos]) + { + if (-1 === $iStart) + { + $iStart = $iPos; + } + else + { + $aResult = \is_array($aResult) ? $aResult : array(); + $aResult[$iIndex++] = \substr($sLine, $iStart + 1, $iPos - $iStart - 1); + $iStart = -1; + } + } + } + + return \is_array($aResult) && isset($aResult[0]) ? $aResult : false; + } + + /** + * @param string $mResponse + * + * @return void + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + */ + private function parseStartupResponse($mResponse) + { + foreach ($mResponse as $sLine) + { + $aTokens = $this->parseLine($sLine); + + if (false === $aTokens || !isset($aTokens[0]) || + \in_array(\substr($sLine, 0, 2), array('OK', 'NO'))) + { + continue; + } + + $sToken = \strtoupper($aTokens[0]); + $this->aCapa[$sToken] = isset($aTokens[1]) ? $aTokens[1] : ''; + + if (isset($aTokens[1])) + { + switch ($sToken) { + case 'SASL': + $this->aAuth = \explode(' ', \strtoupper($aTokens[1])); + break; + case 'SIEVE': + $this->aModules = \explode(' ', \strtolower($aTokens[1])); + break; + } + } + } + } + + /** + * @param string $sRequest + * + * @return void + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + */ + private function sendRequest($sRequest) + { + if (!\MailSo\Base\Validator::NotEmptyString($sRequest, true)) + { + $this->writeLogException( + new \MailSo\Base\Exceptions\InvalidArgumentException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + $this->IsConnected(true); + + $this->sendRaw($sRequest); + } + + /** + * @param string $sRequest + * + * @return void + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Sieve\Exceptions\NegativeResponseException + */ + private function sendRequestWithCheck($sRequest) + { + $this->sendRequest($sRequest); + $this->validateResponse($this->parseResponse()); + } + + /** + * @param string $sLine + * + * @return string + */ + private function convertEndOfLine($sLine) + { + $sLine = \trim($sLine); + if ('}' === \substr($sLine, -1)) + { + $iPos = \strrpos($sLine, '{'); + if (false !== $iPos) + { + $sSunLine = \substr($sLine, $iPos + 1, -1); + if (\is_numeric($sSunLine) && 0 < (int) $sSunLine) + { + $iLen = (int) $sSunLine; + + $this->getNextBuffer($iLen, true); + + if (\strlen($this->sResponseBuffer) === $iLen) + { + $sLine = \trim(\substr_replace($sLine, $this->sResponseBuffer, $iPos)); + } + } + } + } + + return $sLine; + } + + /** + * @return array|bool + */ + private function parseResponse() + { + $this->iRequestTime = \microtime(true); + + $aResult = array(); + do + { + $this->getNextBuffer(); + + $sLine = $this->sResponseBuffer; + if (false === $sLine) + { + break; + } + else if (\in_array(\substr($sLine, 0, 2), array('OK', 'NO'))) + { + $aResult[] = $this->convertEndOfLine($sLine); + break; + } + else + { + $aResult[] = $this->convertEndOfLine($sLine); + } + } + while (true); + + $this->writeLog((\microtime(true) - $this->iRequestTime), + \MailSo\Log\Enumerations\Type::TIME); + + return $aResult; + } + + /** + * @throws \MailSo\Sieve\Exceptions\NegativeResponseException + */ + private function validateResponse($aResponse) + { + if (!\is_array($aResponse) || 0 === \count($aResponse) || + 'OK' !== \substr($aResponse[\count($aResponse) - 1], 0, 2)) + { + $this->writeLogException( + new \MailSo\Sieve\Exceptions\NegativeResponseException($aResponse), + \MailSo\Log\Enumerations\Type::WARNING, true); + } + } + + /** + * @return string + */ + protected function getLogName() + { + return 'SIEVE'; + } + + /** + * @param \MailSo\Log\Logger $oLogger + * + * @return \MailSo\Sieve\ManageSieveClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + */ + public function SetLogger($oLogger) + { + parent::SetLogger($oLogger); + + return $this; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php index c292520bd..7e3823125 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php @@ -4210,6 +4210,7 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack { $oSieveClient = \MailSo\Sieve\ManageSieveClient::NewInstance()->SetLogger($this->Logger()); $oSieveClient->SetTimeOuts($iConnectionTimeout); + $oSieveClient->__USE_INITIAL_AUTH_PLAIN_COMMAND = !!$this->Config()->Get('labs', 'sieve_auth_plain_initial', true); $iTime = \microtime(true); $oSieveClient->Connect($oDomain->SieveHost(), $oDomain->SievePort(), $oDomain->SieveSecure(), diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php index b3f8b3f63..efbd86179 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php @@ -421,6 +421,7 @@ Enables caching in the system'), 'smtp_use_auth_cram_md5' => array(false), 'sieve_allow_raw_script' => array(false), 'sieve_utf8_folder_name' => array(true), + 'sieve_auth_plain_initial' => array(true), 'imap_timeout' => array(300), 'smtp_timeout' => array(60), 'sieve_timeout' => array(10), diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Model/Account.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Model/Account.php index 7c30a4724..fd3bbd9eb 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Model/Account.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Model/Account.php @@ -1,579 +1,585 @@ -sEmail = \MailSo\Base\Utils::IdnToAscii($sEmail, true); - $this->sLogin = \MailSo\Base\Utils::IdnToAscii($sLogin); - $this->sPassword = $sPassword; - $this->oDomain = $oDomain; - $this->sSignMeToken = $sSignMeToken; - $this->sProxyAuthUser = $sProxyAuthUser; - $this->sProxyAuthPassword = $sProxyAuthPassword; - $this->sParentEmail = ''; - } - - /** - * @param string $sEmail - * @param string $sLogin - * @param string $sPassword - * @param \RainLoop\Model\Domain $oDomain - * @param string $sSignMeToken = '' - * @param string $sProxyAuthUser = '' - * @param string $sProxyAuthPassword = '' - * - * @return \RainLoop\Model\Account - */ - public static function NewInstance($sEmail, $sLogin, $sPassword, \RainLoop\Model\Domain $oDomain, - $sSignMeToken = '', $sProxyAuthUser = '', $sProxyAuthPassword = '') - { - return new self($sEmail, $sLogin, $sPassword, $oDomain, $sSignMeToken, $sProxyAuthUser, $sProxyAuthPassword); - } - - /** - * @return string - */ - public function Email() - { - return $this->sEmail; - } - - /** - * @return string - */ - public function ParentEmail() - { - return $this->sParentEmail; - } - - /** - * @return string - */ - public function ProxyAuthUser() - { - return $this->sProxyAuthUser; - } - - /** - * @return string - */ - public function ProxyAuthPassword() - { - return $this->sProxyAuthPassword; - } - - /** - * @return string - */ - public function ParentEmailHelper() - { - return 0 < \strlen($this->sParentEmail) ? $this->sParentEmail : $this->sEmail; - } - - /** - * @return string - */ - public function IsAdditionalAccount() - { - return 0 < \strlen($this->sParentEmail); - } - - /** - * @return string - */ - public function IncLogin() - { - $sLogin = $this->sLogin; - if ($this->oDomain->IncShortLogin()) - { - $sLogin = \MailSo\Base\Utils::GetAccountNameFromEmail($this->sLogin); - } - - return $sLogin; - } - - /** - * @return string - */ - public function IncPassword() - { - return $this->sPassword; - } - - /** - * @return string - */ - public function OutLogin() - { - $sLogin = $this->sLogin; - if ($this->oDomain->OutShortLogin()) - { - $sLogin = \MailSo\Base\Utils::GetAccountNameFromEmail($this->sLogin); - } - - return $sLogin; - } - - /** - * @return string - */ - public function Login() - { - return $this->IncLogin(); - } - - /** - * @return string - */ - public function Password() - { - return $this->IncPassword(); - } - - /** - * @return bool - */ - public function SignMe() - { - return 0 < \strlen($this->sSignMeToken); - } - - /** - * @return string - */ - public function SignMeToken() - { - return $this->sSignMeToken; - } - - /** - * @return \RainLoop\Model\Domain - */ - public function Domain() - { - return $this->oDomain; - } - - /** - * @return string - */ - public function Hash() - { - return md5(APP_SALT.$this->Email().APP_SALT.$this->DomainIncHost(). - APP_SALT.$this->DomainIncPort().APP_SALT.$this->Password().APP_SALT.'0'.APP_SALT.$this->ParentEmail().APP_SALT); - } - - /** - * @param string $sPassword - * - * @return void - */ - public function SetPassword($sPassword) - { - $this->sPassword = $sPassword; - } - - /** - * @param string $sParentEmail - * - * @return void - */ - public function SetParentEmail($sParentEmail) - { - $this->sParentEmail = \trim(\MailSo\Base\Utils::IdnToAscii($sParentEmail, true)); - } - - /** - * @param string $sProxyAuthUser - * - * @return void - */ - public function SetProxyAuthUser($sProxyAuthUser) - { - return $this->sProxyAuthUser = $sProxyAuthUser; - } - - /** - * @param string $sProxyAuthPassword - * - * @return void - */ - public function SetProxyAuthPassword($sProxyAuthPassword) - { - return $this->sProxyAuthPassword = $sProxyAuthPassword; - } - - /** - * @return string - */ - public function DomainIncHost() - { - return $this->Domain()->IncHost(); - } - - /** - * @return int - */ - public function DomainIncPort() - { - return $this->Domain()->IncPort(); - } - - /** - * @return int - */ - public function DomainIncSecure() - { - return $this->Domain()->IncSecure(); - } - - /** - * @return string - */ - public function DomainOutHost() - { - return $this->Domain()->OutHost(); - } - - /** - * @return int - */ - public function DomainOutPort() - { - return $this->Domain()->OutPort(); - } - - /** - * @return int - */ - public function DomainOutSecure() - { - return $this->Domain()->OutSecure(); - } - - /** - * @return bool - */ - public function DomainOutAuth() - { - return $this->Domain()->OutAuth(); - } - - /** - * @return string - */ - public function DomainSieveHost() - { - return $this->Domain()->SieveHost(); - } - - /** - * @return int - */ - public function DomainSievePort() - { - return $this->Domain()->SievePort(); - } - - /** - * @return int - */ - public function DomainSieveSecure() - { - return $this->Domain()->SieveSecure(); - } - - /** - * @return bool - */ - public function DomainSieveAllowRaw() - { - return $this->Domain()->SieveAllowRaw(); - } - - /** - * @return string - */ - public function GetAuthToken() - { - return \RainLoop\Utils::EncodeKeyValues(array( - 'token', // 0 - $this->sEmail, // 1 - $this->sLogin, // 2 - $this->sPassword, // 3 - \RainLoop\Utils::Fingerprint(), // 4 - $this->sSignMeToken, // 5 - $this->sParentEmail, // 6 - \RainLoop\Utils::GetShortToken(), // 7 - $this->sProxyAuthUser, // 8 - $this->sProxyAuthPassword, // 9 - 0 // 10 // timelife - )); - } - - /** - * @return string - */ - public function GetAuthTokenQ() - { - return \RainLoop\Utils::EncodeKeyValuesQ(array( - 'token', // 0 - $this->sEmail, // 1 - $this->sLogin, // 2 - $this->sPassword, // 3 - \RainLoop\Utils::Fingerprint(), // 4 - $this->sSignMeToken, // 5 - $this->sParentEmail, // 6 - \RainLoop\Utils::GetShortToken(), // 7 - $this->sProxyAuthUser, // 8 - $this->sProxyAuthPassword, // 9 - 0 // 10 // timelife - )); - } - - /** - * @param \RainLoop\Plugins\Manager $oPlugins - * @param \MailSo\Mail\MailClient $oMailClient - * @param \RainLoop\Application $oConfig - * - * @return bool - */ - public function IncConnectAndLoginHelper($oPlugins, $oMailClient, $oConfig) - { - $bLogin = false; - - $aImapCredentials = array( - 'UseConnect' => true, - 'UseAuth' => true, - 'Host' => $this->DomainIncHost(), - 'Port' => $this->DomainIncPort(), - 'Secure' => $this->DomainIncSecure(), - 'Login' => $this->IncLogin(), - 'Password' => $this->Password(), - 'ProxyAuthUser' => $this->ProxyAuthUser(), - 'ProxyAuthPassword' => $this->ProxyAuthPassword(), - 'VerifySsl' => !!$oConfig->Get('ssl', 'verify_certificate', false), - 'AllowSelfSigned' => !!$oConfig->Get('ssl', 'allow_self_signed', true), - 'UseAuthPlainIfSupported' => !!$oConfig->Get('labs', 'imap_use_auth_plain', true), - 'UseAuthCramMd5IfSupported' => !!$oConfig->Get('labs', 'imap_use_auth_cram_md5', true) - ); - - $oPlugins->RunHook('filter.imap-credentials', array($this, &$aImapCredentials)); - - $oPlugins->RunHook('event.imap-pre-connect', array($this, $aImapCredentials['UseConnect'], $aImapCredentials)); - - if ($aImapCredentials['UseConnect']) - { - $oMailClient - ->Connect($aImapCredentials['Host'], $aImapCredentials['Port'], - $aImapCredentials['Secure'], $aImapCredentials['VerifySsl'], $aImapCredentials['AllowSelfSigned']); - - } - - $oPlugins->RunHook('event.imap-pre-login', array($this, $aImapCredentials['UseAuth'], $aImapCredentials)); - - if ($aImapCredentials['UseAuth']) - { - if (0 < \strlen($aImapCredentials['ProxyAuthUser']) && - 0 < \strlen($aImapCredentials['ProxyAuthPassword'])) - { - $oMailClient - ->Login($aImapCredentials['ProxyAuthUser'], $aImapCredentials['ProxyAuthPassword'], - $aImapCredentials['Login'], $aImapCredentials['UseAuthPlainIfSupported'], $aImapCredentials['UseAuthCramMd5IfSupported']); - } - else - { - $iGatLen = \strlen(APP_GOOGLE_ACCESS_TOKEN_PREFIX); - $sPassword = $aImapCredentials['Password']; - if (APP_GOOGLE_ACCESS_TOKEN_PREFIX === \substr($sPassword, 0, $iGatLen)) - { - $oMailClient->LoginWithXOauth2( - \base64_encode('user='.$aImapCredentials['Login']."\1".'auth=Bearer '.\substr($sPassword, $iGatLen)."\1\1")); - } - else - { - $oMailClient->Login($aImapCredentials['Login'], $aImapCredentials['Password'], '', - $aImapCredentials['UseAuthPlainIfSupported'], $aImapCredentials['UseAuthCramMd5IfSupported']); - } - } - - $bLogin = true; - } - - $oPlugins->RunHook('event.imap-post-login', array($this, $aImapCredentials['UseAuth'], $bLogin, $aImapCredentials)); - - return $bLogin; - } - - /** - * @param \RainLoop\Plugins\Manager $oPlugins - * @param \MailSo\Smtp\SmtpClient|null $oSmtpClient - * @param \RainLoop\Application $oConfig - * @param bool $bUsePhpMail = false - * - * @return bool - */ - public function OutConnectAndLoginHelper($oPlugins, $oSmtpClient, $oConfig, &$bUsePhpMail = false) - { - $bLogin = false; - - $aSmtpCredentials = array( - 'UseConnect' => true, - 'UseAuth' => $this->DomainOutAuth(), - 'UsePhpMail' => $bUsePhpMail, - 'Ehlo' => \MailSo\Smtp\SmtpClient::EhloHelper(), - 'Host' => $this->DomainOutHost(), - 'Port' => $this->DomainOutPort(), - 'Secure' => $this->DomainOutSecure(), - 'Login' => $this->OutLogin(), - 'Password' => $this->Password(), - 'ProxyAuthUser' => $this->ProxyAuthUser(), - 'ProxyAuthPassword' => $this->ProxyAuthPassword(), - 'VerifySsl' => !!$oConfig->Get('ssl', 'verify_certificate', false), - 'AllowSelfSigned' => !!$oConfig->Get('ssl', 'allow_self_signed', true), - 'UseAuthPlainIfSupported' => !!$oConfig->Get('labs', 'smtp_use_auth_plain', true), - 'UseAuthCramMd5IfSupported' => !!$oConfig->Get('labs', 'smtp_use_auth_cram_md5', true) - ); - - $oPlugins->RunHook('filter.smtp-credentials', array($this, &$aSmtpCredentials)); - - $bUsePhpMail = $aSmtpCredentials['UsePhpMail']; - - $oPlugins->RunHook('event.smtp-pre-connect', array($this, $aSmtpCredentials['UseConnect'], $aSmtpCredentials)); - - if ($aSmtpCredentials['UseConnect'] && !$aSmtpCredentials['UsePhpMail'] && $oSmtpClient) - { - $oSmtpClient->Connect($aSmtpCredentials['Host'], $aSmtpCredentials['Port'], $aSmtpCredentials['Ehlo'], - $aSmtpCredentials['Secure'], $aSmtpCredentials['VerifySsl'], $aSmtpCredentials['AllowSelfSigned'] - ); - } - - $oPlugins->RunHook('event.smtp-post-connect', array($this, $aSmtpCredentials['UseConnect'], $aSmtpCredentials)); - $oPlugins->RunHook('event.smtp-pre-login', array($this, $aSmtpCredentials['UseAuth'], $aSmtpCredentials)); - - if ($aSmtpCredentials['UseAuth'] && !$aSmtpCredentials['UsePhpMail'] && $oSmtpClient) - { - $iGatLen = \strlen(APP_GOOGLE_ACCESS_TOKEN_PREFIX); - $sPassword = $aSmtpCredentials['Password']; - if (APP_GOOGLE_ACCESS_TOKEN_PREFIX === \substr($sPassword, 0, $iGatLen)) - { - $oSmtpClient->LoginWithXOauth2( - \base64_encode('user='.$aSmtpCredentials['Login']."\1".'auth=Bearer '.\substr($sPassword, $iGatLen)."\1\1")); - } - else - { - $oSmtpClient->Login($aSmtpCredentials['Login'], $aSmtpCredentials['Password'], - $aSmtpCredentials['UseAuthPlainIfSupported'], $aSmtpCredentials['UseAuthCramMd5IfSupported']); - } - - $bLogin = true; - } - - $oPlugins->RunHook('event.smtp-post-login', array($this, $aSmtpCredentials['UseAuth'], $bLogin, $aSmtpCredentials)); - - return $bLogin; - } - - /** - * @param \RainLoop\Plugins\Manager $oPlugins - * @param \MailSo\Sieve\ManageSieveClient $oSieveClient - * @param \RainLoop\Application $oConfig - */ - public function SieveConnectAndLoginHelper($oPlugins, $oSieveClient, $oConfig) - { - $bLogin = false; - - $aSieveCredentials = array( - 'UseConnect' => true, - 'UseAuth' => true, - 'Host' => $this->DomainSieveHost(), - 'Port' => $this->DomainSievePort(), - 'Secure' => $this->DomainSieveSecure(), - 'Login' => $this->IncLogin(), - 'Password' => $this->Password(), - 'VerifySsl' => !!$oConfig->Get('ssl', 'verify_certificate', false), - 'AllowSelfSigned' => !!$oConfig->Get('ssl', 'allow_self_signed', true) - ); - - $oPlugins->RunHook('filter.sieve-credentials', array($this, &$aSieveCredentials)); - - $oPlugins->RunHook('event.sieve-pre-connect', array($this, $aSieveCredentials['UseConnect'], $aSieveCredentials)); - - if ($aSieveCredentials['UseConnect'] && $oSieveClient) - { - $oSieveClient->Connect($aSieveCredentials['Host'], $aSieveCredentials['Port'], - $aSieveCredentials['Secure'], $aSieveCredentials['VerifySsl'], $aSieveCredentials['AllowSelfSigned'] - ); - } - - $oPlugins->RunHook('event.sieve-post-connect', array($this, $aSieveCredentials['UseConnect'], $aSieveCredentials)); - - $oPlugins->RunHook('event.sieve-pre-login', array($this, $aSieveCredentials['UseAuth'], $aSieveCredentials)); - - if ($aSieveCredentials['UseAuth']) - { - $oSieveClient->Login($aSieveCredentials['Login'], $aSieveCredentials['Password']); - - $bLogin = true; - } - - $oPlugins->RunHook('event.sieve-post-login', array($this, $aSieveCredentials['UseAuth'], $bLogin, $aSieveCredentials)); - - return $bLogin; - } -} +sEmail = \MailSo\Base\Utils::IdnToAscii($sEmail, true); + $this->sLogin = \MailSo\Base\Utils::IdnToAscii($sLogin); + $this->sPassword = $sPassword; + $this->oDomain = $oDomain; + $this->sSignMeToken = $sSignMeToken; + $this->sProxyAuthUser = $sProxyAuthUser; + $this->sProxyAuthPassword = $sProxyAuthPassword; + $this->sParentEmail = ''; + } + + /** + * @param string $sEmail + * @param string $sLogin + * @param string $sPassword + * @param \RainLoop\Model\Domain $oDomain + * @param string $sSignMeToken = '' + * @param string $sProxyAuthUser = '' + * @param string $sProxyAuthPassword = '' + * + * @return \RainLoop\Model\Account + */ + public static function NewInstance($sEmail, $sLogin, $sPassword, \RainLoop\Model\Domain $oDomain, + $sSignMeToken = '', $sProxyAuthUser = '', $sProxyAuthPassword = '') + { + return new self($sEmail, $sLogin, $sPassword, $oDomain, $sSignMeToken, $sProxyAuthUser, $sProxyAuthPassword); + } + + /** + * @return string + */ + public function Email() + { + return $this->sEmail; + } + + /** + * @return string + */ + public function ParentEmail() + { + return $this->sParentEmail; + } + + /** + * @return string + */ + public function ProxyAuthUser() + { + return $this->sProxyAuthUser; + } + + /** + * @return string + */ + public function ProxyAuthPassword() + { + return $this->sProxyAuthPassword; + } + + /** + * @return string + */ + public function ParentEmailHelper() + { + return 0 < \strlen($this->sParentEmail) ? $this->sParentEmail : $this->sEmail; + } + + /** + * @return string + */ + public function IsAdditionalAccount() + { + return 0 < \strlen($this->sParentEmail); + } + + /** + * @return string + */ + public function IncLogin() + { + $sLogin = $this->sLogin; + if ($this->oDomain->IncShortLogin()) + { + $sLogin = \MailSo\Base\Utils::GetAccountNameFromEmail($this->sLogin); + } + + return $sLogin; + } + + /** + * @return string + */ + public function IncPassword() + { + return $this->sPassword; + } + + /** + * @return string + */ + public function OutLogin() + { + $sLogin = $this->sLogin; + if ($this->oDomain->OutShortLogin()) + { + $sLogin = \MailSo\Base\Utils::GetAccountNameFromEmail($this->sLogin); + } + + return $sLogin; + } + + /** + * @return string + */ + public function Login() + { + return $this->IncLogin(); + } + + /** + * @return string + */ + public function Password() + { + return $this->IncPassword(); + } + + /** + * @return bool + */ + public function SignMe() + { + return 0 < \strlen($this->sSignMeToken); + } + + /** + * @return string + */ + public function SignMeToken() + { + return $this->sSignMeToken; + } + + /** + * @return \RainLoop\Model\Domain + */ + public function Domain() + { + return $this->oDomain; + } + + /** + * @return string + */ + public function Hash() + { + return md5(APP_SALT.$this->Email().APP_SALT.$this->DomainIncHost(). + APP_SALT.$this->DomainIncPort().APP_SALT.$this->Password().APP_SALT.'0'.APP_SALT.$this->ParentEmail().APP_SALT); + } + + /** + * @param string $sPassword + * + * @return void + */ + public function SetPassword($sPassword) + { + $this->sPassword = $sPassword; + } + + /** + * @param string $sParentEmail + * + * @return void + */ + public function SetParentEmail($sParentEmail) + { + $this->sParentEmail = \trim(\MailSo\Base\Utils::IdnToAscii($sParentEmail, true)); + } + + /** + * @param string $sProxyAuthUser + * + * @return void + */ + public function SetProxyAuthUser($sProxyAuthUser) + { + return $this->sProxyAuthUser = $sProxyAuthUser; + } + + /** + * @param string $sProxyAuthPassword + * + * @return void + */ + public function SetProxyAuthPassword($sProxyAuthPassword) + { + return $this->sProxyAuthPassword = $sProxyAuthPassword; + } + + /** + * @return string + */ + public function DomainIncHost() + { + return $this->Domain()->IncHost(); + } + + /** + * @return int + */ + public function DomainIncPort() + { + return $this->Domain()->IncPort(); + } + + /** + * @return int + */ + public function DomainIncSecure() + { + return $this->Domain()->IncSecure(); + } + + /** + * @return string + */ + public function DomainOutHost() + { + return $this->Domain()->OutHost(); + } + + /** + * @return int + */ + public function DomainOutPort() + { + return $this->Domain()->OutPort(); + } + + /** + * @return int + */ + public function DomainOutSecure() + { + return $this->Domain()->OutSecure(); + } + + /** + * @return bool + */ + public function DomainOutAuth() + { + return $this->Domain()->OutAuth(); + } + + /** + * @return string + */ + public function DomainSieveHost() + { + return $this->Domain()->SieveHost(); + } + + /** + * @return int + */ + public function DomainSievePort() + { + return $this->Domain()->SievePort(); + } + + /** + * @return int + */ + public function DomainSieveSecure() + { + return $this->Domain()->SieveSecure(); + } + + /** + * @return bool + */ + public function DomainSieveAllowRaw() + { + return $this->Domain()->SieveAllowRaw(); + } + + /** + * @return string + */ + public function GetAuthToken() + { + return \RainLoop\Utils::EncodeKeyValues(array( + 'token', // 0 + $this->sEmail, // 1 + $this->sLogin, // 2 + $this->sPassword, // 3 + \RainLoop\Utils::Fingerprint(), // 4 + $this->sSignMeToken, // 5 + $this->sParentEmail, // 6 + \RainLoop\Utils::GetShortToken(), // 7 + $this->sProxyAuthUser, // 8 + $this->sProxyAuthPassword, // 9 + 0 // 10 // timelife + )); + } + + /** + * @return string + */ + public function GetAuthTokenQ() + { + return \RainLoop\Utils::EncodeKeyValuesQ(array( + 'token', // 0 + $this->sEmail, // 1 + $this->sLogin, // 2 + $this->sPassword, // 3 + \RainLoop\Utils::Fingerprint(), // 4 + $this->sSignMeToken, // 5 + $this->sParentEmail, // 6 + \RainLoop\Utils::GetShortToken(), // 7 + $this->sProxyAuthUser, // 8 + $this->sProxyAuthPassword, // 9 + 0 // 10 // timelife + )); + } + + /** + * @param \RainLoop\Plugins\Manager $oPlugins + * @param \MailSo\Mail\MailClient $oMailClient + * @param \RainLoop\Application $oConfig + * + * @return bool + */ + public function IncConnectAndLoginHelper($oPlugins, $oMailClient, $oConfig) + { + $bLogin = false; + + $aImapCredentials = array( + 'UseConnect' => true, + 'UseAuth' => true, + 'Host' => $this->DomainIncHost(), + 'Port' => $this->DomainIncPort(), + 'Secure' => $this->DomainIncSecure(), + 'Login' => $this->IncLogin(), + 'Password' => $this->Password(), + 'ProxyAuthUser' => $this->ProxyAuthUser(), + 'ProxyAuthPassword' => $this->ProxyAuthPassword(), + 'VerifySsl' => !!$oConfig->Get('ssl', 'verify_certificate', false), + 'AllowSelfSigned' => !!$oConfig->Get('ssl', 'allow_self_signed', true), + 'UseAuthPlainIfSupported' => !!$oConfig->Get('labs', 'imap_use_auth_plain', true), + 'UseAuthCramMd5IfSupported' => !!$oConfig->Get('labs', 'imap_use_auth_cram_md5', true) + ); + + $oPlugins->RunHook('filter.imap-credentials', array($this, &$aImapCredentials)); + + $oPlugins->RunHook('event.imap-pre-connect', array($this, $aImapCredentials['UseConnect'], $aImapCredentials)); + + if ($aImapCredentials['UseConnect']) + { + $oMailClient + ->Connect($aImapCredentials['Host'], $aImapCredentials['Port'], + $aImapCredentials['Secure'], $aImapCredentials['VerifySsl'], $aImapCredentials['AllowSelfSigned']); + + } + + $oPlugins->RunHook('event.imap-pre-login', array($this, $aImapCredentials['UseAuth'], $aImapCredentials)); + + if ($aImapCredentials['UseAuth']) + { + if (0 < \strlen($aImapCredentials['ProxyAuthUser']) && + 0 < \strlen($aImapCredentials['ProxyAuthPassword'])) + { + $oMailClient + ->Login($aImapCredentials['ProxyAuthUser'], $aImapCredentials['ProxyAuthPassword'], + $aImapCredentials['Login'], $aImapCredentials['UseAuthPlainIfSupported'], $aImapCredentials['UseAuthCramMd5IfSupported']); + } + else + { + $iGatLen = \strlen(APP_GOOGLE_ACCESS_TOKEN_PREFIX); + $sPassword = $aImapCredentials['Password']; + if (APP_GOOGLE_ACCESS_TOKEN_PREFIX === \substr($sPassword, 0, $iGatLen)) + { + $oMailClient->LoginWithXOauth2( + \base64_encode('user='.$aImapCredentials['Login']."\1".'auth=Bearer '.\substr($sPassword, $iGatLen)."\1\1")); + } + else + { + $oMailClient->Login($aImapCredentials['Login'], $aImapCredentials['Password'], '', + $aImapCredentials['UseAuthPlainIfSupported'], $aImapCredentials['UseAuthCramMd5IfSupported']); + } + } + + $bLogin = true; + } + + $oPlugins->RunHook('event.imap-post-login', array($this, $aImapCredentials['UseAuth'], $bLogin, $aImapCredentials)); + + return $bLogin; + } + + /** + * @param \RainLoop\Plugins\Manager $oPlugins + * @param \MailSo\Smtp\SmtpClient|null $oSmtpClient + * @param \RainLoop\Application $oConfig + * @param bool $bUsePhpMail = false + * + * @return bool + */ + public function OutConnectAndLoginHelper($oPlugins, $oSmtpClient, $oConfig, &$bUsePhpMail = false) + { + $bLogin = false; + + $aSmtpCredentials = array( + 'UseConnect' => true, + 'UseAuth' => $this->DomainOutAuth(), + 'UsePhpMail' => $bUsePhpMail, + 'Ehlo' => \MailSo\Smtp\SmtpClient::EhloHelper(), + 'Host' => $this->DomainOutHost(), + 'Port' => $this->DomainOutPort(), + 'Secure' => $this->DomainOutSecure(), + 'Login' => $this->OutLogin(), + 'Password' => $this->Password(), + 'ProxyAuthUser' => $this->ProxyAuthUser(), + 'ProxyAuthPassword' => $this->ProxyAuthPassword(), + 'VerifySsl' => !!$oConfig->Get('ssl', 'verify_certificate', false), + 'AllowSelfSigned' => !!$oConfig->Get('ssl', 'allow_self_signed', true), + 'UseAuthPlainIfSupported' => !!$oConfig->Get('labs', 'smtp_use_auth_plain', true), + 'UseAuthCramMd5IfSupported' => !!$oConfig->Get('labs', 'smtp_use_auth_cram_md5', true) + ); + + $oPlugins->RunHook('filter.smtp-credentials', array($this, &$aSmtpCredentials)); + + $bUsePhpMail = $aSmtpCredentials['UsePhpMail']; + + $oPlugins->RunHook('event.smtp-pre-connect', array($this, $aSmtpCredentials['UseConnect'], $aSmtpCredentials)); + + if ($aSmtpCredentials['UseConnect'] && !$aSmtpCredentials['UsePhpMail'] && $oSmtpClient) + { + $oSmtpClient->Connect($aSmtpCredentials['Host'], $aSmtpCredentials['Port'], $aSmtpCredentials['Ehlo'], + $aSmtpCredentials['Secure'], $aSmtpCredentials['VerifySsl'], $aSmtpCredentials['AllowSelfSigned'] + ); + } + + $oPlugins->RunHook('event.smtp-post-connect', array($this, $aSmtpCredentials['UseConnect'], $aSmtpCredentials)); + $oPlugins->RunHook('event.smtp-pre-login', array($this, $aSmtpCredentials['UseAuth'], $aSmtpCredentials)); + + if ($aSmtpCredentials['UseAuth'] && !$aSmtpCredentials['UsePhpMail'] && $oSmtpClient) + { + $iGatLen = \strlen(APP_GOOGLE_ACCESS_TOKEN_PREFIX); + $sPassword = $aSmtpCredentials['Password']; + if (APP_GOOGLE_ACCESS_TOKEN_PREFIX === \substr($sPassword, 0, $iGatLen)) + { + $oSmtpClient->LoginWithXOauth2( + \base64_encode('user='.$aSmtpCredentials['Login']."\1".'auth=Bearer '.\substr($sPassword, $iGatLen)."\1\1")); + } + else + { + $oSmtpClient->Login($aSmtpCredentials['Login'], $aSmtpCredentials['Password'], + $aSmtpCredentials['UseAuthPlainIfSupported'], $aSmtpCredentials['UseAuthCramMd5IfSupported']); + } + + $bLogin = true; + } + + $oPlugins->RunHook('event.smtp-post-login', array($this, $aSmtpCredentials['UseAuth'], $bLogin, $aSmtpCredentials)); + + return $bLogin; + } + + /** + * @param \RainLoop\Plugins\Manager $oPlugins + * @param \MailSo\Sieve\ManageSieveClient $oSieveClient + * @param \RainLoop\Application $oConfig + */ + public function SieveConnectAndLoginHelper($oPlugins, $oSieveClient, $oConfig) + { + $bLogin = false; + + $aSieveCredentials = array( + 'UseConnect' => true, + 'UseAuth' => true, + 'Host' => $this->DomainSieveHost(), + 'Port' => $this->DomainSievePort(), + 'Secure' => $this->DomainSieveSecure(), + 'Login' => $this->IncLogin(), + 'Password' => $this->Password(), + 'VerifySsl' => !!$oConfig->Get('ssl', 'verify_certificate', false), + 'AllowSelfSigned' => !!$oConfig->Get('ssl', 'allow_self_signed', true), + 'InitialAuthPlain' => !!$oConfig->Get('ssl', 'sieve_auth_plain_initial', true) + ); + + $oPlugins->RunHook('filter.sieve-credentials', array($this, &$aSieveCredentials)); + + $oPlugins->RunHook('event.sieve-pre-connect', array($this, $aSieveCredentials['UseConnect'], $aSieveCredentials)); + + if ($oSieveClient) + { + $oSieveClient->__USE_INITIAL_AUTH_PLAIN_COMMAND = $aSieveCredentials['InitialAuthPlain']; + + if ($aSieveCredentials['UseConnect']) + { + $oSieveClient->Connect($aSieveCredentials['Host'], $aSieveCredentials['Port'], + $aSieveCredentials['Secure'], $aSieveCredentials['VerifySsl'], $aSieveCredentials['AllowSelfSigned'] + ); + } + } + + $oPlugins->RunHook('event.sieve-post-connect', array($this, $aSieveCredentials['UseConnect'], $aSieveCredentials)); + + $oPlugins->RunHook('event.sieve-pre-login', array($this, $aSieveCredentials['UseAuth'], $aSieveCredentials)); + + if ($aSieveCredentials['UseAuth']) + { + $oSieveClient->Login($aSieveCredentials['Login'], $aSieveCredentials['Password']); + + $bLogin = true; + } + + $oPlugins->RunHook('event.sieve-post-login', array($this, $aSieveCredentials['UseAuth'], $bLogin, $aSieveCredentials)); + + return $bLogin; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters/SieveStorage.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters/SieveStorage.php index fbc97c1d7..04b15f8e8 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters/SieveStorage.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters/SieveStorage.php @@ -1,564 +1,564 @@ -oLogger = null; - - $this->oPlugins = $oPlugins; - $this->oConfig = $oConfig; - - $this->bUtf8FolderName = !!$this->oConfig->Get('labs', 'sieve_utf8_folder_name', true); - } - - /** - * @param \RainLoop\Model\Account $oAccount - * @param bool $bAllowRaw = false - * - * @return array - */ - public function Load($oAccount, $bAllowRaw = false) - { - $sRaw = ''; - - $bBasicIsActive = false; - $bRawIsActive = false; - - $aModules = array(); - $aFilters = array(); - - $oSieveClient = \MailSo\Sieve\ManageSieveClient::NewInstance()->SetLogger($this->oLogger); - $oSieveClient->SetTimeOuts(10, (int) \RainLoop\Api::Config()->Get('labs', 'sieve_timeout', 10)); - - if ($oAccount->SieveConnectAndLoginHelper($this->oPlugins, $oSieveClient, $this->oConfig)) - { - $aModules = $oSieveClient->Modules(); - $aList = $oSieveClient->ListScripts(); - - if (\is_array($aList) && 0 < \count($aList)) - { - if (isset($aList[self::SIEVE_FILE_NAME])) - { - $bBasicIsActive = !!$aList[self::SIEVE_FILE_NAME]; - $sS = $oSieveClient->GetScript(self::SIEVE_FILE_NAME); - if ($sS) - { - $aFilters = $this->fileStringToCollection($sS); - } - } - - if ($bAllowRaw && isset($aList[self::SIEVE_FILE_NAME_RAW])) - { - $bRawIsActive = !!$aList[self::SIEVE_FILE_NAME_RAW]; - $sRaw = \trim($oSieveClient->GetScript(self::SIEVE_FILE_NAME_RAW)); - } - } - - $oSieveClient->LogoutAndDisconnect(); - } - - return array( - 'RawIsAllow' => $bAllowRaw, - 'RawIsActive' => $bRawIsActive, - 'Raw' => $bAllowRaw ? $sRaw : '', - 'Filters' => !$bBasicIsActive && !$bRawIsActive ? array() : $aFilters, - 'Capa' => $bAllowRaw ? $aModules : array(), - 'Modules' => array( - 'redirect' => \in_array('fileinto', $aModules), - 'regex' => \in_array('regex', $aModules), - 'relational' => \in_array('relational', $aModules), - 'date' => \in_array('date', $aModules), - 'moveto' => \in_array('fileinto', $aModules), - 'reject' => \in_array('reject', $aModules), - 'vacation' => \in_array('vacation', $aModules), - 'markasread' => \in_array('imap4flags', $aModules) - ) - ); - } - - /** - * @param \RainLoop\Model\Account $oAccount - * @param array $aFilters - * @param string $sRaw = '' - * @param bool $bRawIsActive = false - * - * @return bool - */ - public function Save($oAccount, $aFilters, $sRaw = '', $bRawIsActive = false) - { - $oSieveClient = \MailSo\Sieve\ManageSieveClient::NewInstance()->SetLogger($this->oLogger); - $oSieveClient->SetTimeOuts(10, (int) \RainLoop\Api::Config()->Get('labs', 'sieve_timeout', 10)); - - if ($oAccount->SieveConnectAndLoginHelper($this->oPlugins, $oSieveClient, $this->oConfig)) - { - $aList = $oSieveClient->ListScripts(); - - if ($bRawIsActive) - { - if (!empty($sRaw)) - { - $oSieveClient->PutScript(self::SIEVE_FILE_NAME_RAW, $sRaw); - $oSieveClient->SetActiveScript(self::SIEVE_FILE_NAME_RAW); - } - else if (isset($aList[self::SIEVE_FILE_NAME_RAW])) - { - $oSieveClient->DeleteScript(self::SIEVE_FILE_NAME_RAW); - } - } - else - { - $sUserFilter = $this->collectionToFileString($aFilters); - - if (!empty($sUserFilter)) - { - $oSieveClient->PutScript(self::SIEVE_FILE_NAME, $sUserFilter); - $oSieveClient->SetActiveScript(self::SIEVE_FILE_NAME); - } - else if (isset($aList[self::SIEVE_FILE_NAME])) - { - $oSieveClient->DeleteScript(self::SIEVE_FILE_NAME); - } - } - - $oSieveClient->LogoutAndDisconnect(); - - return true; - } - - return false; - } - - /** - * @param \RainLoop\Providers\Filters\Classes\FilterCondition $oCondition - * - * @return string - */ - private function conditionToSieveScript($oCondition, &$aCapa) - { - $sResult = ''; - $sTypeWord = ''; - $bTrue = true; - - $sValue = \trim($oCondition->Value()); - $sValueSecond = \trim($oCondition->ValueSecond()); - - if (0 < \strlen($sValue) || - (0 < \strlen($sValue) && 0 < \strlen($sValueSecond) && - \RainLoop\Providers\Filters\Enumerations\ConditionField::HEADER === $oCondition->Field())) - { - switch ($oCondition->Type()) - { - case \RainLoop\Providers\Filters\Enumerations\ConditionType::OVER: - $sTypeWord = ':over'; - break; - case \RainLoop\Providers\Filters\Enumerations\ConditionType::UNDER: - $sTypeWord = ':under'; - break; - case \RainLoop\Providers\Filters\Enumerations\ConditionType::NOT_EQUAL_TO: - $sResult .= 'not '; - case \RainLoop\Providers\Filters\Enumerations\ConditionType::EQUAL_TO: - $sTypeWord = ':is'; - break; - case \RainLoop\Providers\Filters\Enumerations\ConditionType::NOT_CONTAINS: - $sResult .= 'not '; - case \RainLoop\Providers\Filters\Enumerations\ConditionType::CONTAINS: - $sTypeWord = ':contains'; - break; - case \RainLoop\Providers\Filters\Enumerations\ConditionType::REGEX: - $sTypeWord = ':regex'; - $aCapa['regex'] = true; - break; - default: - $bTrue = false; - $sResult = '/* @Error: unknown type value */ false'; - break; - } - - switch ($oCondition->Field()) - { - case \RainLoop\Providers\Filters\Enumerations\ConditionField::FROM: - $sResult .= 'header '.$sTypeWord.' ["From"]'; - break; - case \RainLoop\Providers\Filters\Enumerations\ConditionField::RECIPIENT: - $sResult .= 'header '.$sTypeWord.' ["To", "CC"]'; - break; - case \RainLoop\Providers\Filters\Enumerations\ConditionField::SUBJECT: - $sResult .= 'header '.$sTypeWord.' ["Subject"]'; - break; - case \RainLoop\Providers\Filters\Enumerations\ConditionField::HEADER: - $sResult .= 'header '.$sTypeWord.' ["'.$this->quote($sValueSecond).'"]'; - break; - case \RainLoop\Providers\Filters\Enumerations\ConditionField::SIZE: - $sResult .= 'size '.$sTypeWord; - break; - default: - $bTrue = false; - $sResult = '/* @Error: unknown field value */ false'; - break; - } - - if ($bTrue) - { - if (\in_array($oCondition->Field(), array( - \RainLoop\Providers\Filters\Enumerations\ConditionField::FROM, - \RainLoop\Providers\Filters\Enumerations\ConditionField::RECIPIENT - )) && false !== \strpos($sValue, ',')) - { - $self = $this; - $aValue = \array_map(function ($sValue) use ($self) { - return '"'.$self->quote(\trim($sValue)).'"'; - }, \explode(',', $sValue)); - - $sResult .= ' ['.\trim(\implode(', ', $aValue)).']'; - } - else if (\RainLoop\Providers\Filters\Enumerations\ConditionField::SIZE === $oCondition->Field()) - { - $sResult .= ' '.$this->quote($sValue); - } - else - { - $sResult .= ' "'.$this->quote($sValue).'"'; - } - - $sResult = \MailSo\Base\Utils::StripSpaces($sResult); - } - } - else - { - $sResult = '/* @Error: empty condition value */ false'; - } - - return $sResult; - } - - /** - * @param \RainLoop\Providers\Filters\Classes\Filter $oFilter - * @param array $aCapa - * - * @return string - */ - private function filterToSieveScript($oFilter, &$aCapa) - { - $sNL = \RainLoop\Providers\Filters\SieveStorage::NEW_LINE; - $sTab = ' '; - - $bAll = false; - $aResult = array(); - - // Conditions - $aConditions = $oFilter->Conditions(); - if (\is_array($aConditions)) - { - if (1 < \count($aConditions)) - { - if (\RainLoop\Providers\Filters\Enumerations\ConditionsType::ANY === - $oFilter->ConditionsType()) - { - $aResult[] = 'if anyof('; - - $bTrim = false; - foreach ($aConditions as $oCond) - { - $bTrim = true; - $sCons = $this->conditionToSieveScript($oCond, $aCapa); - if (!empty($sCons)) - { - $aResult[] = $sTab.$sCons.','; - } - } - if ($bTrim) - { - $aResult[\count($aResult) - 1] = \rtrim($aResult[\count($aResult) - 1], ','); - } - - $aResult[] = ')'; - } - else - { - $aResult[] = 'if allof('; - foreach ($aConditions as $oCond) - { - $aResult[] = $sTab.$this->conditionToSieveScript($oCond, $aCapa).','; - } - - $aResult[\count($aResult) - 1] = \rtrim($aResult[\count($aResult) - 1], ','); - $aResult[] = ')'; - } - } - else if (1 === \count($aConditions)) - { - $aResult[] = 'if '.$this->conditionToSieveScript($aConditions[0], $aCapa).''; - } - else - { - $bAll = true; - } - } - - // actions - if (!$bAll) - { - $aResult[] = '{'; - } - else - { - $sTab = ''; - } - - if ($oFilter->MarkAsRead() && \in_array($oFilter->ActionType(), array( - \RainLoop\Providers\Filters\Enumerations\ActionType::NONE, - \RainLoop\Providers\Filters\Enumerations\ActionType::MOVE_TO, - \RainLoop\Providers\Filters\Enumerations\ActionType::FORWARD - ))) - { - $aCapa['imap4flags'] = true; - $aResult[] = $sTab.'addflag "\\\\Seen";'; - } - - switch ($oFilter->ActionType()) - { - case \RainLoop\Providers\Filters\Enumerations\ActionType::NONE: - $aResult[] = $sTab.'stop;'; - break; - case \RainLoop\Providers\Filters\Enumerations\ActionType::DISCARD: - $aResult[] = $sTab.'discard;'; - $aResult[] = $sTab.'stop;'; - break; - case \RainLoop\Providers\Filters\Enumerations\ActionType::VACATION: - $sValue = \trim($oFilter->ActionValue()); - $sValueSecond = \trim($oFilter->ActionValueSecond()); - $sValueThird = \trim($oFilter->ActionValueThird()); - $sValueFourth = \trim($oFilter->ActionValueFourth()); - if (0 < \strlen($sValue)) - { - $aCapa['vacation'] = true; - - $iDays = 1; - $sSubject = ''; - if (0 < \strlen($sValueSecond)) - { - $sSubject = ':subject "'. - $this->quote(\MailSo\Base\Utils::StripSpaces($sValueSecond)).'" '; - } - - if (0 < \strlen($sValueThird) && \is_numeric($sValueThird) && 1 < (int) $sValueThird) - { - $iDays = (int) $sValueThird; - } - - $sAddresses = ''; - if (0 < \strlen($sValueFourth)) - { - $self = $this; - - $aAddresses = \explode(',', $sValueFourth); - $aAddresses = \array_filter(\array_map(function ($sEmail) use ($self) { - $sEmail = \trim($sEmail); - return 0 < \strlen($sEmail) ? '"'.$self->quote($sEmail).'"' : ''; - }, $aAddresses), 'strlen'); - - if (0 < \count($aAddresses)) - { - $sAddresses = ':addresses ['.\implode(', ', $aAddresses).'] '; - } - } - - $aResult[] = $sTab.'vacation :days '.$iDays.' '.$sAddresses.$sSubject.'"'.$this->quote($sValue).'";'; - if ($oFilter->Stop()) - { - $aResult[] = $sTab.'stop;'; - } - } - else - { - $aResult[] = $sTab.'# @Error (vacation): empty action value'; - } - break; - case \RainLoop\Providers\Filters\Enumerations\ActionType::REJECT: - $sValue = \trim($oFilter->ActionValue()); - if (0 < \strlen($sValue)) - { - $aCapa['reject'] = true; - - $aResult[] = $sTab.'reject "'.$this->quote($sValue).'";'; - $aResult[] = $sTab.'stop;'; - } - else - { - $aResult[] = $sTab.'# @Error (reject): empty action value'; - } - break; - case \RainLoop\Providers\Filters\Enumerations\ActionType::FORWARD: - $sValue = $oFilter->ActionValue(); - if (0 < \strlen($sValue)) - { - if ($oFilter->Keep()) - { - $aCapa['fileinto'] = true; - $aResult[] = $sTab.'fileinto "INBOX";'; - } - - $aResult[] = $sTab.'redirect "'.$this->quote($sValue).'";'; - $aResult[] = $sTab.'stop;'; - } - else - { - $aResult[] = $sTab.'# @Error (redirect): empty action value'; - } - break; - case \RainLoop\Providers\Filters\Enumerations\ActionType::MOVE_TO: - $sValue = $oFilter->ActionValue(); - if (0 < \strlen($sValue)) - { - $sFolderName = $sValue; // utf7-imap - if ($this->bUtf8FolderName) // to utf-8 - { - $sFolderName = \MailSo\Base\Utils::ConvertEncoding($sFolderName, - \MailSo\Base\Enumerations\Charset::UTF_7_IMAP, - \MailSo\Base\Enumerations\Charset::UTF_8); - } - - $aCapa['fileinto'] = true; - $aResult[] = $sTab.'fileinto "'.$this->quote($sFolderName).'";'; - $aResult[] = $sTab.'stop;'; - } - else - { - $aResult[] = $sTab.'# @Error (fileinto): empty action value'; - } - break; - } - - if (!$bAll) - { - $aResult[] = '}'; - } - - return \implode($sNL, $aResult); - } - - /** - * @param array $aFilters - * - * @return string - */ - private function collectionToFileString($aFilters) - { - $sNL = \RainLoop\Providers\Filters\SieveStorage::NEW_LINE; - - $aCapa = array(); - $aParts = array(); - - $aParts[] = '# This is RainLoop Webmail sieve script.'; - $aParts[] = '# Please don\'t change anything here.'; - $aParts[] = '# RAINLOOP:SIEVE'; - $aParts[] = ''; - - foreach ($aFilters as /* @var $oItem \RainLoop\Providers\Filters\Classes\Filter */ $oItem) - { - $aData = array(); - $aData[] = '/*'; - $aData[] = 'BEGIN:FILTER:'.$oItem->ID(); - $aData[] = 'BEGIN:HEADER'; - $aData[] = \chunk_split(\base64_encode($oItem->serializeToJson()), 74, $sNL).'END:HEADER'; - $aData[] = '*/'; - $aData[] = $oItem->Enabled() ? '' : '/* @Filter is disabled '; - $aData[] = $this->filterToSieveScript($oItem, $aCapa); - $aData[] = $oItem->Enabled() ? '' : '*/'; - $aData[] = '/* END:FILTER */'; - $aData[] = ''; - - $aParts[] = \implode($sNL, $aData); - } - - $aCapa = \array_keys($aCapa); - $sCapa = 0 < \count($aCapa) ? $sNL.'require '. - \str_replace('","', '", "', \json_encode($aCapa)).';'.$sNL : ''; - - return $sCapa.$sNL.\implode($sNL, $aParts).$sNL; - } - - /** - * @param string $sFileString - * - * @return array - */ - private function fileStringToCollection($sFileString) - { - $aResult = array(); - if (!empty($sFileString) && false !== \strpos($sFileString, 'RAINLOOP:SIEVE')) - { - $aMatch = array(); - if (\preg_match_all('/BEGIN:FILTER(.+?)BEGIN:HEADER(.+?)END:HEADER/s', $sFileString, $aMatch) && - isset($aMatch[2]) && \is_array($aMatch[2])) - { - foreach ($aMatch[2] as $sEncodedLine) - { - if (!empty($sEncodedLine)) - { - $sDecodedLine = \base64_decode(\preg_replace('/[\s]+/', '', $sEncodedLine)); - if (!empty($sDecodedLine)) - { - $oItem = new \RainLoop\Providers\Filters\Classes\Filter(); - if ($oItem && $oItem->unserializeFromJson($sDecodedLine)) - { - $aResult[] = $oItem; - } - } - } - } - } - } - - return $aResult; - } - - /** - * @param string $sValue - * - * @return string - */ - public function quote($sValue) - { - return \str_replace(array('\\', '"'), array('\\\\', '\\"'), \trim($sValue)); - } - - /** - * @param \MailSo\Log\Logger $oLogger - */ - public function SetLogger($oLogger) - { - $this->oLogger = $oLogger instanceof \MailSo\Log\Logger ? $oLogger : null; - } -} +oLogger = null; + + $this->oPlugins = $oPlugins; + $this->oConfig = $oConfig; + + $this->bUtf8FolderName = !!$this->oConfig->Get('labs', 'sieve_utf8_folder_name', true); + } + + /** + * @param \RainLoop\Model\Account $oAccount + * @param bool $bAllowRaw = false + * + * @return array + */ + public function Load($oAccount, $bAllowRaw = false) + { + $sRaw = ''; + + $bBasicIsActive = false; + $bRawIsActive = false; + + $aModules = array(); + $aFilters = array(); + + $oSieveClient = \MailSo\Sieve\ManageSieveClient::NewInstance()->SetLogger($this->oLogger); + $oSieveClient->SetTimeOuts(10, (int) $this->oConfig->Get('labs', 'sieve_timeout', 10)); + + if ($oAccount->SieveConnectAndLoginHelper($this->oPlugins, $oSieveClient, $this->oConfig)) + { + $aModules = $oSieveClient->Modules(); + $aList = $oSieveClient->ListScripts(); + + if (\is_array($aList) && 0 < \count($aList)) + { + if (isset($aList[self::SIEVE_FILE_NAME])) + { + $bBasicIsActive = !!$aList[self::SIEVE_FILE_NAME]; + $sS = $oSieveClient->GetScript(self::SIEVE_FILE_NAME); + if ($sS) + { + $aFilters = $this->fileStringToCollection($sS); + } + } + + if ($bAllowRaw && isset($aList[self::SIEVE_FILE_NAME_RAW])) + { + $bRawIsActive = !!$aList[self::SIEVE_FILE_NAME_RAW]; + $sRaw = \trim($oSieveClient->GetScript(self::SIEVE_FILE_NAME_RAW)); + } + } + + $oSieveClient->LogoutAndDisconnect(); + } + + return array( + 'RawIsAllow' => $bAllowRaw, + 'RawIsActive' => $bRawIsActive, + 'Raw' => $bAllowRaw ? $sRaw : '', + 'Filters' => !$bBasicIsActive && !$bRawIsActive ? array() : $aFilters, + 'Capa' => $bAllowRaw ? $aModules : array(), + 'Modules' => array( + 'redirect' => \in_array('fileinto', $aModules), + 'regex' => \in_array('regex', $aModules), + 'relational' => \in_array('relational', $aModules), + 'date' => \in_array('date', $aModules), + 'moveto' => \in_array('fileinto', $aModules), + 'reject' => \in_array('reject', $aModules), + 'vacation' => \in_array('vacation', $aModules), + 'markasread' => \in_array('imap4flags', $aModules) + ) + ); + } + + /** + * @param \RainLoop\Model\Account $oAccount + * @param array $aFilters + * @param string $sRaw = '' + * @param bool $bRawIsActive = false + * + * @return bool + */ + public function Save($oAccount, $aFilters, $sRaw = '', $bRawIsActive = false) + { + $oSieveClient = \MailSo\Sieve\ManageSieveClient::NewInstance()->SetLogger($this->oLogger); + $oSieveClient->SetTimeOuts(10, (int) \RainLoop\Api::Config()->Get('labs', 'sieve_timeout', 10)); + + if ($oAccount->SieveConnectAndLoginHelper($this->oPlugins, $oSieveClient, $this->oConfig)) + { + $aList = $oSieveClient->ListScripts(); + + if ($bRawIsActive) + { + if (!empty($sRaw)) + { + $oSieveClient->PutScript(self::SIEVE_FILE_NAME_RAW, $sRaw); + $oSieveClient->SetActiveScript(self::SIEVE_FILE_NAME_RAW); + } + else if (isset($aList[self::SIEVE_FILE_NAME_RAW])) + { + $oSieveClient->DeleteScript(self::SIEVE_FILE_NAME_RAW); + } + } + else + { + $sUserFilter = $this->collectionToFileString($aFilters); + + if (!empty($sUserFilter)) + { + $oSieveClient->PutScript(self::SIEVE_FILE_NAME, $sUserFilter); + $oSieveClient->SetActiveScript(self::SIEVE_FILE_NAME); + } + else if (isset($aList[self::SIEVE_FILE_NAME])) + { + $oSieveClient->DeleteScript(self::SIEVE_FILE_NAME); + } + } + + $oSieveClient->LogoutAndDisconnect(); + + return true; + } + + return false; + } + + /** + * @param \RainLoop\Providers\Filters\Classes\FilterCondition $oCondition + * + * @return string + */ + private function conditionToSieveScript($oCondition, &$aCapa) + { + $sResult = ''; + $sTypeWord = ''; + $bTrue = true; + + $sValue = \trim($oCondition->Value()); + $sValueSecond = \trim($oCondition->ValueSecond()); + + if (0 < \strlen($sValue) || + (0 < \strlen($sValue) && 0 < \strlen($sValueSecond) && + \RainLoop\Providers\Filters\Enumerations\ConditionField::HEADER === $oCondition->Field())) + { + switch ($oCondition->Type()) + { + case \RainLoop\Providers\Filters\Enumerations\ConditionType::OVER: + $sTypeWord = ':over'; + break; + case \RainLoop\Providers\Filters\Enumerations\ConditionType::UNDER: + $sTypeWord = ':under'; + break; + case \RainLoop\Providers\Filters\Enumerations\ConditionType::NOT_EQUAL_TO: + $sResult .= 'not '; + case \RainLoop\Providers\Filters\Enumerations\ConditionType::EQUAL_TO: + $sTypeWord = ':is'; + break; + case \RainLoop\Providers\Filters\Enumerations\ConditionType::NOT_CONTAINS: + $sResult .= 'not '; + case \RainLoop\Providers\Filters\Enumerations\ConditionType::CONTAINS: + $sTypeWord = ':contains'; + break; + case \RainLoop\Providers\Filters\Enumerations\ConditionType::REGEX: + $sTypeWord = ':regex'; + $aCapa['regex'] = true; + break; + default: + $bTrue = false; + $sResult = '/* @Error: unknown type value */ false'; + break; + } + + switch ($oCondition->Field()) + { + case \RainLoop\Providers\Filters\Enumerations\ConditionField::FROM: + $sResult .= 'header '.$sTypeWord.' ["From"]'; + break; + case \RainLoop\Providers\Filters\Enumerations\ConditionField::RECIPIENT: + $sResult .= 'header '.$sTypeWord.' ["To", "CC"]'; + break; + case \RainLoop\Providers\Filters\Enumerations\ConditionField::SUBJECT: + $sResult .= 'header '.$sTypeWord.' ["Subject"]'; + break; + case \RainLoop\Providers\Filters\Enumerations\ConditionField::HEADER: + $sResult .= 'header '.$sTypeWord.' ["'.$this->quote($sValueSecond).'"]'; + break; + case \RainLoop\Providers\Filters\Enumerations\ConditionField::SIZE: + $sResult .= 'size '.$sTypeWord; + break; + default: + $bTrue = false; + $sResult = '/* @Error: unknown field value */ false'; + break; + } + + if ($bTrue) + { + if (\in_array($oCondition->Field(), array( + \RainLoop\Providers\Filters\Enumerations\ConditionField::FROM, + \RainLoop\Providers\Filters\Enumerations\ConditionField::RECIPIENT + )) && false !== \strpos($sValue, ',')) + { + $self = $this; + $aValue = \array_map(function ($sValue) use ($self) { + return '"'.$self->quote(\trim($sValue)).'"'; + }, \explode(',', $sValue)); + + $sResult .= ' ['.\trim(\implode(', ', $aValue)).']'; + } + else if (\RainLoop\Providers\Filters\Enumerations\ConditionField::SIZE === $oCondition->Field()) + { + $sResult .= ' '.$this->quote($sValue); + } + else + { + $sResult .= ' "'.$this->quote($sValue).'"'; + } + + $sResult = \MailSo\Base\Utils::StripSpaces($sResult); + } + } + else + { + $sResult = '/* @Error: empty condition value */ false'; + } + + return $sResult; + } + + /** + * @param \RainLoop\Providers\Filters\Classes\Filter $oFilter + * @param array $aCapa + * + * @return string + */ + private function filterToSieveScript($oFilter, &$aCapa) + { + $sNL = \RainLoop\Providers\Filters\SieveStorage::NEW_LINE; + $sTab = ' '; + + $bAll = false; + $aResult = array(); + + // Conditions + $aConditions = $oFilter->Conditions(); + if (\is_array($aConditions)) + { + if (1 < \count($aConditions)) + { + if (\RainLoop\Providers\Filters\Enumerations\ConditionsType::ANY === + $oFilter->ConditionsType()) + { + $aResult[] = 'if anyof('; + + $bTrim = false; + foreach ($aConditions as $oCond) + { + $bTrim = true; + $sCons = $this->conditionToSieveScript($oCond, $aCapa); + if (!empty($sCons)) + { + $aResult[] = $sTab.$sCons.','; + } + } + if ($bTrim) + { + $aResult[\count($aResult) - 1] = \rtrim($aResult[\count($aResult) - 1], ','); + } + + $aResult[] = ')'; + } + else + { + $aResult[] = 'if allof('; + foreach ($aConditions as $oCond) + { + $aResult[] = $sTab.$this->conditionToSieveScript($oCond, $aCapa).','; + } + + $aResult[\count($aResult) - 1] = \rtrim($aResult[\count($aResult) - 1], ','); + $aResult[] = ')'; + } + } + else if (1 === \count($aConditions)) + { + $aResult[] = 'if '.$this->conditionToSieveScript($aConditions[0], $aCapa).''; + } + else + { + $bAll = true; + } + } + + // actions + if (!$bAll) + { + $aResult[] = '{'; + } + else + { + $sTab = ''; + } + + if ($oFilter->MarkAsRead() && \in_array($oFilter->ActionType(), array( + \RainLoop\Providers\Filters\Enumerations\ActionType::NONE, + \RainLoop\Providers\Filters\Enumerations\ActionType::MOVE_TO, + \RainLoop\Providers\Filters\Enumerations\ActionType::FORWARD + ))) + { + $aCapa['imap4flags'] = true; + $aResult[] = $sTab.'addflag "\\\\Seen";'; + } + + switch ($oFilter->ActionType()) + { + case \RainLoop\Providers\Filters\Enumerations\ActionType::NONE: + $aResult[] = $sTab.'stop;'; + break; + case \RainLoop\Providers\Filters\Enumerations\ActionType::DISCARD: + $aResult[] = $sTab.'discard;'; + $aResult[] = $sTab.'stop;'; + break; + case \RainLoop\Providers\Filters\Enumerations\ActionType::VACATION: + $sValue = \trim($oFilter->ActionValue()); + $sValueSecond = \trim($oFilter->ActionValueSecond()); + $sValueThird = \trim($oFilter->ActionValueThird()); + $sValueFourth = \trim($oFilter->ActionValueFourth()); + if (0 < \strlen($sValue)) + { + $aCapa['vacation'] = true; + + $iDays = 1; + $sSubject = ''; + if (0 < \strlen($sValueSecond)) + { + $sSubject = ':subject "'. + $this->quote(\MailSo\Base\Utils::StripSpaces($sValueSecond)).'" '; + } + + if (0 < \strlen($sValueThird) && \is_numeric($sValueThird) && 1 < (int) $sValueThird) + { + $iDays = (int) $sValueThird; + } + + $sAddresses = ''; + if (0 < \strlen($sValueFourth)) + { + $self = $this; + + $aAddresses = \explode(',', $sValueFourth); + $aAddresses = \array_filter(\array_map(function ($sEmail) use ($self) { + $sEmail = \trim($sEmail); + return 0 < \strlen($sEmail) ? '"'.$self->quote($sEmail).'"' : ''; + }, $aAddresses), 'strlen'); + + if (0 < \count($aAddresses)) + { + $sAddresses = ':addresses ['.\implode(', ', $aAddresses).'] '; + } + } + + $aResult[] = $sTab.'vacation :days '.$iDays.' '.$sAddresses.$sSubject.'"'.$this->quote($sValue).'";'; + if ($oFilter->Stop()) + { + $aResult[] = $sTab.'stop;'; + } + } + else + { + $aResult[] = $sTab.'# @Error (vacation): empty action value'; + } + break; + case \RainLoop\Providers\Filters\Enumerations\ActionType::REJECT: + $sValue = \trim($oFilter->ActionValue()); + if (0 < \strlen($sValue)) + { + $aCapa['reject'] = true; + + $aResult[] = $sTab.'reject "'.$this->quote($sValue).'";'; + $aResult[] = $sTab.'stop;'; + } + else + { + $aResult[] = $sTab.'# @Error (reject): empty action value'; + } + break; + case \RainLoop\Providers\Filters\Enumerations\ActionType::FORWARD: + $sValue = $oFilter->ActionValue(); + if (0 < \strlen($sValue)) + { + if ($oFilter->Keep()) + { + $aCapa['fileinto'] = true; + $aResult[] = $sTab.'fileinto "INBOX";'; + } + + $aResult[] = $sTab.'redirect "'.$this->quote($sValue).'";'; + $aResult[] = $sTab.'stop;'; + } + else + { + $aResult[] = $sTab.'# @Error (redirect): empty action value'; + } + break; + case \RainLoop\Providers\Filters\Enumerations\ActionType::MOVE_TO: + $sValue = $oFilter->ActionValue(); + if (0 < \strlen($sValue)) + { + $sFolderName = $sValue; // utf7-imap + if ($this->bUtf8FolderName) // to utf-8 + { + $sFolderName = \MailSo\Base\Utils::ConvertEncoding($sFolderName, + \MailSo\Base\Enumerations\Charset::UTF_7_IMAP, + \MailSo\Base\Enumerations\Charset::UTF_8); + } + + $aCapa['fileinto'] = true; + $aResult[] = $sTab.'fileinto "'.$this->quote($sFolderName).'";'; + $aResult[] = $sTab.'stop;'; + } + else + { + $aResult[] = $sTab.'# @Error (fileinto): empty action value'; + } + break; + } + + if (!$bAll) + { + $aResult[] = '}'; + } + + return \implode($sNL, $aResult); + } + + /** + * @param array $aFilters + * + * @return string + */ + private function collectionToFileString($aFilters) + { + $sNL = \RainLoop\Providers\Filters\SieveStorage::NEW_LINE; + + $aCapa = array(); + $aParts = array(); + + $aParts[] = '# This is RainLoop Webmail sieve script.'; + $aParts[] = '# Please don\'t change anything here.'; + $aParts[] = '# RAINLOOP:SIEVE'; + $aParts[] = ''; + + foreach ($aFilters as /* @var $oItem \RainLoop\Providers\Filters\Classes\Filter */ $oItem) + { + $aData = array(); + $aData[] = '/*'; + $aData[] = 'BEGIN:FILTER:'.$oItem->ID(); + $aData[] = 'BEGIN:HEADER'; + $aData[] = \chunk_split(\base64_encode($oItem->serializeToJson()), 74, $sNL).'END:HEADER'; + $aData[] = '*/'; + $aData[] = $oItem->Enabled() ? '' : '/* @Filter is disabled '; + $aData[] = $this->filterToSieveScript($oItem, $aCapa); + $aData[] = $oItem->Enabled() ? '' : '*/'; + $aData[] = '/* END:FILTER */'; + $aData[] = ''; + + $aParts[] = \implode($sNL, $aData); + } + + $aCapa = \array_keys($aCapa); + $sCapa = 0 < \count($aCapa) ? $sNL.'require '. + \str_replace('","', '", "', \json_encode($aCapa)).';'.$sNL : ''; + + return $sCapa.$sNL.\implode($sNL, $aParts).$sNL; + } + + /** + * @param string $sFileString + * + * @return array + */ + private function fileStringToCollection($sFileString) + { + $aResult = array(); + if (!empty($sFileString) && false !== \strpos($sFileString, 'RAINLOOP:SIEVE')) + { + $aMatch = array(); + if (\preg_match_all('/BEGIN:FILTER(.+?)BEGIN:HEADER(.+?)END:HEADER/s', $sFileString, $aMatch) && + isset($aMatch[2]) && \is_array($aMatch[2])) + { + foreach ($aMatch[2] as $sEncodedLine) + { + if (!empty($sEncodedLine)) + { + $sDecodedLine = \base64_decode(\preg_replace('/[\s]+/', '', $sEncodedLine)); + if (!empty($sDecodedLine)) + { + $oItem = new \RainLoop\Providers\Filters\Classes\Filter(); + if ($oItem && $oItem->unserializeFromJson($sDecodedLine)) + { + $aResult[] = $oItem; + } + } + } + } + } + } + + return $aResult; + } + + /** + * @param string $sValue + * + * @return string + */ + public function quote($sValue) + { + return \str_replace(array('\\', '"'), array('\\\\', '\\"'), \trim($sValue)); + } + + /** + * @param \MailSo\Log\Logger $oLogger + */ + public function SetLogger($oLogger) + { + $this->oLogger = $oLogger instanceof \MailSo\Log\Logger ? $oLogger : null; + } +}