From 426a0f8c3b70b08919dc3541428a7ffd7be74f09 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Thu, 12 May 2022 11:59:23 +0200 Subject: [PATCH] Improved NetClient connection handling as discovered at #381 --- .../app/libraries/MailSo/Imap/ImapClient.php | 7 +- .../libraries/MailSo/Net/ConnectSettings.php | 65 +++++++++++++ .../app/libraries/MailSo/Net/NetClient.php | 35 ++----- .../MailSo/Sieve/ManageSieveClient.php | 6 +- .../app/libraries/MailSo/Smtp/SmtpClient.php | 7 +- .../libraries/RainLoop/Actions/UserAuth.php | 5 +- .../app/libraries/RainLoop/ActionsAdmin.php | 29 +++--- .../libraries/RainLoop/Config/Application.php | 1 + .../app/libraries/RainLoop/Model/Account.php | 96 ++++++++++--------- 9 files changed, 147 insertions(+), 104 deletions(-) create mode 100644 snappymail/v/0.0.0/app/libraries/MailSo/Net/ConnectSettings.php diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php index 38cbc88c9..7a80be91b 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php @@ -84,14 +84,11 @@ class ImapClient extends \MailSo\Net\NetClient * @throws \MailSo\Net\Exceptions\Exception * @throws \MailSo\Imap\Exceptions\Exception */ - public function Connect(string $sServerName, int $iPort = 143, - int $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT, - bool $bVerifySsl = false, bool $bAllowSelfSigned = true, - string $sClientCert = '') : void + public function Connect(\MailSo\Net\ConnectSettings $oSettings) : void { $this->aTagTimeouts['*'] = \microtime(true); - parent::Connect($sServerName, $iPort, $iSecurityType, $bVerifySsl, $bAllowSelfSigned, $sClientCert); + parent::Connect($oSettings); $this->setCapabilities($this->getResponse('*')); diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Net/ConnectSettings.php b/snappymail/v/0.0.0/app/libraries/MailSo/Net/ConnectSettings.php new file mode 100644 index 000000000..e5ca89a6b --- /dev/null +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Net/ConnectSettings.php @@ -0,0 +1,65 @@ + '', +// 'peer_fingerprint' => '', // string | array + 'verify_peer' => true, + 'verify_peer_name' => true, + 'allow_self_signed' => false, +// 'cafile' => '', +// 'capath' => '', + +// 'ciphers' => 'HIGH:!SSLv2:!SSLv3', + 'SNI_enabled' => true, + 'disable_compression' => true, + 'security_level' => 1, + +// 'local_cert' => '', +// 'local_pk' => '', +// 'passphrase' => '', + +// 'verify_depth' => 0, +// 'capture_peer_cert' => false, +// 'capture_peer_cert_chain' => false, + ]; + + function __construct() + { + // TODO: This should be moved to \RainLoop\Model\Domain + $oConfig = \RainLoop\API::Config(); + $this->ssl['verify_peer'] = !!$oConfig->Get('ssl', 'verify_certificate', false); + $this->ssl['verify_peer_name'] = !!$oConfig->Get('ssl', 'verify_certificate', false); + $this->ssl['allow_self_signed'] = !!$oConfig->Get('ssl', 'allow_self_signed', true); + $this->ssl['security_level'] = (int) $oConfig->Get('ssl', 'security_level', 1); +// $this->ssl['local_cert'] = (string) $oConfig->Get('ssl', 'client_cert', ''); +// $this->ssl['cafile'] = (string) $oConfig->Get('ssl', 'cafile', ''); +// $this->ssl['capath'] = (string) $oConfig->Get('ssl', 'capath', ''); + } + +} diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Net/NetClient.php b/snappymail/v/0.0.0/app/libraries/MailSo/Net/NetClient.php index 498896f7f..fe781a113 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Net/NetClient.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Net/NetClient.php @@ -120,13 +120,10 @@ abstract class NetClient * @throws \MailSo\Net\Exceptions\SocketAlreadyConnectedException * @throws \MailSo\Net\Exceptions\SocketCanNotConnectToHostException */ - public function Connect(string $sServerName, int $iPort, - int $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT, - bool $bVerifySsl = false, bool $bAllowSelfSigned = true, - string $sClientCert = '') : void + public function Connect(ConnectSettings $oSettings) { - if (!\strlen(\trim($sServerName)) || !\MailSo\Base\Validator::PortInt($iPort)) - { + $oSettings->host = \trim($oSettings->host); + if (!\strlen($oSettings->host) || !\MailSo\Base\Validator::PortInt($oSettings->port)) { $this->writeLogException( new \MailSo\Base\Exceptions\InvalidArgumentException, \MailSo\Log\Enumerations\Type::ERROR, true); @@ -139,14 +136,12 @@ abstract class NetClient \MailSo\Log\Enumerations\Type::ERROR, true); } - $sServerName = \trim($sServerName); - $sErrorStr = ''; $iErrorNo = 0; - $this->sConnectedHost = $sServerName; - $this->iConnectedPort = $iPort; - $this->iSecurityType = $iSecurityType; + $this->sConnectedHost = $oSettings->host; + $this->iConnectedPort = $oSettings->port; + $this->iSecurityType = $oSettings->type; $this->bSecure = \MailSo\Net\Enumerations\ConnectionSecurityType::UseSSL( $this->iConnectedPort, $this->iSecurityType); @@ -168,23 +163,9 @@ abstract class NetClient \MailSo\Log\Enumerations\Type::NOTE); $aStreamContextSettings = array( - 'ssl' => array( - 'verify_host' => $bVerifySsl, - 'verify_peer' => $bVerifySsl, - 'verify_peer_name' => $bVerifySsl, - 'allow_self_signed' => $bVerifySsl ? $bAllowSelfSigned : true, -// 'ciphers' => 'HIGH:!SSLv2:!SSLv3', - 'SNI_enabled' => true, -// 'disable_compression' => true, - 'security_level' => 1 - ) + 'ssl' => $oSettings->ssl ); - if (!empty($sClientCert)) - { - $aStreamContextSettings['ssl']['local_cert'] = $sClientCert; - } - \MailSo\Hooks::Run('Net.NetClient.StreamContextSettings/Filter', array(&$aStreamContextSettings)); $rStreamContext = \stream_context_create($aStreamContextSettings); @@ -228,7 +209,7 @@ abstract class NetClient } } - public function EnableCrypto(bool $insecure = true) + public function EnableCrypto(bool $insecure = false) { $bError = true; if ($this->rConnect && \MailSo\Base\Utils::FunctionExistsAndEnabled('stream_socket_enable_crypto')) { diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Sieve/ManageSieveClient.php b/snappymail/v/0.0.0/app/libraries/MailSo/Sieve/ManageSieveClient.php index f4ff3375c..2721fa848 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Sieve/ManageSieveClient.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Sieve/ManageSieveClient.php @@ -62,11 +62,9 @@ class ManageSieveClient extends \MailSo\Net\NetClient * @throws \MailSo\Base\Exceptions\InvalidArgumentException * @throws \MailSo\Sieve\Exceptions\ResponseException */ - public function Connect(string $sServerName, int $iPort, - int $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT, - bool $bVerifySsl = false, bool $bAllowSelfSigned = true, string $sClientCert = '') : void + public function Connect(\MailSo\Net\ConnectSettings $oSettings) : void { - parent::Connect($sServerName, $iPort, $iSecurityType, $bVerifySsl, $bAllowSelfSigned); + parent::Connect($oSettings); $aResponse = $this->parseResponse(); $this->validateResponse($aResponse); diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Smtp/SmtpClient.php b/snappymail/v/0.0.0/app/libraries/MailSo/Smtp/SmtpClient.php index 2a17f6401..22ec5ff61 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Smtp/SmtpClient.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Smtp/SmtpClient.php @@ -100,12 +100,9 @@ class SmtpClient extends \MailSo\Net\NetClient * @throws \MailSo\Net\Exceptions\Exception * @throws \MailSo\Smtp\Exceptions\ResponseException */ - public function Connect(string $sServerName, int $iPort = 25, - int $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT, - bool $bVerifySsl = false, bool $bAllowSelfSigned = true, - string $sClientCert = '', string $sEhloHost = '[127.0.0.1]') : void + public function Connect(\MailSo\Net\ConnectSettings $oSettings, string $sEhloHost = '[127.0.0.1]') : void { - parent::Connect($sServerName, $iPort, $iSecurityType, $bVerifySsl, $bAllowSelfSigned); + parent::Connect($oSettings); $this->validateResponse(220); diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/UserAuth.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/UserAuth.php index c00dc519d..83087f95e 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/UserAuth.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/UserAuth.php @@ -112,11 +112,10 @@ trait UserAuth $this->Logger()->AddSecret($sPassword); $oAccount = null; - $sClientCert = \trim($this->Config()->Get('ssl', 'client_cert', '')); try { $oAccount = $bMainAccount - ? MainAccount::NewInstanceFromCredentials($this, $sEmail, $sLogin, $sPassword, $sClientCert, true) - : AdditionalAccount::NewInstanceFromCredentials($this, $sEmail, $sLogin, $sPassword, $sClientCert, true); + ? MainAccount::NewInstanceFromCredentials($this, $sEmail, $sLogin, $sPassword, true) + : AdditionalAccount::NewInstanceFromCredentials($this, $sEmail, $sLogin, $sPassword, true); if (!$oAccount) { throw new ClientException(Notifications::AuthError); } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/ActionsAdmin.php b/snappymail/v/0.0.0/app/libraries/RainLoop/ActionsAdmin.php index 6d37e5a24..3aa05bc75 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/ActionsAdmin.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/ActionsAdmin.php @@ -295,11 +295,11 @@ class ActionsAdmin extends Actions $oImapClient->SetTimeOuts($iConnectionTimeout); $iTime = \microtime(true); - $oImapClient->Connect($oDomain->IncHost(), $oDomain->IncPort(), $oDomain->IncSecure(), - !!$this->Config()->Get('ssl', 'verify_certificate', false), - !!$this->Config()->Get('ssl', 'allow_self_signed', true), - $this->Config()->Get('ssl', 'client_cert', '') - ); + $oSettings = new \MailSo\Net\ConnectSettings; + $oSettings->host = $oDomain->IncHost(); + $oSettings->port = $oDomain->IncPort(); + $oSettings->type = $oDomain->IncSecure(); + $oImapClient->Connect($oSettings); $oImapClient->Disconnect(); $bImapResult = true; @@ -336,11 +336,11 @@ class ActionsAdmin extends Actions $oSmtpClient->SetTimeOuts($iConnectionTimeout); $iTime = \microtime(true); - $oSmtpClient->Connect($oDomain->OutHost(), $oDomain->OutPort(), $oDomain->OutSecure(), - !!$this->Config()->Get('ssl', 'verify_certificate', false), - !!$this->Config()->Get('ssl', 'allow_self_signed', true), - '', \MailSo\Smtp\SmtpClient::EhloHelper() - ); + $oSettings = new \MailSo\Net\ConnectSettings; + $oSettings->host = $oDomain->OutHost(); + $oSettings->port = $oDomain->OutPort(); + $oSettings->type = $oDomain->OutSecure(); + $oSmtpClient->Connect($oSettings, \MailSo\Smtp\SmtpClient::EhloHelper()); $oSmtpClient->Disconnect(); $bSmtpResult = true; @@ -370,10 +370,11 @@ class ActionsAdmin extends Actions $oSieveClient->SetTimeOuts($iConnectionTimeout); $iTime = \microtime(true); - $oSieveClient->Connect($oDomain->SieveHost(), $oDomain->SievePort(), $oDomain->SieveSecure(), - !!$this->Config()->Get('ssl', 'verify_certificate', false), - !!$this->Config()->Get('ssl', 'allow_self_signed', true) - ); + $oSettings = new \MailSo\Net\ConnectSettings; + $oSettings->host = $oDomain->SieveHost(); + $oSettings->port = $oDomain->SievePort(); + $oSettings->type = $oDomain->SieveSecure(); + $oSieveClient->Connect($oSettings); $oSieveClient->Disconnect(); $bSieveResult = true; diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Config/Application.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Config/Application.php index 36a508409..0846806a1 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Config/Application.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Config/Application.php @@ -201,6 +201,7 @@ class Application extends \RainLoop\Config\AbstractConfig 'ssl' => array( 'verify_certificate' => array(false, 'Require verification of SSL certificate used.'), 'allow_self_signed' => array(true, 'Allow self-signed certificates. Requires verify_certificate.'), + 'security_level' => array(1, 'https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_security_level.html'), 'cafile' => array('', 'Location of Certificate Authority file on local filesystem (/etc/ssl/certs/ca-certificates.crt)'), 'capath' => array('', 'capath must be a correctly hashed certificate directory. (/etc/ssl/certs/)'), 'client_cert' => array('', 'Location of client certificate file (pem format with private key) on local filesystem'), diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Account.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Account.php index eb8aa36f6..ad8eb8a88 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Account.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Account.php @@ -33,11 +33,6 @@ abstract class Account implements \JsonSerializable */ private $sProxyAuthPassword = ''; - /** - * @var string - */ - private $sClientCert; - /** * @var \RainLoop\Model\Domain */ @@ -95,11 +90,6 @@ abstract class Account implements \JsonSerializable return $this->IncPassword(); } - public function ClientCert() : string - { - return $this->sClientCert; - } - public function Domain() : Domain { return $this->oDomain; @@ -138,15 +128,14 @@ abstract class Account implements \JsonSerializable $this->sEmail, // 1 $this->sLogin, // 2 $this->sPassword, // 3 - $this->sClientCert, // 4 + '', // 4 sClientCert $this->sProxyAuthUser, // 5 $this->sProxyAuthPassword // 6 ); } public static function NewInstanceFromCredentials(\RainLoop\Actions $oActions, - string $sEmail, string $sLogin, string $sPassword, string $sClientCert = '', - bool $bThrowException = false): ?self + string $sEmail, string $sLogin, string $sPassword, bool $bThrowException = false): ?self { $oAccount = null; if ($sEmail && $sLogin && $sPassword) { @@ -159,7 +148,6 @@ abstract class Account implements \JsonSerializable $oAccount->sLogin = \MailSo\Base\Utils::IdnToAscii($sLogin); $oAccount->sPassword = $sPassword; $oAccount->oDomain = $oDomain; - $oAccount->sClientCert = $sClientCert; $oActions->Plugins()->RunHook('filter.account', array($oAccount)); @@ -188,7 +176,6 @@ abstract class Account implements \JsonSerializable $aAccountHash[1] ?: '', $aAccountHash[2] ?: '', $aAccountHash[3] ?: '', - $aAccountHash[4] ?: '', $bThrowExceptionOnFalse ); @@ -212,31 +199,38 @@ abstract class Account implements \JsonSerializable { $oImapClient = $oMailClient->ImapClient(); - $aImapCredentials = \array_merge( + $aCredentials = \array_merge( $this->Domain()->ImapSettings(), array( 'Login' => $this->IncLogin(), 'VerifySsl' => !!$oConfig->Get('ssl', 'verify_certificate', false), - 'ClientCert' => $this->ClientCert(), - 'AllowSelfSigned' => !!$oConfig->Get('ssl', 'allow_self_signed', true) + 'AllowSelfSigned' => !!$oConfig->Get('ssl', 'allow_self_signed', true), + 'ClientCert' => \trim($oConfig->Get('ssl', 'client_cert', '')) ) ); - $oPlugins->RunHook('imap.before-connect', array($this, $oImapClient, &$aImapCredentials)); - if ($aImapCredentials['UseConnect']) { - $oImapClient->Connect($aImapCredentials['Host'], $aImapCredentials['Port'], - $aImapCredentials['Secure'], $aImapCredentials['VerifySsl'], - $aImapCredentials['AllowSelfSigned'], $aImapCredentials['ClientCert']); - + $oPlugins->RunHook('imap.before-connect', array($this, $oImapClient, &$aCredentials)); + if ($aCredentials['UseConnect']) { + $oSettings = new \MailSo\Net\ConnectSettings; + $oSettings->host = $aCredentials['Host']; + $oSettings->port = $aCredentials['Port']; + $oSettings->type = $aCredentials['Secure']; + $oSettings->ssl['verify_peer'] = !!$aCredentials['VerifySsl']; + $oSettings->ssl['verify_peer_name'] = !!$aCredentials['VerifySsl']; + $oSettings->ssl['allow_self_signed'] = !!$aCredentials['AllowSelfSigned']; + if ($aCredentials['ClientCert']) { + $oSettings->ssl['local_cert'] = $aCredentials['ClientCert']; + } + $oImapClient->Connect($oSettings); } - $oPlugins->RunHook('imap.after-connect', array($this, $oImapClient, $aImapCredentials)); + $oPlugins->RunHook('imap.after-connect', array($this, $oImapClient, $aCredentials)); - return $this->netClientLogin($oImapClient, $oConfig, $oPlugins, $aImapCredentials); + return $this->netClientLogin($oImapClient, $oConfig, $oPlugins, $aCredentials); } public function OutConnectAndLoginHelper(\RainLoop\Plugins\Manager $oPlugins, \MailSo\Smtp\SmtpClient $oSmtpClient, \RainLoop\Config\Application $oConfig, bool &$bUsePhpMail = false) : bool { - $aSmtpCredentials = \array_merge( + $aCredentials = \array_merge( $this->Domain()->SmtpSettings(), array( 'UseConnect' => !$bUsePhpMail, @@ -247,23 +241,28 @@ abstract class Account implements \JsonSerializable ) ); - $oPlugins->RunHook('smtp.before-connect', array($this, $oSmtpClient, &$aSmtpCredentials)); - $bUsePhpMail = $aSmtpCredentials['UsePhpMail']; - $aSmtpCredentials['UseAuth'] = $aSmtpCredentials['UseAuth'] && !$aSmtpCredentials['UsePhpMail']; - if ($aSmtpCredentials['UseConnect'] && !$aSmtpCredentials['UsePhpMail']) { - $oSmtpClient->Connect($aSmtpCredentials['Host'], $aSmtpCredentials['Port'], - $aSmtpCredentials['Secure'], $aSmtpCredentials['VerifySsl'], $aSmtpCredentials['AllowSelfSigned'], - '', $aSmtpCredentials['Ehlo'] - ); - } - $oPlugins->RunHook('smtp.after-connect', array($this, $oSmtpClient, $aSmtpCredentials)); + $oPlugins->RunHook('smtp.before-connect', array($this, $oSmtpClient, &$aCredentials)); + $bUsePhpMail = $aCredentials['UsePhpMail']; + $aCredentials['UseAuth'] = $aCredentials['UseAuth'] && !$aCredentials['UsePhpMail']; - return $this->netClientLogin($oSmtpClient, $oConfig, $oPlugins, $aSmtpCredentials); + if ($aCredentials['UseConnect'] && !$aCredentials['UsePhpMail']) { + $oSettings = new \MailSo\Net\ConnectSettings; + $oSettings->host = $aCredentials['Host']; + $oSettings->port = $aCredentials['Port']; + $oSettings->type = $aCredentials['Secure']; + $oSettings->ssl['verify_peer'] = !!$aCredentials['VerifySsl']; + $oSettings->ssl['verify_peer_name'] = !!$aCredentials['VerifySsl']; + $oSettings->ssl['allow_self_signed'] = !!$aCredentials['AllowSelfSigned']; + $oSmtpClient->Connect($oSettings, $aCredentials['Ehlo']); + } + $oPlugins->RunHook('smtp.after-connect', array($this, $oSmtpClient, $aCredentials)); + + return $this->netClientLogin($oSmtpClient, $oConfig, $oPlugins, $aCredentials); } public function SieveConnectAndLoginHelper(\RainLoop\Plugins\Manager $oPlugins, \MailSo\Sieve\ManageSieveClient $oSieveClient, \RainLoop\Config\Application $oConfig) { - $aSieveCredentials = \array_merge( + $aCredentials = \array_merge( $this->Domain()->SieveSettings(), array( 'Login' => $this->IncLogin(), @@ -273,15 +272,20 @@ abstract class Account implements \JsonSerializable ) ); - $oPlugins->RunHook('sieve.before-connect', array($this, $oSieveClient, &$aSieveCredentials)); - if ($aSieveCredentials['UseConnect']) { - $oSieveClient->Connect($aSieveCredentials['Host'], $aSieveCredentials['Port'], - $aSieveCredentials['Secure'], $aSieveCredentials['VerifySsl'], $aSieveCredentials['AllowSelfSigned'] - ); + $oPlugins->RunHook('sieve.before-connect', array($this, $oSieveClient, &$aCredentials)); + if ($aCredentials['UseConnect']) { + $oSettings = new \MailSo\Net\ConnectSettings; + $oSettings->host = $aCredentials['Host']; + $oSettings->port = $aCredentials['Port']; + $oSettings->type = $aCredentials['Secure']; + $oSettings->ssl['verify_peer'] = !!$aCredentials['VerifySsl']; + $oSettings->ssl['verify_peer_name'] = !!$aCredentials['VerifySsl']; + $oSettings->ssl['allow_self_signed'] = !!$aCredentials['AllowSelfSigned']; + $oSieveClient->Connect($oSettings); } - $oPlugins->RunHook('sieve.after-connect', array($this, $oSieveClient, $aSieveCredentials)); + $oPlugins->RunHook('sieve.after-connect', array($this, $oSieveClient, $aCredentials)); - return $this->netClientLogin($oSieveClient, $oConfig, $oPlugins, $aSieveCredentials); + return $this->netClientLogin($oSieveClient, $oConfig, $oPlugins, $aCredentials); } private function netClientLogin(\MailSo\Net\NetClient $oClient, \RainLoop\Config\Application $oConfig, \RainLoop\Plugins\Manager $oPlugins, array $aCredentials) : bool