Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle IMAP disconnects more explicitly #6174

Merged
merged 1 commit into from
Apr 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion lib/Command/DiagnoseAccount.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$output->writeln('<error>No IMAP passwort set. The user might have to log into their account to set it.</error>');
}
$imapClient = $this->clientFactory->getClient($account);

try {
$this->printCapabilitiesStats($output, $imapClient);
$this->printMailboxesMessagesStats($output, $imapClient);
Expand All @@ -94,6 +93,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int
]);
$output->writeln("<error>Horde error occurred: " . $e->getMessage() . ". See nextcloud.log for more details.</error>");
return 2;
} finally {
$imapClient->logout();
}

return 0;
Expand Down
74 changes: 37 additions & 37 deletions lib/IMAP/IMAPClientFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ class IMAPClientFactory {
/** @var ICacheFactory */
private $cacheFactory;

private $cache = [];

/**
* @param ICrypto $crypto
* @param IConfig $config
Expand All @@ -55,47 +53,49 @@ public function __construct(ICrypto $crypto, IConfig $config, ICacheFactory $cac
}

/**
* Get the connection object for the given account
*
* Connections are not closed until destruction, so the caller site is
* responsible to log out as soon as possible to keep the number of open
* (and stale) connections at a minimum.
*
* @param Account $account
* @return Horde_Imap_Client_Socket
*/
public function getClient(Account $account): Horde_Imap_Client_Socket {
if (!isset($this->cache[$account->getId()])) {
$host = $account->getMailAccount()->getInboundHost();
$user = $account->getMailAccount()->getInboundUser();
$password = $account->getMailAccount()->getInboundPassword();
$password = $this->crypto->decrypt($password);
$port = $account->getMailAccount()->getInboundPort();
$sslMode = $account->getMailAccount()->getInboundSslMode();
if ($sslMode === 'none') {
$sslMode = false;
}
$host = $account->getMailAccount()->getInboundHost();
$user = $account->getMailAccount()->getInboundUser();
$password = $account->getMailAccount()->getInboundPassword();
$password = $this->crypto->decrypt($password);
$port = $account->getMailAccount()->getInboundPort();
$sslMode = $account->getMailAccount()->getInboundSslMode();
if ($sslMode === 'none') {
$sslMode = false;
}

$params = [
'username' => $user,
'password' => $password,
'hostspec' => $host,
'port' => $port,
'secure' => $sslMode,
'timeout' => (int)$this->config->getSystemValue('app.mail.imap.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),
],
$params = [
'username' => $user,
'password' => $password,
'hostspec' => $host,
'port' => $port,
'secure' => $sslMode,
'timeout' => (int)$this->config->getSystemValue('app.mail.imap.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->cacheFactory->isAvailable()) {
$params['cache'] = [
'backend' => new Cache([
'cacheob' => $this->cacheFactory->createDistributed(md5((string)$account->getId())),
])];
}
if ($this->config->getSystemValue('debug', false)) {
$params['debug'] = $this->config->getSystemValue('datadirectory') . '/horde_imap.log';
}
$this->cache[$account->getId()] = new Horde_Imap_Client_Socket($params);
],
];
if ($this->cacheFactory->isAvailable()) {
$params['cache'] = [
'backend' => new Cache([
'cacheob' => $this->cacheFactory->createDistributed(md5((string)$account->getId())),
])];
}

return $this->cache[$account->getId()];
if ($this->config->getSystemValue('debug', false)) {
$params['debug'] = $this->config->getSystemValue('datadirectory') . '/horde_imap.log';
}
return new Horde_Imap_Client_Socket($params);
}
}
5 changes: 4 additions & 1 deletion lib/IMAP/MailboxSync.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ public function sync(Account $account,
);
} catch (Horde_Imap_Client_Exception $e) {
$logger->debug('Getting namespaces for account ' . $account->getId() . ' failed: ' . $e->getMessage());
} finally {
$client->logout();
}

