Files
park/vendor/fastadminnet/fastadmin-mailer/src/Mailer/SMTP.php

493 lines
12 KiB
PHP
Raw Normal View History

2025-12-01 11:19:23 +08:00
<?php
/***************************************************\
*
* Mailer (https://github.com/txthinking/Mailer)
*
* A lightweight PHP SMTP mail sender.
* Implement RFC0821, RFC0822, RFC1869, RFC2045, RFC2821
*
* Support html body, don't worry that the receiver's
* mail client can't support html, because Mailer will
* send both text/plain and text/html body, so if the
* mail client can't support html, it will display the
* text/plain body.
*
* Create Date 2012-07-25.
* Under the MIT license.
*
\***************************************************/
namespace Tx\Mailer;
use Psr\Log\LoggerInterface;
use Tx\Mailer\Exceptions\CodeException;
use Tx\Mailer\Exceptions\CryptoException;
use Tx\Mailer\Exceptions\SMTPException;
class SMTP
{
/**
* smtp socket
*/
protected $smtp;
/**
* smtp server
*/
protected $host;
/**
* smtp server port
*/
protected $port;
/**
* smtp secure ssl tls tlsv1.0 tlsv1.1 tlsv1.2
*/
protected $secure;
/**
* smtp allow insecure ssl
*/
protected $allowInsecure;
/**
* EHLO message
*/
protected $ehlo;
/**
* smtp username
*/
protected $username;
/**
* smtp password
*/
protected $password;
/**
* oauth access token
*/
protected $oauthToken;
/**
* $this->CRLF
* @var string
*/
protected $CRLF = "\r\n";
/**
* @var Message
*/
protected $message;
/**
* @var LoggerInterface - Used to make things prettier than self::$logger
*/
protected $logger;
/**
* Stack of all commands issued to SMTP
* @var array
*/
protected $commandStack = array();
/**
* Stack of all results issued to SMTP
* @var array
*/
protected $resultStack = array();
public function __construct(LoggerInterface $logger=null)
{
$this->logger = $logger;
}
/**
* set server and port
* @param string $host server
* @param int $port port
* @param string $secure ssl tls tlsv1.0 tlsv1.1 tlsv1.2
* @return $this
*/
public function setServer($host, $port, $secure=null, $allowInsecure=null)
{
$this->host = $host;
$this->port = $port;
$this->secure = $secure;
$this->allowInsecure = $allowInsecure;
if(!$this->ehlo) $this->ehlo = $host;
$this->logger && $this->logger->debug("Set: the server");
return $this;
}
/**
* auth login with server
* @param string $username
* @param string $password
* @return $this
*/
public function setAuth($username, $password)
{
$this->username = $username;
$this->password = $password;
$this->logger && $this->logger->debug("Set: the auth login");
return $this;
}
/**
* auth oauthbearer with server
* @param string $accessToken
* @return $this
*/
public function setOAuth($accessToken)
{
$this->oauthToken = $accessToken;
$this->logger && $this->logger->debug("Set: the auth oauthbearer");
return $this;
}
/**
* set the EHLO message
* @param $ehlo
* @return $this
*/
public function setEhlo($ehlo)
{
$this->ehlo = $ehlo;
return $this;
}
/**
* Send the message
*
* @param Message $message
* @return bool
* @throws CodeException
* @throws CryptoException
* @throws SMTPException
*/
public function send(Message $message)
{
$this->logger && $this->logger->debug('Set: a message will be sent');
$this->message = $message;
$this->connect()
->ehlo();
if ($this->secure === 'tls' || $this->secure === 'tlsv1.0' || $this->secure === 'tlsv1.1' | $this->secure === 'tlsv1.2') {
$this->starttls()
->ehlo();
}
if ($this->username !== null || $this->password !== null) {
$this->authLogin();
} elseif ($this->oauthToken !== null) {
$this->authOAuthBearer();
}
$this->mailFrom()
->rcptTo()
->data()
->quit();
return fclose($this->smtp);
}
/**
* connect the server
* SUCCESS 220
* @return $this
* @throws CodeException
* @throws SMTPException
*/
protected function connect()
{
$this->logger && $this->logger->debug("Connecting to {$this->host} at {$this->port}");
$host = ($this->secure == 'ssl') ? 'ssl://' . $this->host : $this->host;
// Create connection
$context = null;
if ($this->allowInsecure) {
$context = stream_context_create([
'ssl' => [
'security_level' => 0,
'verify_peer' => false,
'verify_peer_name' => false
]
]);
} else {
$context = stream_context_create();
}
$this->smtp = stream_socket_client(
$host.':'.$this->port,
$error_code,
$error_message,
ini_get('default_socket_timeout'),
STREAM_CLIENT_CONNECT,
$context
);
//set block mode
// stream_set_blocking($this->smtp, 1);
if (!$this->smtp){
throw new SMTPException("Could not open SMTP Port.");
}
$code = $this->getCode();
if ($code !== '220'){
throw new CodeException('220', $code, array_pop($this->resultStack));
}
return $this;
}
/**
* SMTP STARTTLS
* SUCCESS 220
* @return $this
* @throws CodeException
* @throws CryptoException
* @throws SMTPException
*/
protected function starttls()
{
$in = "STARTTLS" . $this->CRLF;
$code = $this->pushStack($in);
if ($code !== '220'){
throw new CodeException('220', $code, array_pop($this->resultStack));
}
if ($this->secure !== 'tls' && version_compare(phpversion(), '5.6.0', '<')) {
throw new CryptoException('Crypto type expected PHP 5.6 or greater');
}
switch ($this->secure) {
case 'tlsv1.0':
$crypto_type = STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT;
break;
case 'tlsv1.1':
$crypto_type = STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
break;
case 'tlsv1.2':
$crypto_type = STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
break;
default:
$crypto_type = STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT |
STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT |
STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
break;
}
if(!\stream_socket_enable_crypto($this->smtp, true, $crypto_type)) {
throw new CryptoException("Start TLS failed to enable crypto");
}
return $this;
}
/**
* SMTP EHLO
* SUCCESS 250
* @return $this
* @throws CodeException
* @throws SMTPException
*/
protected function ehlo()
{
$in = "EHLO " . $this->ehlo . $this->CRLF;
$code = $this->pushStack($in);
if ($code !== '250'){
throw new CodeException('250', $code, array_pop($this->resultStack));
}
return $this;
}
/**
* SMTP AUTH LOGIN
* SUCCESS 334
* SUCCESS 334
* SUCCESS 235
* @return $this
* @throws CodeException
* @throws SMTPException
*/
protected function authLogin()
{
$in = "AUTH LOGIN" . $this->CRLF;
$code = $this->pushStack($in);
if ($code !== '334'){
throw new CodeException('334', $code, array_pop($this->resultStack));
}
$in = base64_encode($this->username) . $this->CRLF;
$code = $this->pushStack($in);
if ($code !== '334'){
throw new CodeException('334', $code, array_pop($this->resultStack));
}
$in = base64_encode($this->password) . $this->CRLF;
$code = $this->pushStack($in);
if ($code !== '235'){
throw new CodeException('235', $code, array_pop($this->resultStack));
}
return $this;
}
/**
* SMTP AUTH OAUTHBEARER
* SUCCESS 235
* @return $this
* @throws CodeException
* @throws SMTPException
*/
protected function authOAuthBearer()
{
$authStr = sprintf("n,a=%s,%shost=%s%sport=%s%sauth=Bearer %s%s%s",
$this->message->getFromEmail(),
chr(1),
$this->host,
chr(1),
$this->port,
chr(1),
$this->oauthToken,
chr(1),
chr(1)
);
$authStr = base64_encode($authStr);
$in = "AUTH OAUTHBEARER $authStr" . $this->CRLF;
$code = $this->pushStack($in);
if ($code !== '235'){
throw new CodeException('235', $code, array_pop($this->resultStack));
}
return $this;
}
/**
* SMTP AUTH XOAUTH2
* SUCCESS 235
* @return $this
* @throws CodeException
* @throws SMTPException
*/
protected function authXOAuth2()
{
$authStr = sprintf("user=%s%sauth=Bearer %s%s%s",
$this->message->getFromEmail(),
chr(1),
$this->oauthToken,
chr(1),
chr(1)
);
$authStr = base64_encode($authStr);
$in = "AUTH XOAUTH2 $authStr" . $this->CRLF;
$code = $this->pushStack($in);
if ($code !== '235'){
throw new CodeException('235', $code, array_pop($this->resultStack));
}
return $this;
}
/**
* SMTP MAIL FROM
* SUCCESS 250
* @return $this
* @throws CodeException
* @throws SMTPException
*/
protected function mailFrom()
{
$in = "MAIL FROM:<{$this->message->getFromEmail()}>" . $this->CRLF;
$code = $this->pushStack($in);
if ($code !== '250') {
throw new CodeException('250', $code, array_pop($this->resultStack));
}
return $this;
}
/**
* SMTP RCPT TO
* SUCCESS 250
* @return $this
* @throws CodeException
* @throws SMTPException
*/
protected function rcptTo()
{
$to = array_merge(
$this->message->getTo(),
$this->message->getCc(),
$this->message->getBcc()
);
foreach ($to as $toEmail=>$_) {
$in = "RCPT TO:<" . $toEmail . ">" . $this->CRLF;
$code = $this->pushStack($in);
if ($code !== '250') {
throw new CodeException('250', $code, array_pop($this->resultStack));
}
}
return $this;
}
/**
* SMTP DATA
* SUCCESS 354
* SUCCESS 250
* @return $this
* @throws CodeException
* @throws SMTPException
*/
protected function data()
{
$in = "DATA" . $this->CRLF;
$code = $this->pushStack($in);
if ($code !== '354') {
throw new CodeException('354', $code, array_pop($this->resultStack));
}
$in = $this->message->toString();
$code = $this->pushStack($in);
if ($code !== '250'){
throw new CodeException('250', $code, array_pop($this->resultStack));
}
return $this;
}
/**
* SMTP QUIT
* SUCCESS 221
* @return $this
* @throws CodeException
* @throws SMTPException
*/
protected function quit()
{
$in = "QUIT" . $this->CRLF;
$code = $this->pushStack($in);
if ($code !== '221'){
throw new CodeException('221', $code, array_pop($this->resultStack));
}
return $this;
}
protected function pushStack($string)
{
$this->commandStack[] = $string;
fputs($this->smtp, $string, strlen($string));
$this->logger && $this->logger->debug('Sent: '. $string);
return $this->getCode();
}
/**
* get smtp response code
* once time has three digital and a space
* @return string
* @throws SMTPException
*/
protected function getCode()
{
while ($str = fgets($this->smtp, 515)) {
$this->logger && $this->logger->debug("Got: ". $str);
$this->resultStack[] = $str;
$str = ltrim($str);
if(substr($str,3,1) == " ") {
$code = substr($str,0,3);
return $code;
}
}
throw new SMTPException("SMTP Server did not respond with anything I recognized");
}
}