PageRenderTime 165ms CodeModel.GetById 52ms app.highlight 43ms RepoModel.GetById 57ms app.codeStats 1ms

/lib/external/phpmailer/class.smtp.php

https://bitbucket.org/navigatecms/navigatecms
PHP | 1249 lines | 649 code | 93 blank | 507 comment | 82 complexity | eaf2322fced76ad1bd3e3d1e9e4e35df MD5 | raw file
   1<?php
   2/**
   3 * PHPMailer RFC821 SMTP email transport class.
   4 * PHP Version 5
   5 * @package PHPMailer
   6 * @link https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
   7 * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
   8 * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
   9 * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
  10 * @author Brent R. Matzelle (original founder)
  11 * @copyright 2014 Marcus Bointon
  12 * @copyright 2010 - 2012 Jim Jagielski
  13 * @copyright 2004 - 2009 Andy Prevost
  14 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
  15 * @note This program is distributed in the hope that it will be useful - WITHOUT
  16 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  17 * FITNESS FOR A PARTICULAR PURPOSE.
  18 */
  19
  20/**
  21 * PHPMailer RFC821 SMTP email transport class.
  22 * Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server.
  23 * @package PHPMailer
  24 * @author Chris Ryan
  25 * @author Marcus Bointon <phpmailer@synchromedia.co.uk>
  26 */
  27class SMTP
  28{
  29    /**
  30     * The PHPMailer SMTP version number.
  31     * @var string
  32     */
  33    const VERSION = '5.2.22';
  34
  35    /**
  36     * SMTP line break constant.
  37     * @var string
  38     */
  39    const CRLF = "\r\n";
  40
  41    /**
  42     * The SMTP port to use if one is not specified.
  43     * @var integer
  44     */
  45    const DEFAULT_SMTP_PORT = 25;
  46
  47    /**
  48     * The maximum line length allowed by RFC 2822 section 2.1.1
  49     * @var integer
  50     */
  51    const MAX_LINE_LENGTH = 998;
  52
  53    /**
  54     * Debug level for no output
  55     */
  56    const DEBUG_OFF = 0;
  57
  58    /**
  59     * Debug level to show client -> server messages
  60     */
  61    const DEBUG_CLIENT = 1;
  62
  63    /**
  64     * Debug level to show client -> server and server -> client messages
  65     */
  66    const DEBUG_SERVER = 2;
  67
  68    /**
  69     * Debug level to show connection status, client -> server and server -> client messages
  70     */
  71    const DEBUG_CONNECTION = 3;
  72
  73    /**
  74     * Debug level to show all messages
  75     */
  76    const DEBUG_LOWLEVEL = 4;
  77
  78    /**
  79     * The PHPMailer SMTP Version number.
  80     * @var string
  81     * @deprecated Use the `VERSION` constant instead
  82     * @see SMTP::VERSION
  83     */
  84    public $Version = '5.2.22';
  85
  86    /**
  87     * SMTP server port number.
  88     * @var integer
  89     * @deprecated This is only ever used as a default value, so use the `DEFAULT_SMTP_PORT` constant instead
  90     * @see SMTP::DEFAULT_SMTP_PORT
  91     */
  92    public $SMTP_PORT = 25;
  93
  94    /**
  95     * SMTP reply line ending.
  96     * @var string
  97     * @deprecated Use the `CRLF` constant instead
  98     * @see SMTP::CRLF
  99     */
 100    public $CRLF = "\r\n";
 101
 102    /**
 103     * Debug output level.
 104     * Options:
 105     * * self::DEBUG_OFF (`0`) No debug output, default
 106     * * self::DEBUG_CLIENT (`1`) Client commands
 107     * * self::DEBUG_SERVER (`2`) Client commands and server responses
 108     * * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status
 109     * * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages
 110     * @var integer
 111     */
 112    public $do_debug = self::DEBUG_OFF;
 113
 114    /**
 115     * How to handle debug output.
 116     * Options:
 117     * * `echo` Output plain-text as-is, appropriate for CLI
 118     * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output
 119     * * `error_log` Output to error log as configured in php.ini
 120     *
 121     * Alternatively, you can provide a callable expecting two params: a message string and the debug level:
 122     * <code>
 123     * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
 124     * </code>
 125     * @var string|callable
 126     */
 127    public $Debugoutput = 'echo';
 128
 129    /**
 130     * Whether to use VERP.
 131     * @link http://en.wikipedia.org/wiki/Variable_envelope_return_path
 132     * @link http://www.postfix.org/VERP_README.html Info on VERP
 133     * @var boolean
 134     */
 135    public $do_verp = false;
 136
 137    /**
 138     * The timeout value for connection, in seconds.
 139     * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2
 140     * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure.
 141     * @link http://tools.ietf.org/html/rfc2821#section-4.5.3.2
 142     * @var integer
 143     */
 144    public $Timeout = 300;
 145
 146    /**
 147     * How long to wait for commands to complete, in seconds.
 148     * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2
 149     * @var integer
 150     */
 151    public $Timelimit = 300;
 152
 153	/**
 154	 * @var array patterns to extract smtp transaction id from smtp reply
 155	 * Only first capture group will be use, use non-capturing group to deal with it
 156	 * Extend this class to override this property to fulfil your needs.
 157	 */
 158	protected $smtp_transaction_id_patterns = array(
 159		'exim' => '/[0-9]{3} OK id=(.*)/',
 160		'sendmail' => '/[0-9]{3} 2.0.0 (.*) Message/',
 161		'postfix' => '/[0-9]{3} 2.0.0 Ok: queued as (.*)/'
 162	);
 163
 164    /**
 165     * The socket for the server connection.
 166     * @var resource
 167     */
 168    protected $smtp_conn;
 169
 170    /**
 171     * Error information, if any, for the last SMTP command.
 172     * @var array
 173     */
 174    protected $error = array(
 175        'error' => '',
 176        'detail' => '',
 177        'smtp_code' => '',
 178        'smtp_code_ex' => ''
 179    );
 180
 181    /**
 182     * The reply the server sent to us for HELO.
 183     * If null, no HELO string has yet been received.
 184     * @var string|null
 185     */
 186    protected $helo_rply = null;
 187
 188    /**
 189     * The set of SMTP extensions sent in reply to EHLO command.
 190     * Indexes of the array are extension names.
 191     * Value at index 'HELO' or 'EHLO' (according to command that was sent)
 192     * represents the server name. In case of HELO it is the only element of the array.
 193     * Other values can be boolean TRUE or an array containing extension options.
 194     * If null, no HELO/EHLO string has yet been received.
 195     * @var array|null
 196     */
 197    protected $server_caps = null;
 198
 199    /**
 200     * The most recent reply received from the server.
 201     * @var string
 202     */
 203    protected $last_reply = '';
 204
 205    /**
 206     * Output debugging info via a user-selected method.
 207     * @see SMTP::$Debugoutput
 208     * @see SMTP::$do_debug
 209     * @param string $str Debug string to output
 210     * @param integer $level The debug level of this message; see DEBUG_* constants
 211     * @return void
 212     */
 213    protected function edebug($str, $level = 0)
 214    {
 215        if ($level > $this->do_debug) {
 216            return;
 217        }
 218        //Avoid clash with built-in function names
 219        if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) {
 220            call_user_func($this->Debugoutput, $str, $level);
 221            return;
 222        }
 223        switch ($this->Debugoutput) {
 224            case 'error_log':
 225                //Don't output, just log
 226                error_log($str);
 227                break;
 228            case 'html':
 229                //Cleans up output a bit for a better looking, HTML-safe output
 230                echo htmlentities(
 231                    preg_replace('/[\r\n]+/', '', $str),
 232                    ENT_QUOTES,
 233                    'UTF-8'
 234                )
 235                . "<br>\n";
 236                break;
 237            case 'echo':
 238            default:
 239                //Normalize line breaks
 240                $str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str);
 241                echo gmdate('Y-m-d H:i:s') . "\t" . str_replace(
 242                    "\n",
 243                    "\n                   \t                  ",
 244                    trim($str)
 245                )."\n";
 246        }
 247    }
 248
 249    /**
 250     * Connect to an SMTP server.
 251     * @param string $host SMTP server IP or host name
 252     * @param integer $port The port number to connect to
 253     * @param integer $timeout How long to wait for the connection to open
 254     * @param array $options An array of options for stream_context_create()
 255     * @access public
 256     * @return boolean
 257     */
 258    public function connect($host, $port = null, $timeout = 30, $options = array())
 259    {
 260        static $streamok;
 261        //This is enabled by default since 5.0.0 but some providers disable it
 262        //Check this once and cache the result
 263        if (is_null($streamok)) {
 264            $streamok = function_exists('stream_socket_client');
 265        }
 266        // Clear errors to avoid confusion
 267        $this->setError('');
 268        // Make sure we are __not__ connected
 269        if ($this->connected()) {
 270            // Already connected, generate error
 271            $this->setError('Already connected to a server');
 272            return false;
 273        }
 274        if (empty($port)) {
 275            $port = self::DEFAULT_SMTP_PORT;
 276        }
 277        // Connect to the SMTP server
 278        $this->edebug(
 279            "Connection: opening to $host:$port, timeout=$timeout, options=".var_export($options, true),
 280            self::DEBUG_CONNECTION
 281        );
 282        $errno = 0;
 283        $errstr = '';
 284        if ($streamok) {
 285            $socket_context = stream_context_create($options);
 286            set_error_handler(array($this, 'errorHandler'));
 287            $this->smtp_conn = stream_socket_client(
 288                $host . ":" . $port,
 289                $errno,
 290                $errstr,
 291                $timeout,
 292                STREAM_CLIENT_CONNECT,
 293                $socket_context
 294            );
 295            restore_error_handler();
 296        } else {
 297            //Fall back to fsockopen which should work in more places, but is missing some features
 298            $this->edebug(
 299                "Connection: stream_socket_client not available, falling back to fsockopen",
 300                self::DEBUG_CONNECTION
 301            );
 302            set_error_handler(array($this, 'errorHandler'));
 303            $this->smtp_conn = fsockopen(
 304                $host,
 305                $port,
 306                $errno,
 307                $errstr,
 308                $timeout
 309            );
 310            restore_error_handler();
 311        }
 312        // Verify we connected properly
 313        if (!is_resource($this->smtp_conn)) {
 314            $this->setError(
 315                'Failed to connect to server',
 316                $errno,
 317                $errstr
 318            );
 319            $this->edebug(
 320                'SMTP ERROR: ' . $this->error['error']
 321                . ": $errstr ($errno)",
 322                self::DEBUG_CLIENT
 323            );
 324            return false;
 325        }
 326        $this->edebug('Connection: opened', self::DEBUG_CONNECTION);
 327        // SMTP server can take longer to respond, give longer timeout for first read
 328        // Windows does not have support for this timeout function
 329        if (substr(PHP_OS, 0, 3) != 'WIN') {
 330            $max = ini_get('max_execution_time');
 331            // Don't bother if unlimited
 332            if ($max != 0 && $timeout > $max) {
 333                @set_time_limit($timeout);
 334            }
 335            stream_set_timeout($this->smtp_conn, $timeout, 0);
 336        }
 337        // Get any announcement
 338        $announce = $this->get_lines();
 339        $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER);
 340        return true;
 341    }
 342
 343    /**
 344     * Initiate a TLS (encrypted) session.
 345     * @access public
 346     * @return boolean
 347     */
 348    public function startTLS()
 349    {
 350        if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) {
 351            return false;
 352        }
 353
 354        //Allow the best TLS version(s) we can
 355        $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
 356
 357        //PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT
 358        //so add them back in manually if we can
 359        if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
 360            $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
 361            $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
 362        }
 363
 364        // Begin encrypted connection
 365        if (!stream_socket_enable_crypto(
 366            $this->smtp_conn,
 367            true,
 368            $crypto_method
 369        )) {
 370            return false;
 371        }
 372        return true;
 373    }
 374
 375    /**
 376     * Perform SMTP authentication.
 377     * Must be run after hello().
 378     * @see hello()
 379     * @param string $username The user name
 380     * @param string $password The password
 381     * @param string $authtype The auth type (PLAIN, LOGIN, NTLM, CRAM-MD5, XOAUTH2)
 382     * @param string $realm The auth realm for NTLM
 383     * @param string $workstation The auth workstation for NTLM
 384     * @param null|OAuth $OAuth An optional OAuth instance (@see PHPMailerOAuth)
 385     * @return bool True if successfully authenticated.* @access public
 386     */
 387    public function authenticate(
 388        $username,
 389        $password,
 390        $authtype = null,
 391        $realm = '',
 392        $workstation = '',
 393        $OAuth = null
 394    ) {
 395        if (!$this->server_caps) {
 396            $this->setError('Authentication is not allowed before HELO/EHLO');
 397            return false;
 398        }
 399
 400        if (array_key_exists('EHLO', $this->server_caps)) {
 401        // SMTP extensions are available. Let's try to find a proper authentication method
 402
 403            if (!array_key_exists('AUTH', $this->server_caps)) {
 404                $this->setError('Authentication is not allowed at this stage');
 405                // 'at this stage' means that auth may be allowed after the stage changes
 406                // e.g. after STARTTLS
 407                return false;
 408            }
 409
 410            self::edebug('Auth method requested: ' . ($authtype ? $authtype : 'UNKNOWN'), self::DEBUG_LOWLEVEL);
 411            self::edebug(
 412                'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']),
 413                self::DEBUG_LOWLEVEL
 414            );
 415
 416            if (empty($authtype)) {
 417                foreach (array('CRAM-MD5', 'LOGIN', 'PLAIN', 'NTLM', 'XOAUTH2') as $method) {
 418                    if (in_array($method, $this->server_caps['AUTH'])) {
 419                        $authtype = $method;
 420                        break;
 421                    }
 422                }
 423                if (empty($authtype)) {
 424                    $this->setError('No supported authentication methods found');
 425                    return false;
 426                }
 427                self::edebug('Auth method selected: '.$authtype, self::DEBUG_LOWLEVEL);
 428            }
 429
 430            if (!in_array($authtype, $this->server_caps['AUTH'])) {
 431                $this->setError("The requested authentication method \"$authtype\" is not supported by the server");
 432                return false;
 433            }
 434        } elseif (empty($authtype)) {
 435            $authtype = 'LOGIN';
 436        }
 437        switch ($authtype) {
 438            case 'PLAIN':
 439                // Start authentication
 440                if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) {
 441                    return false;
 442                }
 443                // Send encoded username and password
 444                if (!$this->sendCommand(
 445                    'User & Password',
 446                    base64_encode("\0" . $username . "\0" . $password),
 447                    235
 448                )
 449                ) {
 450                    return false;
 451                }
 452                break;
 453            case 'LOGIN':
 454                // Start authentication
 455                if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) {
 456                    return false;
 457                }
 458                if (!$this->sendCommand("Username", base64_encode($username), 334)) {
 459                    return false;
 460                }
 461                if (!$this->sendCommand("Password", base64_encode($password), 235)) {
 462                    return false;
 463                }
 464                break;
 465            case 'XOAUTH2':
 466                //If the OAuth Instance is not set. Can be a case when PHPMailer is used
 467                //instead of PHPMailerOAuth
 468                if (is_null($OAuth)) {
 469                    return false;
 470                }
 471                $oauth = $OAuth->getOauth64();
 472
 473                // Start authentication
 474                if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
 475                    return false;
 476                }
 477                break;
 478            case 'NTLM':
 479                /*
 480                 * ntlm_sasl_client.php
 481                 * Bundled with Permission
 482                 *
 483                 * How to telnet in windows:
 484                 * http://technet.microsoft.com/en-us/library/aa995718%28EXCHG.65%29.aspx
 485                 * PROTOCOL Docs http://curl.haxx.se/rfc/ntlm.html#ntlmSmtpAuthentication
 486                 */
 487                require_once 'extras/ntlm_sasl_client.php';
 488                $temp = new stdClass;
 489                $ntlm_client = new ntlm_sasl_client_class;
 490                //Check that functions are available
 491                if (!$ntlm_client->initialize($temp)) {
 492                    $this->setError($temp->error);
 493                    $this->edebug(
 494                        'You need to enable some modules in your php.ini file: '
 495                        . $this->error['error'],
 496                        self::DEBUG_CLIENT
 497                    );
 498                    return false;
 499                }
 500                //msg1
 501                $msg1 = $ntlm_client->typeMsg1($realm, $workstation); //msg1
 502
 503                if (!$this->sendCommand(
 504                    'AUTH NTLM',
 505                    'AUTH NTLM ' . base64_encode($msg1),
 506                    334
 507                )
 508                ) {
 509                    return false;
 510                }
 511                //Though 0 based, there is a white space after the 3 digit number
 512                //msg2
 513                $challenge = substr($this->last_reply, 3);
 514                $challenge = base64_decode($challenge);
 515                $ntlm_res = $ntlm_client->NTLMResponse(
 516                    substr($challenge, 24, 8),
 517                    $password
 518                );
 519                //msg3
 520                $msg3 = $ntlm_client->typeMsg3(
 521                    $ntlm_res,
 522                    $username,
 523                    $realm,
 524                    $workstation
 525                );
 526                // send encoded username
 527                return $this->sendCommand('Username', base64_encode($msg3), 235);
 528            case 'CRAM-MD5':
 529                // Start authentication
 530                if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) {
 531                    return false;
 532                }
 533                // Get the challenge
 534                $challenge = base64_decode(substr($this->last_reply, 4));
 535
 536                // Build the response
 537                $response = $username . ' ' . $this->hmac($challenge, $password);
 538
 539                // send encoded credentials
 540                return $this->sendCommand('Username', base64_encode($response), 235);
 541            default:
 542                $this->setError("Authentication method \"$authtype\" is not supported");
 543                return false;
 544        }
 545        return true;
 546    }
 547
 548    /**
 549     * Calculate an MD5 HMAC hash.
 550     * Works like hash_hmac('md5', $data, $key)
 551     * in case that function is not available
 552     * @param string $data The data to hash
 553     * @param string $key  The key to hash with
 554     * @access protected
 555     * @return string
 556     */
 557    protected function hmac($data, $key)
 558    {
 559        if (function_exists('hash_hmac')) {
 560            return hash_hmac('md5', $data, $key);
 561        }
 562
 563        // The following borrowed from
 564        // http://php.net/manual/en/function.mhash.php#27225
 565
 566        // RFC 2104 HMAC implementation for php.
 567        // Creates an md5 HMAC.
 568        // Eliminates the need to install mhash to compute a HMAC
 569        // by Lance Rushing
 570
 571        $bytelen = 64; // byte length for md5
 572        if (strlen($key) > $bytelen) {
 573            $key = pack('H*', md5($key));
 574        }
 575        $key = str_pad($key, $bytelen, chr(0x00));
 576        $ipad = str_pad('', $bytelen, chr(0x36));
 577        $opad = str_pad('', $bytelen, chr(0x5c));
 578        $k_ipad = $key ^ $ipad;
 579        $k_opad = $key ^ $opad;
 580
 581        return md5($k_opad . pack('H*', md5($k_ipad . $data)));
 582    }
 583
 584    /**
 585     * Check connection state.
 586     * @access public
 587     * @return boolean True if connected.
 588     */
 589    public function connected()
 590    {
 591        if (is_resource($this->smtp_conn)) {
 592            $sock_status = stream_get_meta_data($this->smtp_conn);
 593            if ($sock_status['eof']) {
 594                // The socket is valid but we are not connected
 595                $this->edebug(
 596                    'SMTP NOTICE: EOF caught while checking if connected',
 597                    self::DEBUG_CLIENT
 598                );
 599                $this->close();
 600                return false;
 601            }
 602            return true; // everything looks good
 603        }
 604        return false;
 605    }
 606
 607    /**
 608     * Close the socket and clean up the state of the class.
 609     * Don't use this function without first trying to use QUIT.
 610     * @see quit()
 611     * @access public
 612     * @return void
 613     */
 614    public function close()
 615    {
 616        $this->setError('');
 617        $this->server_caps = null;
 618        $this->helo_rply = null;
 619        if (is_resource($this->smtp_conn)) {
 620            // close the connection and cleanup
 621            fclose($this->smtp_conn);
 622            $this->smtp_conn = null; //Makes for cleaner serialization
 623            $this->edebug('Connection: closed', self::DEBUG_CONNECTION);
 624        }
 625    }
 626
 627    /**
 628     * Send an SMTP DATA command.
 629     * Issues a data command and sends the msg_data to the server,
 630     * finializing the mail transaction. $msg_data is the message
 631     * that is to be send with the headers. Each header needs to be
 632     * on a single line followed by a <CRLF> with the message headers
 633     * and the message body being separated by and additional <CRLF>.
 634     * Implements rfc 821: DATA <CRLF>
 635     * @param string $msg_data Message data to send
 636     * @access public
 637     * @return boolean
 638     */
 639    public function data($msg_data)
 640    {
 641        //This will use the standard timelimit
 642        if (!$this->sendCommand('DATA', 'DATA', 354)) {
 643            return false;
 644        }
 645
 646        /* The server is ready to accept data!
 647         * According to rfc821 we should not send more than 1000 characters on a single line (including the CRLF)
 648         * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into
 649         * smaller lines to fit within the limit.
 650         * We will also look for lines that start with a '.' and prepend an additional '.'.
 651         * NOTE: this does not count towards line-length limit.
 652         */
 653
 654        // Normalize line breaks before exploding
 655        $lines = explode("\n", str_replace(array("\r\n", "\r"), "\n", $msg_data));
 656
 657        /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field
 658         * of the first line (':' separated) does not contain a space then it _should_ be a header and we will
 659         * process all lines before a blank line as headers.
 660         */
 661
 662        $field = substr($lines[0], 0, strpos($lines[0], ':'));
 663        $in_headers = false;
 664        if (!empty($field) && strpos($field, ' ') === false) {
 665            $in_headers = true;
 666        }
 667
 668        foreach ($lines as $line) {
 669            $lines_out = array();
 670            if ($in_headers and $line == '') {
 671                $in_headers = false;
 672            }
 673            //Break this line up into several smaller lines if it's too long
 674            //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len),
 675            while (isset($line[self::MAX_LINE_LENGTH])) {
 676                //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on
 677                //so as to avoid breaking in the middle of a word
 678                $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' ');
 679                //Deliberately matches both false and 0
 680                if (!$pos) {
 681                    //No nice break found, add a hard break
 682                    $pos = self::MAX_LINE_LENGTH - 1;
 683                    $lines_out[] = substr($line, 0, $pos);
 684                    $line = substr($line, $pos);
 685                } else {
 686                    //Break at the found point
 687                    $lines_out[] = substr($line, 0, $pos);
 688                    //Move along by the amount we dealt with
 689                    $line = substr($line, $pos + 1);
 690                }
 691                //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1
 692                if ($in_headers) {
 693                    $line = "\t" . $line;
 694                }
 695            }
 696            $lines_out[] = $line;
 697
 698            //Send the lines to the server
 699            foreach ($lines_out as $line_out) {
 700                //RFC2821 section 4.5.2
 701                if (!empty($line_out) and $line_out[0] == '.') {
 702                    $line_out = '.' . $line_out;
 703                }
 704                $this->client_send($line_out . self::CRLF);
 705            }
 706        }
 707
 708        //Message data has been sent, complete the command
 709        //Increase timelimit for end of DATA command
 710        $savetimelimit = $this->Timelimit;
 711        $this->Timelimit = $this->Timelimit * 2;
 712        $result = $this->sendCommand('DATA END', '.', 250);
 713        //Restore timelimit
 714        $this->Timelimit = $savetimelimit;
 715        return $result;
 716    }
 717
 718    /**
 719     * Send an SMTP HELO or EHLO command.
 720     * Used to identify the sending server to the receiving server.
 721     * This makes sure that client and server are in a known state.
 722     * Implements RFC 821: HELO <SP> <domain> <CRLF>
 723     * and RFC 2821 EHLO.
 724     * @param string $host The host name or IP to connect to
 725     * @access public
 726     * @return boolean
 727     */
 728    public function hello($host = '')
 729    {
 730        //Try extended hello first (RFC 2821)
 731        return (boolean)($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host));
 732    }
 733
 734    /**
 735     * Send an SMTP HELO or EHLO command.
 736     * Low-level implementation used by hello()
 737     * @see hello()
 738     * @param string $hello The HELO string
 739     * @param string $host The hostname to say we are
 740     * @access protected
 741     * @return boolean
 742     */
 743    protected function sendHello($hello, $host)
 744    {
 745        $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250);
 746        $this->helo_rply = $this->last_reply;
 747        if ($noerror) {
 748            $this->parseHelloFields($hello);
 749        } else {
 750            $this->server_caps = null;
 751        }
 752        return $noerror;
 753    }
 754
 755    /**
 756     * Parse a reply to HELO/EHLO command to discover server extensions.
 757     * In case of HELO, the only parameter that can be discovered is a server name.
 758     * @access protected
 759     * @param string $type - 'HELO' or 'EHLO'
 760     */
 761    protected function parseHelloFields($type)
 762    {
 763        $this->server_caps = array();
 764        $lines = explode("\n", $this->helo_rply);
 765
 766        foreach ($lines as $n => $s) {
 767            //First 4 chars contain response code followed by - or space
 768            $s = trim(substr($s, 4));
 769            if (empty($s)) {
 770                continue;
 771            }
 772            $fields = explode(' ', $s);
 773            if (!empty($fields)) {
 774                if (!$n) {
 775                    $name = $type;
 776                    $fields = $fields[0];
 777                } else {
 778                    $name = array_shift($fields);
 779                    switch ($name) {
 780                        case 'SIZE':
 781                            $fields = ($fields ? $fields[0] : 0);
 782                            break;
 783                        case 'AUTH':
 784                            if (!is_array($fields)) {
 785                                $fields = array();
 786                            }
 787                            break;
 788                        default:
 789                            $fields = true;
 790                    }
 791                }
 792                $this->server_caps[$name] = $fields;
 793            }
 794        }
 795    }
 796
 797    /**
 798     * Send an SMTP MAIL command.
 799     * Starts a mail transaction from the email address specified in
 800     * $from. Returns true if successful or false otherwise. If True
 801     * the mail transaction is started and then one or more recipient
 802     * commands may be called followed by a data command.
 803     * Implements rfc 821: MAIL <SP> FROM:<reverse-path> <CRLF>
 804     * @param string $from Source address of this message
 805     * @access public
 806     * @return boolean
 807     */
 808    public function mail($from)
 809    {
 810        $useVerp = ($this->do_verp ? ' XVERP' : '');
 811        return $this->sendCommand(
 812            'MAIL FROM',
 813            'MAIL FROM:<' . $from . '>' . $useVerp,
 814            250
 815        );
 816    }
 817
 818    /**
 819     * Send an SMTP QUIT command.
 820     * Closes the socket if there is no error or the $close_on_error argument is true.
 821     * Implements from rfc 821: QUIT <CRLF>
 822     * @param boolean $close_on_error Should the connection close if an error occurs?
 823     * @access public
 824     * @return boolean
 825     */
 826    public function quit($close_on_error = true)
 827    {
 828        $noerror = $this->sendCommand('QUIT', 'QUIT', 221);
 829        $err = $this->error; //Save any error
 830        if ($noerror or $close_on_error) {
 831            $this->close();
 832            $this->error = $err; //Restore any error from the quit command
 833        }
 834        return $noerror;
 835    }
 836
 837    /**
 838     * Send an SMTP RCPT command.
 839     * Sets the TO argument to $toaddr.
 840     * Returns true if the recipient was accepted false if it was rejected.
 841     * Implements from rfc 821: RCPT <SP> TO:<forward-path> <CRLF>
 842     * @param string $address The address the message is being sent to
 843     * @access public
 844     * @return boolean
 845     */
 846    public function recipient($address)
 847    {
 848        return $this->sendCommand(
 849            'RCPT TO',
 850            'RCPT TO:<' . $address . '>',
 851            array(250, 251)
 852        );
 853    }
 854
 855    /**
 856     * Send an SMTP RSET command.
 857     * Abort any transaction that is currently in progress.
 858     * Implements rfc 821: RSET <CRLF>
 859     * @access public
 860     * @return boolean True on success.
 861     */
 862    public function reset()
 863    {
 864        return $this->sendCommand('RSET', 'RSET', 250);
 865    }
 866
 867    /**
 868     * Send a command to an SMTP server and check its return code.
 869     * @param string $command The command name - not sent to the server
 870     * @param string $commandstring The actual command to send
 871     * @param integer|array $expect One or more expected integer success codes
 872     * @access protected
 873     * @return boolean True on success.
 874     */
 875    protected function sendCommand($command, $commandstring, $expect)
 876    {
 877        if (!$this->connected()) {
 878            $this->setError("Called $command without being connected");
 879            return false;
 880        }
 881        //Reject line breaks in all commands
 882        if (strpos($commandstring, "\n") !== false or strpos($commandstring, "\r") !== false) {
 883            $this->setError("Command '$command' contained line breaks");
 884            return false;
 885        }
 886        $this->client_send($commandstring . self::CRLF);
 887
 888        $this->last_reply = $this->get_lines();
 889        // Fetch SMTP code and possible error code explanation
 890        $matches = array();
 891        if (preg_match("/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]) )?/", $this->last_reply, $matches)) {
 892            $code = $matches[1];
 893            $code_ex = (count($matches) > 2 ? $matches[2] : null);
 894            // Cut off error code from each response line
 895            $detail = preg_replace(
 896                "/{$code}[ -]".($code_ex ? str_replace('.', '\\.', $code_ex).' ' : '')."/m",
 897                '',
 898                $this->last_reply
 899            );
 900        } else {
 901            // Fall back to simple parsing if regex fails
 902            $code = substr($this->last_reply, 0, 3);
 903            $code_ex = null;
 904            $detail = substr($this->last_reply, 4);
 905        }
 906
 907        $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
 908
 909        if (!in_array($code, (array)$expect)) {
 910            $this->setError(
 911                "$command command failed",
 912                $detail,
 913                $code,
 914                $code_ex
 915            );
 916            $this->edebug(
 917                'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply,
 918                self::DEBUG_CLIENT
 919            );
 920            return false;
 921        }
 922
 923        $this->setError('');
 924        return true;
 925    }
 926
 927    /**
 928     * Send an SMTP SAML command.
 929     * Starts a mail transaction from the email address specified in $from.
 930     * Returns true if successful or false otherwise. If True
 931     * the mail transaction is started and then one or more recipient
 932     * commands may be called followed by a data command. This command
 933     * will send the message to the users terminal if they are logged
 934     * in and send them an email.
 935     * Implements rfc 821: SAML <SP> FROM:<reverse-path> <CRLF>
 936     * @param string $from The address the message is from
 937     * @access public
 938     * @return boolean
 939     */
 940    public function sendAndMail($from)
 941    {
 942        return $this->sendCommand('SAML', "SAML FROM:$from", 250);
 943    }
 944
 945    /**
 946     * Send an SMTP VRFY command.
 947     * @param string $name The name to verify
 948     * @access public
 949     * @return boolean
 950     */
 951    public function verify($name)
 952    {
 953        return $this->sendCommand('VRFY', "VRFY $name", array(250, 251));
 954    }
 955
 956    /**
 957     * Send an SMTP NOOP command.
 958     * Used to keep keep-alives alive, doesn't actually do anything
 959     * @access public
 960     * @return boolean
 961     */
 962    public function noop()
 963    {
 964        return $this->sendCommand('NOOP', 'NOOP', 250);
 965    }
 966
 967    /**
 968     * Send an SMTP TURN command.
 969     * This is an optional command for SMTP that this class does not support.
 970     * This method is here to make the RFC821 Definition complete for this class
 971     * and _may_ be implemented in future
 972     * Implements from rfc 821: TURN <CRLF>
 973     * @access public
 974     * @return boolean
 975     */
 976    public function turn()
 977    {
 978        $this->setError('The SMTP TURN command is not implemented');
 979        $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT);
 980        return false;
 981    }
 982
 983    /**
 984     * Send raw data to the server.
 985     * @param string $data The data to send
 986     * @access public
 987     * @return integer|boolean The number of bytes sent to the server or false on error
 988     */
 989    public function client_send($data)
 990    {
 991        $this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT);
 992        return fwrite($this->smtp_conn, $data);
 993    }
 994
 995    /**
 996     * Get the latest error.
 997     * @access public
 998     * @return array
 999     */
