diff options
author | Daniel Kesselberg <mail@danielkesselberg.de> | 2021-01-27 18:54:37 +0300 |
---|---|---|
committer | Christoph Wurst <christoph@winzerhof-wurst.at> | 2021-02-26 18:36:46 +0300 |
commit | 82ab9d00075bf00d611e819ef9c3a6cc72c12c26 (patch) | |
tree | d59f99e49ebfe0be9c389ae8aee7311c3d9040d8 /lib | |
parent | 685580fff2859112bc1dd37b3823f0ada0950811 (diff) |
Add Sieve support
* Expose managesieve port
* Add sieve client factory
* Add support for sieve to provisioning
* Refactor test connectivity logic and add sieve.
* Add support for sieve to provisioning
* Add sieve to account form
* Add debug logger for ManageSieve
* Add api to get and update active script
* Add error for managesieve exception
* Add text editor to update existing script
Signed-off-by: Daniel Kesselberg <mail@danielkesselberg.de>
Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Controller/SettingsController.php | 14 | ||||
-rw-r--r-- | lib/Controller/SieveController.php | 219 | ||||
-rw-r--r-- | lib/Db/MailAccount.php | 35 | ||||
-rw-r--r-- | lib/Exception/CouldNotConnectException.php | 36 | ||||
-rw-r--r-- | lib/Migration/AddSieveToProvisioningConfig.php | 85 | ||||
-rw-r--r-- | lib/Migration/Version1090Date20210127160127.php | 56 | ||||
-rw-r--r-- | lib/Service/Provisioning/Config.php | 39 | ||||
-rw-r--r-- | lib/Service/Provisioning/Manager.php | 25 | ||||
-rw-r--r-- | lib/Service/SetupService.php | 37 | ||||
-rw-r--r-- | lib/Settings/AdminSettings.php | 5 | ||||
-rw-r--r-- | lib/Sieve/SieveClientFactory.php | 116 | ||||
-rw-r--r-- | lib/Sieve/SieveLogger.php | 46 |
12 files changed, 708 insertions, 5 deletions
diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php index 829068fc2..d65f04f03 100644 --- a/lib/Controller/SettingsController.php +++ b/lib/Controller/SettingsController.php @@ -50,7 +50,12 @@ class SettingsController extends Controller { string $smtpUser, string $smtpHost, int $smtpPort, - string $smtpSslMode): JSONResponse { + string $smtpSslMode, + bool $sieveEnabled, + string $sieveUser, + string $sieveHost, + int $sievePort, + string $sieveSslMode): JSONResponse { $this->provisioningManager->newProvisioning( $emailTemplate, $imapUser, @@ -60,7 +65,12 @@ class SettingsController extends Controller { $smtpUser, $smtpHost, $smtpPort, - $smtpSslMode + $smtpSslMode, + $sieveEnabled, + $sieveUser, + $sieveHost, + $sievePort, + $sieveSslMode ); return new JSONResponse([]); diff --git a/lib/Controller/SieveController.php b/lib/Controller/SieveController.php new file mode 100644 index 000000000..11fc5b186 --- /dev/null +++ b/lib/Controller/SieveController.php @@ -0,0 +1,219 @@ +<?php + +declare(strict_types=1); + +/** + * @author Daniel Kesselberg <mail@danielkesselberg.de> + * + * Mail + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\Mail\Controller; + +use Horde\ManageSieve\Exception as ManagesieveException; +use OCA\Mail\AppInfo\Application; +use OCA\Mail\Db\MailAccountMapper; +use OCA\Mail\Exception\ClientException; +use OCA\Mail\Exception\CouldNotConnectException; +use OCA\Mail\Service\AccountService; +use OCA\Mail\Sieve\SieveClientFactory; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Http\JSONResponse; +use OCP\IRequest; +use OCP\Security\ICrypto; + +class SieveController extends Controller { + + /** @var AccountService */ + private $accountService; + + /** @var MailAccountMapper */ + private $mailAccountMapper; + + /** @var SieveClientFactory */ + private $sieveClientFactory; + + /** @var string */ + private $currentUserId; + + /** @var ICrypto */ + private $crypto; + + /** + * AccountsController constructor. + * + * @param IRequest $request + * @param string $UserId + * @param AccountService $accountService + * @param MailAccountMapper $mailAccountMapper + * @param SieveClientFactory $sieveClientFactory + * @param ICrypto $crypto + */ + public function __construct(IRequest $request, + string $UserId, + AccountService $accountService, + MailAccountMapper $mailAccountMapper, + SieveClientFactory $sieveClientFactory, + ICrypto $crypto + ) { + parent::__construct(Application::APP_ID, $request); + $this->currentUserId = $UserId; + $this->accountService = $accountService; + $this->mailAccountMapper = $mailAccountMapper; + $this->sieveClientFactory = $sieveClientFactory; + $this->crypto = $crypto; + } + + /** + * @NoAdminRequired + * @TrapError + * + * @param int $id account id + * + * @return JSONResponse + * + * @throws CouldNotConnectException + * @throws ClientException + */ + public function getActiveScript(int $id): JSONResponse { + $sieve = $this->getClient($id); + + $scriptName = $sieve->getActive(); + if ($scriptName === null) { + $script = ''; + } else { + $script = $sieve->getScript($scriptName); + } + + return new JSONResponse([ + 'scriptName' => $scriptName, + 'script' => $script, + ]); + } + + /** + * @NoAdminRequired + * @TrapError + * + * @param int $id account id + * @param string $script + * + * @return JSONResponse + * + * @throws ClientException + * @throws CouldNotConnectException + * @throws ManagesieveException + */ + public function updateActiveScript(int $id, string $script): JSONResponse { + $sieve = $this->getClient($id); + + $scriptName = $sieve->getActive() ?? 'nextcloud'; + $sieve->installScript($scriptName, $script, true); + + return new JSONResponse(); + } + + /** + * @NoAdminRequired + * @TrapError + * + * @param int $id account id + * @param bool $sieveEnabled + * @param string $sieveHost + * @param int $sievePort + * @param string $sieveUser + * @param string $sievePassword + * @param string $sieveSslMode + * + * @return JSONResponse + * + * @throws CouldNotConnectException + * @throws DoesNotExistException + */ + public function updateAccount(int $id, + bool $sieveEnabled, + string $sieveHost, + int $sievePort, + string $sieveUser, + string $sievePassword, + string $sieveSslMode + ): JSONResponse { + $mailAccount = $this->mailAccountMapper->find($this->currentUserId, $id); + + if ($sieveEnabled === false) { + $mailAccount->setSieveEnabled(false); + $mailAccount->setSieveHost(null); + $mailAccount->setSievePort(null); + $mailAccount->setSieveUser(null); + $mailAccount->setSievePassword(null); + $mailAccount->setSieveSslMode(null); + + $this->mailAccountMapper->save($mailAccount); + return new JSONResponse(['sieveEnabled' => $mailAccount->isSieveEnabled()]); + } + + if (empty($sieveUser)) { + $sieveUser = $mailAccount->getInboundUser(); + } + + if (empty($sievePassword)) { + $sievePassword = $mailAccount->getInboundPassword(); + } else { + $sievePassword = $this->crypto->encrypt($sievePassword); + } + + try { + $this->sieveClientFactory->createClient($sieveHost, $sievePort, $sieveUser, $sievePassword, $sieveSslMode); + } catch (ManagesieveException $e) { + throw CouldNotConnectException::create($e, 'ManageSieve', $sieveHost, $sievePort); + } + + $mailAccount->setSieveEnabled(true); + $mailAccount->setSieveHost($sieveHost); + $mailAccount->setSievePort($sievePort); + $mailAccount->setSieveUser($mailAccount->getInboundUser() === $sieveUser ? null : $sieveUser); + $mailAccount->setSievePassword($mailAccount->getInboundPassword() === $sievePassword ? null : $sievePassword); + $mailAccount->setSieveSslMode($sieveSslMode); + + $this->mailAccountMapper->save($mailAccount); + return new JSONResponse(['sieveEnabled' => $mailAccount->isSieveEnabled()]); + } + + /** + * @param int $id + * + * @return \Horde\ManageSieve + * + * @throws ClientException + * @throws CouldNotConnectException + */ + protected function getClient(int $id): \Horde\ManageSieve { + $account = $this->accountService->find($this->currentUserId, $id); + + if (!$account->getMailAccount()->isSieveEnabled()) { + throw new CouldNotConnectException('ManageSieve is disabled.'); + } + + try { + $sieve = $this->sieveClientFactory->getClient($account); + } catch (ManagesieveException $e) { + throw CouldNotConnectException::create($e, 'ManageSieve', $account->getMailAccount()->getSieveHost(), $account->getMailAccount()->getSievePort()); + } + + return $sieve; + } +} diff --git a/lib/Db/MailAccount.php b/lib/Db/MailAccount.php index b5aa8ecae..3df3c1a67 100644 --- a/lib/Db/MailAccount.php +++ b/lib/Db/MailAccount.php @@ -77,6 +77,18 @@ use OCP\AppFramework\Db\Entity; * @method int|null getSentMailboxId() * @method void setTrashMailboxId(?int $id) * @method int|null getTrashMailboxId() + * @method bool isSieveEnabled() + * @method void setSieveEnabled(bool $sieveEnabled) + * @method string|null getSieveHost() + * @method void setSieveHost(?string $sieveHost) + * @method int|null getSievePort() + * @method void setSievePort(?int $sievePort) + * @method string|null getSieveSslMode() + * @method void setSieveSslMode(?string $sieveSslMode) + * @method string|null getSieveUser() + * @method void setSieveUser(?string $sieveUser) + * @method string|null getSievePassword() + * @method void setSievePassword(?string $sievePassword) */ class MailAccount extends Entity { protected $userId; @@ -109,6 +121,19 @@ class MailAccount extends Entity { /** @var int|null */ protected $trashMailboxId; + /** @var bool */ + protected $sieveEnabled = false; + /** @var string|null */ + protected $sieveHost; + /** @var integer|null */ + protected $sievePort; + /** @var string|null */ + protected $sieveSslMode; + /** @var string|null */ + protected $sieveUser; + /** @var string|null */ + protected $sievePassword; + /** * @param array $params */ @@ -168,6 +193,8 @@ class MailAccount extends Entity { $this->addType('draftsMailboxId', 'integer'); $this->addType('sentMailboxId', 'integer'); $this->addType('trashMailboxId', 'integer'); + $this->addType('sieveEnabled', 'boolean'); + $this->addType('sievePort', 'integer'); } /** @@ -192,6 +219,7 @@ class MailAccount extends Entity { 'draftsMailboxId' => $this->getDraftsMailboxId(), 'sentMailboxId' => $this->getSentMailboxId(), 'trashMailboxId' => $this->getTrashMailboxId(), + 'sieveEnabled' => $this->isSieveEnabled(), ]; if (!is_null($this->getOutboundHost())) { @@ -201,6 +229,13 @@ class MailAccount extends Entity { $result['smtpSslMode'] = $this->getOutboundSslMode(); } + if ($this->isSieveEnabled()) { + $result['sieveHost'] = $this->getSieveHost(); + $result['sievePort'] = $this->getSievePort(); + $result['sieveUser'] = $this->getSieveUser(); + $result['sieveSslMode'] = $this->getSieveSslMode(); + } + return $result; } } diff --git a/lib/Exception/CouldNotConnectException.php b/lib/Exception/CouldNotConnectException.php new file mode 100644 index 000000000..c996095ee --- /dev/null +++ b/lib/Exception/CouldNotConnectException.php @@ -0,0 +1,36 @@ +<?php + +declare(strict_types=1); + +/** + * @author Daniel Kesselberg <mail@danielkesselberg.de> + * + * Mail + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\Mail\Exception; + +use Throwable; + +class CouldNotConnectException extends ServiceException { + public static function create(Throwable $exception, string $service, string $host, int $port): self { + return new self( + "Connection to {$service} at {$host}:{$port} failed. {$exception->getMessage()}", + (int)$exception->getCode(), + $exception + ); + } +} diff --git a/lib/Migration/AddSieveToProvisioningConfig.php b/lib/Migration/AddSieveToProvisioningConfig.php new file mode 100644 index 000000000..e871ff1cc --- /dev/null +++ b/lib/Migration/AddSieveToProvisioningConfig.php @@ -0,0 +1,85 @@ +<?php + +declare(strict_types=1); + +/** + * @author Daniel Kesselberg <mail@danielkesselberg.de> + * + * Mail + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\Mail\Migration; + +use OCA\Mail\Service\Provisioning\Config as ProvisioningConfig; +use OCA\Mail\Service\Provisioning\ConfigMapper as ProvisioningConfigMapper; +use OCP\IConfig; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class AddSieveToProvisioningConfig implements IRepairStep { + + /** @var IConfig */ + private $config; + + /** @var ProvisioningConfigMapper */ + private $configMapper; + + public function __construct(IConfig $config, ProvisioningConfigMapper $configMapper) { + $this->config = $config; + $this->configMapper = $configMapper; + } + + public function getName(): string { + return 'Add sieve defaults to provisioning config'; + } + + public function run(IOutput $output) { + if (!$this->shouldRun()) { + return; + } + + $config = $this->configMapper->load(); + if ($config === null) { + return; + } + + $reflectionClass = new \ReflectionClass(ProvisioningConfig::class); + $reflectionProperty = $reflectionClass->getProperty('data'); + + $reflectionProperty->setAccessible(true); + $data = $reflectionProperty->getValue($config); + + if (!isset($data['sieveEnabled'])) { + $data = array_merge($data, [ + 'sieveEnabled' => false, + 'sieveHost' => '', + 'sievePort' => 4190, + 'sieveUser' => '', + 'sieveSslMode' => 'tls', + ]); + } + + $reflectionProperty->setValue($config, $data); + $this->configMapper->save($config); + + $output->info('added sieve defaults to provisioning config'); + } + + protected function shouldRun(): bool { + $appVersion = $this->config->getAppValue('mail', 'installed_version', '0.0.0'); + return version_compare($appVersion, '1.9.0', '<'); + } +} diff --git a/lib/Migration/Version1090Date20210127160127.php b/lib/Migration/Version1090Date20210127160127.php new file mode 100644 index 000000000..8a52ae4fc --- /dev/null +++ b/lib/Migration/Version1090Date20210127160127.php @@ -0,0 +1,56 @@ +<?php + +declare(strict_types=1); + +namespace OCA\Mail\Migration; + +use Closure; +use OCP\DB\ISchemaWrapper; +use OCP\Migration\IOutput; +use OCP\Migration\SimpleMigrationStep; + +class Version1090Date20210127160127 extends SimpleMigrationStep { + /** + * @param IOutput $output + * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + * @return null|ISchemaWrapper + */ + public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + + $table = $schema->getTable('mail_accounts'); + $table->addColumn('sieve_enabled', 'boolean', [ + 'notnull' => true, + 'default' => false, + ]); + $table->addColumn('sieve_host', 'string', [ + 'notnull' => false, + 'length' => 64, + 'default' => null, + ]); + $table->addColumn('sieve_port', 'string', [ + 'notnull' => false, + 'length' => 6, + 'default' => null, + ]); + $table->addColumn('sieve_ssl_mode', 'string', [ + 'notnull' => false, + 'length' => 10, + 'default' => null, + ]); + $table->addColumn('sieve_user', 'string', [ + 'notnull' => false, + 'length' => 64, + 'default' => null, + ]); + $table->addColumn('sieve_password', 'string', [ + 'notnull' => false, + 'length' => 2048, + 'default' => null, + ]); + + return $schema; + } +} diff --git a/lib/Service/Provisioning/Config.php b/lib/Service/Provisioning/Config.php index 2af2f1827..74b8cda8d 100644 --- a/lib/Service/Provisioning/Config.php +++ b/lib/Service/Provisioning/Config.php @@ -111,6 +111,45 @@ class Config implements JsonSerializable { } /** + * @return boolean + */ + public function getSieveEnabled(): bool { + return (bool)$this->data['sieveEnabled']; + } + + /** + * @return string + */ + public function getSieveHost() { + return $this->data['sieveHost']; + } + + /** + * @return int + */ + public function getSievePort(): int { + return (int)$this->data['sievePort']; + } + + /** + * @param IUser $user + * @return string + */ + public function buildSieveUser(IUser $user) { + if (isset($this->data['sieveUser'])) { + return $this->buildUserEmail($this->data['sieveUser'], $user); + } + return $this->buildEmail($user); + } + + /** + * @return string + */ + public function getSieveSslMode() { + return $this->data['sieveSslMode']; + } + + /** * Replace %USERID% and %EMAIL% to allow special configurations * * @param string $original diff --git a/lib/Service/Provisioning/Manager.php b/lib/Service/Provisioning/Manager.php index c948f0e69..742c87f13 100644 --- a/lib/Service/Provisioning/Manager.php +++ b/lib/Service/Provisioning/Manager.php @@ -100,7 +100,12 @@ class Manager { string $smtpUser, string $smtpHost, int $smtpPort, - string $smtpSslMode): void { + string $smtpSslMode, + bool $sieveEnabled, + string $sieveUser, + string $sieveHost, + int $sievePort, + string $sieveSslMode): void { $config = $this->configMapper->save(new Config([ 'active' => true, 'email' => $email, @@ -112,6 +117,11 @@ class Manager { 'smtpHost' => $smtpHost, 'smtpPort' => $smtpPort, 'smtpSslMode' => $smtpSslMode, + 'sieveEnabled' => $sieveEnabled, + 'sieveUser' => $sieveUser, + 'sieveHost' => $sieveHost, + 'sievePort' => $sievePort, + 'sieveSslMode' => $sieveSslMode, ])); $this->provision($config); @@ -128,6 +138,19 @@ class Manager { $account->setOutboundHost($config->getSmtpHost()); $account->setOutboundPort($config->getSmtpPort()); $account->setOutboundSslMode($config->getSmtpSslMode()); + $account->setSieveEnabled($config->getSieveEnabled()); + + if ($config->getSieveEnabled()) { + $account->setSieveUser($config->buildSieveUser($user)); + $account->setSieveHost($config->getSieveHost()); + $account->setSievePort($config->getSievePort()); + $account->setSieveSslMode($config->getSieveSslMode()); + } else { + $account->setSieveUser(null); + $account->setSieveHost(null); + $account->setSievePort(null); + $account->setSieveSslMode(null); + } return $account; } diff --git a/lib/Service/SetupService.php b/lib/Service/SetupService.php index 0be5984bf..ffd6146ac 100644 --- a/lib/Service/SetupService.php +++ b/lib/Service/SetupService.php @@ -26,9 +26,14 @@ declare(strict_types=1); namespace OCA\Mail\Service; +use Horde_Imap_Client_Exception; +use Horde_Mail_Exception; +use Horde_Mail_Transport_Smtphorde; use OCA\Mail\Account; use OCA\Mail\Db\MailAccount; +use OCA\Mail\Exception\CouldNotConnectException; use OCA\Mail\Exception\ServiceException; +use OCA\Mail\IMAP\IMAPClientFactory; use OCA\Mail\Service\AutoConfig\AutoConfig; use OCA\Mail\SMTP\SmtpClientFactory; use OCP\Security\ICrypto; @@ -48,6 +53,9 @@ class SetupService { /** @var SmtpClientFactory */ private $smtpClientFactory; + /** @var IMAPClientFactory */ + private $imapClientFactory; + /** var LoggerInterface */ private $logger; @@ -55,11 +63,13 @@ class SetupService { AccountService $accountService, ICrypto $crypto, SmtpClientFactory $smtpClientFactory, + IMAPClientFactory $imapClientFactory, LoggerInterface $logger) { $this->autoConfig = $autoConfig; $this->accountService = $accountService; $this->crypto = $crypto; $this->smtpClientFactory = $smtpClientFactory; + $this->imapClientFactory = $imapClientFactory; $this->logger = $logger; } @@ -124,12 +134,35 @@ class SetupService { $account = new Account($newAccount); $this->logger->debug('Connecting to account {account}', ['account' => $newAccount->getEmail()]); - $transport = $this->smtpClientFactory->create($account); - $account->testConnectivity($transport); + $this->testConnectivity($account); $this->accountService->save($newAccount); $this->logger->debug("account created " . $newAccount->getId()); return $account; } + + /** + * @param Account $account + * @throws CouldNotConnectException + */ + protected function testConnectivity(Account $account): void { + $mailAccount = $account->getMailAccount(); + + $imapClient = $this->imapClientFactory->getClient($account); + try { + $imapClient->login(); + } catch (Horde_Imap_Client_Exception $e) { + throw CouldNotConnectException::create($e, 'IMAP', $mailAccount->getInboundHost(), $mailAccount->getInboundPort()); + } + + $transport = $this->smtpClientFactory->create($account); + if ($transport instanceof Horde_Mail_Transport_Smtphorde) { + try { + $transport->getSMTPObject(); + } catch (Horde_Mail_Exception $e) { + throw CouldNotConnectException::create($e, 'SMTP', $mailAccount->getOutboundHost(), $mailAccount->getOutboundPort()); + } + } + } } diff --git a/lib/Settings/AdminSettings.php b/lib/Settings/AdminSettings.php index 532749c85..7e4c47c98 100644 --- a/lib/Settings/AdminSettings.php +++ b/lib/Settings/AdminSettings.php @@ -61,6 +61,11 @@ class AdminSettings implements ISettings { 'smtpHost' => 'smtp.domain.com', 'smtpPort' => 587, 'smtpSslMode' => 'tls', + 'sieveEnabled' => false, + 'sieveUser' => '%USERID%@domain.com', + 'sieveHost' => 'imap.domain.com', + 'sievePort' => 4190, + 'sieveSslMode' => 'tls', ]) ); diff --git a/lib/Sieve/SieveClientFactory.php b/lib/Sieve/SieveClientFactory.php new file mode 100644 index 000000000..4e7359741 --- /dev/null +++ b/lib/Sieve/SieveClientFactory.php @@ -0,0 +1,116 @@ +<?php + +declare(strict_types=1); + +/** + * @author Daniel Kesselberg <mail@danielkesselberg.de> + * + * Mail + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\Mail\Sieve; + +use Horde\ManageSieve; +use OCA\Mail\Account; +use OCP\IConfig; +use OCP\Security\ICrypto; + +class SieveClientFactory { + + /** @var ICrypto */ + private $crypto; + + /** @var IConfig */ + private $config; + + private $cache = []; + + /** + * @param ICrypto $crypto + * @param IConfig $config + */ + public function __construct(ICrypto $crypto, IConfig $config) { + $this->crypto = $crypto; + $this->config = $config; + } + + /** + * @param Account $account + * @return ManageSieve + * @throws ManageSieve\Exception + */ + public function getClient(Account $account): ManageSieve { + if (!isset($this->cache[$account->getId()])) { + $user = $account->getMailAccount()->getSieveUser(); + if (empty($user)) { + $user = $account->getMailAccount()->getInboundUser(); + } + $password = $account->getMailAccount()->getSievePassword(); + if (empty($password)) { + $password = $account->getMailAccount()->getInboundPassword(); + } + + $this->cache[$account->getId()] = $this->createClient( + $account->getMailAccount()->getSieveHost(), + $account->getMailAccount()->getSievePort(), + $user, + $password, + $account->getMailAccount()->getSieveSslMode() + ); + } + + return $this->cache[$account->getId()]; + } + + /** + * @param string $host + * @param int $port + * @param string $user + * @param string $password + * @param string $sslMode + * @return ManageSieve + * @throws ManageSieve\Exception + */ + public function createClient(string $host, int $port, string $user, string $password, string $sslMode): ManageSieve { + if (empty($sslMode)) { + $sslMode = true; + } elseif ($sslMode === 'none') { + $sslMode = false; + } + + $params = [ + 'host' => $host, + 'port' => $port, + 'user' => $user, + 'password' => $this->crypto->decrypt($password), + 'secure' => $sslMode, + 'timeout' => (int)$this->config->getSystemValue('app.mail.sieve.timeout', 5), + 'context' => [ + 'ssl' => [ + 'verify_peer' => $this->config->getSystemValueBool('app.mail.verify-tls-peer', true), + 'verify_peer_name' => $this->config->getSystemValueBool('app.mail.verify-tls-peer', true), + + ] + ], + ]; + + if ($this->config->getSystemValue('debug', false)) { + $params['logger'] = new SieveLogger($this->config->getSystemValue('datadirectory') . '/horde_sieve.log'); + } + + return new ManageSieve($params); + } +} diff --git a/lib/Sieve/SieveLogger.php b/lib/Sieve/SieveLogger.php new file mode 100644 index 000000000..752467cdf --- /dev/null +++ b/lib/Sieve/SieveLogger.php @@ -0,0 +1,46 @@ +<?php + +declare(strict_types=1); + +/** + * @author Daniel Kesselberg <mail@danielkesselberg.de> + * + * Mail + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\Mail\Sieve; + +class SieveLogger { + /** @var resource */ + protected $stream; + + public function __construct(string $logFile) { + $stream = @fopen($logFile, 'ab'); + if ($stream === false) { + throw new \InvalidArgumentException('Unable to use "' . $logFile . '" as log file for sieve.'); + } + $this->stream = $stream; + } + + public function debug(string $message): void { + fwrite($this->stream, $message); + } + + public function __destruct() { + fflush($this->stream); + fclose($this->stream); + } +} |