try {
Expand Down Expand Up @@ -137,7 +139,6 @@ public function sync(Account $account,
*/
public function syncStats(Account $account, Mailbox $mailbox): void {
$client = $this->imapClientFactory->getClient($account);

try {
$stats = $this->folderMapper->getFoldersStatusAsObject($client, $mailbox->getName());
} catch (Horde_Imap_Client_Exception $e) {
Expand All @@ -147,6 +148,8 @@ public function syncStats(Account $account, Mailbox $mailbox): void {
(int)$e->getCode(),
$e
);
} finally {
$client->logout();
}

$mailbox->setMessages($stats->getTotal());
Expand Down
5 changes: 4 additions & 1 deletion lib/IMAP/PreviewEnhancer.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,10 @@ public function process(Account $account, Mailbox $mailbox, array $messages): ar
return $messages;
}

$client = $this->clientFactory->getClient($account);
try {
$data = $this->imapMapper->getBodyStructureData(
$this->clientFactory->getClient($account),
$client,
$mailbox->getName(),
$needAnalyze
);
Expand All @@ -94,6 +95,8 @@ public function process(Account $account, Mailbox $mailbox, array $messages): ar
]);

return $messages;
} finally {
$client->logout();
}

return $this->mapper->updatePreviewDataBulk(...array_map(function (Message $message) use ($data) {
Expand Down
3 changes: 2 additions & 1 deletion lib/IMAP/Search/Provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,15 @@ public function findMatches(Account $account,
Mailbox $mailbox,
SearchQuery $searchQuery): array {
$client = $this->clientFactory->getClient($account);

try {
$fetchResult = $client->search(
$mailbox->getName(),
$this->convertMailQueryToHordeQuery($searchQuery)
);
} catch (Horde_Imap_Client_Exception $e) {
throw new ServiceException('Could not get message IDs: ' . $e->getMessage(), 0, $e);
} finally {
$client->logout();
}

return $fetchResult['match']->ids;
Expand Down
2 changes: 2 additions & 0 deletions lib/Listener/DeleteDraftListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ private function deleteDraft(Account $account, Message $draft): void {
} catch (DoesNotExistException $e) {
$this->logger->warning("Account has no draft mailbox set, can't delete the draft");
return;
} finally {
$client->logout();
}

try {
Expand Down
4 changes: 3 additions & 1 deletion lib/Listener/FlagRepliedMessageListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ public function handle(Event $event): void {
return;
}

$client = $this->imapClientFactory->getClient($event->getAccount());
try {
$client = $this->imapClientFactory->getClient($event->getAccount());
$this->messageMapper->addFlag(
$client,
$mailbox,
Expand All @@ -90,6 +90,8 @@ public function handle(Event $event): void {
$this->logger->warning('Could not flag replied message: ' . $e, [
'exception' => $e,
]);
} finally {
$client->logout();
}
}
}
5 changes: 4 additions & 1 deletion lib/Listener/SaveSentMessageListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,17 @@ public function handle(Event $event): void {
return;
}

$client = $this->imapClientFactory->getClient($event->getAccount());
try {
$this->messageMapper->save(
$this->imapClientFactory->getClient($event->getAccount()),
$client,
$sentMailbox,
$event->getMail()
);
} catch (Horde_Imap_Client_Exception $e) {
throw new ServiceException('Could not save sent message on IMAP', 0, $e);
} finally {
$client->logout();
}
}
}
44 changes: 26 additions & 18 deletions lib/Migration/MigrateImportantFromImapAndDb.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,34 +65,42 @@ public function __construct(IMAPClientFactory $clientFactory,

public function migrateImportantOnImap(Account $account, Mailbox $mailbox): void {
$client = $this->clientFactory->getClient($account);
//get all messages that have an $important label from IMAP
try {
$uids = $this->messageMapper->getFlagged($client, $mailbox, '$important');
} catch (Horde_Imap_Client_Exception $e) {
throw new ServiceException("Could not fetch UIDs of important messages: " . $e->getMessage(), 0, $e);
}
// add $label1 for all that are tagged on IMAP
if (!empty($uids)) {
//get all messages that have an $important label from IMAP
try {
$this->messageMapper->addFlag($client, $mailbox, $uids, Tag::LABEL_IMPORTANT);
$uids = $this->messageMapper->getFlagged($client, $mailbox, '$important');
} catch (Horde_Imap_Client_Exception $e) {
$this->logger->debug('Could not flag messages in mailbox <' . $mailbox->getId() . '>');
throw new ServiceException($e->getMessage(), 0, $e);
throw new ServiceException("Could not fetch UIDs of important messages: " . $e->getMessage(), 0, $e);
}
// add $label1 for all that are tagged on IMAP
if (!empty($uids)) {
try {
$this->messageMapper->addFlag($client, $mailbox, $uids, Tag::LABEL_IMPORTANT);
} catch (Horde_Imap_Client_Exception $e) {
$this->logger->debug('Could not flag messages in mailbox <' . $mailbox->getId() . '>');
throw new ServiceException($e->getMessage(), 0, $e);
}
}
} finally {
$client->logout();
}
}

public function migrateImportantFromDb(Account $account, Mailbox $mailbox): void {
$client = $this->clientFactory->getClient($account);
$uids = $this->mailboxMapper->findFlaggedImportantUids($mailbox->getId());
// store our data on imap
if (!empty($uids)) {
try {
$this->messageMapper->addFlag($client, $mailbox, $uids, Tag::LABEL_IMPORTANT);
} catch (Horde_Imap_Client_Exception $e) {
$this->logger->debug('Could not flag messages in mailbox <' . $mailbox->getId() . '>');
throw new ServiceException($e->getMessage(), 0, $e);
try {
$uids = $this->mailboxMapper->findFlaggedImportantUids($mailbox->getId());
// store our data on imap
if (!empty($uids)) {
try {
$this->messageMapper->addFlag($client, $mailbox, $uids, Tag::LABEL_IMPORTANT);
} catch (Horde_Imap_Client_Exception $e) {
$this->logger->debug('Could not flag messages in mailbox <' . $mailbox->getId() . '>');
throw new ServiceException($e->getMessage(), 0, $e);
}
}
} finally {
$client->logout();
}
}
}
10 changes: 7 additions & 3 deletions lib/Service/AutoConfig/ImapConnector.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,12 @@ public function connect(string $email,

$a = new Account($account);
$client = $this->clientFactory->getClient($a);
$client->login();
$this->logger->info("Test-Account-Successful: $this->userId, $host, $port, $user, $encryptionProtocol");
return $account;
try {
$client->login();
$this->logger->info("Test-Account-Successful: $this->userId, $host, $port, $user, $encryptionProtocol");
return $account;
} finally {
$client->logout();
}
}
}
25 changes: 14 additions & 11 deletions lib/Service/ItineraryService.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,18 +81,21 @@ public function extract(Account $account, string $mailbox, int $id): Itinerary {
}

$client = $this->clientFactory->getClient($account);

$itinerary = new Itinerary();
$htmlBody = $this->messageMapper->getHtmlBody($client, $mailbox->getName(), $id);
if ($htmlBody !== null) {
$itinerary = $itinerary->merge(
$this->extractor->extract($htmlBody)
);
$this->logger->debug('Extracted ' . count($itinerary) . ' itinerary entries from the message HTML body');
} else {
$this->logger->debug('Message does not have an HTML body, can\'t extract itinerary info');
try {
$itinerary = new Itinerary();
$htmlBody = $this->messageMapper->getHtmlBody($client, $mailbox->getName(), $id);
if ($htmlBody !== null) {
$itinerary = $itinerary->merge(
$this->extractor->extract($htmlBody)
);
$this->logger->debug('Extracted ' . count($itinerary) . ' itinerary entries from the message HTML body');
} else {
$this->logger->debug('Message does not have an HTML body, can\'t extract itinerary info');
}
$attachments = $this->messageMapper->getRawAttachments($client, $mailbox->getName(), $id);
} finally {
$client->logout();
}
$attachments = $this->messageMapper->getRawAttachments($client, $mailbox->getName(), $id);
$itinerary = array_reduce($attachments, function (Itinerary $combined, string $attachment) {
$extracted = $this->extractor->extract($attachment);
$this->logger->debug('Extracted ' . count($extracted) . ' itinerary entries from an attachment');
Expand Down
Loading