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   * All rights reserved.
   5   *
   6   * Redistribution and use in source and binary forms, with or without
   7   * modification, are permitted provided that the following conditions
   8   * are met:
   9   *
  10   * o Redistributions of source code must retain the above copyright
  11   *   notice, this list of conditions and the following disclaimer.
  12   * o Redistributions in binary form must reproduce the above copyright
  13   *   notice, this list of conditions and the following disclaimer in the
  14   *   documentation and/or other materials provided with the distribution.
  15   * o The names of the authors may not be used to endorse or promote
  16   *   products derived from this software without specific prior written
  17   *   permission.
  18   *
  19   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  20   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  21   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  22   * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  23   * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  24   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  25   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  26   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  27   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  28   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  29   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30   *
  31   * @category  Horde
  32   * @copyright 2010-2017 Horde LLC
  33   * @license   http://www.horde.org/licenses/bsd New BSD License
  34   * @package   Mail
  35   */
  36  
  37  /**
  38   * SMTP implementation.
  39   *
  40   * @author     Chuck Hagenbuch <chuck@horde.org>
  41   * @author     Jon Parise <jon@php.net>
  42   * @author     Michael Slusarz <slusarz@horde.org>
  43   * @category   Horde
  44   * @copyright  2010-2016 Horde LLC
  45   * @deprecated Use Horde_Mail_Transport_Hordesmtp instead
  46   * @license    http://www.horde.org/licenses/bsd New BSD License
  47   * @package    Mail
  48   */
  49  class Horde_Mail_Transport_Smtp extends Horde_Mail_Transport
  50  {
  51      /* Error: Failed to create a Net_SMTP object */
  52      const ERROR_CREATE = 10000;
  53  
  54      /* Error: Failed to connect to SMTP server */
  55      const ERROR_CONNECT = 10001;
  56  
  57      /* Error: SMTP authentication failure */
  58      const ERROR_AUTH = 10002;
  59  
  60      /* Error: No From: address has been provided */
  61      const ERROR_FROM = 10003;
  62  
  63      /* Error: Failed to set sender */
  64      const ERROR_SENDER = 10004;
  65  
  66      /* Error: Failed to add recipient */
  67      const ERROR_RECIPIENT = 10005;
  68  
  69      /* Error: Failed to send data */
  70      const ERROR_DATA = 10006;
  71  
  72      /**
  73       * The SMTP greeting.
  74       *
  75       * @var string
  76       */
  77      public $greeting = null;
  78  
  79      /**
  80       * The SMTP queued response.
  81       *
  82       * @var string
  83       */
  84      public $queuedAs = null;
  85  
  86      /**
  87       * SMTP connection object.
  88       *
  89       * @var Net_SMTP
  90       */
  91      protected $_smtp = null;
  92  
  93      /**
  94       * The list of service extension parameters to pass to the Net_SMTP
  95       * mailFrom() command.
  96       *
  97       * @var array
  98       */
  99      protected $_extparams = array();
 100  
 101      /**
 102       * Constructor.
 103       *
 104       * @param array $params  Additional parameters:
 105       *   - auth: (mixed) SMTP authentication.
 106       *           This value may be set to true, false or the name of a
 107       *           specific authentication method. If the value is set to true,
 108       *           the Net_SMTP package will attempt to use the best
 109       *           authentication method advertised by the remote SMTP server.
 110       *           DEFAULT: false.
 111       *   - debug: (boolean) Activate SMTP debug mode?
 112       *            DEFAULT: false
 113       *   - host: (string) The server to connect to.
 114       *           DEFAULT: localhost
 115       *   - localhost: (string) Hostname or domain that will be sent to the
 116       *                remote SMTP server in the HELO / EHLO message.
 117       *                DEFAULT: localhost
 118       *   - password: (string) The password to use for SMTP auth.
 119       *               DEFAULT: NONE
 120       *   - persist: (boolean) Should the SMTP connection persist?
 121       *              DEFAULT: false
 122       *   - pipelining: (boolean) Use SMTP command pipelining.
 123       *                 Use SMTP command pipelining (specified in RFC 2920) if
 124       *                 the SMTP server supports it. This speeds up delivery
 125       *                 over high-latency connections.
 126       *                 DEFAULT: false (use default value from Net_SMTP)
 127       *   - port: (integer) The port to connect to.
 128       *           DEFAULT: 25
 129       *   - timeout: (integer) The SMTP connection timeout.
 130       *              DEFAULT: NONE
 131       *   - username: (string) The username to use for SMTP auth.
 132       *               DEFAULT: NONE
 133       */
 134      public function __construct(array $params = array())
 135      {
 136          $this->_params = array_merge(array(
 137              'auth' => false,
 138              'debug' => false,
 139              'host' => 'localhost',
 140              'localhost' => 'localhost',
 141              'password' => '',
 142              'persist' => false,
 143              'pipelining' => false,
 144              'port' => 25,
 145              'timeout' => null,
 146              'username' => ''
 147          ), $params);
 148  
 149          /* Destructor implementation to ensure that we disconnect from any
 150           * potentially-alive persistent SMTP connections. */
 151          register_shutdown_function(array($this, 'disconnect'));
 152  
 153          /* SMTP requires CRLF line endings. */
 154          $this->sep = "\r\n";
 155      }
 156  
 157      /**
 158       */
 159      public function send($recipients, array $headers, $body)
 160      {
 161          /* If we don't already have an SMTP object, create one. */
 162          $this->getSMTPObject();
 163  
 164          $headers = $this->_sanitizeHeaders($headers);
 165  
 166          /* Make sure the message has a trailing newline. */
 167          if (is_resource($body)) {
 168              fseek($body, -1, SEEK_END);
 169              switch (fgetc($body)) {
 170              case "\r":
 171                  if (fgetc($body) != "\n") {
 172                      fputs($body, "\n");
 173                  }
 174                  break;
 175  
 176              default:
 177                  fputs($body, "\r\n");
 178                  break;
 179              }
 180              rewind($body);
 181          } elseif (substr($body, -2, 0) != "\r\n") {
 182              $body .= "\r\n";
 183          }
 184  
 185          try {
 186              list($from, $textHeaders) = $this->prepareHeaders($headers);
 187          } catch (Horde_Mail_Exception $e) {
 188              $this->_smtp->rset();
 189              throw $e;
 190          }
 191  
 192          try {
 193              $from = $this->_getFrom($from, $headers);
 194          } catch (Horde_Mail_Exception $e) {
 195              $this->_smtp->rset();
 196              throw new Horde_Mail_Exception('No From: address has been provided', self::ERROR_FROM);
 197          }
 198  
 199          $params = '';
 200          foreach ($this->_extparams as $key => $val) {
 201              $params .= ' ' . $key . (is_null($val) ? '' : '=' . $val);
 202          }
 203  
 204          $res = $this->_smtp->mailFrom($from, ltrim($params));
 205          if ($res instanceof PEAR_Error) {
 206              $this->_error(sprintf("Failed to set sender: %s", $from), $res, self::ERROR_SENDER);
 207          }
 208  
 209          try {
 210              $recipients = $this->parseRecipients($recipients);
 211          } catch (Horde_Mail_Exception $e) {
 212              $this->_smtp->rset();
 213              throw $e;
 214          }
 215  
 216          foreach ($recipients as $recipient) {
 217              $res = $this->_smtp->rcptTo($recipient);
 218              if ($res instanceof PEAR_Error) {
 219                  $this->_error("Failed to add recipient: $recipient", $res, self::ERROR_RECIPIENT);
 220              }
 221          }
 222  
 223          /* Send the message's headers and the body as SMTP data. Net_SMTP does
 224           * the necessary EOL conversions. */
 225          $res = $this->_smtp->data($body, $textHeaders);
 226          list(,$args) = $this->_smtp->getResponse();
 227  
 228          if (preg_match("/Ok: queued as (.*)/", $args, $queued)) {
 229              $this->queuedAs = $queued[1];
 230          }
 231  
 232          /* We need the greeting; from it we can extract the authorative name
 233           * of the mail server we've really connected to. Ideal if we're
 234           * connecting to a round-robin of relay servers and need to track
 235           * which exact one took the email */
 236          $this->greeting = $this->_smtp->getGreeting();
 237  
 238          if ($res instanceof PEAR_Error) {
 239              $this->_error('Failed to send data', $res, self::ERROR_DATA);
 240          }
 241  
 242          /* If persistent connections are disabled, destroy our SMTP object. */
 243          if (!$this->_params['persist']) {
 244              $this->disconnect();
 245          }
 246      }
 247  
 248      /**
 249       * Connect to the SMTP server by instantiating a Net_SMTP object.
 250       *
 251       * @return Net_SMTP  The SMTP object.
 252       * @throws Horde_Mail_Exception
 253       */
 254      public function getSMTPObject()
 255      {
 256          if ($this->_smtp) {
 257              return $this->_smtp;
 258          }
 259  
 260          $this->_smtp = new Net_SMTP(
 261              $this->_params['host'],
 262              $this->_params['port'],
 263              $this->_params['localhost']
 264          );
 265  
 266          /* Set pipelining. */
 267          if ($this->_params['pipelining']) {
 268              $this->_smtp->pipelining = true;
 269          }
 270  
 271          /* If we still don't have an SMTP object at this point, fail. */
 272          if (!($this->_smtp instanceof Net_SMTP)) {
 273              throw new Horde_Mail_Exception('Failed to create a Net_SMTP object', self::ERROR_CREATE);
 274          }
 275  
 276          /* Configure the SMTP connection. */
 277          if ($this->_params['debug']) {
 278              $this->_smtp->setDebug(true);
 279          }
 280  
 281          /* Attempt to connect to the configured SMTP server. */
 282          $res = $this->_smtp->connect($this->_params['timeout']);
 283          if ($res instanceof PEAR_Error) {
 284              $this->_error('Failed to connect to ' . $this->_params['host'] . ':' . $this->_params['port'], $res, self::ERROR_CONNECT);
 285          }
 286  
 287          /* Attempt to authenticate if authentication has been enabled. */
 288          if ($this->_params['auth']) {
 289              $method = is_string($this->_params['auth'])
 290                  ? $this->_params['auth']
 291                  : '';
 292  
 293              $res = $this->_smtp->auth($this->_params['username'], $this->_params['password'], $method);
 294              if ($res instanceof PEAR_Error) {
 295                  $this->_error("$method authentication failure", $res, self::ERROR_AUTH);
 296              }
 297          }
 298  
 299          return $this->_smtp;
 300      }
 301  
 302      /**
 303       * Add parameter associated with a SMTP service extension.
 304       *
 305       * @param string $keyword  Extension keyword.
 306       * @param string $value    Any value the keyword needs.
 307       */
 308      public function addServiceExtensionParameter($keyword, $value = null)
 309      {
 310          $this->_extparams[$keyword] = $value;
 311      }
 312  
 313      /**
 314       * Disconnect and destroy the current SMTP connection.
 315       *
 316       * @return boolean True if the SMTP connection no longer exists.
 317       */
 318      public function disconnect()
 319      {
 320          /* If we have an SMTP object, disconnect and destroy it. */
 321          if (is_object($this->_smtp) && $this->_smtp->disconnect()) {
 322              $this->_smtp = null;
 323          }
 324  
 325          /* We are disconnected if we no longer have an SMTP object. */
 326          return ($this->_smtp === null);
 327      }
 328  
 329      /**
 330       * Build a standardized string describing the current SMTP error.
 331       *
 332       * @param string $text       Custom string describing the error context.
 333       * @param PEAR_Error $error  PEAR_Error object.
 334       * @param integer $e_code    Error code.
 335       *
 336       * @throws Horde_Mail_Exception
 337       */
 338      protected function _error($text, $error, $e_code)
 339      {
 340          /* Split the SMTP response into a code and a response string. */
 341          list($code, $response) = $this->_smtp->getResponse();
 342  
 343          /* Abort current SMTP transaction. */
 344          $this->_smtp->rset();
 345  
 346          /* Build our standardized error string. */
 347          throw new Horde_Mail_Exception($text . ' [SMTP: ' . $error->getMessage() . " (code: $code, response: $response)]", $e_code);
 348      }
 349  
 350  }