home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress / wp-includes / class-smtp.php < prev    next >
Encoding:
PHP Script  |  2017-01-10  |  38.6 KB  |  1,187 lines

  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.  */
  27. class 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, CRAM-MD5)
  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') 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 'CRAM-MD5':
  466.                 // Start authentication
  467.                 if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) {
  468.                     return false;
  469.                 }
  470.                 // Get the challenge
  471.                 $challenge = base64_decode(substr($this->last_reply, 4));
  472.  
  473.                 // Build the response
  474.                 $response = $username . ' ' . $this->hmac($challenge, $password);
  475.  
  476.                 // send encoded credentials
  477.                 return $this->sendCommand('Username', base64_encode($response), 235);
  478.             default:
  479.                 $this->setError("Authentication method \"$authtype\" is not supported");
  480.                 return false;
  481.         }
  482.         return true;
  483.     }
  484.  
  485.     /**
  486.      * Calculate an MD5 HMAC hash.
  487.      * Works like hash_hmac('md5', $data, $key)
  488.      * in case that function is not available
  489.      * @param string $data The data to hash
  490.      * @param string $key  The key to hash with
  491.      * @access protected
  492.      * @return string
  493.      */
  494.     protected function hmac($data, $key)
  495.     {
  496.         if (function_exists('hash_hmac')) {
  497.             return hash_hmac('md5', $data, $key);
  498.         }
  499.  
  500.         // The following borrowed from
  501.         // http://php.net/manual/en/function.mhash.php#27225
  502.  
  503.         // RFC 2104 HMAC implementation for php.
  504.         // Creates an md5 HMAC.
  505.         // Eliminates the need to install mhash to compute a HMAC
  506.         // by Lance Rushing
  507.  
  508.         $bytelen = 64; // byte length for md5
  509.         if (strlen($key) > $bytelen) {
  510.             $key = pack('H*', md5($key));
  511.         }
  512.         $key = str_pad($key, $bytelen, chr(0x00));
  513.         $ipad = str_pad('', $bytelen, chr(0x36));
  514.         $opad = str_pad('', $bytelen, chr(0x5c));
  515.         $k_ipad = $key ^ $ipad;
  516.         $k_opad = $key ^ $opad;
  517.  
  518.         return md5($k_opad . pack('H*', md5($k_ipad . $data)));
  519.     }
  520.  
  521.     /**
  522.      * Check connection state.
  523.      * @access public
  524.      * @return boolean True if connected.
  525.      */
  526.     public function connected()
  527.     {
  528.         if (is_resource($this->smtp_conn)) {
  529.             $sock_status = stream_get_meta_data($this->smtp_conn);
  530.             if ($sock_status['eof']) {
  531.                 // The socket is valid but we are not connected
  532.                 $this->edebug(
  533.                     'SMTP NOTICE: EOF caught while checking if connected',
  534.                     self::DEBUG_CLIENT
  535.                 );
  536.                 $this->close();
  537.                 return false;
  538.             }
  539.             return true; // everything looks good
  540.         }
  541.         return false;
  542.     }
  543.  
  544.     /**
  545.      * Close the socket and clean up the state of the class.
  546.      * Don't use this function without first trying to use QUIT.
  547.      * @see quit()
  548.      * @access public
  549.      * @return void
  550.      */
  551.     public function close()
  552.     {
  553.         $this->setError('');
  554.         $this->server_caps = null;
  555.         $this->helo_rply = null;
  556.         if (is_resource($this->smtp_conn)) {
  557.             // close the connection and cleanup
  558.             fclose($this->smtp_conn);
  559.             $this->smtp_conn = null; //Makes for cleaner serialization
  560.             $this->edebug('Connection: closed', self::DEBUG_CONNECTION);
  561.         }
  562.     }
  563.  
  564.     /**
  565.      * Send an SMTP DATA command.
  566.      * Issues a data command and sends the msg_data to the server,
  567.      * finializing the mail transaction. $msg_data is the message
  568.      * that is to be send with the headers. Each header needs to be
  569.      * on a single line followed by a <CRLF> with the message headers
  570.      * and the message body being separated by and additional <CRLF>.
  571.      * Implements rfc 821: DATA <CRLF>
  572.      * @param string $msg_data Message data to send
  573.      * @access public
  574.      * @return boolean
  575.      */
  576.     public function data($msg_data)
  577.     {
  578.         //This will use the standard timelimit
  579.         if (!$this->sendCommand('DATA', 'DATA', 354)) {
  580.             return false;
  581.         }
  582.  
  583.         /* The server is ready to accept data!
  584.          * According to rfc821 we should not send more than 1000 characters on a single line (including the CRLF)
  585.          * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into
  586.          * smaller lines to fit within the limit.
  587.          * We will also look for lines that start with a '.' and prepend an additional '.'.
  588.          * NOTE: this does not count towards line-length limit.
  589.          */
  590.  
  591.         // Normalize line breaks before exploding
  592.         $lines = explode("\n", str_replace(array("\r\n", "\r"), "\n", $msg_data));
  593.  
  594.         /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field
  595.          * of the first line (':' separated) does not contain a space then it _should_ be a header and we will
  596.          * process all lines before a blank line as headers.
  597.          */
  598.  
  599.         $field = substr($lines[0], 0, strpos($lines[0], ':'));
  600.         $in_headers = false;
  601.         if (!empty($field) && strpos($field, ' ') === false) {
  602.             $in_headers = true;
  603.         }
  604.  
  605.         foreach ($lines as $line) {
  606.             $lines_out = array();
  607.             if ($in_headers and $line == '') {
  608.                 $in_headers = false;
  609.             }
  610.             //Break this line up into several smaller lines if it's too long
  611.             //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len),
  612.             while (isset($line[self::MAX_LINE_LENGTH])) {
  613.                 //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on
  614.                 //so as to avoid breaking in the middle of a word
  615.                 $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' ');
  616.                 //Deliberately matches both false and 0
  617.                 if (!$pos) {
  618.                     //No nice break found, add a hard break
  619.                     $pos = self::MAX_LINE_LENGTH - 1;
  620.                     $lines_out[] = substr($line, 0, $pos);
  621.                     $line = substr($line, $pos);
  622.                 } else {
  623.                     //Break at the found point
  624.                     $lines_out[] = substr($line, 0, $pos);
  625.                     //Move along by the amount we dealt with
  626.                     $line = substr($line, $pos + 1);
  627.                 }
  628.                 //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1
  629.                 if ($in_headers) {
  630.                     $line = "\t" . $line;
  631.                 }
  632.             }
  633.             $lines_out[] = $line;
  634.  
  635.             //Send the lines to the server
  636.             foreach ($lines_out as $line_out) {
  637.                 //RFC2821 section 4.5.2
  638.                 if (!empty($line_out) and $line_out[0] == '.') {
  639.                     $line_out = '.' . $line_out;
  640.                 }
  641.                 $this->client_send($line_out . self::CRLF);
  642.             }
  643.         }
  644.  
  645.         //Message data has been sent, complete the command
  646.         //Increase timelimit for end of DATA command
  647.         $savetimelimit = $this->Timelimit;
  648.         $this->Timelimit = $this->Timelimit * 2;
  649.         $result = $this->sendCommand('DATA END', '.', 250);
  650.         //Restore timelimit
  651.         $this->Timelimit = $savetimelimit;
  652.         return $result;
  653.     }
  654.  
  655.     /**
  656.      * Send an SMTP HELO or EHLO command.
  657.      * Used to identify the sending server to the receiving server.
  658.      * This makes sure that client and server are in a known state.
  659.      * Implements RFC 821: HELO <SP> <domain> <CRLF>
  660.      * and RFC 2821 EHLO.
  661.      * @param string $host The host name or IP to connect to
  662.      * @access public
  663.      * @return boolean
  664.      */
  665.     public function hello($host = '')
  666.     {
  667.         //Try extended hello first (RFC 2821)
  668.         return (boolean)($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host));
  669.     }
  670.  
  671.     /**
  672.      * Send an SMTP HELO or EHLO command.
  673.      * Low-level implementation used by hello()
  674.      * @see hello()
  675.      * @param string $hello The HELO string
  676.      * @param string $host The hostname to say we are
  677.      * @access protected
  678.      * @return boolean
  679.      */
  680.     protected function sendHello($hello, $host)
  681.     {
  682.         $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250);
  683.         $this->helo_rply = $this->last_reply;
  684.         if ($noerror) {
  685.             $this->parseHelloFields($hello);
  686.         } else {
  687.             $this->server_caps = null;
  688.         }
  689.         return $noerror;
  690.     }
  691.  
  692.     /**
  693.      * Parse a reply to HELO/EHLO command to discover server extensions.
  694.      * In case of HELO, the only parameter that can be discovered is a server name.
  695.      * @access protected
  696.      * @param string $type - 'HELO' or 'EHLO'
  697.      */
  698.     protected function parseHelloFields($type)
  699.     {
  700.         $this->server_caps = array();
  701.         $lines = explode("\n", $this->helo_rply);
  702.  
  703.         foreach ($lines as $n => $s) {
  704.             //First 4 chars contain response code followed by - or space
  705.             $s = trim(substr($s, 4));
  706.             if (empty($s)) {
  707.                 continue;
  708.             }
  709.             $fields = explode(' ', $s);
  710.             if (!empty($fields)) {
  711.                 if (!$n) {
  712.                     $name = $type;
  713.                     $fields = $fields[0];
  714.                 } else {
  715.                     $name = array_shift($fields);
  716.                     switch ($name) {
  717.                         case 'SIZE':
  718.                             $fields = ($fields ? $fields[0] : 0);
  719.                             break;
  720.                         case 'AUTH':
  721.                             if (!is_array($fields)) {
  722.                                 $fields = array();
  723.                             }
  724.                             break;
  725.                         default:
  726.                             $fields = true;
  727.                     }
  728.                 }
  729.                 $this->server_caps[$name] = $fields;
  730.             }
  731.         }
  732.     }
  733.  
  734.     /**
  735.      * Send an SMTP MAIL command.
  736.      * Starts a mail transaction from the email address specified in
  737.      * $from. Returns true if successful or false otherwise. If True
  738.      * the mail transaction is started and then one or more recipient
  739.      * commands may be called followed by a data command.
  740.      * Implements rfc 821: MAIL <SP> FROM:<reverse-path> <CRLF>
  741.      * @param string $from Source address of this message
  742.      * @access public
  743.      * @return boolean
  744.      */
  745.     public function mail($from)
  746.     {
  747.         $useVerp = ($this->do_verp ? ' XVERP' : '');
  748.         return $this->sendCommand(
  749.             'MAIL FROM',
  750.             'MAIL FROM:<' . $from . '>' . $useVerp,
  751.             250
  752.         );
  753.     }
  754.  
  755.     /**
  756.      * Send an SMTP QUIT command.
  757.      * Closes the socket if there is no error or the $close_on_error argument is true.
  758.      * Implements from rfc 821: QUIT <CRLF>
  759.      * @param boolean $close_on_error Should the connection close if an error occurs?
  760.      * @access public
  761.      * @return boolean
  762.      */
  763.     public function quit($close_on_error = true)
  764.     {
  765.         $noerror = $this->sendCommand('QUIT', 'QUIT', 221);
  766.         $err = $this->error; //Save any error
  767.         if ($noerror or $close_on_error) {
  768.             $this->close();
  769.             $this->error = $err; //Restore any error from the quit command
  770.         }
  771.         return $noerror;
  772.     }
  773.  
  774.     /**
  775.      * Send an SMTP RCPT command.
  776.      * Sets the TO argument to $toaddr.
  777.      * Returns true if the recipient was accepted false if it was rejected.
  778.      * Implements from rfc 821: RCPT <SP> TO:<forward-path> <CRLF>
  779.      * @param string $address The address the message is being sent to
  780.      * @access public
  781.      * @return boolean
  782.      */
  783.     public function recipient($address)
  784.     {
  785.         return $this->sendCommand(
  786.             'RCPT TO',
  787.             'RCPT TO:<' . $address . '>',
  788.             array(250, 251)
  789.         );
  790.     }
  791.  
  792.     /**
  793.      * Send an SMTP RSET command.
  794.      * Abort any transaction that is currently in progress.
  795.      * Implements rfc 821: RSET <CRLF>
  796.      * @access public
  797.      * @return boolean True on success.
  798.      */
  799.     public function reset()
  800.     {
  801.         return $this->sendCommand('RSET', 'RSET', 250);
  802.     }
  803.  
  804.     /**
  805.      * Send a command to an SMTP server and check its return code.
  806.      * @param string $command The command name - not sent to the server
  807.      * @param string $commandstring The actual command to send
  808.      * @param integer|array $expect One or more expected integer success codes
  809.      * @access protected
  810.      * @return boolean True on success.
  811.      */
  812.     protected function sendCommand($command, $commandstring, $expect)
  813.     {
  814.         if (!$this->connected()) {
  815.             $this->setError("Called $command without being connected");
  816.             return false;
  817.         }
  818.         //Reject line breaks in all commands
  819.         if (strpos($commandstring, "\n") !== false or strpos($commandstring, "\r") !== false) {
  820.             $this->setError("Command '$command' contained line breaks");
  821.             return false;
  822.         }
  823.         $this->client_send($commandstring . self::CRLF);
  824.  
  825.         $this->last_reply = $this->get_lines();
  826.         // Fetch SMTP code and possible error code explanation
  827.         $matches = array();
  828.         if (preg_match("/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]) )?/", $this->last_reply, $matches)) {
  829.             $code = $matches[1];
  830.             $code_ex = (count($matches) > 2 ? $matches[2] : null);
  831.             // Cut off error code from each response line
  832.             $detail = preg_replace(
  833.                 "/{$code}[ -]".($code_ex ? str_replace('.', '\\.', $code_ex).' ' : '')."/m",
  834.                 '',
  835.                 $this->last_reply
  836.             );
  837.         } else {
  838.             // Fall back to simple parsing if regex fails
  839.             $code = substr($this->last_reply, 0, 3);
  840.             $code_ex = null;
  841.             $detail = substr($this->last_reply, 4);
  842.         }
  843.  
  844.         $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
  845.  
  846.         if (!in_array($code, (array)$expect)) {
  847.             $this->setError(
  848.                 "$command command failed",
  849.                 $detail,
  850.                 $code,
  851.                 $code_ex
  852.             );
  853.             $this->edebug(
  854.                 'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply,
  855.                 self::DEBUG_CLIENT
  856.             );
  857.             return false;
  858.         }
  859.  
  860.         $this->setError('');
  861.         return true;
  862.     }
  863.  
  864.     /**
  865.      * Send an SMTP SAML command.
  866.      * Starts a mail transaction from the email address specified in $from.
  867.      * Returns true if successful or false otherwise. If True
  868.      * the mail transaction is started and then one or more recipient
  869.      * commands may be called followed by a data command. This command
  870.      * will send the message to the users terminal if they are logged
  871.      * in and send them an email.
  872.      * Implements rfc 821: SAML <SP> FROM:<reverse-path> <CRLF>
  873.      * @param string $from The address the message is from
  874.      * @access public
  875.      * @return boolean
  876.      */
  877.     public function sendAndMail($from)
  878.     {
  879.         return $this->sendCommand('SAML', "SAML FROM:$from", 250);
  880.     }
  881.  
  882.     /**
  883.      * Send an SMTP VRFY command.
  884.      * @param string $name The name to verify
  885.      * @access public
  886.      * @return boolean
  887.      */
  888.     public function verify($name)
  889.     {
  890.         return $this->sendCommand('VRFY', "VRFY $name", array(250, 251));
  891.     }
  892.  
  893.     /**
  894.      * Send an SMTP NOOP command.
  895.      * Used to keep keep-alives alive, doesn't actually do anything
  896.      * @access public
  897.      * @return boolean
  898.      */
  899.     public function noop()
  900.     {
  901.         return $this->sendCommand('NOOP', 'NOOP', 250);
  902.     }
  903.  
  904.     /**
  905.      * Send an SMTP TURN command.
  906.      * This is an optional command for SMTP that this class does not support.
  907.      * This method is here to make the RFC821 Definition complete for this class
  908.      * and _may_ be implemented in future
  909.      * Implements from rfc 821: TURN <CRLF>
  910.      * @access public
  911.      * @return boolean
  912.      */
  913.     public function turn()
  914.     {
  915.         $this->setError('The SMTP TURN command is not implemented');
  916.         $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT);
  917.         return false;
  918.     }
  919.  
  920.     /**
  921.      * Send raw data to the server.
  922.      * @param string $data The data to send
  923.      * @access public
  924.      * @return integer|boolean The number of bytes sent to the server or false on error
  925.      */
  926.     public function client_send($data)
  927.     {
  928.         $this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT);
  929.         return fwrite($this->smtp_conn, $data);
  930.     }
  931.  
  932.     /**
  933.      * Get the latest error.
  934.      * @access public
  935.      * @return array
  936.      */
  937.     public function getError()
  938.     {
  939.         return $this->error;
  940.     }
  941.  
  942.     /**
  943.      * Get SMTP extensions available on the server
  944.      * @access public
  945.      * @return array|null
  946.      */
  947.     public function getServerExtList()
  948.     {
  949.         return $this->server_caps;
  950.     }
  951.  
  952.     /**
  953.      * A multipurpose method
  954.      * The method works in three ways, dependent on argument value and current state
  955.      *   1. HELO/EHLO was not sent - returns null and set up $this->error
  956.      *   2. HELO was sent
  957.      *     $name = 'HELO': returns server name
  958.      *     $name = 'EHLO': returns boolean false
  959.      *     $name = any string: returns null and set up $this->error
  960.      *   3. EHLO was sent
  961.      *     $name = 'HELO'|'EHLO': returns server name
  962.      *     $name = any string: if extension $name exists, returns boolean True
  963.      *       or its options. Otherwise returns boolean False
  964.      * In other words, one can use this method to detect 3 conditions:
  965.      *  - null returned: handshake was not or we don't know about ext (refer to $this->error)
  966.      *  - false returned: the requested feature exactly not exists
  967.      *  - positive value returned: the requested feature exists
  968.      * @param string $name Name of SMTP extension or 'HELO'|'EHLO'
  969.      * @return mixed
  970.      */
  971.     public function getServerExt($name)
  972.     {
  973.         if (!$this->server_caps) {
  974.             $this->setError('No HELO/EHLO was sent');
  975.             return null;
  976.         }
  977.  
  978.         // the tight logic knot ;)
  979.         if (!array_key_exists($name, $this->server_caps)) {
  980.             if ($name == 'HELO') {
  981.                 return $this->server_caps['EHLO'];
  982.             }
  983.             if ($name == 'EHLO' || array_key_exists('EHLO', $this->server_caps)) {
  984.                 return false;
  985.             }
  986.             $this->setError('HELO handshake was used. Client knows nothing about server extensions');
  987.             return null;
  988.         }
  989.  
  990.         return $this->server_caps[$name];
  991.     }
  992.  
  993.     /**
  994.      * Get the last reply from the server.
  995.      * @access public
  996.      * @return string
  997.      */
  998.     public function getLastReply()
  999.     {
  1000.         return $this->last_reply;
  1001.     }
  1002.  
  1003.     /**
  1004.      * Read the SMTP server's response.
  1005.      * Either before eof or socket timeout occurs on the operation.
  1006.      * With SMTP we can tell if we have more lines to read if the
  1007.      * 4th character is '-' symbol. If it is a space then we don't
  1008.      * need to read anything else.
  1009.      * @access protected
  1010.      * @return string
  1011.      */
  1012.     protected function get_lines()
  1013.     {
  1014.         // If the connection is bad, give up straight away
  1015.         if (!is_resource($this->smtp_conn)) {
  1016.             return '';
  1017.         }
  1018.         $data = '';
  1019.         $endtime = 0;
  1020.         stream_set_timeout($this->smtp_conn, $this->Timeout);
  1021.         if ($this->Timelimit > 0) {
  1022.             $endtime = time() + $this->Timelimit;
  1023.         }
  1024.         while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
  1025.             $str = @fgets($this->smtp_conn, 515);
  1026.             $this->edebug("SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL);
  1027.             $this->edebug("SMTP -> get_lines(): \$str is  \"$str\"", self::DEBUG_LOWLEVEL);
  1028.             $data .= $str;
  1029.             // If 4th character is a space, we are done reading, break the loop, micro-optimisation over strlen
  1030.             if ((isset($str[3]) and $str[3] == ' ')) {
  1031.                 break;
  1032.             }
  1033.             // Timed-out? Log and break
  1034.             $info = stream_get_meta_data($this->smtp_conn);
  1035.             if ($info['timed_out']) {
  1036.                 $this->edebug(
  1037.                     'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)',
  1038.                     self::DEBUG_LOWLEVEL
  1039.                 );
  1040.                 break;
  1041.             }
  1042.             // Now check if reads took too long
  1043.             if ($endtime and time() > $endtime) {
  1044.                 $this->edebug(
  1045.                     'SMTP -> get_lines(): timelimit reached ('.
  1046.                     $this->Timelimit . ' sec)',
  1047.                     self::DEBUG_LOWLEVEL
  1048.                 );
  1049.                 break;
  1050.             }
  1051.         }
  1052.         return $data;
  1053.     }
  1054.  
  1055.     /**
  1056.      * Enable or disable VERP address generation.
  1057.      * @param boolean $enabled
  1058.      */
  1059.     public function setVerp($enabled = false)
  1060.     {
  1061.         $this->do_verp = $enabled;
  1062.     }
  1063.  
  1064.     /**
  1065.      * Get VERP address generation mode.
  1066.      * @return boolean
  1067.      */
  1068.     public function getVerp()
  1069.     {
  1070.         return $this->do_verp;
  1071.     }
  1072.  
  1073.     /**
  1074.      * Set error messages and codes.
  1075.      * @param string $message The error message
  1076.      * @param string $detail Further detail on the error
  1077.      * @param string $smtp_code An associated SMTP error code
  1078.      * @param string $smtp_code_ex Extended SMTP code
  1079.      */
  1080.     protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '')
  1081.     {
  1082.         $this->error = array(
  1083.             'error' => $message,
  1084.             'detail' => $detail,
  1085.             'smtp_code' => $smtp_code,
  1086.             'smtp_code_ex' => $smtp_code_ex
  1087.         );
  1088.     }
  1089.  
  1090.     /**
  1091.      * Set debug output method.
  1092.      * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it.
  1093.      */
  1094.     public function setDebugOutput($method = 'echo')
  1095.     {
  1096.         $this->Debugoutput = $method;
  1097.     }
  1098.  
  1099.     /**
  1100.      * Get debug output method.
  1101.      * @return string
  1102.      */
  1103.     public function getDebugOutput()
  1104.     {
  1105.         return $this->Debugoutput;
  1106.     }
  1107.  
  1108.     /**
  1109.      * Set debug output level.
  1110.      * @param integer $level
  1111.      */
  1112.     public function setDebugLevel($level = 0)
  1113.     {
  1114.         $this->do_debug = $level;
  1115.     }
  1116.  
  1117.     /**
  1118.      * Get debug output level.
  1119.      * @return integer
  1120.      */
  1121.     public function getDebugLevel()
  1122.     {
  1123.         return $this->do_debug;
  1124.     }
  1125.  
  1126.     /**
  1127.      * Set SMTP timeout.
  1128.      * @param integer $timeout
  1129.      */
  1130.     public function setTimeout($timeout = 0)
  1131.     {
  1132.         $this->Timeout = $timeout;
  1133.     }
  1134.  
  1135.     /**
  1136.      * Get SMTP timeout.
  1137.      * @return integer
  1138.      */
  1139.     public function getTimeout()
  1140.     {
  1141.         return $this->Timeout;
  1142.     }
  1143.  
  1144.     /**
  1145.      * Reports an error number and string.
  1146.      * @param integer $errno The error number returned by PHP.
  1147.      * @param string $errmsg The error message returned by PHP.
  1148.      */
  1149.     protected function errorHandler($errno, $errmsg)
  1150.     {
  1151.         $notice = 'Connection: Failed to connect to server.';
  1152.         $this->setError(
  1153.             $notice,
  1154.             $errno,
  1155.             $errmsg
  1156.         );
  1157.         $this->edebug(
  1158.             $notice . ' Error number ' . $errno . '. "Error notice: ' . $errmsg,
  1159.             self::DEBUG_CONNECTION
  1160.         );
  1161.     }
  1162.  
  1163.     /**
  1164.      * Will return the ID of the last smtp transaction based on a list of patterns provided
  1165.      * in SMTP::$smtp_transaction_id_patterns.
  1166.      * If no reply has been received yet, it will return null.
  1167.      * If no pattern has been matched, it will return false.
  1168.      * @return bool|null|string
  1169.      */
  1170.     public function getLastTransactionID()
  1171.     {
  1172.         $reply = $this->getLastReply();
  1173.  
  1174.         if (empty($reply)) {
  1175.             return null;
  1176.         }
  1177.  
  1178.         foreach($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
  1179.             if(preg_match($smtp_transaction_id_pattern, $reply, $matches)) {
  1180.                 return $matches[1];
  1181.             }
  1182.         }
  1183.  
  1184.         return false;
  1185.     }
  1186. }
  1187.