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

Source for file smtpmx.php

Documentation is available at smtpmx.php

  1. <?PHP
  2. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
  3.  
  4. /**
  5.  * SMTP MX
  6.  *
  7.  * SMTP MX implementation of the PEAR Mail interface. Requires the Net_SMTP class.
  8.  *
  9.  * PHP versions 4 and 5
  10.  *
  11.  * LICENSE: This source file is subject to version 3.0 of the PHP license
  12.  * that is available through the world-wide-web at the following URI:
  13.  * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
  14.  * the PHP License and are unable to obtain it through the web, please
  15.  * send a note to license@php.net so we can mail you a copy immediately.
  16.  *
  17.  * @category   Mail
  18.  * @package    Mail_smtpmx
  19.  * @author     gERD Schaufelberger <gerd@php-tools.net>
  20.  * @copyright  1997-2005 The PHP Group
  21.  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
  22.  * @version    CVS: $Id: smtpmx.php,v 1.2 2007/10/06 17:00:00 chagenbu Exp $
  23.  * @see        Mail
  24.  */
  25.  
  26. require_once 'Net/SMTP.php';
  27.  
  28. /**
  29.  * SMTP MX implementation of the PEAR Mail interface. Requires the Net_SMTP class.
  30.  *
  31.  *
  32.  * @access public
  33.  * @author  gERD Schaufelberger <gerd@php-tools.net>
  34.  * @package Mail
  35.  * @version $Revision: 1.2 $
  36.  */
  37. class Mail_smtpmx extends Mail {
  38.  
  39.     /**
  40.      * SMTP connection object.
  41.      *
  42.      * @var object 
  43.      * @access private
  44.      */
  45.     var $_smtp = null;
  46.  
  47.     /**
  48.      * The port the SMTP server is on.
  49.      * @var integer 
  50.      * @see getservicebyname()
  51.      */
  52.     var $port = 25;
  53.  
  54.     /**
  55.      * Hostname or domain that will be sent to the remote SMTP server in the
  56.      * HELO / EHLO message.
  57.      *
  58.      * @var string 
  59.      * @see posix_uname()
  60.      */
  61.     var $mailname = 'localhost';
  62.  
  63.     /**
  64.      * SMTP connection timeout value.  NULL indicates no timeout.
  65.      *
  66.      * @var integer 
  67.      */
  68.     var $timeout = 10;
  69.  
  70.     /**
  71.      * use either PEAR:Net_DNS or getmxrr
  72.      *
  73.      * @var boolean 
  74.      */
  75.     var $withNetDns = true;
  76.  
  77.     /**
  78.      * PEAR:Net_DNS_Resolver
  79.      *
  80.      * @var object 
  81.      */
  82.     var $resolver;
  83.  
  84.     /**
  85.      * Whether to use VERP or not. If not a boolean, the string value
  86.      * will be used as the VERP separators.
  87.      *
  88.      * @var mixed boolean or string
  89.      */
  90.     var $verp = false;
  91.  
  92.     /**
  93.      * Whether to use VRFY or not.
  94.      *
  95.      * @var boolean $vrfy 
  96.      */
  97.     var $vrfy = false;
  98.  
  99.     /**
  100.      * Switch to test mode - don't send emails for real
  101.      *
  102.      * @var boolean $debug 
  103.      */
  104.     var $test = false;
  105.  
  106.     /**
  107.      * Turn on Net_SMTP debugging?
  108.      *
  109.      * @var boolean $peardebug 
  110.      */
  111.     var $debug = false;
  112.  
  113.     /**
  114.      * internal error codes
  115.      *
  116.      * translate internal error identifier to PEAR-Error codes and human
  117.      * readable messages.
  118.      *
  119.      * @var boolean $debug 
  120.      * @todo as I need unique error-codes to identify what exactly went wrond
  121.      *        I did not use intergers as it should be. Instead I added a "namespace"
  122.      *        for each code. This avoids conflicts with error codes from different
  123.      *        classes. How can I use unique error codes and stay conform with PEAR?
  124.      */
  125.     var $errorCode = array(
  126.         'not_connected' => array(
  127.             'code'  => 1,
  128.             'msg'   => 'Could not connect to any mail server ({HOST}) at port {PORT} to send mail to {RCPT}.'
  129.         ),
  130.         'failed_vrfy_rcpt' => array(
  131.             'code'  => 2,
  132.             'msg'   => 'Recipient "{RCPT}" could not be veryfied.'
  133.         ),
  134.         'failed_set_from' => array(
  135.             'code'  => 3,
  136.             'msg'   => 'Failed to set sender: {FROM}.'
  137.         ),
  138.         'failed_set_rcpt' => array(
  139.             'code'  => 4,
  140.             'msg'   => 'Failed to set recipient: {RCPT}.'
  141.         ),
  142.         'failed_send_data' => array(
  143.             'code'  => 5,
  144.             'msg'   => 'Failed to send mail to: {RCPT}.'
  145.         ),
  146.         'no_from' => array(
  147.             'code'  => 5,
  148.             'msg'   => 'No from address has be provided.'
  149.         ),
  150.         'send_data' => array(
  151.             'code'  => 7,
  152.             'msg'   => 'Failed to create Net_SMTP object.'
  153.         ),
  154.         'no_mx' => array(
  155.             'code'  => 8,
  156.             'msg'   => 'No MX-record for {RCPT} found.'
  157.         ),
  158.         'no_resolver' => array(
  159.             'code'  => 9,
  160.             'msg'   => 'Could not start resolver! Install PEAR:Net_DNS or switch off "netdns"'
  161.         ),
  162.         'failed_rset' => array(
  163.             'code'  => 10,
  164.             'msg'   => 'RSET command failed, SMTP-connection corrupt.'
  165.         ),
  166.     );
  167.  
  168.     /**
  169.      * Constructor.
  170.      *
  171.      * Instantiates a new Mail_smtp:: object based on the parameters
  172.      * passed in. It looks for the following parameters:
  173.      *     mailname    The name of the local mail system (a valid hostname which matches the reverse lookup)
  174.      *     port        smtp-port - the default comes from getservicebyname() and should work fine
  175.      *     timeout     The SMTP connection timeout. Defaults to 30 seconds.
  176.      *     vrfy        Whether to use VRFY or not. Defaults to false.
  177.      *     verp        Whether to use VERP or not. Defaults to false.
  178.      *     test        Activate test mode? Defaults to false.
  179.      *     debug       Activate SMTP and Net_DNS debug mode? Defaults to false.
  180.      *     netdns      whether to use PEAR:Net_DNS or the PHP build in function getmxrr, default is true
  181.      *
  182.      * If a parameter is present in the $params array, it replaces the
  183.      * default.
  184.      *
  185.      * @access public
  186.      * @param array Hash containing any parameters different from the
  187.      *               defaults.
  188.      * @see _Mail_smtpmx()
  189.      */
  190.     function __construct($params)
  191.     {
  192.         if (isset($params['mailname'])) {
  193.             $this->mailname = $params['mailname'];
  194.         else {
  195.             // try to find a valid mailname
  196.             if (function_exists('posix_uname')) {
  197.                 $uname posix_uname();
  198.                 $this->mailname = $uname['nodename'];
  199.             }
  200.         }
  201.  
  202.         // port number
  203.         if (isset($params['port'])) {
  204.             $this->_port $params['port'];
  205.         else {
  206.             $this->_port getservbyname('smtp''tcp');
  207.         }
  208.  
  209.         if (isset($params['timeout'])) $this->timeout = $params['timeout'];
  210.         if (isset($params['verp'])) $this->verp = $params['verp'];
  211.         if (isset($params['test'])) $this->test = $params['test'];
  212.         if (isset($params['peardebug'])) $this->test = $params['peardebug'];
  213.         if (isset($params['netdns'])) $this->withNetDns = $params['netdns'];
  214.     }
  215.  
  216.     /**
  217.      * Constructor wrapper for PHP4
  218.      *
  219.      * @access public
  220.      * @param array Hash containing any parameters different from the defaults
  221.      * @see __construct()
  222.      */
  223.     function Mail_smtpmx($params)
  224.     {
  225.         $this->__construct($params);
  226.         register_shutdown_function(array(&$this'__destruct'));
  227.     }
  228.  
  229.     /**
  230.      * Destructor implementation to ensure that we disconnect from any
  231.      * potentially-alive persistent SMTP connections.
  232.      */
  233.     function __destruct()
  234.     {
  235.         if (is_object($this->_smtp)) {
  236.             $this->_smtp->disconnect();
  237.             $this->_smtp = null;
  238.         }
  239.     }
  240.  
  241.     /**
  242.      * Implements Mail::send() function using SMTP direct delivery
  243.      *
  244.      * @access public
  245.      * @param mixed $recipients in RFC822 style or array
  246.      * @param array $headers The array of headers to send with the mail.
  247.      * @param string $body The full text of the message body,
  248.      * @return mixed Returns true on success, or a PEAR_Error
  249.      */
  250.     function send($recipients$headers$body)
  251.     {
  252.         if (!is_array($headers)) {
  253.             return PEAR::raiseError('$headers must be an array');
  254.         }
  255.  
  256.         $result $this->_sanitizeHeaders($headers);
  257.         if (is_a($result'PEAR_Error')) {
  258.             return $result;
  259.         }
  260.  
  261.         // Prepare headers
  262.         $headerElements $this->prepareHeaders($headers);
  263.         if (is_a($headerElements'PEAR_Error')) {
  264.             return $headerElements;
  265.         }
  266.         list($from$textHeaders$headerElements;
  267.  
  268.         // use 'Return-Path' if possible
  269.         if (!empty($headers['Return-Path'])) {
  270.             $from $headers['Return-Path'];
  271.         }
  272.         if (!isset($from)) {
  273.             return $this->_raiseError('no_from');
  274.         }
  275.  
  276.         // Prepare recipients
  277.         $recipients $this->parseRecipients($recipients);
  278.         if (is_a($recipients'PEAR_Error')) {
  279.             return $recipients;
  280.         }
  281.  
  282.         foreach ($recipients as $rcpt{
  283.             list($user$hostexplode('@'$rcpt);
  284.  
  285.             $mx $this->_getMx($host);
  286.             if (is_a($mx'PEAR_Error')) {
  287.                 return $mx;
  288.             }
  289.  
  290.             if (empty($mx)) {
  291.                 $info = array('rcpt' => $rcpt);
  292.                 return $this->_raiseError('no_mx'$info);
  293.             }
  294.  
  295.             $connected = false;
  296.             foreach ($mx as $mserver => $mpriority{
  297.                 $this->_smtp = new Net_SMTP($mserver$this->port$this->mailname);
  298.  
  299.                 // configure the SMTP connection.
  300.                 if ($this->debug{
  301.                     $this->_smtp->setDebug(true);
  302.                 }
  303.  
  304.                 // attempt to connect to the configured SMTP server.
  305.                 $res $this->_smtp->connect($this->timeout);
  306.                 if (is_a($res'PEAR_Error')) {
  307.                     $this->_smtp = null;
  308.                     continue;
  309.                 }
  310.  
  311.                 // connection established
  312.                 if ($res{
  313.                     $connected = true;
  314.                     break;
  315.                 }
  316.             }
  317.  
  318.             if (!$connected{
  319.                 $info = array(
  320.                     'host' => implode(', 'array_keys($mx)),
  321.                     'port' => $this->port,
  322.                     'rcpt' => $rcpt,
  323.                 );
  324.                 return $this->_raiseError('not_connected'$info);
  325.             }
  326.  
  327.             // Verify recipient
  328.             if ($this->vrfy{
  329.                 $res $this->_smtp->vrfy($rcpt);
  330.                 if (is_a($res'PEAR_Error')) {
  331.                     $info = array('rcpt' => $rcpt);
  332.                     return $this->_raiseError('failed_vrfy_rcpt'$info);
  333.                 }
  334.             }
  335.  
  336.             // mail from:
  337.             $args['verp'$this->verp;
  338.             $res $this->_smtp->mailFrom($from$args);
  339.             if (is_a($res'PEAR_Error')) {
  340.                 $info = array('from' => $from);
  341.                 return $this->_raiseError('failed_set_from'$info);
  342.             }
  343.  
  344.             // rcpt to:
  345.             $res $this->_smtp->rcptTo($rcpt);
  346.             if (is_a($res'PEAR_Error')) {
  347.                 $info = array('rcpt' => $rcpt);
  348.                 return $this->_raiseError('failed_set_rcpt'$info);
  349.             }
  350.  
  351.             // Don't send anything in test mode
  352.             if ($this->test{
  353.                 $result $this->_smtp->rset();
  354.                 $res $this->_smtp->rset();
  355.                 if (is_a($res'PEAR_Error')) {
  356.                     return $this->_raiseError('failed_rset');
  357.                 }
  358.  
  359.                 $this->_smtp->disconnect();
  360.                 $this->_smtp = null;
  361.                 return true;
  362.             }
  363.  
  364.             // Send data
  365.             $res $this->_smtp->data("$textHeaders\r\n$body");
  366.             if (is_a($res'PEAR_Error')) {
  367.                 $info = array('rcpt' => $rcpt);
  368.                 return $this->_raiseError('failed_send_data'$info);
  369.             }
  370.  
  371.             $this->_smtp->disconnect();
  372.             $this->_smtp = null;
  373.         }
  374.  
  375.         return true;
  376.     }
  377.  
  378.     /**
  379.      * Recieve mx rexords for a spciefied host
  380.      *
  381.      * The MX records
  382.      *
  383.      * @access private
  384.      * @param string $host mail host
  385.      * @return mixed sorted
  386.      */
  387.     function _getMx($host)
  388.     {
  389.         $mx = array();
  390.  
  391.         if ($this->withNetDns{
  392.             $res $this->_loadNetDns();
  393.             if (is_a($res'PEAR_Error')) {
  394.                 return $res;
  395.             }
  396.  
  397.             $response $this->resolver->query($host'MX');
  398.             if (!$response{
  399.                 return false;
  400.             }
  401.  
  402.             foreach ($response->answer as $rr{
  403.                 if ($rr->type == 'MX'{
  404.                     $mx[$rr->exchange$rr->preference;
  405.                 }
  406.             }
  407.         else {
  408.             $mxHost = array();
  409.             $mxWeight = array();
  410.  
  411.             if (!getmxrr($host$mxHost$mxWeight)) {
  412.                 return false;
  413.             }
  414.             for ($i = 0; $i count($mxHost); ++$i{
  415.                 $mx[$mxHost[$i]] $mxWeight[$i];
  416.             }
  417.         }
  418.  
  419.         asort($mx);
  420.         return $mx;
  421.     }
  422.  
  423.     /**
  424.      * initialize PEAR:Net_DNS_Resolver
  425.      *
  426.      * @access private
  427.      * @return boolean true on success
  428.      */
  429.     function _loadNetDns()
  430.     {
  431.         if (is_object($this->resolver)) {
  432.             return true;
  433.         }
  434.  
  435.         if (!include_once 'Net/DNS.php'{
  436.             return $this->_raiseError('no_resolver');
  437.         }
  438.  
  439.         $this->resolver = new Net_DNS_Resolver();
  440.         if ($this->debug{
  441.             $this->resolver->test = 1;
  442.         }
  443.  
  444.         return true;
  445.     }
  446.  
  447.     /**
  448.      * raise standardized error
  449.      *
  450.      * include additional information in error message
  451.      *
  452.      * @access private
  453.      * @param string $id maps error ids to codes and message
  454.      * @param array $info optional information in associative array
  455.      * @see _errorCode
  456.      */
  457.     function _raiseError($id$info = array())
  458.     {
  459.         $code $this->errorCode[$id]['code'];
  460.         $msg $this->errorCode[$id]['msg'];
  461.  
  462.         // include info to messages
  463.         if (!empty($info)) {
  464.             $search = array();
  465.             $replace = array();
  466.  
  467.             foreach ($info as $key => $value{
  468.                 array_push($search'{' strtoupper($key'}');
  469.                 array_push($replace$value);
  470.             }
  471.  
  472.             $msg str_replace($search$replace$msg);
  473.         }
  474.  
  475.         return PEAR::raiseError($msg$code);
  476.     }
  477.  
  478. }

Documentation generated on Fri, 15 May 2009 01:30:05 +0000 by phpDocumentor 1.4.2. PEAR Logo Copyright © PHP Group 2004.