Revamp NetClient (SMTP, IMAP, Sieve) login for rfc7628 OAuth plugin support

This commit is contained in:
djmaze 2021-10-22 12:45:14 +02:00
parent dff2415762
commit 617d700a5d
11 changed files with 194 additions and 178 deletions

View file

@ -29,48 +29,38 @@ $Plugin->addHook('hook.name', 'functionName');
## IMAP
### imap.credentials
params:
\RainLoop\Model\Account $oAccount
array &$aCredentials
### imap.before-connect
params:
\RainLoop\Model\Account $oAccount
\MailSo\Mail\MailClient $oMailClient
array $aCredentials
\MailSo\Imap\ImapClient $oImapClient
array &$aCredentials
### imap.after-connect
params:
\RainLoop\Model\Account $oAccount
\MailSo\Mail\MailClient $oMailClient
\MailSo\Imap\ImapClient $oImapClient
array $aCredentials
### imap.before-login
params:
\RainLoop\Model\Account $oAccount
\MailSo\Mail\MailClient $oMailClient
array $aCredentials
\MailSo\Imap\ImapClient $oImapClient
array &$aCredentials
### imap.after-login
params:
\RainLoop\Model\Account $oAccount
\MailSo\Mail\MailClient $oMailClient
\MailSo\Imap\ImapClient $oImapClient
bool $bSuccess
array $aCredentials
## Sieve
### sieve.credentials
params:
\RainLoop\Model\Account $oAccount
array &$aCredentials
### sieve.before-connect
params:
\RainLoop\Model\Account $oAccount
\MailSo\Sieve\ManageSieveClient $oSieveClient
array $aCredentials
array &$aCredentials
### sieve.after-connect
params:
@ -82,27 +72,22 @@ $Plugin->addHook('hook.name', 'functionName');
params:
\RainLoop\Model\Account $oAccount
\MailSo\Sieve\ManageSieveClient $oSieveClient
bool $bSuccess
array $aCredentials
array &$aCredentials
### sieve.after-login
params:
\RainLoop\Model\Account $oAccount
\MailSo\Sieve\ManageSieveClient $oSieveClient
bool $bSuccess
array $aCredentials
## SMTP
### smtp.credentials
params:
\RainLoop\Model\Account $oAccount
array &$aCredentials
### smtp.before-connect
params:
\RainLoop\Model\Account $oAccount
\MailSo\Smtp\SmtpClient $oSmtpClient
array $aCredentials
array &$aCredentials
### smtp.after-connect
params:
@ -114,7 +99,7 @@ $Plugin->addHook('hook.name', 'functionName');
params:
\RainLoop\Model\Account $oAccount
\MailSo\Smtp\SmtpClient $oSmtpClient
array $aCredentials
array &$aCredentials
### smtp.after-login
params:

View file

