Guest User

smtp.class.php

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