1<?php 2/** 3 * SMTP implementation of the PEAR Mail interface. Requires the Net_SMTP class. 4 * 5 * PHP version 5 6 * 7 * LICENSE: 8 * 9 * Copyright (c) 2010-2017, Chuck Hagenbuch & Jon Parise 10 * All rights reserved. 11 * 12 * Redistribution and use in source and binary forms, with or without 13 * modification, are permitted provided that the following conditions 14 * are met: 15 * 16 * 1. Redistributions of source code must retain the above copyright 17 * notice, this list of conditions and the following disclaimer. 18 * 19 * 2. Redistributions in binary form must reproduce the above copyright 20 * notice, this list of conditions and the following disclaimer in the 21 * documentation and/or other materials provided with the distribution. 22 * 23 * 3. Neither the name of the copyright holder nor the names of its 24 * contributors may be used to endorse or promote products derived from 25 * this software without specific prior written permission. 26 * 27 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 28 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 29 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 30 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 31 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 32 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 33 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 34 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 35 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 36 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 37 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 38 * 39 * @category HTTP 40 * @package HTTP_Request 41 * @author Jon Parise <jon@php.net> 42 * @author Chuck Hagenbuch <chuck@horde.org> 43 * @copyright 2010-2017 Chuck Hagenbuch 44 * @license http://opensource.org/licenses/BSD-3-Clause New BSD License 45 * @version CVS: $Id$ 46 * @link http://pear.php.net/package/Mail/ 47 */ 48 49/** Error: Failed to create a Net_SMTP object */ 50define('PEAR_MAIL_SMTP_ERROR_CREATE', 10000); 51 52/** Error: Failed to connect to SMTP server */ 53define('PEAR_MAIL_SMTP_ERROR_CONNECT', 10001); 54 55/** Error: SMTP authentication failure */ 56define('PEAR_MAIL_SMTP_ERROR_AUTH', 10002); 57 58/** Error: No From: address has been provided */ 59define('PEAR_MAIL_SMTP_ERROR_FROM', 10003); 60 61/** Error: Failed to set sender */ 62define('PEAR_MAIL_SMTP_ERROR_SENDER', 10004); 63 64/** Error: Failed to add recipient */ 65define('PEAR_MAIL_SMTP_ERROR_RECIPIENT', 10005); 66 67/** Error: Failed to send data */ 68define('PEAR_MAIL_SMTP_ERROR_DATA', 10006); 69 70/** 71 * SMTP implementation of the PEAR Mail interface. Requires the Net_SMTP class. 72 * @access public 73 * @package Mail 74 * @version $Revision$ 75 */ 76class Mail_smtp extends Mail { 77 78 /** 79 * SMTP connection object. 80 * 81 * @var object 82 * @access private 83 */ 84 var $_smtp = null; 85 86 /** 87 * The list of service extension parameters to pass to the Net_SMTP 88 * mailFrom() command. 89 * 90 * @var array 91 */ 92 var $_extparams = array(); 93 94 /** 95 * The SMTP host to connect to. 96 * 97 * @var string 98 */ 99 var $host = 'localhost'; 100 101 /** 102 * The port the SMTP server is on. 103 * 104 * @var integer 105 */ 106 var $port = 25; 107 108 /** 109 * Should SMTP authentication be used? 110 * 111 * This value may be set to true, false or the name of a specific 112 * authentication method. 113 * 114 * If the value is set to true, the Net_SMTP package will attempt to use 115 * the best authentication method advertised by the remote SMTP server. 116 * 117 * @var mixed 118 */ 119 var $auth = false; 120 121 /** 122 * The username to use if the SMTP server requires authentication. 123 * 124 * @var string 125 */ 126 var $username = ''; 127 128 /** 129 * The password to use if the SMTP server requires authentication. 130 * 131 * @var string 132 */ 133 var $password = ''; 134 135 /** 136 * Hostname or domain that will be sent to the remote SMTP server in the 137 * HELO / EHLO message. 138 * 139 * @var string 140 */ 141 var $localhost = 'localhost'; 142 143 /** 144 * SMTP connection timeout value. NULL indicates no timeout. 145 * 146 * @var integer 147 */ 148 var $timeout = null; 149 150 /** 151 * Turn on Net_SMTP debugging? 152 * 153 * @var boolean $debug 154 */ 155 var $debug = false; 156 157 /** 158 * Indicates whether or not the SMTP connection should persist over 159 * multiple calls to the send() method. 160 * 161 * @var boolean 162 */ 163 var $persist = false; 164 165 /** 166 * Use SMTP command pipelining (specified in RFC 2920) if the SMTP server 167 * supports it. This speeds up delivery over high-latency connections. By 168 * default, use the default value supplied by Net_SMTP. 169 * 170 * @var boolean 171 */ 172 var $pipelining; 173 174 /** 175 * The list of socket options 176 * 177 * @var array 178 */ 179 var $socket_options = array(); 180 181 /** 182 * Constructor. 183 * 184 * Instantiates a new Mail_smtp:: object based on the parameters 185 * passed in. It looks for the following parameters: 186 * host The server to connect to. Defaults to localhost. 187 * port The port to connect to. Defaults to 25. 188 * auth SMTP authentication. Defaults to none. 189 * username The username to use for SMTP auth. No default. 190 * password The password to use for SMTP auth. No default. 191 * localhost The local hostname / domain. Defaults to localhost. 192 * timeout The SMTP connection timeout. Defaults to none. 193 * verp Whether to use VERP or not. Defaults to false. 194 * DEPRECATED as of 1.2.0 (use setMailParams()). 195 * debug Activate SMTP debug mode? Defaults to false. 196 * persist Should the SMTP connection persist? 197 * pipelining Use SMTP command pipelining 198 * 199 * If a parameter is present in the $params array, it replaces the 200 * default. 201 * 202 * @param array Hash containing any parameters different from the 203 * defaults. 204 */ 205 public function __construct($params) 206 { 207 if (isset($params['host'])) $this->host = $params['host']; 208 if (isset($params['port'])) $this->port = $params['port']; 209 if (isset($params['auth'])) $this->auth = $params['auth']; 210 if (isset($params['username'])) $this->username = $params['username']; 211 if (isset($params['password'])) $this->password = $params['password']; 212 if (isset($params['localhost'])) $this->localhost = $params['localhost']; 213 if (isset($params['timeout'])) $this->timeout = $params['timeout']; 214 if (isset($params['debug'])) $this->debug = (bool)$params['debug']; 215 if (isset($params['persist'])) $this->persist = (bool)$params['persist']; 216 if (isset($params['pipelining'])) $this->pipelining = (bool)$params['pipelining']; 217 if (isset($params['socket_options'])) $this->socket_options = $params['socket_options']; 218 // Deprecated options 219 if (isset($params['verp'])) { 220 $this->addServiceExtensionParameter('XVERP', is_bool($params['verp']) ? null : $params['verp']); 221 } 222 } 223 224 /** 225 * Destructor implementation to ensure that we disconnect from any 226 * potentially-alive persistent SMTP connections. 227 */ 228 public function __destruct() 229 { 230 $this->disconnect(); 231 } 232 233 /** 234 * Implements Mail::send() function using SMTP. 235 * 236 * @param mixed $recipients Either a comma-seperated list of recipients 237 * (RFC822 compliant), or an array of recipients, 238 * each RFC822 valid. This may contain recipients not 239 * specified in the headers, for Bcc:, resending 240 * messages, etc. 241 * 242 * @param array $headers The array of headers to send with the mail, in an 243 * associative array, where the array key is the 244 * header name (e.g., 'Subject'), and the array value 245 * is the header value (e.g., 'test'). The header 246 * produced from those values would be 'Subject: 247 * test'. 248 * 249 * @param string $body The full text of the message body, including any 250 * MIME parts, etc. 251 * 252 * @return mixed Returns true on success, or a PEAR_Error 253 * containing a descriptive error message on 254 * failure. 255 */ 256 public function send($recipients, $headers, $body) 257 { 258 $result = $this->send_or_fail($recipients, $headers, $body); 259 260 /* If persistent connections are disabled, destroy our SMTP object. */ 261 if ($this->persist === false) { 262 $this->disconnect(); 263 } 264 265 return $result; 266 } 267 268 protected function send_or_fail($recipients, $headers, $body) 269 { 270 /* If we don't already have an SMTP object, create one. */ 271 $result = $this->getSMTPObject(); 272 if (PEAR::isError($result)) { 273 return $result; 274 } 275 276 if (!is_array($headers)) { 277 return PEAR::raiseError('$headers must be an array'); 278 } 279 280 $this->_sanitizeHeaders($headers); 281 282 $headerElements = $this->prepareHeaders($headers); 283 if (is_a($headerElements, 'PEAR_Error')) { 284 $this->_smtp->rset(); 285 return $headerElements; 286 } 287 list($from, $textHeaders) = $headerElements; 288 289 /* Since few MTAs are going to allow this header to be forged 290 * unless it's in the MAIL FROM: exchange, we'll use 291 * Return-Path instead of From: if it's set. */ 292 if (!empty($headers['Return-Path'])) { 293 $from = $headers['Return-Path']; 294 } 295 296 if (!isset($from)) { 297 $this->_smtp->rset(); 298 return PEAR::raiseError('No From: address has been provided', 299 PEAR_MAIL_SMTP_ERROR_FROM); 300 } 301 302 $params = null; 303 if (!empty($this->_extparams)) { 304 foreach ($this->_extparams as $key => $val) { 305 $params .= ' ' . $key . (is_null($val) ? '' : '=' . $val); 306 } 307 } 308 if (PEAR::isError($res = $this->_smtp->mailFrom($from, ltrim($params)))) { 309 $error = $this->_error("Failed to set sender: $from", $res); 310 $this->_smtp->rset(); 311 return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_SENDER); 312 } 313 314 $recipients = $this->parseRecipients($recipients); 315 if (is_a($recipients, 'PEAR_Error')) { 316 $this->_smtp->rset(); 317 return $recipients; 318 } 319 320 foreach ($recipients as $recipient) { 321 $res = $this->_smtp->rcptTo($recipient); 322 if (is_a($res, 'PEAR_Error')) { 323 $error = $this->_error("Failed to add recipient: $recipient", $res); 324 $this->_smtp->rset(); 325 return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_RECIPIENT); 326 } 327 } 328 329 /* Send the message's headers and the body as SMTP data. */ 330 $res = $this->_smtp->data($body, $textHeaders); 331 list(,$args) = $this->_smtp->getResponse(); 332 333 if (preg_match("/ queued as (.*)/", $args, $queued)) { 334 $this->queued_as = $queued[1]; 335 } 336 337 /* we need the greeting; from it we can extract the authorative name of the mail server we've really connected to. 338 * ideal if we're connecting to a round-robin of relay servers and need to track which exact one took the email */ 339 $this->greeting = $this->_smtp->getGreeting(); 340 341 if (is_a($res, 'PEAR_Error')) { 342 $error = $this->_error('Failed to send data', $res); 343 $this->_smtp->rset(); 344 return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_DATA); 345 } 346 347 return true; 348 } 349 350 /** 351 * Connect to the SMTP server by instantiating a Net_SMTP object. 352 * 353 * @return mixed Returns a reference to the Net_SMTP object on success, or 354 * a PEAR_Error containing a descriptive error message on 355 * failure. 356 * 357 * @since 1.2.0 358 */ 359 public function getSMTPObject() 360 { 361 if (is_object($this->_smtp) !== false) { 362 return $this->_smtp; 363 } 364 365 include_once 'Net/SMTP.php'; 366 $this->_smtp = new Net_SMTP($this->host, 367 $this->port, 368 $this->localhost, 369 $this->pipelining, 370 0, 371 $this->socket_options); 372 373 /* If we still don't have an SMTP object at this point, fail. */ 374 if (is_object($this->_smtp) === false) { 375 return PEAR::raiseError('Failed to create a Net_SMTP object', 376 PEAR_MAIL_SMTP_ERROR_CREATE); 377 } 378 379 /* Configure the SMTP connection. */ 380 if ($this->debug) { 381 $this->_smtp->setDebug(true); 382 } 383 384 /* Attempt to connect to the configured SMTP server. */ 385 if (PEAR::isError($res = $this->_smtp->connect($this->timeout))) { 386 $error = $this->_error('Failed to connect to ' . 387 $this->host . ':' . $this->port, 388 $res); 389 return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_CONNECT); 390 } 391 392 /* Attempt to authenticate if authentication has been enabled. */ 393 if ($this->auth) { 394 $method = is_string($this->auth) ? $this->auth : ''; 395 396 if (PEAR::isError($res = $this->_smtp->auth($this->username, 397 $this->password, 398 $method))) { 399 $error = $this->_error("$method authentication failure", 400 $res); 401 $this->_smtp->rset(); 402 return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_AUTH); 403 } 404 } 405 406 return $this->_smtp; 407 } 408 409 /** 410 * Add parameter associated with a SMTP service extension. 411 * 412 * @param string Extension keyword. 413 * @param string Any value the keyword needs. 414 * 415 * @since 1.2.0 416 */ 417 public function addServiceExtensionParameter($keyword, $value = null) 418 { 419 $this->_extparams[$keyword] = $value; 420 } 421 422 /** 423 * Disconnect and destroy the current SMTP connection. 424 * 425 * @return boolean True if the SMTP connection no longer exists. 426 * 427 * @since 1.1.9 428 */ 429 public function disconnect() 430 { 431 /* If we have an SMTP object, disconnect and destroy it. */ 432 if (is_object($this->_smtp) && $this->_smtp->disconnect()) { 433 $this->_smtp = null; 434 } 435 436 /* We are disconnected if we no longer have an SMTP object. */ 437 return ($this->_smtp === null); 438 } 439 440 /** 441 * Build a standardized string describing the current SMTP error. 442 * 443 * @param string $text Custom string describing the error context. 444 * @param object $error Reference to the current PEAR_Error object. 445 * 446 * @return string A string describing the current SMTP error. 447 * 448 * @since 1.1.7 449 */ 450 protected function _error($text, $error) 451 { 452 /* Split the SMTP response into a code and a response string. */ 453 list($code, $response) = $this->_smtp->getResponse(); 454 455 /* Build our standardized error string. */ 456 return $text 457 . ' [SMTP: ' . $error->getMessage() 458 . " (code: $code, response: $response)]"; 459 } 460 461} 462