Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/mail.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorDaniel Kesselberg <mail@danielkesselberg.de>2021-01-27 18:54:37 +0300
committerChristoph Wurst <christoph@winzerhof-wurst.at>2021-02-26 18:36:46 +0300
commit82ab9d00075bf00d611e819ef9c3a6cc72c12c26 (patch)
treed59f99e49ebfe0be9c389ae8aee7311c3d9040d8 /lib
parent685580fff2859112bc1dd37b3823f0ada0950811 (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.php14
-rw-r--r--lib/Controller/SieveController.php219
-rw-r--r--lib/Db/MailAccount.php35
-rw-r--r--lib/Exception/CouldNotConnectException.php36
-rw-r--r--lib/Migration/AddSieveToProvisioningConfig.php85
-rw-r--r--lib/Migration/Version1090Date20210127160127.php56
-rw-r--r--lib/Service/Provisioning/Config.php39
-rw-r--r--lib/Service/Provisioning/Manager.php25
-rw-r--r--lib/Service/SetupService.php37
-rw-r--r--lib/Settings/AdminSettings.php5
-rw-r--r--lib/Sieve/SieveClientFactory.php116
-rw-r--r--lib/Sieve/SieveLogger.php46
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);
+ }
+}