Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
   1  <?php
   2  /**
   3   * Copyright 2010-2017 Horde LLC (http://www.horde.org/)
   4   * Copyright (c) 2010 Gerd Schaufelberger
   5   * All rights reserved.
   6   *
   7   * Redistribution and use in source and binary forms, with or without
   8   * modification, are permitted provided that the following conditions
   9   * are met:
  10   *
  11   * o Redistributions of source code must retain the above copyright
  12   *   notice, this list of conditions and the following disclaimer.
  13   * o Redistributions in binary form must reproduce the above copyright
  14   *   notice, this list of conditions and the following disclaimer in the
  15   *   documentation and/or other materials provided with the distribution.
  16   * o The names of the authors may not be used to endorse or promote
  17   *   products derived from this software without specific prior written
  18   *   permission.
  19   *
  20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  22   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  23   * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  24   * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  25   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  26   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  27   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  28   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  29   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  30   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31   *
  32   * @category  Horde
  33   * @copyright 2010-2017 Horde LLC
  34   * @copyright 2010 Gerd Schaufelberger
  35   * @license   http://www.horde.org/licenses/bsd New BSD License
  36   * @package   Mail
  37   */
  38  
  39  /**
  40   * SMTP MX implementation.
  41   *
  42   * @author     Gerd Schaufelberger <gerd@php-tools.net>
  43   * @author     Michael Slusarz <slusarz@horde.org>
  44   * @category   Horde
  45   * @copyright  2010-2016 Horde LLC
  46   * @copyright  2010 Gerd Schaufelberger
  47   * @deprecated Use Horde_Mail_Transport_Hordesmtp instead
  48   * @license    http://www.horde.org/licenses/bsd New BSD License
  49   * @package    Mail
  50   */
  51  class Horde_Mail_Transport_Smtpmx extends Horde_Mail_Transport
  52  {
  53      /**
  54       * SMTP connection object.
  55       *
  56       * @var Net_SMTP
  57       */
  58      protected $_smtp = null;
  59  
  60      /**
  61       * Net_DNS2_Resolver object.
  62       *
  63       * @var Net_DNS2_Resolver
  64       */
  65      protected $_resolver;
  66  
  67      /**
  68       * Internal error codes.
  69       * Translate internal error identifier to human readable messages.
  70       *
  71       * @var array
  72       */
  73      protected $_errorCode = array(
  74          'not_connected' => array(
  75              'code' => 1,
  76              'msg' => 'Could not connect to any mail server ({HOST}) at port {PORT} to send mail to {RCPT}.'
  77          ),
  78          'failed_vrfy_rcpt' => array(
  79              'code' => 2,
  80              'msg' => 'Recipient "{RCPT}" could not be veryfied.'
  81          ),
  82          'failed_set_from' => array(
  83              'code' => 3,
  84              'msg' => 'Failed to set sender: {FROM}.'
  85          ),
  86          'failed_set_rcpt' => array(
  87              'code' => 4,
  88              'msg' => 'Failed to set recipient: {RCPT}.'
  89          ),
  90          'failed_send_data' => array(
  91              'code' => 5,
  92              'msg' => 'Failed to send mail to: {RCPT}.'
  93          ),
  94          'no_from' => array(
  95              'code' => 5,
  96              'msg' => 'No from address has be provided.'
  97          ),
  98          'send_data' => array(
  99              'code' => 7,
 100              'msg' => 'Failed to create Net_SMTP object.'
 101          ),
 102          'no_mx' => array(
 103              'code' => 8,
 104              'msg' => 'No MX-record for {RCPT} found.'
 105          ),
 106          'no_resolver' => array(
 107              'code' => 9,
 108              'msg' => 'Could not start resolver! Install PEAR:Net_DNS2 or switch off "netdns"'
 109          ),
 110          'failed_rset' => array(
 111              'code' => 10,
 112              'msg' => 'RSET command failed, SMTP-connection corrupt.'
 113          )
 114      );
 115  
 116      /**
 117       * @param array $params  Additional options:
 118       *   - debug: (boolean) Activate SMTP debug mode?
 119       *            DEFAULT: false
 120       *   - mailname: (string) The name of the local mail system (a valid
 121       *               hostname which matches the reverse lookup)
 122       *               DEFAULT: Auto-determined
 123       *   - netdns: (boolean) Use PEAR:Net_DNS2 (true) or the PHP builtin
 124       *             getmxrr().
 125       *             DEFAULT: true
 126       *   - port: (integer) Port.
 127       *           DEFAULT: Auto-determined
 128       *   - test: (boolean) Activate test mode?
 129       *           DEFAULT: false
 130       *   - timeout: (integer) The SMTP connection timeout (in seconds).
 131       *              DEFAULT: 10
 132       *   - verp: (boolean) Whether to use VERP.
 133       *           If not a boolean, the string value will be used as the VERP
 134       *           separators.
 135       *           DEFAULT: false
 136       *   - vrfy: (boolean) Whether to use VRFY.
 137       *           DEFAULT: false
 138       */
 139      public function __construct(array $params = array())
 140      {
 141          /* Try to find a valid mailname. */
 142          if (!isset($params['mailname']) && function_exists('posix_uname')) {
 143              $uname = posix_uname();
 144              $params['mailname'] = $uname['nodename'];
 145          }
 146  
 147          if (!isset($params['port'])) {
 148              $params['port'] = getservbyname('smtp', 'tcp');
 149          }
 150  
 151          $this->_params = array_merge(array(
 152              'debug' => false,
 153              'mailname' => 'localhost',
 154              'netdns' => true,
 155              'port' => 25,
 156              'test' => false,
 157              'timeout' => 10,
 158              'verp' => false,
 159              'vrfy' => false
 160          ), $params);
 161  
 162          /* SMTP requires CRLF line endings. */
 163          $this->sep = "\r\n";
 164      }
 165  
 166      /**
 167       * Destructor implementation to ensure that we disconnect from any
 168       * potentially-alive persistent SMTP connections.
 169       */
 170      public function __destruct()
 171      {
 172          if (is_object($this->_smtp)) {
 173              $this->_smtp->disconnect();
 174              $this->_smtp = null;
 175          }
 176      }
 177  
 178      /**
 179       */
 180      public function send($recipients, array $headers, $body)
 181      {
 182          $headers = $this->_sanitizeHeaders($headers);
 183  
 184          // Prepare headers
 185          list($from, $textHeaders) = $this->prepareHeaders($headers);
 186  
 187          try {
 188              $from = $this->_getFrom($from, $headers);
 189          } catch (Horde_Mail_Exception $e) {
 190              $this->_error('no_from');
 191          }
 192  
 193          // Prepare recipients
 194          foreach ($this->parseRecipients($recipients) as $rcpt) {
 195              list(,$host) = explode('@', $rcpt);
 196  
 197              $mx = $this->_getMx($host);
 198              if (!$mx) {
 199                  $this->_error('no_mx', array('rcpt' => $rcpt));
 200              }
 201  
 202              $connected = false;
 203              foreach (array_keys($mx) as $mserver) {
 204                  $this->_smtp = new Net_SMTP($mserver, $this->_params['port'], $this->_params['mailname']);
 205  
 206                  // configure the SMTP connection.
 207                  if ($this->_params['debug']) {
 208                      $this->_smtp->setDebug(true);
 209                  }
 210  
 211                  // attempt to connect to the configured SMTP server.
 212                  $res = $this->_smtp->connect($this->_params['timeout']);
 213                  if ($res instanceof PEAR_Error) {
 214                      $this->_smtp = null;
 215                      continue;
 216                  }
 217  
 218                  // connection established
 219                  if ($res) {
 220                      $connected = true;
 221                      break;
 222                  }
 223              }
 224  
 225              if (!$connected) {
 226                  $this->_error('not_connected', array(
 227                      'host' => implode(', ', array_keys($mx)),
 228                      'port' => $this->_params['port'],
 229                      'rcpt' => $rcpt
 230                  ));
 231              }
 232  
 233              // Verify recipient
 234              if ($this->_params['vrfy']) {
 235                  $res = $this->_smtp->vrfy($rcpt);
 236                  if ($res instanceof PEAR_Error) {
 237                      $this->_error('failed_vrfy_rcpt', array('rcpt' => $rcpt));
 238                  }
 239              }
 240  
 241              // mail from:
 242              $args['verp'] = $this->_params['verp'];
 243              $res = $this->_smtp->mailFrom($from, $args);
 244              if ($res instanceof PEAR_Error) {
 245                  $this->_error('failed_set_from', array('from' => $from));
 246              }
 247  
 248              // rcpt to:
 249              $res = $this->_smtp->rcptTo($rcpt);
 250              if ($res instanceof PEAR_Error) {
 251                  $this->_error('failed_set_rcpt', array('rcpt' => $rcpt));
 252              }
 253  
 254              // Don't send anything in test mode
 255              if ($this->_params['test']) {
 256                  $res = $this->_smtp->rset();
 257                  if ($res instanceof PEAR_Error) {
 258                      $this->_error('failed_rset');
 259                  }
 260  
 261                  $this->_smtp->disconnect();
 262                  $this->_smtp = null;
 263                  return;
 264              }
 265  
 266              // Send data. Net_SMTP does necessary EOL conversions.
 267              $res = $this->_smtp->data($body, $textHeaders);
 268              if ($res instanceof PEAR_Error) {
 269                  $this->_error('failed_send_data', array('rcpt' => $rcpt));
 270              }
 271  
 272              $this->_smtp->disconnect();
 273              $this->_smtp = null;
 274          }
 275      }
 276  
 277      /**
 278       * Recieve MX records for a host.
 279       *
 280       * @param string $host  Mail host.
 281       *
 282       * @return mixed  Sorted MX list or false on error.
 283       */
 284      protected function _getMx($host)
 285      {
 286          $mx = array();
 287  
 288          if ($this->params['netdns']) {
 289              $this->_loadNetDns();
 290  
 291              try {
 292                  $response = $this->_resolver->query($host, 'MX');
 293                  if (!$response) {
 294                      return false;
 295                  }
 296              } catch (Exception $e) {
 297                  throw new Horde_Mail_Exception($e);
 298              }
 299  
 300              foreach ($response->answer as $rr) {
 301                  if ($rr->type == 'MX') {
 302                      $mx[$rr->exchange] = $rr->preference;
 303                  }
 304              }
 305          } else {
 306              $mxHost = $mxWeight = array();
 307  
 308              if (!getmxrr($host, $mxHost, $mxWeight)) {
 309                  return false;
 310              }
 311  
 312              for ($i = 0; $i < count($mxHost); ++$i) {
 313                  $mx[$mxHost[$i]] = $mxWeight[$i];
 314              }
 315          }
 316  
 317          asort($mx);
 318  
 319          return $mx;
 320      }
 321  
 322      /**
 323       * Initialize Net_DNS2_Resolver.
 324       */
 325      protected function _loadNetDns()
 326      {
 327          if (!$this->_resolver) {
 328              if (!class_exists('Net_DNS2_Resolver')) {
 329                  $this->_error('no_resolver');
 330              }
 331              $this->_resolver = new Net_DNS2_Resolver();
 332          }
 333      }
 334  
 335      /**
 336       * Format error message.
 337       *
 338       * @param string $id   Maps error ids to codes and message.
 339       * @param array $info  Optional information in associative array.
 340       *
 341       * @throws Horde_Mail_Exception
 342       */
 343      protected function _error($id, $info = array())
 344      {
 345          $msg = $this->_errorCode[$id]['msg'];
 346  
 347          // include info to messages
 348          if (!empty($info)) {
 349              $replace = $search = array();
 350  
 351              foreach ($info as $key => $value) {
 352                  $search[] = '{' . Horde_String::upper($key) . '}';
 353                  $replace[] = $value;
 354              }
 355  
 356              $msg = str_replace($search, $replace, $msg);
 357          }
 358  
 359          throw new Horde_Mail_Exception($msg, $this->_errorCode[$id]['code']);
 360      }
 361  
 362  }