smtp.php 15 KB

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