Improved NetClient connection handling as discovered at #381

This commit is contained in:
the-djmaze 2022-05-12 11:59:23 +02:00
parent 7e30cb95ec
commit 426a0f8c3b
9 changed files with 147 additions and 104 deletions

View file

@ -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('*'));

View file

@ -0,0 +1,65 @@
<?php
/*
* This file is part of MailSo.
*
* (c) 2022 DJMaze
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace MailSo\Net;
/**
* @category MailSo
* @package Net
*/
class ConnectSettings
{
public
$host,
$port,
// none, TLS, STARTTLS
$type = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT,
// https://www.php.net/context.ssl
$ssl = [
// 'peer_name' => '',
// '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', '');
}
}

View file

@ -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')) {

View file

@ -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);

View file

@ -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);

View file

@ -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);
}

View file

@ -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;

View file

@ -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'),

View file

@ -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