← Back
Editing: HordeImapClient.php
<?php declare(strict_types=1); /** * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Mail\IMAP; use Horde_Imap_Client_Exception; use Horde_Imap_Client_Exception_NoSupportExtension; use Horde_Imap_Client_Socket; use OCP\AppFramework\Utility\ITimeFactory; use OCP\IMemcache; use OCP\IMemcacheTTL; use function floor; /** * "Decorator" around Horde's IMAP client to add auth error rate limiting. * * This is not a real decorator because the component to decorate doesn't have * an interface, making it hard to base a decorator on composition. * For simplicity the component is decorated by inheritance. */ class HordeImapClient extends Horde_Imap_Client_Socket { private ?IMemcache $rateLimiterCache = null; private ?ITimeFactory $timeFactory = null; private ?string $hash = null; private IMAPClientFactory $factory; public function __construct(array $params, IMAPClientFactory $factory) { parent::__construct($params); $this->factory = $factory; } public function enableRateLimiter( IMemcache $cache, string $hash, ITimeFactory $timeFactory, ): void { $this->rateLimiterCache = $cache; $this->timeFactory = $timeFactory; $this->hash = $hash; } #[\Override] public function login() { parent::login(); if ($this->capability->query('ID')) { try { $this->sendID(); /* ID is queued - force sending the queued command. */ $this->_sendCmd($this->_pipeline()); } catch (Horde_Imap_Client_Exception_NoSupportExtension) { // Ignore if server doesn't support ID extension. } } } private const RATE_LIMIT_WINDOW = 3 * 60 * 60; protected function imapLogin() { $result = parent::_login(); $this->factory->recordLogin($this->_params['hostspec']); return $result; } #[\Override] protected function _login() { if ($this->rateLimiterCache === null) { return $this->imapLogin(); } $now = $this->timeFactory->getTime(); $window = floor($now / self::RATE_LIMIT_WINDOW); $cacheKey = $this->hash . (string)$window; $counter = $this->rateLimiterCache->get($cacheKey); if ($counter !== null && $counter >= 3) { // Enough errors. Let's fail without involving IMAP throw new Horde_Imap_Client_Exception( 'Too many auth attempts', Horde_Imap_Client_Exception::LOGIN_AUTHENTICATIONFAILED ); } try { return $this->imapLogin(); } catch (Horde_Imap_Client_Exception $e) { if ($e->getCode() === Horde_Imap_Client_Exception::LOGIN_AUTHENTICATIONFAILED && $e->getMessage() === 'Authentication failed.') { $this->rateLimiterCache->inc($cacheKey); if ($this->rateLimiterCache instanceof IMemcacheTTL) { $this->rateLimiterCache->setTTL($cacheKey, self::RATE_LIMIT_WINDOW); } } throw $e; } } }
Save File
Cancel