mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-08-25 10:09:20 +03:00
Replaced references to https://github.com/the-djmaze/snappymail with https://github.com/nextgen-networks/snappymail/ across the repository, excluding README files as requested. The replacement handles trailing slashes carefully to ensure bare URLs include the slash while sub-paths (like /issues/ or .git) do not result in double slashes. Files that already used the new domain were also updated to ensure consistent trailing slash usage. Co-authored-by: nextgen-networks <21206978+nextgen-networks@users.noreply.github.com>
622 lines
16 KiB
PHP
622 lines
16 KiB
PHP
<?php
|
|
|
|
/*
|
|
* This file is part of MailSo.
|
|
*
|
|
* (c) 2014 Usenko Timur
|
|
*
|
|
* For the full copyright and license information, please view the LICENSE
|
|
* file that was distributed with this source code.
|
|
*
|
|
* https://datatracker.ietf.org/doc/html/rfc2034
|
|
* https://datatracker.ietf.org/doc/html/rfc2821
|
|
*/
|
|
|
|
namespace MailSo\Smtp;
|
|
|
|
use MailSo\Net\Enumerations\ConnectionSecurityType;
|
|
use SnappyMail\IDN;
|
|
|
|
/**
|
|
response codes:
|
|
220 ready
|
|
221 Closing
|
|
235 Authentication succeeded
|
|
250 Requested mail action okay, completed
|
|
251 User not local; will forward
|
|
252 Cannot verify the user, but it will try to deliver the message anyway
|
|
334 Server challenge (the text part contains the Base64-encoded challenge)
|
|
354 Start mail input
|
|
*/
|
|
|
|
/**
|
|
* @category MailSo
|
|
* @package Smtp
|
|
*/
|
|
class SmtpClient extends \MailSo\Net\NetClient
|
|
{
|
|
private bool $bIsLoggined = false;
|
|
|
|
private string $sEhlo = '';
|
|
|
|
private bool $bRcpt = false;
|
|
|
|
private bool $bMail = false;
|
|
|
|
private array $aAuthTypes = array();
|
|
|
|
private array $aCapa = array();
|
|
|
|
/**
|
|
* RFC 1870
|
|
*/
|
|
private int $iSizeCapaValue = 0;
|
|
|
|
private array $aResults = array();
|
|
|
|
public function Capability() : array
|
|
{
|
|
return $this->aCapa;
|
|
}
|
|
|
|
public function hasCapability(string $sCapa) : bool
|
|
{
|
|
return \in_array(\strtoupper($sCapa), $this->aCapa);
|
|
}
|
|
|
|
public function maxSize() : int
|
|
{
|
|
return $this->iSizeCapaValue;
|
|
}
|
|
|
|
public static function EhloHelper() : string
|
|
{
|
|
$sEhloHost = empty($_SERVER['SERVER_NAME']) ? '' : \trim($_SERVER['SERVER_NAME']);
|
|
if (empty($sEhloHost)) {
|
|
$sEhloHost = empty($_SERVER['HTTP_HOST']) ? '' : \trim($_SERVER['HTTP_HOST']);
|
|
}
|
|
|
|
if (empty($sEhloHost)) {
|
|
$sEhloHost = \function_exists('gethostname') ? \gethostname() : 'localhost';
|
|
}
|
|
|
|
$sEhloHost = \trim(\preg_replace('/:\d+$/', '', \trim($sEhloHost)));
|
|
|
|
if (\preg_match('/^\d+\.\d+\.\d+\.\d+$/', $sEhloHost)) {
|
|
$sEhloHost = '['.$sEhloHost.']';
|
|
}
|
|
|
|
return empty($sEhloHost) ? 'localhost' : $sEhloHost;
|
|
}
|
|
|
|
/**
|
|
* @throws \InvalidArgumentException
|
|
* @throws \MailSo\RuntimeException
|
|
* @throws \MailSo\Net\Exceptions\*
|
|
* @throws \MailSo\Smtp\Exceptions\*
|
|
*/
|
|
public function Connect(\MailSo\Net\ConnectSettings $oSettings) : void
|
|
{
|
|
parent::Connect($oSettings);
|
|
|
|
$this->validateResponse(220);
|
|
|
|
$this->ehloOrHelo($oSettings->Ehlo);
|
|
$this->sEhlo = $oSettings->Ehlo;
|
|
|
|
if (ConnectionSecurityType::STARTTLS === $this->Settings->type
|
|
|| (ConnectionSecurityType::AUTO_DETECT === $this->Settings->type && $this->hasCapability('STARTTLS'))) {
|
|
$this->StartTLS();
|
|
}
|
|
}
|
|
|
|
private function StartTLS() : void
|
|
{
|
|
if ($this->hasCapability('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);
|
|
}
|
|
}
|
|
|
|
public function supportsAuthType(string $sasl_type) : bool
|
|
{
|
|
return \in_array(\strtoupper($sasl_type), $this->aAuthTypes);
|
|
}
|
|
|
|
/**
|
|
* @throws \InvalidArgumentException
|
|
* @throws \MailSo\RuntimeException
|
|
* @throws \MailSo\Net\Exceptions\*
|
|
* @throws \MailSo\Smtp\Exceptions\*
|
|
*/
|
|
public function Login(Settings $oSettings) : self
|
|
{
|
|
if ($this->bIsLoggined) {
|
|
return $this;
|
|
}
|
|
|
|
$sLogin = $oSettings->username;
|
|
$sPassword = $oSettings->passphrase;
|
|
|
|
$type = '';
|
|
foreach ($oSettings->SASLMechanisms as $sasl_type) {
|
|
if (\in_array(\strtoupper($sasl_type), $this->aAuthTypes) && \SnappyMail\SASL::isSupported($sasl_type)) {
|
|
$type = $sasl_type;
|
|
break;
|
|
}
|
|
}
|
|
if (!$type) {
|
|
if (!$this->Encrypted() && $this->hasCapability('STARTTLS')) {
|
|
$this->StartTLS();
|
|
return $this->Login($oSettings);
|
|
}
|
|
\trigger_error("SMTP {$this->GetConnectedHost()} no supported AUTH options. Disable login");
|
|
$this->writeLogException(new \MailSo\Smtp\Exceptions\LoginBadMethodException);
|
|
}
|
|
|
|
$SASL = \SnappyMail\SASL::factory($type);
|
|
|
|
if ($this->Settings->authPlainLine && $SASL instanceof \SnappyMail\SASL\Plain) {
|
|
// https://github.com/nextgen-networks/snappymail/issues/1038
|
|
try
|
|
{
|
|
$sRequest = $SASL->authenticate($sLogin, $sPassword);
|
|
$this->logMask($sRequest);
|
|
$sResult = $this->sendRequestWithCheck('AUTH PLAIN ' . $sRequest, 235);
|
|
}
|
|
catch (\MailSo\Smtp\Exceptions\NegativeResponseException $oException)
|
|
{
|
|
$this->writeLogException(
|
|
new \MailSo\Smtp\Exceptions\LoginBadCredentialsException($oException->GetResponses(), $oException->getMessage(), 0, $oException)
|
|
);
|
|
}
|
|
} else {
|
|
// Start authentication
|
|
try
|
|
{
|
|
$sResult = $this->sendRequestWithCheck("AUTH {$type}", 334);
|
|
}
|
|
catch (\MailSo\Smtp\Exceptions\NegativeResponseException $oException)
|
|
{
|
|
$this->writeLogException(
|
|
new \MailSo\Smtp\Exceptions\LoginBadMethodException($oException->GetResponses(), $oException->getMessage(), 0, $oException)
|
|
);
|
|
}
|
|
|
|
try
|
|
{
|
|
$sRequest = '';
|
|
if (\str_starts_with($type, 'SCRAM-')) {
|
|
// RFC 5802 send "client-first-message" and receive "server-first-message"
|
|
$sRequest = $SASL->authenticate($sLogin, $sPassword, $sResult);
|
|
$this->logMask($sRequest);
|
|
$sResult = $this->sendRequestWithCheck($sRequest, 334);
|
|
// RFC 5802 send "client-final-message" and receive "server-final-message"
|
|
$sRequest = $SASL->challenge($sResult);
|
|
$this->logMask($sRequest);
|
|
$sResult = $this->sendRequestWithCheck($sRequest, 334);
|
|
$SASL->verify($sResult);
|
|
// Now end the authentication
|
|
$sRequest = '';
|
|
$this->sendRaw($sRequest, true, '');
|
|
$this->validateResponse(235, '');
|
|
} else switch ($type) {
|
|
// RFC 4616
|
|
case 'PLAIN':
|
|
case 'XOAUTH2':
|
|
case 'OAUTHBEARER':
|
|
$sRequest = $SASL->authenticate($sLogin, $sPassword);
|
|
break;
|
|
|
|
case 'LOGIN':
|
|
$sRequest = $SASL->authenticate($sLogin, $sPassword, $sResult);
|
|
$this->logMask($sRequest);
|
|
$sResult = $this->sendRequestWithCheck($sRequest, 334);
|
|
$sRequest = $SASL->challenge($sResult);
|
|
break;
|
|
|
|
// RFC 2195
|
|
case 'CRAM-MD5':
|
|
$sRequest = $SASL->authenticate($sLogin, $sPassword, $sResult);
|
|
break;
|
|
}
|
|
if ($sRequest) {
|
|
$this->logMask($sRequest);
|
|
$SASL->verify($this->sendRequestWithCheck($sRequest, 235));
|
|
}
|
|
}
|
|
catch (\MailSo\Smtp\Exceptions\NegativeResponseException $oException)
|
|
{
|
|
$this->writeLogException(
|
|
new \MailSo\Smtp\Exceptions\LoginBadCredentialsException($oException->GetResponses(), $oException->getMessage(), 0, $oException)
|
|
);
|
|
}
|
|
}
|
|
|
|
$this->bIsLoggined = true;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @throws \MailSo\RuntimeException
|
|
* @throws \MailSo\Net\Exceptions\*
|
|
* @throws \MailSo\Smtp\Exceptions\*
|
|
*/
|
|
public function MailFrom(string $sFrom, int $iSizeIfSupported = 0, bool $bDsn = false, bool $bRequireTLS = false) : self
|
|
{
|
|
// $sFrom = IDN::emailToAscii($sFrom);
|
|
$sCmd = "FROM:<{$sFrom}>";
|
|
// RFC 6531
|
|
if ($this->hasCapability('SMTPUTF8')) {
|
|
// $sFrom = IDN::emailToUtf8($sFrom);
|
|
// $sCmd = "FROM:<{$sFrom}> SMTPUTF8";
|
|
}
|
|
|
|
if (0 < $iSizeIfSupported && $this->hasCapability('SIZE')) {
|
|
$sCmd .= ' SIZE='.$iSizeIfSupported;
|
|
}
|
|
|
|
// RFC 3461
|
|
if ($bDsn && $this->hasCapability('DSN')) {
|
|
$sCmd .= ' RET=HDRS';
|
|
}
|
|
|
|
// RFC 6152
|
|
if ($this->hasCapability('8BITMIME')) {
|
|
// $sCmd .= ' BODY=8BITMIME';
|
|
}
|
|
// RFC 3030
|
|
else if ($this->hasCapability('BINARYMIME')) {
|
|
// $sCmd .= ' BODY=BINARYMIME';
|
|
}
|
|
|
|
// RFC 8689
|
|
if ($bRequireTLS && $this->hasCapability('REQUIRETLS')) {
|
|
$sCmd .= ' REQUIRETLS';
|
|
}
|
|
|
|
$this->sendRequestWithCheck("MAIL {$sCmd}", 250);
|
|
|
|
$this->bMail = true;
|
|
$this->bRcpt = false;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @throws \MailSo\RuntimeException
|
|
* @throws \MailSo\Net\Exceptions\*
|
|
* @throws \MailSo\Smtp\Exceptions\*
|
|
*/
|
|
public function Rcpt(string $sTo, bool $bDsn = false) : self
|
|
{
|
|
if (!$this->bMail) {
|
|
$this->writeLogException(new \MailSo\RuntimeException('No sender reverse path has been supplied'), \LOG_ERR);
|
|
}
|
|
|
|
// $sTo = IDN::emailToAscii($sTo);
|
|
|
|
$sCmd = 'TO:<'.$sTo.'>';
|
|
|
|
if ($bDsn && $this->hasCapability('DSN')) {
|
|
$sCmd .= ' NOTIFY=SUCCESS,FAILURE';
|
|
}
|
|
|
|
$this->sendRequestWithCheck("RCPT {$sCmd}", [250, 251], "Failed to add recipient '{$sTo}'");
|
|
|
|
$this->bRcpt = true;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @throws \MailSo\RuntimeException
|
|
* @throws \MailSo\Net\Exceptions\*
|
|
* @throws \MailSo\Smtp\Exceptions\*
|
|
*/
|
|
public function MailTo(string $sTo) : self
|
|
{
|
|
return $this->Rcpt($sTo);
|
|
}
|
|
|
|
/**
|
|
* @throws \MailSo\RuntimeException
|
|
* @throws \MailSo\Net\Exceptions\*
|
|
* @throws \MailSo\Smtp\Exceptions\*
|
|
*/
|
|
public function Data(string $sData) : self
|
|
{
|
|
if (!\strlen(\trim($sData))) {
|
|
throw new \InvalidArgumentException;
|
|
}
|
|
|
|
$rDataStream = \MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString($sData);
|
|
unset($sData);
|
|
$this->DataWithStream($rDataStream);
|
|
\MailSo\Base\ResourceRegistry::CloseMemoryResource($rDataStream);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @param resource $rDataStream
|
|
*
|
|
* @throws \InvalidArgumentException
|
|
* @throws \MailSo\RuntimeException
|
|
* @throws \MailSo\Net\Exceptions\*
|
|
* @throws \MailSo\Smtp\Exceptions\*
|
|
*/
|
|
public function DataWithStream($rDataStream) : self
|
|
{
|
|
if (!\is_resource($rDataStream)) {
|
|
throw new \InvalidArgumentException;
|
|
}
|
|
|
|
if (!$this->bRcpt) {
|
|
$this->writeLogException(new \MailSo\RuntimeException('No recipient forward path has been supplied'), \LOG_ERR);
|
|
}
|
|
|
|
/*
|
|
// RFC 3030
|
|
if ($this->hasCapability('CHUNKING')) {
|
|
$this->bRunningCallback = true;
|
|
while (!\feof($rDataStream)) {
|
|
$sBuffer = \fgets($rDataStream);
|
|
if (false === $sBuffer) {
|
|
if (!\feof($rDataStream)) {
|
|
$this->writeLogException(new \MailSo\RuntimeException('Cannot read input resource'), \LOG_ERR);
|
|
}
|
|
break;
|
|
}
|
|
$this->sendRequestWithCheck("BDAT " . \strlen($sBuffer) . "\r\n{$sBuffer}", 250);
|
|
\MailSo\Base\Utils::ResetTimeLimit();
|
|
}
|
|
$this->sendRequestWithCheck("BDAT 0 LAST\r\n", 250);
|
|
}
|
|
else {
|
|
*/
|
|
$this->sendRequestWithCheck('DATA', 354);
|
|
|
|
$this->writeLog('Message data.');
|
|
|
|
$this->bRunningCallback = true;
|
|
|
|
while (!\feof($rDataStream)) {
|
|
$sBuffer = \fgets($rDataStream);
|
|
if (false === $sBuffer) {
|
|
if (!\feof($rDataStream)) {
|
|
$this->writeLogException(new \MailSo\RuntimeException('Cannot read input resource'), \LOG_ERR);
|
|
}
|
|
break;
|
|
}
|
|
if (\str_starts_with($sBuffer, '.')) {
|
|
$sBuffer = '.' . $sBuffer;
|
|
}
|
|
$this->sendRaw(\rtrim($sBuffer, "\r\n"), false);
|
|
\MailSo\Base\Utils::ResetTimeLimit();
|
|
}
|
|
|
|
$this->sendRequestWithCheck('.', 250);
|
|
|
|
$this->bRunningCallback = false;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @throws \MailSo\RuntimeException
|
|
* @throws \MailSo\Net\Exceptions\*
|
|
* @throws \MailSo\Smtp\Exceptions\*
|
|
*/
|
|
public function Rset() : self
|
|
{
|
|
$this->sendRequestWithCheck('RSET', [250, 220]);
|
|
|
|
$this->bMail = false;
|
|
$this->bRcpt = false;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* VERIFY
|
|
* @throws \MailSo\RuntimeException
|
|
* @throws \MailSo\Net\Exceptions\*
|
|
* @throws \MailSo\Smtp\Exceptions\*
|
|
*/
|
|
public function Vrfy(string $sUser) : self
|
|
{
|
|
$sUser = \MailSo\Base\Utils::Trim($sUser);
|
|
/*
|
|
// RFC 6531
|
|
if ($this->hasCapability('SMTPUTF8')) {
|
|
$this->sendRequestWithCheck('VRFY ' . IDN::emailToUtf8($sUser) . ' SMTPUTF8', [250, 251, 252]);
|
|
} else {
|
|
*/
|
|
$this->sendRequestWithCheck('VRFY ' . IDN::emailToAscii($sUser), [250, 251, 252]);
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* EXPAND command, the string identifies a mailing list, and the
|
|
* successful (i.e., 250) multiline response MAY include the full name
|
|
* of the users and MUST give the mailboxes on the mailing list.
|
|
*
|
|
* @throws \MailSo\RuntimeException
|
|
* @throws \MailSo\Net\Exceptions\*
|
|
* @throws \MailSo\Smtp\Exceptions\*
|
|
*/
|
|
/*
|
|
public function Expn(string $sUser) : self
|
|
{
|
|
$sUser = \MailSo\Base\Utils::Trim($sUser);
|
|
// RFC 6531
|
|
if ($this->hasCapability('SMTPUTF8')) {
|
|
$this->sendRequestWithCheck('EXPN ' . IDN::emailToUtf8($sUser) . ' SMTPUTF8', [250, 251, 252]);
|
|
} else {
|
|
$this->sendRequestWithCheck('EXPN ' . IDN::emailToAscii($sUser), [250, 251, 252]);
|
|
}
|
|
return $this;
|
|
}
|
|
*/
|
|
|
|
/**
|
|
* @throws \MailSo\RuntimeException
|
|
* @throws \MailSo\Net\Exceptions\*
|
|
* @throws \MailSo\Smtp\Exceptions\*
|
|
*/
|
|
public function Noop() : self
|
|
{
|
|
$this->sendRequestWithCheck('NOOP', 250);
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @throws \MailSo\RuntimeException
|
|
* @throws \MailSo\Net\Exceptions\*
|
|
* @throws \MailSo\Smtp\Exceptions\*
|
|
*/
|
|
public function Logout() : void
|
|
{
|
|
if ($this->IsConnected()) {
|
|
$this->sendRequestWithCheck('QUIT', 221);
|
|
}
|
|
$this->bMail = false;
|
|
$this->bRcpt = false;
|
|
}
|
|
|
|
/**
|
|
* @throws \InvalidArgumentException
|
|
* @throws \MailSo\RuntimeException
|
|
* @throws \MailSo\Net\Exceptions\*
|
|
* @throws \MailSo\Smtp\Exceptions\*
|
|
*/
|
|
private function sendRequestWithCheck(string $sCommand, $mExpectCode, string $sErrorPrefix = '') : string
|
|
{
|
|
if (!\strlen(\trim($sCommand))) {
|
|
$this->writeLogException(new \InvalidArgumentException, \LOG_ERR);
|
|
}
|
|
$this->IsConnected(true);
|
|
$this->sendRaw($sCommand, true, '');
|
|
$this->validateResponse($mExpectCode, $sErrorPrefix);
|
|
return empty($this->aResults[0]) ? '' : \trim(\substr($this->aResults[0], 4));
|
|
}
|
|
|
|
private function ehloOrHelo(string $sHost) : void
|
|
{
|
|
try
|
|
{
|
|
$this->ehlo($sHost);
|
|
}
|
|
catch (\Throwable $oException)
|
|
{
|
|
try
|
|
{
|
|
$this->helo($sHost);
|
|
}
|
|
catch (\Throwable $oException)
|
|
{
|
|
throw $oException;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @throws \MailSo\RuntimeException
|
|
* @throws \MailSo\Net\Exceptions\*
|
|
* @throws \MailSo\Smtp\Exceptions\*
|
|
*/
|
|
private function ehlo(string $sHost) : void
|
|
{
|
|
$this->sendRequestWithCheck("EHLO {$sHost}", 250);
|
|
/*
|
|
250-PIPELINING\r\n
|
|
250-SIZE 256000000\r\n
|
|
250-ETRN\r\n
|
|
250-ENHANCEDSTATUSCODES\r\n
|
|
*/
|
|
$this->aCapa = [];
|
|
foreach ($this->aResults as $sLine) {
|
|
$aMatch = array();
|
|
if (\preg_match('/[\d]+[ \-](.+)$/', $sLine, $aMatch) && isset($aMatch[1]) && \strlen($aMatch[1])) {
|
|
$aLine = \preg_split('/[ =]/', \trim($aMatch[1]), 2);
|
|
if (!empty($aLine[0])) {
|
|
$sCapa = \strtoupper($aLine[0]);
|
|
if (!empty($aLine[1]) && ('AUTH' === $sCapa || 'SIZE' === $sCapa)) {
|
|
$sSubLine = \trim(\strtoupper($aLine[1]));
|
|
if (\strlen($sSubLine)) {
|
|
if ('AUTH' === $sCapa) {
|
|
$this->aAuthTypes = \explode(' ', $sSubLine);
|
|
} else if ('SIZE' === $sCapa && \is_numeric($sSubLine)) {
|
|
$this->iSizeCapaValue = (int) $sSubLine;
|
|
}
|
|
}
|
|
}
|
|
$this->aCapa[] = $sCapa;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @throws \MailSo\RuntimeException
|
|
* @throws \MailSo\Net\Exceptions\*
|
|
* @throws \MailSo\Smtp\Exceptions\*
|
|
*/
|
|
private function helo(string $sHost) : void
|
|
{
|
|
$this->sendRequestWithCheck("HELO {$sHost}", 250);
|
|
$this->aAuthTypes = array();
|
|
$this->iSizeCapaValue = 0;
|
|
$this->aCapa = [];
|
|
}
|
|
|
|
/**
|
|
* @throws \MailSo\Smtp\Exceptions\ResponseException
|
|
*/
|
|
private function validateResponse($mExpectCode, string $sErrorPrefix = '') : void
|
|
{
|
|
$mExpectCode = \is_array($mExpectCode)
|
|
? \array_map('intval', $mExpectCode)
|
|
: array((int) $mExpectCode);
|
|
|
|
$aParts = array('', '', '');
|
|
$this->aResults = array();
|
|
do
|
|
{
|
|
$sResponse = $this->getNextBuffer();
|
|
$aParts = \preg_split('/([\s\-]+)/', $sResponse, 2, PREG_SPLIT_DELIM_CAPTURE);
|
|
|
|
if (3 === \count($aParts) && \is_numeric($aParts[0])) {
|
|
if ('-' !== \substr($aParts[1], 0, 1) && !\in_array((int) $aParts[0], $mExpectCode)) {
|
|
$this->writeLogException(
|
|
new Exceptions\NegativeResponseException($this->aResults,
|
|
('' === $sErrorPrefix ? '' : $sErrorPrefix.': ').\trim(
|
|
(\count($this->aResults) ? \implode("\r\n", $this->aResults)."\r\n" : '').
|
|
$sResponse)), \LOG_ERR);
|
|
}
|
|
} else {
|
|
$this->writeLogException(
|
|
new Exceptions\ResponseException($this->aResults,
|
|
('' === $sErrorPrefix ? '' : $sErrorPrefix.': ').\trim(
|
|
(\count($this->aResults) ? \implode("\r\n", $this->aResults)."\r\n" : '').
|
|
$sResponse)), \LOG_ERR);
|
|
}
|
|
|
|
$this->aResults[] = $sResponse;
|
|
}
|
|
while ('-' === \substr($aParts[1], 0, 1));
|
|
}
|
|
|
|
public function getLogName() : string
|
|
{
|
|
return 'SMTP';
|
|
}
|
|
|
|
}
|