|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463 |
- <?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");
- }
-
- }
-
|