Guest User

class_smtp

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