1000    public function getError()
1001    {
1002        return $this->error;
1003    }
1004
1005    /**
1006     * Get SMTP extensions available on the server
1007     * @access public
1008     * @return array|null
1009     */
1010    public function getServerExtList()
1011    {
1012        return $this->server_caps;
1013    }
1014
1015    /**
1016     * A multipurpose method
1017     * The method works in three ways, dependent on argument value and current state
1018     *   1. HELO/EHLO was not sent - returns null and set up $this->error
1019     *   2. HELO was sent
1020     *     $name = 'HELO': returns server name
1021     *     $name = 'EHLO': returns boolean false
1022     *     $name = any string: returns null and set up $this->error
1023     *   3. EHLO was sent
1024     *     $name = 'HELO'|'EHLO': returns server name
1025     *     $name = any string: if extension $name exists, returns boolean True
1026     *       or its options. Otherwise returns boolean False
1027     * In other words, one can use this method to detect 3 conditions:
1028     *  - null returned: handshake was not or we don't know about ext (refer to $this->error)
1029     *  - false returned: the requested feature exactly not exists
1030     *  - positive value returned: the requested feature exists
1031     * @param string $name Name of SMTP extension or 'HELO'|'EHLO'
1032     * @return mixed
1033     */
1034    public function getServerExt($name)
1035    {
1036        if (!$this->server_caps) {
1037            $this->setError('No HELO/EHLO was sent');
1038            return null;
1039        }
1040
1041        // the tight logic knot ;)
1042        if (!array_key_exists($name, $this->server_caps)) {
1043            if ($name == 'HELO') {
1044                return $this->server_caps['EHLO'];
1045            }
1046            if ($name == 'EHLO' || array_key_exists('EHLO', $this->server_caps)) {
1047                return false;
1048            }
1049            $this->setError('HELO handshake was used. Client knows nothing about server extensions');
1050            return null;
1051        }
1052
1053        return $this->server_caps[$name];
1054    }
1055
1056    /**
1057     * Get the last reply from the server.
1058     * @access public
1059     * @return string
1060     */
1061    public function getLastReply()
1062    {
1063        return $this->last_reply;
1064    }
1065
1066    /**
1067     * Read the SMTP server's response.
1068     * Either before eof or socket timeout occurs on the operation.
1069     * With SMTP we can tell if we have more lines to read if the
1070     * 4th character is '-' symbol. If it is a space then we don't
1071     * need to read anything else.
1072     * @access protected
1073     * @return string
1074     */
1075    protected function get_lines()
1076    {
1077        // If the connection is bad, give up straight away
1078        if (!is_resource($this->smtp_conn)) {
1079            return '';
1080        }
1081        $data = '';
1082        $endtime = 0;
1083        stream_set_timeout($this->smtp_conn, $this->Timeout);
1084        if ($this->Timelimit > 0) {
1085            $endtime = time() + $this->Timelimit;
1086        }
1087        while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
1088            $str = @fgets($this->smtp_conn, 515);
1089            $this->edebug("SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL);
1090            $this->edebug("SMTP -> get_lines(): \$str is  \"$str\"", self::DEBUG_LOWLEVEL);
1091            $data .= $str;
1092            // If 4th character is a space, we are done reading, break the loop, micro-optimisation over strlen
1093            if ((isset($str[3]) and $str[3] == ' ')) {
1094                break;
1095            }
1096            // Timed-out? Log and break
1097            $info = stream_get_meta_data($this->smtp_conn);
1098            if ($info['timed_out']) {
1099                $this->edebug(
1100                    'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)',
1101                    self::DEBUG_LOWLEVEL
1102                );
1103                break;
1104            }
1105            // Now check if reads took too long
1106            if ($endtime and time() > $endtime) {
1107                $this->edebug(
1108                    'SMTP -> get_lines(): timelimit reached ('.
1109                    $this->Timelimit . ' sec)',
1110                    self::DEBUG_LOWLEVEL
1111                );
1112                break;
1113            }
1114        }
1115        return $data;
1116    }
1117
1118    /**
1119     * Enable or disable VERP address generation.
1120     * @param boolean $enabled
1121     */
1122    public function setVerp($enabled = false)
1123    {
1124        $this->do_verp = $enabled;
1125    }
1126
1127    /**
1128     * Get VERP address generation mode.
1129     * @return boolean
1130     */
1131    public function getVerp()
1132    {
1133        return $this->do_verp;
1134    }
1135
1136    /**
1137     * Set error messages and codes.
1138     * @param string $message The error message
1139     * @param string $detail Further detail on the error
1140     * @param string $smtp_code An associated SMTP error code
1141     * @param string $smtp_code_ex Extended SMTP code
1142     */
1143    protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '')
1144    {
1145        $this->error = array(
1146            'error' => $message,
1147            'detail' => $detail,
1148            'smtp_code' => $smtp_code,
1149            'smtp_code_ex' => $smtp_code_ex
1150        );
1151    }
1152
1153    /**
1154     * Set debug output method.
1155     * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it.
1156     */
1157    public function setDebugOutput($method = 'echo')
1158    {
1159        $this->Debugoutput = $method;
1160    }
1161
1162    /**
1163     * Get debug output method.
1164     * @return string
1165     */
1166    public function getDebugOutput()
1167    {
1168        return $this->Debugoutput;
1169    }
1170
1171    /**
1172     * Set debug output level.
1173     * @param integer $level
1174     */
1175    public function setDebugLevel($level = 0)
1176    {
1177        $this->do_debug = $level;
1178    }
1179
1180    /**
1181     * Get debug output level.
1182     * @return integer
1183     */
1184    public function getDebugLevel()
1185    {
1186        return $this->do_debug;
1187    }
1188
1189    /**
1190     * Set SMTP timeout.
1191     * @param integer $timeout
1192     */
1193    public function setTimeout($timeout = 0)
1194    {
1195        $this->Timeout = $timeout;
1196    }
1197
1198    /**
1199     * Get SMTP timeout.
1200     * @return integer
1201     */
1202    public function getTimeout()
1203    {
1204        return $this->Timeout;
1205    }
1206
1207    /**
1208     * Reports an error number and string.
1209     * @param integer $errno The error number returned by PHP.
1210     * @param string $errmsg The error message returned by PHP.
1211     */
1212    protected function errorHandler($errno, $errmsg)
1213    {
1214        $notice = 'Connection: Failed to connect to server.';
1215        $this->setError(
1216            $notice,
1217            $errno,
1218            $errmsg
1219        );
1220        $this->edebug(
1221            $notice . ' Error number ' . $errno . '. "Error notice: ' . $errmsg,
1222            self::DEBUG_CONNECTION
1223        );
1224    }
1225
1226	/**
1227	 * Will return the ID of the last smtp transaction based on a list of patterns provided
1228	 * in SMTP::$smtp_transaction_id_patterns.
1229	 * If no reply has been received yet, it will return null.
1230	 * If no pattern has been matched, it will return false.
1231	 * @return bool|null|string
1232	 */
1233	public function getLastTransactionID()
1234	{
1235		$reply = $this->getLastReply();
1236
1237		if (empty($reply)) {
1238			return null;
1239		}
1240
1241		foreach($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
1242			if(preg_match($smtp_transaction_id_pattern, $reply, $matches)) {
1243				return $matches[1];
1244			}
1245		}
1246
1247		return false;
1248    }
1249}