Net_SMTP2
[ class tree: Net_SMTP2 ] [ index: Net_SMTP2 ] [ all elements ]

Source for file SMTP2.php

Documentation is available at SMTP2.php

  1. <?php
  2. /* vim: set expandtab softtabstop=4 tabstop=4 shiftwidth=4: */
  3. // +----------------------------------------------------------------------+
  4. // | PHP Version 5                                                        |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2003 The PHP Group                                |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 2.02 of the PHP license,      |
  9. // | that is bundled with this package in the file LICENSE, and is        |
  10. // | available at through the world-wide-web at                           |
  11. // | http://www.php.net/license/2_02.txt.                                 |
  12. // | If you did not receive a copy of the PHP license and are unable to   |
  13. // | obtain it through the world-wide-web, please send a note to          |
  14. // | license@php.net so we can mail you a copy immediately.               |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Chuck Hagenbuch <chuck@horde.org>                           |
  17. // |          Jon Parise <jon@php.net>                                    |
  18. // |          Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar>      |
  19. // +----------------------------------------------------------------------+
  20.  
  21. require_once 'PEAR/Exception.php';
  22. require_once 'Net/Socket2.php';
  23.  
  24. /**
  25.  * Provides an implementation of the SMTP protocol using PEAR's
  26.  * Net_Socket2:: class.
  27.  *
  28.  * @package Net_SMTP2
  29.  * @author  Chuck Hagenbuch <chuck@horde.org>
  30.  * @author  Jon Parise <jon@php.net>
  31.  * @author  Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar>
  32.  *
  33.  * @example basic.php   A basic implementation of the Net_SMTP2 package.
  34.  */
  35. class Net_SMTP2
  36. {
  37.     /**
  38.      * The server to connect to.
  39.      *
  40.      * @var string 
  41.      */
  42.     public $host = 'localhost';
  43.  
  44.     /**
  45.      * The port to connect to.
  46.      *
  47.      * @var int 
  48.      */
  49.     public $port = 25;
  50.  
  51.     /**
  52.      * The value to give when sending EHLO or HELO.
  53.      *
  54.      * @var string 
  55.      */
  56.     public $localhost = 'localhost';
  57.  
  58.     /**
  59.      * List of supported authentication methods, in preferential order.
  60.      *
  61.      * @var array 
  62.      */
  63.     public $auth_methods = array();
  64.  
  65.     /**
  66.      * Use SMTP command pipelining (specified in RFC 2920) if the SMTP
  67.      * server supports it.
  68.      *
  69.      * When pipeling is enabled, rcptTo(), mailFrom(), sendFrom(),
  70.      * somlFrom() and samlFrom() do not wait for a response from the
  71.      * SMTP server but return immediately.
  72.      *
  73.      * @var bool 
  74.      */
  75.     public $pipelining = false;
  76.  
  77.     /**
  78.      * Number of pipelined commands.
  79.      *
  80.      * @var int 
  81.      */
  82.     protected $_pipelined_commands = 0;
  83.  
  84.     /**
  85.      * Should debugging output be enabled?
  86.      *
  87.      * @var boolean 
  88.      */
  89.     protected $_debug = false;
  90.  
  91.     /**
  92.      * Debug output handler.
  93.      *
  94.      * @var callback 
  95.      */
  96.     protected $_debug_handler = null;
  97.  
  98.     /**
  99.      * The socket resource being used to connect to the SMTP server.
  100.      *
  101.      * @var resource 
  102.      */
  103.     protected $_socket = null;
  104.  
  105.     /**
  106.      * Array of socket options that will be passed to Net_Socket2::connect().
  107.      *
  108.      * @see stream_context_create()
  109.      *
  110.      * @var array 
  111.      */
  112.     protected $_socket_options = null;
  113.  
  114.     /**
  115.      * The socket I/O timeout value in seconds.
  116.      *
  117.      * @var int 
  118.      */
  119.     protected $_timeout = 0;
  120.  
  121.     /**
  122.      * The most recent server response code.
  123.      *
  124.      * @var int 
  125.      */
  126.     protected $_code = -1;
  127.  
  128.     /**
  129.      * The most recent server response arguments.
  130.      *
  131.      * @var array 
  132.      */
  133.     protected $_arguments = array();
  134.  
  135.     /**
  136.      * Stores the SMTP server's greeting string.
  137.      *
  138.      * @var string 
  139.      */
  140.     protected $_greeting = null;
  141.  
  142.     /**
  143.      * Stores detected features of the SMTP server.
  144.      *
  145.      * @var array 
  146.      */
  147.     protected $_esmtp = array();
  148.  
  149.     /**
  150.      * Instantiates a new Net_SMTP2 object, overriding any defaults
  151.      * with parameters that are passed in.
  152.      *
  153.      * If you have SSL support in PHP, you can connect to a server
  154.      * over SSL using an 'ssl://' prefix:
  155.      *
  156.      *   // 465 is a common smtps port.
  157.      *   $smtp = new Net_SMTP2('ssl://mail.host.com', 465);
  158.      *   $smtp->connect();
  159.      *
  160.      * @param string  $host       The server to connect to.
  161.      * @param integer $port       The port to connect to.
  162.      * @param string  $localhost  The value to give when sending EHLO or HELO.
  163.      * @param boolean $pipeling   Use SMTP command pipelining
  164.      * @param integer $timeout    Socket I/O timeout in seconds.
  165.      * @param array   $socket_options Socket stream_context_create() options.
  166.      */
  167.     public function __construct($host = null$port = null$localhost = null,
  168.                                 $pipelining = false$timeout = 0,
  169.                                 $socket_options = null)
  170.     {
  171.         if (isset($host)) {
  172.             $this->host = $host;
  173.         }
  174.         if (isset($port)) {
  175.             $this->port = $port;
  176.         }
  177.         if (isset($localhost)) {
  178.             $this->localhost = $localhost;
  179.         }
  180.         $this->pipelining = $pipelining;
  181.  
  182.         $this->_socket = new Net_Socket2();
  183.         $this->_socket_options = $socket_options;
  184.         $this->_timeout = $timeout;
  185.  
  186.         /* Include the Auth_SASL2 package.  If the package is available, we
  187.          * enable the authentication methods that depend upon it. */
  188.         if (@include_once 'Auth/SASL2.php'{
  189.             $this->setAuthMethod('CRAM-MD5'array($this'_authCram_MD5'));
  190.             $this->setAuthMethod('DIGEST-MD5'array($this'_authDigest_MD5'));
  191.         }
  192.  
  193.         /* These standard authentication methods are always available. */
  194.         $this->setAuthMethod('LOGIN'array($this'_authLogin')false);
  195.         $this->setAuthMethod('PLAIN'array($this'_authPlain')false);
  196.     }
  197.  
  198.     /**
  199.      * Set the socket I/O timeout value in seconds plus microseconds.
  200.      *
  201.      * @param integer $seconds       Timeout value in seconds.
  202.      * @param integer $microseconds  Additional value in microseconds.
  203.      */
  204.     public function setTimeout($seconds$microseconds = 0)
  205.     {
  206.         return $this->_socket->setTimeout($seconds$microseconds);
  207.     }
  208.  
  209.     /**
  210.      * Set the value of the debugging flag.
  211.      *
  212.      * @param boolean $debug  New value for the debugging flag.
  213.      */
  214.     public function setDebug($debug$handler = null)
  215.     {
  216.         $this->_debug = $debug;
  217.         $this->_debug_handler = $handler;
  218.     }
  219.  
  220.     /**
  221.      * Write the given debug text to the current debug output handler.
  222.      *
  223.      * @param string $message  Debug message text.
  224.      */
  225.     protected function _debug($message)
  226.     {
  227.         if ($this->_debug{
  228.             if ($this->_debug_handler{
  229.                 call_user_func_array($this->_debug_handler,
  230.                                      array(&$this$message));
  231.             else {
  232.                 echo "DEBUG: $message\n";
  233.             }
  234.         }
  235.     }
  236.  
  237.     /**
  238.      * Send the given string of data to the server.
  239.      *
  240.      * @param string $data  The string of data to send.
  241.      *
  242.      * @return integer  The number of bytes that were actually written.
  243.      * @throws PEAR_Exception
  244.      */
  245.     protected function _send($data)
  246.     {
  247.         $this->_debug("Send: $data");
  248.  
  249.         $result $this->_socket->write($data);
  250.         if (!$result{
  251.             throw new PEAR_Exception('Failed to write to socket: unknown error');
  252.         }
  253.  
  254.         return $result;
  255.     }
  256.  
  257.     /**
  258.      * Send a command to the server with an optional string of
  259.      * arguments.  A carriage return / linefeed (CRLF) sequence will
  260.      * be appended to each command string before it is sent to the
  261.      * SMTP server - an error will be thrown if the command string
  262.      * already contains any newline characters. Use _send() for
  263.      * commands that must contain newlines.
  264.      *
  265.      * @param string $command  The SMTP command to send to the server.
  266.      * @param string $args     A string of optional arguments to append
  267.      *                          to the command.
  268.      *
  269.      * @return integer  The number of bytes that were actually written.
  270.      * @throws PEAR_Exception
  271.      */
  272.     protected function _put($command$args '')
  273.     {
  274.         if (!empty($args)) {
  275.             $command .= ' ' $args;
  276.         }
  277.  
  278.         if (strcspn($command"\r\n"!== strlen($command)) {
  279.             throw new PEAR_Exception('Commands cannot contain newlines');
  280.         }
  281.  
  282.         return $this->_send($command "\r\n");
  283.     }
  284.  
  285.     /**
  286.      * Read a reply from the SMTP server.  The reply consists of a response
  287.      * code and a response message.
  288.      *
  289.      * @see getResponse
  290.      *
  291.      * @param mixed $valid  The set of valid response codes.  These
  292.      *                       may be specified as an array of integer
  293.      *                       values or as a single integer value.
  294.      * @param bool $later   Do not parse the response now, but wait
  295.      *                       until the last command in the pipelined
  296.      *                       command group
  297.      *
  298.      * @throws PEAR_Exception
  299.      */
  300.     protected function _parseResponse($valid$later = false)
  301.     {
  302.         $this->_code = -1;
  303.         $this->_arguments = array();
  304.  
  305.         if ($later{
  306.             ++$this->_pipelined_commands;
  307.             return;
  308.         }
  309.  
  310.         for ($i = 0; $i <= $this->_pipelined_commands; ++$i{
  311.             while ($line $this->_socket->readLine()) {
  312.                 $this->_debug("Recv: $line");
  313.  
  314.                 /* If we receive an empty line, the connection was closed. */
  315.                 if (empty($line)) {
  316.                     $this->disconnect();
  317.                     throw new PEAR_Exception('Connection was closed');
  318.                 }
  319.  
  320.                 /* Read the code and store the rest in the arguments array. */
  321.                 $code substr($line03);
  322.                 $this->_arguments[trim(substr($line4));
  323.  
  324.                 /* Check the syntax of the response code. */
  325.                 if (is_numeric($code)) {
  326.                     $this->_code = (int)$code;
  327.                 else {
  328.                     $this->_code = -1;
  329.                     break;
  330.                 }
  331.  
  332.                 /* If this is not a multiline response, we're done. */
  333.                 if (substr($line31!= '-'{
  334.                     break;
  335.                 }
  336.             }
  337.         }
  338.  
  339.         $this->_pipelined_commands = 0;
  340.  
  341.         /* Compare the server's response code with the valid code/codes. */
  342.         if ((is_int($valid&& ($this->_code === $valid)) ||
  343.             (is_array($valid&& in_array($this->_code$validtrue))) {
  344.             return;
  345.         }
  346.  
  347.         throw new PEAR_Exception('Invalid response code received from server',
  348.                                  $this->_code);
  349.     }
  350.  
  351.     /**
  352.      * Issue an SMTP command and verify its response.
  353.      *
  354.      * @param string $command  The SMTP command string or data.
  355.      * @param mixed $valid     The set of valid response codes.  These
  356.      *                          may be specified as an array of integer
  357.      *                          values or as a single integer value.
  358.      *
  359.      * @throws PEAR_Exception
  360.      */
  361.     public function command($command$valid)
  362.     {
  363.         $this->_put($command);
  364.         $this->_parseResponse($valid);
  365.     }
  366.  
  367.     /**
  368.      * Return a 2-tuple containing the last response from the SMTP server.
  369.      *
  370.      * @return array  A two-element array: the first element contains the
  371.      *                 response code as an integer and the second element
  372.      *                 contains the response's arguments as a string.
  373.      */
  374.     public function getResponse()
  375.     {
  376.         return array($this->_codejoin("\n"$this->_arguments));
  377.     }
  378.  
  379.     /**
  380.      * Return the SMTP server's greeting string.
  381.      *
  382.      * @return  string  A string containing the greeting string, or null if a
  383.      *                   greeting has not been received.
  384.      */
  385.     public function getGreeting()
  386.     {
  387.         return $this->_greeting;
  388.     }
  389.  
  390.     /**
  391.      * Attempt to connect to the SMTP server.
  392.      *
  393.      * @param int $timeout      The timeout value (in seconds) for the
  394.      *                           socket connection attempt.
  395.      * @param bool $persistent  Should a persistent socket connection
  396.      *                           be used?
  397.      *
  398.      * @throws PEAR_Exception
  399.      */
  400.     public function connect($timeout = null$persistent = false)
  401.     {
  402.         $this->_greeting = null;
  403.         $result $this->_socket->connect($this->host$this->port,
  404.                                           $persistent$timeout,
  405.                                           $this->_socket_options);
  406.  
  407.         /*
  408.          * Now that we're connected, reset the socket's timeout value for 
  409.          * future I/O operations.  This allows us to have different socket 
  410.          * timeout values for the initial connection (our $timeout parameter) 
  411.          * and all other socket operations.
  412.          */
  413.         if ($this->_timeout > 0{
  414.             $this->setTimeout($this->_timeout);
  415.         }
  416.  
  417.         $this->_parseResponse(220);
  418.         
  419.         /* Extract and store a copy of the server's greeting string. */
  420.         list($this->_greeting$this->getResponse();
  421.  
  422.         $this->_negotiate();
  423.  
  424.         return true;
  425.     }
  426.  
  427.     /**
  428.      * Attempt to disconnect from the SMTP server.
  429.      *
  430.      * @throws PEAR_Exception
  431.      */
  432.     public function disconnect()
  433.     {
  434.         try {
  435.             $this->_put('QUIT');
  436.         
  437.             $this->_parseResponse(221);
  438.             $this->_socket->disconnect();
  439.         catch (Net_Socket2_Exception $e{
  440.             // Already disconnected? Silence!
  441.         }
  442.  
  443.         return true;
  444.     }
  445.  
  446.     /**
  447.      * Attempt to send the EHLO command and obtain a list of ESMTP
  448.      * extensions available, and failing that just send HELO.
  449.      *
  450.      * @throws PEAR_Exception
  451.      */
  452.     protected function _negotiate()
  453.     {
  454.         $this->_put('EHLO'$this->localhost);
  455.  
  456.         try {
  457.             $this->_parseResponse(250);
  458.         catch (PEAR_Exception $e{
  459.             /* If the EHLO failed, try the simpler HELO command. */
  460.             $this->_put('HELO'$this->localhost);
  461.             $this->_parseResponse(250);
  462.  
  463.             return true;
  464.         }
  465.  
  466.         foreach ($this->_arguments as $argument{
  467.             $verb strtok($argument' ');
  468.             $arguments substr($argumentstrlen($verb+ 1,
  469.                                 strlen($argumentstrlen($verb- 1);
  470.             $this->_esmtp[$verb$arguments;
  471.         }
  472.  
  473.         if (!isset($this->_esmtp['PIPELINING'])) {
  474.             $this->pipelining = false;
  475.         }
  476.     }
  477.  
  478.     /**
  479.      * Returns the name of the best authentication method that the server
  480.      * has advertised.
  481.      *
  482.      * @return mixed    Returns a string containing the name of the best
  483.      *                   supported authentication method.
  484.      * @throws PEAR_Exception
  485.      */
  486.     protected function _getBestAuthMethod()
  487.     {
  488.         $available_methods explode(' '$this->_esmtp['AUTH']);
  489.  
  490.         foreach ($this->auth_methods as $method => $callback{
  491.             if (in_array($method$available_methods)) {
  492.                 return $method;
  493.             }
  494.         }
  495.  
  496.         throw new PEAR_Exception('No supported authentication methods');
  497.     }
  498.  
  499.     /**
  500.      * Attempt to do SMTP authentication.
  501.      *
  502.      * @param string $uid     The userid to authenticate as.
  503.      * @param string $pwd     The password to authenticate with.
  504.      * @param string $method  The requested authentication method.  If none is
  505.      *                         specified, the best supported method will be
  506.      *                         used.
  507.      * @param bool $tls       Flag indicating whether or not TLS should be
  508.      *                         attempted.
  509.      * @param string $authz   An optional authorization identifier.  If
  510.      *                         specified, this identifier will be used as the
  511.      *                         authorization proxy.
  512.      *
  513.      * @throws PEAR_Exception
  514.      */
  515.     public function auth($uid$pwd$method ''$tls = true$authz '')
  516.     {
  517.         /* We can only attempt a TLS connection if one has been requested,
  518.          * we're running PHP 5.1.0 or later, have access to the OpenSSL
  519.          * extension, are connected to an SMTP server which supports the
  520.          * STARTTLS extension, and aren't already connected over a secure
  521.          * (SSL) socket connection. */
  522.         if ($tls && version_compare(PHP_VERSION'5.1.0''>='&&
  523.             extension_loaded('openssl'&& isset($this->_esmtp['STARTTLS']&&
  524.             strncasecmp($this->host'ssl://'6!== 0{
  525.             /* Start the TLS connection attempt. */
  526.             $this->_put('STARTTLS');
  527.             $this->_parseResponse(220);
  528.  
  529.             $result $this->_socket->enableCrypto(trueSTREAM_CRYPTO_METHOD_TLS_CLIENT);
  530.             if ($result !== true{
  531.                 throw new PEAR_Exception('STARTTLS failed');
  532.             }
  533.  
  534.             /* Send EHLO again to recieve the AUTH string from the
  535.              * SMTP server. */
  536.             $this->_negotiate();
  537.         }
  538.  
  539.         if (empty($this->_esmtp['AUTH'])) {
  540.             throw new PEAR_Exception('SMTP server does not support authentication');
  541.         }
  542.  
  543.         /* If no method has been specified, get the name of the best
  544.          * supported method advertised by the SMTP server. */
  545.         $method = empty($method)
  546.             ? $this->_getBestAuthMethod()
  547.             : strtoupper($method);
  548.         if (!array_key_exists($method$this->auth_methods)) {
  549.             throw new PEAR_Exception($method ' is not a supported authentication method');
  550.         }
  551.  
  552.         if (!is_callable($this->auth_methods[$method]false)) {
  553.             throw new PEAR_Exception($method ' authentication method cannot be called');
  554.         }
  555.  
  556.         if (is_array($this->auth_methods[$method])) {
  557.             list($object$method$this->auth_methods[$method];
  558.             $object->{$method}($uid$pwd$authz$this);
  559.         else {
  560.             $func $this->auth_methods[$method];
  561.             $func($uid$pwd$authz$this);
  562.          }
  563.     }
  564.  
  565.     /**
  566.      * Add a new authentication method.
  567.      *
  568.      * @param string $name     The authentication method name (e.g. 'PLAIN')
  569.      * @param mixed $callback  The authentication callback (given as the name
  570.      *                          of a function or as an (object, method name)
  571.      *                          array).
  572.      * @param bool $prepend    Should the new method be prepended to the list
  573.      *                          of available methods?  This is the default
  574.      *                          behavior, giving the new method the highest
  575.      *                          priority.
  576.      *
  577.      * @throws PEAR_Exception
  578.      */
  579.     public function setAuthMethod($name$callback$prepend = true)
  580.     {
  581.         if (!is_string($name)) {
  582.             throw new InvalidArgumentException('Method name is not a string');
  583.         }
  584.  
  585.         if (!is_string($callback&& !is_array($callback)) {
  586.             throw new InvalidArgumentException('Method callback must be string or array');
  587.         }
  588.  
  589.         if (is_array($callback&&
  590.             (!is_object($callback[0]|| !is_string($callback[1]))) {
  591.             throw new InvalidArgumentException('Bad method callback array');
  592.         }
  593.  
  594.         if ($prepend{
  595.             $this->auth_methods = array_merge(array($name => $callback),
  596.                                               $this->auth_methods);
  597.         else {
  598.             $this->auth_methods[$name$callback;
  599.         }
  600.     }
  601.  
  602.     /**
  603.      * Authenticates the user using the DIGEST-MD5 method.
  604.      *
  605.      * @param string $uid    The userid to authenticate as.
  606.      * @param string $pwd    The password to authenticate with.
  607.      * @param string $authz  The optional authorization proxy identifier.
  608.      *
  609.      * @throws PEAR_Exception
  610.      */
  611.     protected function _authDigest_MD5($uid$pwd$authz '')
  612.     {
  613.         $this->_put('AUTH''DIGEST-MD5');
  614.  
  615.         /* 334: Continue authentication request */
  616.         try {
  617.             $this->_parseResponse(334);
  618.         catch (PEAR_Exception $e{
  619.             /* 503: Error: already authenticated */
  620.             if ($e->getCode(=== 503{
  621.                 return;
  622.             }
  623.             throw $e;
  624.         }
  625.  
  626.         $auth_sasl = new Auth_SASL2();
  627.         $challenge base64_decode($this->_arguments[0]);
  628.         $digest $auth_sasl->factory('digest-md5');
  629.         $auth_str base64_encode($digest->getResponse($uid$pwd$challenge,
  630.                                                        $this->host"smtp",
  631.                                                        $authz));
  632.  
  633.         $this->_put($auth_str);
  634.  
  635.         /* 334: Continue authentication request */
  636.         $this->_parseResponse(334);
  637.  
  638.         /* We don't use the protocol's third step because SMTP doesn't
  639.          * allow subsequent authentication, so we just silently ignore
  640.          * it. */
  641.         $this->_put('');
  642.  
  643.         /* 235: Authentication successful */
  644.         $this->_parseResponse(235);
  645.     }
  646.  
  647.     /**
  648.      * Authenticates the user using the CRAM-MD5 method.
  649.      *
  650.      * @param string $uid    The userid to authenticate as.
  651.      * @param string $pwd    The password to authenticate with.
  652.      * @param string $authz  The optional authorization proxy identifier.
  653.      *
  654.      * @throws PEAR_Exception
  655.      */
  656.     protected function _authCRAM_MD5($uid$pwd$authz '')
  657.     {
  658.         $this->_put('AUTH''CRAM-MD5');
  659.  
  660.         /* 334: Continue authentication request */
  661.         try {
  662.             $this->_parseResponse(334);
  663.         catch (PEAR_Exception $e{
  664.             /* 503: Error: already authenticated */
  665.             if ($e->getCode(=== 503{
  666.                 return;
  667.             }
  668.             throw $e;
  669.         }
  670.         $auth_sasl = new Auth_SASL2();
  671.         $challenge base64_decode($this->_arguments[0]);
  672.         $cram $auth_sasl->factory('cram-md5');
  673.         $auth_str base64_encode($cram->getResponse($uid$pwd$challenge));
  674.  
  675.         $this->_put($auth_str);
  676.  
  677.         /* 235: Authentication successful */
  678.         $this->_parseResponse(235);
  679.     }
  680.  
  681.     /**
  682.      * Authenticates the user using the LOGIN method.
  683.      *
  684.      * @param string $uid    The userid to authenticate as.
  685.      * @param string $pwd    The password to authenticate with.
  686.      * @param string $authz  The optional authorization proxy identifier.
  687.      *
  688.      * @throws PEAR_Exception
  689.      */
  690.     protected function _authLogin($uid$pwd$authz '')
  691.     {
  692.         $this->_put('AUTH''LOGIN');
  693.  
  694.         /* 334: Continue authentication request */
  695.         try {
  696.             $this->_parseResponse(334);
  697.         catch (PEAR_Exception $e{
  698.             /* 503: Error: already authenticated */
  699.             if ($e->getCode(=== 503{
  700.                 return;
  701.             }
  702.             throw $e;
  703.         }
  704.  
  705.         $this->_put(base64_encode($uid));
  706.  
  707.         /* 334: Continue authentication request */
  708.         $this->_parseResponse(334);
  709.  
  710.         $this->_put(base64_encode($pwd));
  711.  
  712.         /* 235: Authentication successful */
  713.         $this->_parseResponse(235);
  714.     }
  715.  
  716.     /**
  717.      * Authenticates the user using the PLAIN method.
  718.      *
  719.      * @param string $uid    The userid to authenticate as.
  720.      * @param string $pwd    The password to authenticate with.
  721.      * @param string $authz  The optional authorization proxy identifier.
  722.      *
  723.      * @throws PEAR_Exception
  724.      */
  725.     protected function _authPlain($uid$pwd$authz '')
  726.     {
  727.         $this->_put('AUTH''PLAIN');
  728.  
  729.         /* 334: Continue authentication request */
  730.         try {
  731.             $this->_parseResponse(334);
  732.         catch (PEAR_Exception $e{
  733.             /* 503: Error: already authenticated */
  734.             if ($e->getCode(=== 503{
  735.                 return;
  736.             }
  737.             throw $e;
  738.         }
  739.  
  740.         $auth_str base64_encode($authz chr(0$uid chr(0$pwd);
  741.  
  742.         $this->_put($auth_str);
  743.  
  744.         /* 235: Authentication successful */
  745.         $this->_parseResponse(235);
  746.     }
  747.  
  748.     /**
  749.      * Send the HELO command.
  750.      *
  751.      * @param string The domain name to say we are.
  752.      *
  753.      * @throws PEAR_Exception
  754.      */
  755.     public function helo($domain)
  756.     {
  757.         $this->_put('HELO'$domain);
  758.         $this->_parseResponse(250);
  759.     }
  760.  
  761.     /**
  762.      * Return the list of SMTP service extensions advertised by the server.
  763.      *
  764.      * @return array The list of SMTP service extensions.
  765.      */
  766.     public function getServiceExtensions()
  767.     {
  768.         return $this->_esmtp;
  769.     }
  770.  
  771.     /**
  772.      * Send the MAIL FROM: command.
  773.      *
  774.      * @param string $sender  The sender (reverse path) to set.
  775.      * @param string $params  String containing additional MAIL parameters,
  776.      *                         such as the NOTIFY flags defined by RFC 1891
  777.      *                         or the VERP protocol.
  778.      *
  779.      * @throws PEAR_Exception
  780.      */
  781.     public function mailFrom($sender$params = null)
  782.     {
  783.         $args = "FROM:<$sender>";
  784.         if (is_string($params&& strlen($params)) {
  785.             $args .= ' ' $params;
  786.         }
  787.  
  788.         $this->_put('MAIL'$args);
  789.         $this->_parseResponse(250$this->pipelining);
  790.     }
  791.  
  792.     /**
  793.      * Send the RCPT TO: command.
  794.      *
  795.      * @param string $recipient  The recipient (forward path) to add.
  796.      * @param string $params     String containing additional RCPT parameters,
  797.      *                            such as the NOTIFY flags defined by RFC 1891.
  798.      *
  799.      * @throws PEAR_Exception
  800.      */
  801.     public function rcptTo($recipient$params = null)
  802.     {
  803.         $args = "TO:<$recipient>";
  804.         if (is_string($params&& strlen($params)) {
  805.             $args .= ' ' $params;
  806.         }
  807.  
  808.         $this->_put('RCPT'$args);
  809.         $this->_parseResponse(array(250251)$this->pipelining);
  810.     }
  811.  
  812.     /**
  813.      * Quote the data so that it meets SMTP standards.
  814.      *
  815.      * This is provided as a separate public function to facilitate
  816.      * easier overloading for the cases where it is desirable to
  817.      * customize the quoting behavior.
  818.      *
  819.      * @param string &$data  The message text to quote. The string must be
  820.      *                        passed by reference, and the text will be
  821.      *                        modified in place.
  822.      */
  823.     public function quotedata(&$data)
  824.     {
  825.         /* Because a single leading period (.) signifies an end to the
  826.          * data, legitimate leading periods need to be "doubled" ('..').
  827.          * Also: change Unix (\n) and Mac (\r) linefeeds into CRLF's
  828.          * (\r\n). */
  829.         $data preg_replace(
  830.             array('/^\./m''/(?:\r\n|\n|\r(?!\n))/'),
  831.             array('..'"\r\n"),
  832.             $data
  833.         );
  834.     }
  835.  
  836.     /**
  837.      * Send the DATA command.
  838.      *
  839.      * @param mixed $data      The message data, either as a string or an open
  840.      *                          file resource.
  841.      * @param string $headers  The message headers. If $headers is provided,
  842.      *                          $data is assumed to contain only body data.
  843.      *
  844.      * @throws PEAR_Exception
  845.      */
  846.     public function data($data$headers = null)
  847.     {
  848.         /* Verify that $data is a supported type. */
  849.         if (!is_string($data&& !is_resource($data)) {
  850.             throw new InvalidArgumentException('Expected a string or file resource');
  851.         }
  852.  
  853.         /* Start by considering the size of the optional headers string.  We
  854.          * also account for the addition 4 character "\r\n\r\n" separator
  855.          * sequence. */
  856.         $size is_null($headers? 0 : strlen($headers+ 4;
  857.  
  858.         if (is_resource($data)) {
  859.             $stat fstat($data);
  860.             if ($stat === false{
  861.                 throw new PEAR_Exception('Failed to get file size');
  862.             }
  863.             $size += $stat['size'];
  864.         else {
  865.             $size += strlen($data);
  866.         }
  867.  
  868.         /* RFC 1870, section 3, subsection 3 states "a value of zero indicates
  869.          * that no fixed maximum message size is in force".  Furthermore, it
  870.          * says that if "the parameter is omitted no information is conveyed
  871.          * about the server's fixed maximum message size". */
  872.         $limit = isset($this->_esmtp['SIZE']$this->_esmtp['SIZE': 0;
  873.         if ($limit > 0 && $size >= $limit{
  874.             $this->disconnect();
  875.             throw new PEAR_Exception('Message size exceeds server limit');
  876.         }
  877.  
  878.         /* Initiate the DATA command. */
  879.         $this->_put('DATA');
  880.         $this->_parseResponse(354);
  881.  
  882.         /* If we have a separate headers string, send it first. */
  883.         if (!is_null($headers)) {
  884.             $this->quotedata($headers);
  885.             $this->_send($headers "\r\n\r\n");
  886.         }
  887.  
  888.         /* Now we can send the message body data. */
  889.         if (is_resource($data)) {
  890.             /* Stream the contents of the file resource out over our socket
  891.              * connection, line by line.  Each line must be run through the
  892.              * quoting routine. */
  893.             while (strlen($line fread($data8192)) > 0{
  894.                 /* If the last character is an newline, we need to grab the
  895.                  * next character to check to see if it is a period. */
  896.                 while (!feof($data)) {
  897.                     $char fread($data1);
  898.                     $line .= $char;
  899.                     if ($char != "\n"{
  900.                         break;
  901.                     }
  902.                 }
  903.                 $this->quotedata($line);
  904.                 $this->_send($line);
  905.             }
  906.         else {
  907.             /* Break up the data by sending one chunk (up to 512k) at a time.
  908.              * This approach reduces our peak memory usage. */
  909.             for ($offset = 0; $offset $size;{
  910.                 $end $offset + 512000;
  911.  
  912.                 /* Ensure we don't read beyond our data size or span multiple
  913.                  * lines.  quotedata() can't properly handle character data
  914.                  * that's split across two line break boundaries. */
  915.                 if ($end >= $size{
  916.                     $end $size;
  917.                 else {
  918.                     for ($end $size$end++{
  919.                         if ($data[$end!= "\n"{
  920.                             break;
  921.                         }
  922.                     }
  923.                 }
  924.  
  925.                 /* Extract our chunk and run it through the quoting routine. */
  926.                 $chunk substr($data$offset$end $offset);
  927.                 $this->quotedata($chunk);
  928.  
  929.                 /* If we run into a problem along the way, abort. */
  930.                 $this->_send($chunk);
  931.  
  932.                 /* Advance the offset to the end of this chunk. */
  933.                 $offset $end;
  934.             }
  935.         }
  936.  
  937.         /* Finally, send the DATA terminator sequence. */
  938.         $this->_send("\r\n.\r\n");
  939.  
  940.         /* Verify that the data was successfully received by the server. */
  941.         $this->_parseResponse(250$this->pipelining);
  942.     }
  943.  
  944.     /**
  945.      * Send the SEND FROM: command.
  946.      *
  947.      * @param string $path  The reverse path to send.
  948.      *
  949.      * @throws PEAR_Exception
  950.      */
  951.     public function sendFrom($path)
  952.     {
  953.         $this->_put('SEND'"FROM:<$path>");
  954.         $this->_parseResponse(250$this->pipelining);
  955.     }
  956.  
  957.     /**
  958.      * Send the SOML FROM: command.
  959.      *
  960.      * @param string $path  The reverse path to send.
  961.      *
  962.      * @throws PEAR_Exception
  963.      */
  964.     public function somlFrom($path)
  965.     {
  966.         $this->_put('SOML'"FROM:<$path>");
  967.         $this->_parseResponse(250$this->pipelining);
  968.     }
  969.  
  970.     /**
  971.      * Send the SAML FROM: command.
  972.      *
  973.      * @param string $path  The reverse path to send.
  974.      *
  975.      * @throws PEAR_Exception
  976.      */
  977.     public function samlFrom($path)
  978.     {
  979.         $this->_put('SAML'"FROM:<$path>");
  980.         $this->_parseResponse(250$this->pipelining);
  981.     }
  982.  
  983.     /**
  984.      * Send the RSET command.
  985.      *
  986.      * @throws PEAR_Exception
  987.      */
  988.     public function rset()
  989.     {
  990.         $this->_put('RSET');
  991.         $this->_parseResponse(250$this->pipelining);
  992.     }
  993.  
  994.     /**
  995.      * Send the VRFY command.
  996.      *
  997.      * @param string $string  The string to verify
  998.      *
  999.      * @throws PEAR_Exception
  1000.      */
  1001.     public function vrfy($string)
  1002.     {
  1003.         /* Note: 251 is also a valid response code */
  1004.         $this->_put('VRFY'$string);
  1005.         $this->_parseResponse(array(250252));
  1006.     }
  1007.  
  1008.     /**
  1009.      * Send the NOOP command.
  1010.      *
  1011.      * @throws PEAR_Exception
  1012.      */
  1013.     public function noop()
  1014.     {
  1015.         $this->_put('NOOP');
  1016.         $this->_parseResponse(250);
  1017.     }
  1018.  
  1019. }

Documentation generated on Mon, 11 Mar 2019 15:57:16 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.