When try to login IMAP/SMTP/SIEVE but STARTTLS is required, force STARTTLS

This commit is contained in:
the-djmaze 2022-10-30 11:43:17 +01:00
parent cef870328c
commit a58276ff45
6 changed files with 145 additions and 140 deletions

View file

@ -56,11 +56,6 @@ class Config
*/
public static $CheckNewMessages = true;
/**
* @var bool
*/
public static $PreferStartTlsIfAutoDetect = true;
/**
* @var string
*/

View file

@ -11,6 +11,8 @@
namespace MailSo\Imap;
use MailSo\Net\Enumerations\ConnectionSecurityType;
/**
* @category MailSo
* @package Imap
@ -93,15 +95,19 @@ class ImapClient extends \MailSo\Net\NetClient
$this->setCapabilities($this->getResponse('*'));
if ($this->IsSupported('STARTTLS') && \MailSo\Net\Enumerations\ConnectionSecurityType::UseStartTLS($this->iSecurityType))
{
if (ConnectionSecurityType::STARTTLS === $this->iSecurityType
|| (ConnectionSecurityType::AUTO_DETECT === $this->iSecurityType && $this->IsSupported('STARTTLS'))) {
$this->StartTLS();
}
}
private function StartTLS() : void
{
if ($this->IsSupported('STARTTLS')) {
$this->SendRequestGetResponse('STARTTLS');
$this->EnableCrypto();
$this->aCapabilityItems = null;
}
else if (\MailSo\Net\Enumerations\ConnectionSecurityType::STARTTLS === $this->iSecurityType)
{
} else {
$this->writeLogException(
new \MailSo\Net\Exceptions\SocketUnsuppoterdSecureConnectionException('STARTTLS is not supported'),
\LOG_ERR, true);
@ -143,6 +149,10 @@ class ImapClient extends \MailSo\Net\NetClient
}
}
if (!$type) {
if (!$this->Encrypted() && $this->IsSupported('STARTTLS')) {
$this->StartTLS();
return $this->Login($aCredentials);
}
throw new \MailSo\RuntimeException('No supported SASL mechanism found, remote server wants: '
. \implode(', ', \array_filter($this->Capability() ?: [], function($var){
return \str_starts_with($var, 'AUTH=');

View file

@ -46,12 +46,4 @@ abstract class ConnectionSecurityType
return self::SSL === $iResult;
}
public static function UseStartTLS(int $iSecurityType, bool $bHasSupportedAuth = true) : bool
{
return
(self::STARTTLS === $iSecurityType
|| (self::AUTO_DETECT === $iSecurityType && (!$bHasSupportedAuth || \MailSo\Config::$PreferStartTlsIfAutoDetect)))
&& \defined('STREAM_CRYPTO_METHOD_TLS_CLIENT') && \MailSo\Base\Utils::FunctionCallable('stream_socket_enable_crypto');
}
}

View file

@ -209,6 +209,11 @@ abstract class NetClient
}
}
public function Encrypted() : bool
{
return $this->rConnect && !empty(\stream_get_meta_data($this->rConnect)['crypto']);
}
public function EnableCrypto(bool $insecure = false)
{
$bError = true;

View file

@ -11,6 +11,8 @@
namespace MailSo\Sieve;
use MailSo\Net\Enumerations\ConnectionSecurityType;
/**
* @category MailSo
* @package Sieve
@ -71,17 +73,21 @@ class ManageSieveClient extends \MailSo\Net\NetClient
$this->validateResponse($aResponse);
$this->parseStartupResponse($aResponse);
if ($this->IsSupported('STARTTLS') && \MailSo\Net\Enumerations\ConnectionSecurityType::UseStartTLS($this->iSecurityType))
{
if (ConnectionSecurityType::STARTTLS === $this->iSecurityType
|| (ConnectionSecurityType::AUTO_DETECT === $this->iSecurityType && $this->IsSupported('STARTTLS'))) {
$this->StartTLS();
}
}
private function StartTLS() : void
{
if ($this->IsSupported('STARTTLS')) {
$this->sendRequestWithCheck('STARTTLS');
$this->EnableCrypto();
$aResponse = $this->parseResponse();
$this->validateResponse($aResponse);
$this->parseStartupResponse($aResponse);
}
else if (\MailSo\Net\Enumerations\ConnectionSecurityType::STARTTLS === $this->iSecurityType)
{
} else {
$this->writeLogException(
new \MailSo\Net\Exceptions\SocketUnsuppoterdSecureConnectionException('STARTTLS is not supported'),
\LOG_ERR, true);
@ -106,95 +112,96 @@ class ManageSieveClient extends \MailSo\Net\NetClient
\LOG_ERR, true);
}
if ($this->IsSupported('SASL'))
{
$type = '';
\array_push($aCredentials['SASLMechanisms'], 'PLAIN', 'LOGIN');
foreach ($aCredentials['SASLMechanisms'] as $sasl_type) {
if ($this->IsAuthSupported($sasl_type) && \SnappyMail\SASL::isSupported($sasl_type)) {
$type = $sasl_type;
break;
}
}
$SASL = \SnappyMail\SASL::factory($type);
$SASL->base64 = true;
$bAuth = false;
try
{
if (0 === \strpos($type, 'SCRAM-'))
{
/*
$sAuthzid = $this->getResponseValue($this->SendRequestGetResponse('AUTHENTICATE', array($type)), \MailSo\Imap\Enumerations\ResponseType::CONTINUATION);
$this->sendRaw($SASL->authenticate($sLogin, $sPassword/*, $sAuthzid* /), true);
$sChallenge = $SASL->challenge($this->getResponseValue($this->getResponse(), \MailSo\Imap\Enumerations\ResponseType::CONTINUATION));
if ($this->oLogger) {
$this->oLogger->AddSecret($sChallenge);
}
$this->sendRaw($sChallenge, true, '*******');
$oResponse = $this->getResponse();
$SASL->verify($this->getResponseValue($oResponse));
*/
}
else if ('PLAIN' === $type || 'OAUTHBEARER' === $type || 'XOAUTH2' === $type)
{
$sAuth = $SASL->authenticate($sLogin, $sPassword, $sLoginAuthKey);
if ($aCredentials['InitialAuthPlain'])
{
$this->sendRaw("AUTHENTICATE \"{$type}\" \"{$sAuth}\"");
}
else
{
$this->sendRaw("AUTHENTICATE \"{$type}\" {".\strlen($sAuth).'+}');
$this->sendRaw($sAuth);
}
$aResponse = $this->parseResponse();
$this->validateResponse($aResponse);
$this->parseStartupResponse($aResponse);
$bAuth = true;
}
else if ('LOGIN' === $type)
{
$sLogin = $SASL->authenticate($sLogin, $sPassword);
$sPassword = $SASL->challenge('');
$this->sendRaw('AUTHENTICATE "LOGIN"');
$this->sendRaw('{'.\strlen($sLogin).'+}');
$this->sendRaw($sLogin);
$this->sendRaw('{'.\strlen($sPassword).'+}');
$this->sendRaw($sPassword);
$aResponse = $this->parseResponse();
$this->validateResponse($aResponse);
$this->parseStartupResponse($aResponse);
$bAuth = true;
}
}
catch (\MailSo\Sieve\Exceptions\NegativeResponseException $oException)
{
$this->writeLogException(
new \MailSo\Sieve\Exceptions\LoginBadCredentialsException(
$oException->GetResponses(), '', 0, $oException),
\LOG_ERR, true);
}
if (!$bAuth)
{
$this->writeLogException(
new \MailSo\Sieve\Exceptions\LoginBadMethodException,
\LOG_ERR, true);
$type = '';
\array_push($aCredentials['SASLMechanisms'], 'PLAIN', 'LOGIN');
foreach ($aCredentials['SASLMechanisms'] as $sasl_type) {
if ($this->IsAuthSupported($sasl_type) && \SnappyMail\SASL::isSupported($sasl_type)) {
$type = $sasl_type;
break;
}
}
else
{
if (!$type) {
if (!$this->Encrypted() && $this->IsSupported('STARTTLS')) {
$this->StartTLS();
return $this->Login($aCredentials);
}
$this->writeLogException(
new \MailSo\Sieve\Exceptions\LoginException,
\LOG_ERR, true);
}
$SASL = \SnappyMail\SASL::factory($type);
$SASL->base64 = true;
$bAuth = false;
try
{
if (0 === \strpos($type, 'SCRAM-'))
{
/*
$sAuthzid = $this->getResponseValue($this->SendRequestGetResponse('AUTHENTICATE', array($type)), \MailSo\Imap\Enumerations\ResponseType::CONTINUATION);
$this->sendRaw($SASL->authenticate($sLogin, $sPassword/*, $sAuthzid* /), true);
$sChallenge = $SASL->challenge($this->getResponseValue($this->getResponse(), \MailSo\Imap\Enumerations\ResponseType::CONTINUATION));
if ($this->oLogger) {
$this->oLogger->AddSecret($sChallenge);
}
$this->sendRaw($sChallenge, true, '*******');
$oResponse = $this->getResponse();
$SASL->verify($this->getResponseValue($oResponse));
*/
}
else if ('PLAIN' === $type || 'OAUTHBEARER' === $type || 'XOAUTH2' === $type)
{
$sAuth = $SASL->authenticate($sLogin, $sPassword, $sLoginAuthKey);
if ($aCredentials['InitialAuthPlain'])
{
$this->sendRaw("AUTHENTICATE \"{$type}\" \"{$sAuth}\"");
}
else
{
$this->sendRaw("AUTHENTICATE \"{$type}\" {".\strlen($sAuth).'+}');
$this->sendRaw($sAuth);
}
$aResponse = $this->parseResponse();
$this->validateResponse($aResponse);
$this->parseStartupResponse($aResponse);
$bAuth = true;
}
else if ('LOGIN' === $type)
{
$sLogin = $SASL->authenticate($sLogin, $sPassword);
$sPassword = $SASL->challenge('');
$this->sendRaw('AUTHENTICATE "LOGIN"');
$this->sendRaw('{'.\strlen($sLogin).'+}');
$this->sendRaw($sLogin);
$this->sendRaw('{'.\strlen($sPassword).'+}');
$this->sendRaw($sPassword);
$aResponse = $this->parseResponse();
$this->validateResponse($aResponse);
$this->parseStartupResponse($aResponse);
$bAuth = true;
}
}
catch (\MailSo\Sieve\Exceptions\NegativeResponseException $oException)
{
$this->writeLogException(
new \MailSo\Sieve\Exceptions\LoginBadCredentialsException(
$oException->GetResponses(), '', 0, $oException),
\LOG_ERR, true);
}
if (!$bAuth)
{
$this->writeLogException(
new \MailSo\Sieve\Exceptions\LoginBadMethodException,
\LOG_ERR, true);
}
$this->bIsLoggined = true;
return $this;

View file

@ -11,6 +11,8 @@
namespace MailSo\Smtp;
use MailSo\Net\Enumerations\ConnectionSecurityType;
/**
* @category MailSo
* @package Smtp
@ -18,9 +20,9 @@ namespace MailSo\Smtp;
class SmtpClient extends \MailSo\Net\NetClient
{
/**
* @var bool
* @var string
*/
private $bHelo = false;
private $sEhlo = '';
/**
* @var bool
@ -107,7 +109,26 @@ class SmtpClient extends \MailSo\Net\NetClient
$this->validateResponse(220);
$this->preLoginStartTLSAndEhloProcess($sEhloHost);
$this->ehloOrHelo($sEhloHost);
$this->sEhlo = $sEhloHost;
if (ConnectionSecurityType::STARTTLS === $this->iSecurityType
|| (ConnectionSecurityType::AUTO_DETECT === $this->iSecurityType && $this->IsSupported('STARTTLS'))) {
$this->StartTLS();
}
}
private function StartTLS() : void
{
if ($this->IsSupported('STARTTLS')) {
$this->sendRequestWithCheck('STARTTLS', 220);
$this->EnableCrypto();
$this->ehloOrHelo($this->sEhlo);
} else {
$this->writeLogException(
new \MailSo\Net\Exceptions\SocketUnsuppoterdSecureConnectionException('STARTTLS is not supported'),
\LOG_ERR, true);
}
}
/**
@ -133,7 +154,11 @@ class SmtpClient extends \MailSo\Net\NetClient
}
if (!$type) {
\trigger_error("SMTP {$this->GetConnectedHost()} no supported AUTH options. Disable login" . ($this->IsSupported('STARTTLS') ? ' or try with STARTTLS' : ''));
if (!$this->Encrypted() && $this->IsSupported('STARTTLS')) {
$this->StartTLS();
return $this->Login($aCredentials);
}
\trigger_error("SMTP {$this->GetConnectedHost()} no supported AUTH options. Disable login");
$this->writeLogException(
new \MailSo\Smtp\Exceptions\LoginBadMethodException,
\LOG_NOTICE, true);
@ -410,40 +435,11 @@ class SmtpClient extends \MailSo\Net\NetClient
$this->sendRequestWithCheck('QUIT', 221);
}
$this->bHelo = false;
$this->bMail = false;
$this->bRcpt = false;
$this->bData = false;
}
private function preLoginStartTLSAndEhloProcess(string $sEhloHost) : void
{
if ($this->bHelo)
{
$this->writeLogException(
new \MailSo\RuntimeException('Cannot issue EHLO/HELO to existing session'),
\LOG_ERR, true);
}
$this->ehloOrHelo($sEhloHost);
if ($this->IsSupported('STARTTLS') && \MailSo\Net\Enumerations\ConnectionSecurityType::UseStartTLS($this->iSecurityType, $this->HasSupportedAuth()))
{
$this->sendRequestWithCheck('STARTTLS', 220);
$this->EnableCrypto();
$this->ehloOrHelo($sEhloHost);
}
else if (\MailSo\Net\Enumerations\ConnectionSecurityType::STARTTLS === $this->iSecurityType)
{
$this->writeLogException(
new \MailSo\Net\Exceptions\SocketUnsuppoterdSecureConnectionException('STARTTLS is not supported'),
\LOG_ERR, true);
}
$this->bHelo = true;
}
/**
* @throws \InvalidArgumentException
* @throws \MailSo\RuntimeException