@ -130,18 +130,24 @@ class ImapClient extends \MailSo\Net\NetClient
* @throws \MailSo\Net\Exceptions\Exception
* @throws \MailSo\Imap\Exceptions\Exception
*/
public function Login(string $sLogin, string $sPassword, string $sProxyAuthUser = '',
bool $bUseAuthPlainIfSupported = true, bool $bUseAuthCramMd5IfSupported = true) : self
public function Login(array $aCredentials) : self
{
if (!\strlen(\trim($sLogin)) || !\strlen(\trim($sPassword)))
if (!empty($aCredentials['ProxyAuthUser']) && !empty($aCredentials['ProxyAuthPassword'])) {
$sLogin = \MailSo\Base\Utils::IdnToAscii(\MailSo\Base\Utils::Trim($aCredentials['ProxyAuthUser']));
$sPassword = $aCredentials['ProxyAuthPassword'];
$sProxyAuthUser = $aCredentials['Login'];
} else {
$sLogin = \MailSo\Base\Utils::IdnToAscii(\MailSo\Base\Utils::Trim($aCredentials['Login']));
$sPassword = $aCredentials['Password'];
}
if (!\strlen($sLogin) || !\strlen($sPassword))
{
$this->writeLogException(
new \MailSo\Base\Exceptions\InvalidArgumentException,
\MailSo\Log\Enumerations\Type::ERROR, true);
}
$sLogin = \MailSo\Base\Utils::IdnToAscii(\MailSo\Base\Utils::Trim($sLogin));
$this->sLogginedUser = $sLogin;
// $encrypted = !empty(\stream_get_meta_data($this->ConnectionResource())['crypto']);
@ -149,9 +155,11 @@ class ImapClient extends \MailSo\Net\NetClient
$types = [
// 'SCRAM-SHA-256' => 1, // !$encrypted
// 'SCRAM-SHA-1' => 1, // !$encrypted
'CRAM-MD5' => $bUseAuthCramMd5IfSupported,
'PLAIN' => $bUseAuthPlainIfSupported,
'LOGIN' => 1,
'CRAM-MD5' => $aCredentials['UseAuthCramMd5IfSupported'],
'PLAIN' => $aCredentials['UseAuthPlainIfSupported'],
'OAUTHBEARER' => $aCredentials['UseAuthOAuth2IfSupported'],
'XOAUTH2' => $aCredentials['UseAuthOAuth2IfSupported'],
'LOGIN' => 1
];
foreach ($types as $sasl_type => $active) {
if ($active && $this->IsSupported("AUTH={$sasl_type}") && \SnappyMail\SASL::isSupported($sasl_type)) {
@ -188,7 +196,7 @@ class ImapClient extends \MailSo\Net\NetClient
$this->sendRaw($sAuth, true, '*******');
$this->getResponse();
}
else if ('PLAIN' === $type)
else if ('PLAIN' === $type || 'OAUTHBEARER' === $type)
{
$sAuth = $SASL->authenticate($sLogin, $sPassword);
if ($this->oLogger) {
@ -202,6 +210,23 @@ class ImapClient extends \MailSo\Net\NetClient
$this->getResponse();
}
}
else if ('XOAUTH2' === $type)
{
$sAuth = $SASL->authenticate($sLogin, $sPassword);
$this->SendRequest('AUTHENTICATE', array($type, $sAuth));
$aR = $this->parseResponseWithValidation();
if (\is_array($aR) && \count($aR) && isset($aR[\count($aR) - 1])) {
$oR = $aR[\count($aR) - 1];
if (\MailSo\Imap\Enumerations\ResponseType::CONTINUATION === $oR->ResponseType) {
if (!empty($oR->ResponseList[1]) && preg_match('/^[a-zA-Z0-9=+\/]+$/', $oR->ResponseList[1])) {
$this->Logger()->Write(\base64_decode($oR->ResponseList[1]),
\MailSo\Log\Enumerations\Type::WARNING);
}
$this->sendRaw('');
$this->parseResponseWithValidation();
}
}
}
else if ($this->IsSupported('LOGINDISABLED'))
{
$sB64 = $this->getResponseValue($this->SendRequestGetResponse('AUTHENTICATE', array($type)), Enumerations\ResponseType::CONTINUATION);
@ -227,7 +252,7 @@ class ImapClient extends \MailSo\Net\NetClient
));
}
if (0 < \strlen($sProxyAuthUser))
if (\strlen($sProxyAuthUser))
{
$this->SendRequestGetResponse('PROXYAUTH', array($this->EscapeString($sProxyAuthUser)));
}
@ -578,7 +603,7 @@ class ImapClient extends \MailSo\Net\NetClient
$aFetchItems = Enumerations\FetchType::ChangeFetchItemsBefourRequest($aInputFetchItems);
foreach ($aFetchItems as $sName => $mItem)
{
if (0 < \strlen($sName) && '' !== $mItem)
if (\strlen($sName) && '' !== $mItem)
{
$this->aFetchCallbacks[$sName] = $mItem;
}
@ -704,7 +729,7 @@ class ImapClient extends \MailSo\Net\NetClient
}
else
{
if (0 < \strlen($sCharset))
if (\strlen($sCharset))
{
$aRequest[] = 'CHARSET';
$aRequest[] = \strtoupper($sCharset);
@ -716,7 +741,7 @@ class ImapClient extends \MailSo\Net\NetClient
$aRequest[] = $sSearchCriterias;
if (0 < \strlen($sLimit))
if (\strlen($sLimit))
{
$aRequest[] = $sLimit;
}
@ -755,7 +780,7 @@ class ImapClient extends \MailSo\Net\NetClient
$sCommandPrefix = ($bReturnUid) ? 'UID ' : '';
$aRequest = array();
if (0 < \strlen($sCharset))
if (\strlen($sCharset))
{
$aRequest[] = 'CHARSET';
$aRequest[] = \strtoupper($sCharset);
@ -890,7 +915,7 @@ class ImapClient extends \MailSo\Net\NetClient
$sCmd = 'EXPUNGE';
$aArguments = array();
if (!$bExpungeAll && $bForceUidExpunge && 0 < \strlen($sUidRangeIfSupported) && $this->IsSupported('UIDPLUS'))
if (!$bExpungeAll && $bForceUidExpunge && \strlen($sUidRangeIfSupported) && $this->IsSupported('UIDPLUS'))
{
$sCmd = 'UID '.$sCmd;
$aArguments = array($sUidRangeIfSupported);
@ -950,8 +975,8 @@ class ImapClient extends \MailSo\Net\NetClient
$oLast = $this->GetLastResponse()->getLast();
if ($oLast && Enumerations\ResponseType::TAGGED === $oLast->ResponseType && \is_array($oLast->OptionalResponse))
{
if (0 < \strlen($oLast->OptionalResponse[0]) &&
0 < \strlen($oLast->OptionalResponse[2]) &&
if (\strlen($oLast->OptionalResponse[0]) &&
\strlen($oLast->OptionalResponse[2]) &&
'APPENDUID' === strtoupper($oLast->OptionalResponse[0]) &&
\is_numeric($oLast->OptionalResponse[2])
)
@ -1603,11 +1628,11 @@ class ImapClient extends \MailSo\Net\NetClient
}
$sFetchKey = '';
if (0 < \strlen($sLiteralAtomUpperCasePeek) && isset($this->aFetchCallbacks[$sLiteralAtomUpperCasePeek]))
if (\strlen($sLiteralAtomUpperCasePeek) && isset($this->aFetchCallbacks[$sLiteralAtomUpperCasePeek]))
{
$sFetchKey = $sLiteralAtomUpperCasePeek;
}
else if (0 < \strlen($sLiteralAtomUpperCase) && isset($this->aFetchCallbacks[$sLiteralAtomUpperCase]))
else if (\strlen($sLiteralAtomUpperCase) && isset($this->aFetchCallbacks[$sLiteralAtomUpperCase]))
{
$sFetchKey = $sLiteralAtomUpperCase;
}
@ -1726,7 +1751,7 @@ class ImapClient extends \MailSo\Net\NetClient
return '"' . \addcslashes($sStringForEscape, '\\"') . '"';
}
protected function getLogName() : string
public function getLogName() : string
{
return 'IMAP';
}

View file

@ -40,31 +40,6 @@ class MailClient
return $this->oImapClient;
}
/**
* @throws \MailSo\Base\Exceptions\InvalidArgumentException
* @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 = false, string $sClientCert = '') : self
{
$this->oImapClient->Connect($sServerName, $iPort, $iSecurityType, $bVerifySsl, $bAllowSelfSigned, $sClientCert);
return $this;
}
/**
* @throws \MailSo\Base\Exceptions\InvalidArgumentException
* @throws \MailSo\Net\Exceptions\Exception
* @throws \MailSo\Imap\Exceptions\LoginException
*/
public function Login(string $sLogin, string $sPassword, string $sProxyAuthUser = '',
bool $bUseAuthPlainIfSupported = true, bool $bUseAuthCramMd5IfSupported = true) : self
{
$this->oImapClient->Login($sLogin, $sPassword, $sProxyAuthUser, $bUseAuthPlainIfSupported, $bUseAuthCramMd5IfSupported);
return $this;
}
/**
* @throws \MailSo\Net\Exceptions\Exception
*/

View file

@ -433,10 +433,7 @@ abstract class NetClient
}
}
protected function getLogName() : string
{
return 'NET';
}
abstract function getLogName() : string;
protected function writeLog(string $sDesc, int $iDescType = \MailSo\Log\Enumerations\Type::INFO, bool $bDiplayCrLf = false) : void
{

View file

@ -37,11 +37,6 @@ class ManageSieveClient extends \MailSo\Net\NetClient
*/
private $aModules = array();
/**
* @var bool
*/
public $__USE_INITIAL_AUTH_PLAIN_COMMAND = true;
public function IsSupported(string $sCapa) : bool
{
return isset($this->aCapa[\strtoupper($sCapa)]);
@ -100,9 +95,12 @@ class ManageSieveClient extends \MailSo\Net\NetClient
* @throws \MailSo\Base\Exceptions\InvalidArgumentException
* @throws \MailSo\Sieve\Exceptions\LoginException
*/
public function Login(string $sLogin, string $sPassword, string $sLoginAuthKey = '') : self
public function Login(array $aCredentials) : self
{
if (!\strlen(\trim($sLogin)) || !\strlen(\trim($sPassword)))
$sLogin = $aCredentials['Login'];
$sPassword = $aCredentials['Password'];
$sLoginAuthKey = '';
if (!\strlen($sLogin) || !\strlen($sPassword))
{
$this->writeLogException(
new \MailSo\Base\Exceptions\InvalidArgumentException,
@ -137,7 +135,7 @@ class ManageSieveClient extends \MailSo\Net\NetClient
{
$sAuth = $SASL->authenticate($sLogin, $sPassword, $sLoginAuthKey);
if ($this->__USE_INITIAL_AUTH_PLAIN_COMMAND)
if ($aCredentials['InitialAuthPlain'])
{
$this->sendRequest('AUTHENTICATE "PLAIN" "'.$sAuth.'"');
}
@ -530,7 +528,7 @@ class ManageSieveClient extends \MailSo\Net\NetClient
}
}
protected function getLogName() : string
public function getLogName() : string
{
return 'SIEVE';
}

View file

@ -117,18 +117,21 @@ class SmtpClient extends \MailSo\Net\NetClient
* @throws \MailSo\Net\Exceptions\Exception
* @throws \MailSo\Smtp\Exceptions\Exception
*/
public function Login(string $sLogin, string $sPassword, bool $bUseAuthPlainIfSupported = true, bool $bUseAuthCramMd5IfSupported = true) : self
public function Login(array $aCredentials) : self
{
$sLogin = \MailSo\Base\Utils::IdnToAscii(\MailSo\Base\Utils::Trim($sLogin));
$sLogin = \MailSo\Base\Utils::IdnToAscii(\MailSo\Base\Utils::Trim($aCredentials['Login']));
$sPassword = $aCredentials['Password'];
// $encrypted = !empty(\stream_get_meta_data($this->ConnectionResource())['crypto']);
$type = '';
$types = [
'SCRAM-SHA-256' => 1, // !$encrypted
'SCRAM-SHA-1' => 1, // !$encrypted
'CRAM-MD5' => $bUseAuthCramMd5IfSupported,
'PLAIN' => $bUseAuthPlainIfSupported,
'LOGIN' => 1 // $encrypted
'CRAM-MD5' => $aCredentials['UseAuthCramMd5IfSupported'],
'PLAIN' => $aCredentials['UseAuthPlainIfSupported'],
'OAUTHBEARER' => $aCredentials['UseAuthOAuth2IfSupported'],
'XOAUTH2' => $aCredentials['UseAuthOAuth2IfSupported'],
'LOGIN' => 1, // $encrypted
];
foreach ($types as $sasl_type => $active) {
if ($active && $this->IsAuthSupported($sasl_type) && \SnappyMail\SASL::isSupported($sasl_type)) {
@ -166,6 +169,8 @@ class SmtpClient extends \MailSo\Net\NetClient
{
// RFC 4616
case 'PLAIN':
case 'XOAUTH2':
case 'OAUTHBEARER':
$this->sendRequestWithCheck($SASL->authenticate($sLogin, $sPassword), 235, '', true);
break;
@ -192,14 +197,6 @@ class SmtpClient extends \MailSo\Net\NetClient
$sResult = $this->sendRequestWithCheck($SASL->challenge($sResult), 235, '', true);
$SASL->verify($sResult);
break;
/*
// https://developers.google.com/gmail/imap/xoauth2-protocol
case 'XOAUTH2':
throw new \Exception('Please use app passphrases: https://support.google.com/mail/answer/185833');
break;
*/
}
}
catch (\MailSo\Smtp\Exceptions\NegativeResponseException $oException)
@ -603,7 +600,7 @@ class SmtpClient extends \MailSo\Net\NetClient
while ('-' === \substr($aParts[1], 0, 1));
}
protected function getLogName() : string
public function getLogName() : string
{
return 'SMTP';
}

View file

@ -430,7 +430,6 @@ trait Admin
$oSieveClient = new \MailSo\Sieve\ManageSieveClient();
$oSieveClient->SetLogger($this->Logger());
$oSieveClient->SetTimeOuts($iConnectionTimeout);
$oSieveClient->__USE_INITIAL_AUTH_PLAIN_COMMAND = !!$this->Config()->Get('labs', 'sieve_auth_plain_initial', true);
$iTime = \microtime(true);
$oSieveClient->Connect($oDomain->SieveHost(), $oDomain->SievePort(), $oDomain->SieveSecure(),

View file

@ -716,7 +716,7 @@ trait Messages
$oSmtpClient->SetTimeOuts(10, (int) \RainLoop\Api::Config()->Get('labs', 'smtp_timeout', 60));
$oAccount->OutConnectAndLoginHelper(
$this->Plugins(), $oSmtpClient, $this->Config(), null, $bUsePhpMail
$this->Plugins(), $oSmtpClient, $this->Config(), $bUsePhpMail
);
if ($bUsePhpMail)

View file

@ -85,12 +85,12 @@ class Account
public function ParentEmailHelper() : string
{
return 0 < \strlen($this->sParentEmail) ? $this->sParentEmail : $this->sEmail;
return \strlen($this->sParentEmail) ? $this->sParentEmail : $this->sEmail;
}
public function IsAdditionalAccount() : string
{
return 0 < \strlen($this->sParentEmail);
return \strlen($this->sParentEmail);
}
public function IncLogin() : string
@ -137,7 +137,7 @@ class Account
public function SignMe() : bool
{
return 0 < \strlen($this->sSignMeToken);
return \strlen($this->sSignMeToken);
}
public function SignMeToken() : string
@ -262,9 +262,9 @@ class Account
));
}
public function IncConnectAndLoginHelper(\RainLoop\Plugins\Manager $oPlugins, \MailSo\Mail\MailClient $oMailClient, \RainLoop\Config\Application $oConfig, ?callable $refreshTokenCallback = null) : bool
public function IncConnectAndLoginHelper(\RainLoop\Plugins\Manager $oPlugins, \MailSo\Mail\MailClient $oMailClient, \RainLoop\Config\Application $oConfig) : bool
{
$bLogin = false;
$oImapClient = $oMailClient->ImapClient();
$aImapCredentials = array(
'UseConnect' => true,
@ -273,54 +273,25 @@ class Account
'Port' => $this->DomainIncPort(),
'Secure' => $this->DomainIncSecure(),
'Login' => $this->IncLogin(),
'Password' => $this->Password(),
'ProxyAuthUser' => $this->ProxyAuthUser(),
'ProxyAuthPassword' => $this->ProxyAuthPassword(),
'VerifySsl' => !!$oConfig->Get('ssl', 'verify_certificate', false),
'ClientCert' => $this->ClientCert(),
'AllowSelfSigned' => !!$oConfig->Get('ssl', 'allow_self_signed', true),
'UseAuthPlainIfSupported' => !!$oConfig->Get('labs', 'imap_use_auth_plain', true),
'UseAuthCramMd5IfSupported' => !!$oConfig->Get('labs', 'imap_use_auth_cram_md5', true)
'AllowSelfSigned' => !!$oConfig->Get('ssl', 'allow_self_signed', true)
);
$oPlugins->RunHook('filter.imap-credentials', array($this, &$aImapCredentials));
$oPlugins->RunHook('imap.before-connect', array($this, $oMailClient, $aImapCredentials));
$oPlugins->RunHook('imap.before-connect', array($this, $oImapClient, &$aImapCredentials));
if ($aImapCredentials['UseConnect']) {
$oMailClient
->Connect($aImapCredentials['Host'], $aImapCredentials['Port'],
$oImapClient->Connect($aImapCredentials['Host'], $aImapCredentials['Port'],
$aImapCredentials['Secure'], $aImapCredentials['VerifySsl'],
$aImapCredentials['AllowSelfSigned'], $aImapCredentials['ClientCert']);
}
$oPlugins->RunHook('imap.after-connect', array($this, $oMailClient, $aImapCredentials));
$oPlugins->RunHook('imap.after-connect', array($this, $oImapClient, $aImapCredentials));
$oPlugins->RunHook('imap.before-login', array($this, $oMailClient, $aImapCredentials));
if ($aImapCredentials['UseAuth']) {
if (0 < \strlen($aImapCredentials['ProxyAuthUser']) &&
0 < \strlen($aImapCredentials['ProxyAuthPassword']))
{
$oMailClient
->Login($aImapCredentials['ProxyAuthUser'], $aImapCredentials['ProxyAuthPassword'],
$aImapCredentials['Login'], $aImapCredentials['UseAuthPlainIfSupported'], $aImapCredentials['UseAuthCramMd5IfSupported']);
}
else
{
$oMailClient->Login($aImapCredentials['Login'], $aImapCredentials['Password'], '',
$aImapCredentials['UseAuthPlainIfSupported'], $aImapCredentials['UseAuthCramMd5IfSupported']);
}
$bLogin = true;
}
$oPlugins->RunHook('imap.after-login', array($this, $oMailClient, $bLogin, $aImapCredentials));
return $bLogin;
return $this->netClientLogin($oImapClient, $oConfig, $oPlugins, $aImapCredentials);
}
public function OutConnectAndLoginHelper(\RainLoop\Plugins\Manager $oPlugins, \MailSo\Smtp\SmtpClient $oSmtpClient, \RainLoop\Config\Application $oConfig, ?callable $refreshTokenCallback = null, bool &$bUsePhpMail = false) : bool
public function OutConnectAndLoginHelper(\RainLoop\Plugins\Manager $oPlugins, \MailSo\Smtp\SmtpClient $oSmtpClient, \RainLoop\Config\Application $oConfig, bool &$bUsePhpMail = false) : bool
{
$bLogin = false;
$aSmtpCredentials = array(
'UseConnect' => !$bUsePhpMail,
'UseAuth' => $this->DomainOutAuth(),
@ -330,21 +301,14 @@ class Account
'Port' => $this->DomainOutPort(),
'Secure' => $this->DomainOutSecure(),
'Login' => $this->OutLogin(),
'Password' => $this->Password(),
'ProxyAuthUser' => $this->ProxyAuthUser(),
'ProxyAuthPassword' => $this->ProxyAuthPassword(),
'VerifySsl' => !!$oConfig->Get('ssl', 'verify_certificate', false),
'AllowSelfSigned' => !!$oConfig->Get('ssl', 'allow_self_signed', true),
'UseAuthPlainIfSupported' => !!$oConfig->Get('labs', 'smtp_use_auth_plain', true),
'UseAuthCramMd5IfSupported' => !!$oConfig->Get('labs', 'smtp_use_auth_cram_md5', true)
'AllowSelfSigned' => !!$oConfig->Get('ssl', 'allow_self_signed', true)
);
$oPlugins->RunHook('smtp.credentials', array($this, &$aSmtpCredentials));
$oPlugins->RunHook('smtp.before-connect', array($this, $oSmtpClient, &$aSmtpCredentials));
$bUsePhpMail = $aSmtpCredentials['UsePhpMail'];
$oPlugins->RunHook('smtp.before-connect', array($this, $oSmtpClient, $aSmtpCredentials));
if ($aSmtpCredentials['UseConnect']) {
$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']
@ -352,23 +316,11 @@ class Account
}
$oPlugins->RunHook('smtp.after-connect', array($this, $oSmtpClient, $aSmtpCredentials));
$oPlugins->RunHook('smtp.before-login', array($this, $oSmtpClient, $aSmtpCredentials));
if ($aSmtpCredentials['UseAuth'] && !$aSmtpCredentials['UsePhpMail'] && $oSmtpClient)
{
$oSmtpClient->Login($aSmtpCredentials['Login'], $aSmtpCredentials['Password'],
$aSmtpCredentials['UseAuthPlainIfSupported'], $aSmtpCredentials['UseAuthCramMd5IfSupported']);
$bLogin = true;
}
$oPlugins->RunHook('smtp.after-login', array($this, $oSmtpClient, $bLogin, $aSmtpCredentials));
return $bLogin;
return $this->netClientLogin($oSmtpClient, $oConfig, $oPlugins, $aSmtpCredentials);
}
public function SieveConnectAndLoginHelper(\RainLoop\Plugins\Manager $oPlugins, \MailSo\Sieve\ManageSieveClient $oSieveClient, \RainLoop\Config\Application $oConfig)
{
$bLogin = false;
$aSieveCredentials = array(
'UseConnect' => true,
'UseAuth' => true,
@ -376,17 +328,12 @@ class Account
'Port' => $this->DomainSievePort(),
'Secure' => $this->DomainSieveSecure(),
'Login' => $this->IncLogin(),
'Password' => $this->Password(),
'VerifySsl' => !!$oConfig->Get('ssl', 'verify_certificate', false),
'AllowSelfSigned' => !!$oConfig->Get('ssl', 'allow_self_signed', true),
'InitialAuthPlain' => !!$oConfig->Get('ssl', 'sieve_auth_plain_initial', true)
);
$oPlugins->RunHook('sieve.credentials', array($this, &$aSieveCredentials));
$oSieveClient->__USE_INITIAL_AUTH_PLAIN_COMMAND = $aSieveCredentials['InitialAuthPlain'];
$oPlugins->RunHook('sieve.before-connect', array($this, $oSieveClient, $aSieveCredentials));
$oPlugins->RunHook('sieve.before-connect', array($this, $oSieveClient, &$aSieveCredentials));
if ($aSieveCredentials['UseConnect']) {
$oSieveClient->Connect($aSieveCredentials['Host'], $aSieveCredentials['Port'],
$aSieveCredentials['Secure'], $aSieveCredentials['VerifySsl'], $aSieveCredentials['AllowSelfSigned']
@ -394,13 +341,82 @@ class Account
}
$oPlugins->RunHook('sieve.after-connect', array($this, $oSieveClient, $aSieveCredentials));
$oPlugins->RunHook('event.sieve-pre-login', array($this, $oSieveClient, $aSieveCredentials));
if ($aSieveCredentials['UseAuth']) {
$oSieveClient->Login($aSieveCredentials['Login'], $aSieveCredentials['Password']);
$bLogin = true;
}
$oPlugins->RunHook('sieve.after-login', array($this, $oSieveClient, $bLogin, $aSieveCredentials));
return $bLogin;
return $this->netClientLogin($oSieveClient, $oConfig, $oPlugins, $aSieveCredentials);
}
private function netClientLogin(\MailSo\Net\NetClient $oClient, \RainLoop\Config\Application $oConfig, \RainLoop\Plugins\Manager $oPlugins, array $aCredentials) : bool
{
$aCredentials = \array_merge(
$aCredentials,
array(
'Password' => $this->Password(),
'ProxyAuthUser' => $this->ProxyAuthUser(),
'ProxyAuthPassword' => $this->ProxyAuthPassword(),
'UseAuthPlainIfSupported' => !!$oConfig->Get('labs', 'imap_use_auth_plain', true),
'UseAuthCramMd5IfSupported' => !!$oConfig->Get('labs', 'imap_use_auth_cram_md5', true),
'UseAuthOAuth2IfSupported' => false
)
);
$client_name = \strtolower($oClient->getLogName());
$oPlugins->RunHook("{$client_name}.before-login", array($this, $oClient, &$aCredentials));
$bResult = $aCredentials['UseAuth'] && $oClient->Login($aCredentials);
$oPlugins->RunHook("{$client_name}.after-login", array($this, $oClient, $bResult, $aCredentials));
return $bResult;
/*
// $encrypted = !empty(\stream_get_meta_data($oClient->ConnectionResource())['crypto']);
$type = $oClient->IsSupported('LOGINDISABLED') ? '' : 'LOGIN'; // RFC3501 6.2.3
$types = [
// 'SCRAM-SHA-256' => 1, // !$encrypted
// 'SCRAM-SHA-1' => 1, // !$encrypted
'CRAM-MD5' => $aSmtpCredentials['UseAuthCramMd5IfSupported'],
'PLAIN' => $aSmtpCredentials['UseAuthPlainIfSupported'],
'OAUTHBEARER' => $aSmtpCredentials['UseAuthOAuth2IfSupported'],
'XOAUTH2' => $aSmtpCredentials['UseAuthOAuth2IfSupported'],
'LOGIN' => 1,
];
foreach ($types as $sasl_type => $active) {
if ($active && $oClient->IsSupported("AUTH={$sasl_type}") && \SnappyMail\SASL::isSupported($sasl_type)) {
$type = $sasl_type;
break;
}
}
$SASL = \SnappyMail\SASL::factory($type);
$SASL->base64 = true;
// $oClient->Login($SASL);
*/
/*
// $encrypted = !empty(\stream_get_meta_data($oClient->ConnectionResource())['crypto']);
$type = '';
$types = [
'SCRAM-SHA-256' => 1, // !$encrypted
'SCRAM-SHA-1' => 1, // !$encrypted
'CRAM-MD5' => $bUseAuthCramMd5IfSupported,
'PLAIN' => $bUseAuthPlainIfSupported,
'LOGIN' => 1, // $encrypted
'XOAUTH2' => 0
];
foreach ($types as $sasl_type => $active) {
if ($active && $oClient->IsAuthSupported($sasl_type) && \SnappyMail\SASL::isSupported($sasl_type)) {
$type = $sasl_type;
break;
}
}
if (!$type) {
\trigger_error("SMTP {$oClient->GetConnectedHost()} no supported AUTH options. Disable login" . ($oClient->IsSupported('STARTTLS') ? ' or try with STARTTLS' : ''));
$oClient->writeLogException(
new \MailSo\Smtp\Exceptions\LoginBadMethodException,
\MailSo\Log\Enumerations\Type::NOTICE, true);
}
$SASL = \SnappyMail\SASL::factory($type);
$SASL->base64 = true;
*/
}
}

View file

@ -33,6 +33,9 @@ abstract class SASL
public static function isSupported(string $type) : bool
{
if (\preg_match('/^([A-Z2]+)(?:-(.+))?$/Di', $type, $m)) {
if ('XOAUTH2' === $m[1] || 'OAUTHBEARER' === $m[1]) {
$m[1] = 'OAUTH';
}
$class = __CLASS__ . "\\{$m[1]}";
return \class_exists($class) && $class::isSupported($m[2] ?? '');
}

View file

@ -0,0 +1,21 @@
<?php
/**
* https://datatracker.ietf.org/doc/html/rfc7628
* https://developers.google.com/gmail/imap/xoauth2-protocol
*/
namespace SnappyMail\SASL;
class OAuth extends \SnappyMail\SASL
{
public function authenticate(string $username, string $passphrase, ?string $authzid = null) : string
{
return $this->encode("user={$username}\x01auth=Bearer {$passphrase}\x01\x01");
}
public static function isSupported(string $param) : bool
{
return true;
}
}