PageRenderTime 41ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/framework/Mail/lib/Horde/Mail/Transport/Smtp.php

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