|
- <?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;
-
- /**
- * 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)
- {
- $this->host = $host;
- $this->port = $port;
- $this->secure = $secure;
- 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;
- $this->smtp = @fsockopen($host, $this->port);
- //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_TLS_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;
- if(substr($str,3,1) == " ") {
- $code = substr($str,0,3);
- return $code;
- }
- }
- throw new SMTPException("SMTP Server did not respond with anything I recognized");
- }
-
- }
|