SmtpTransport.php 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. <?php
  2. /**
  3. * Send mail using SMTP protocol
  4. *
  5. * PHP 5
  6. *
  7. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  8. * Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
  9. *
  10. * Licensed under The MIT License
  11. * Redistributions of files must retain the above copyright notice.
  12. *
  13. * @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
  14. * @link http://cakephp.org CakePHP(tm) Project
  15. * @package Cake.Network.Email
  16. * @since CakePHP(tm) v 2.0.0
  17. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  18. */
  19. App::uses('CakeSocket', 'Network');
  20. /**
  21. * Send mail using SMTP protocol
  22. *
  23. * @package Cake.Network.Email
  24. */
  25. class SmtpTransport extends AbstractTransport {
  26. /**
  27. * Socket to SMTP server
  28. *
  29. * @var CakeSocket
  30. */
  31. protected $_socket;
  32. /**
  33. * CakeEmail
  34. *
  35. * @var CakeEmail
  36. */
  37. protected $_cakeEmail;
  38. /**
  39. * Content of email to return
  40. *
  41. * @var string
  42. */
  43. protected $_content;
  44. /**
  45. * Send mail
  46. *
  47. * @param CakeEmail $email CakeEmail
  48. * @return array
  49. * @throws SocketException
  50. */
  51. public function send(CakeEmail $email) {
  52. $this->_cakeEmail = $email;
  53. $this->_connect();
  54. $this->_auth();
  55. $this->_sendRcpt();
  56. $this->_sendData();
  57. $this->_disconnect();
  58. return $this->_content;
  59. }
  60. /**
  61. * Set the configuration
  62. *
  63. * @param array $config
  64. * @return void
  65. */
  66. public function config($config = array()) {
  67. $default = array(
  68. 'host' => 'localhost',
  69. 'port' => 25,
  70. 'timeout' => 30,
  71. 'username' => null,
  72. 'password' => null,
  73. 'client' => null,
  74. 'tls' => false
  75. );
  76. $this->_config = $config + $default;
  77. }
  78. /**
  79. * Connect to SMTP Server
  80. *
  81. * @return void
  82. * @throws SocketException
  83. */
  84. protected function _connect() {
  85. $this->_generateSocket();
  86. if (!$this->_socket->connect()) {
  87. throw new SocketException(__d('cake_dev', 'Unable to connect to SMTP server.'));
  88. }
  89. $this->_smtpSend(null, '220');
  90. if (isset($this->_config['client'])) {
  91. $host = $this->_config['client'];
  92. } elseif ($httpHost = env('HTTP_HOST')) {
  93. list($host) = explode(':', $httpHost);
  94. } else {
  95. $host = 'localhost';
  96. }
  97. try {
  98. $this->_smtpSend("EHLO {$host}", '250');
  99. if ($this->_config['tls']) {
  100. $this->_smtpSend("STARTTLS", '220');
  101. $this->_socket->enableCrypto('tls');
  102. $this->_smtpSend("EHLO {$host}", '250');
  103. }
  104. } catch (SocketException $e) {
  105. if ($this->_config['tls']) {
  106. throw new SocketException(__d('cake_dev', 'SMTP server did not accept the connection or trying to connect to non TLS SMTP server using TLS.'));
  107. }
  108. try {
  109. $this->_smtpSend("HELO {$host}", '250');
  110. } catch (SocketException $e2) {
  111. throw new SocketException(__d('cake_dev', 'SMTP server did not accept the connection.'));
  112. }
  113. }
  114. }
  115. /**
  116. * Send authentication
  117. *
  118. * @return void
  119. * @throws SocketException
  120. */
  121. protected function _auth() {
  122. if (isset($this->_config['username']) && isset($this->_config['password'])) {
  123. $authRequired = $this->_smtpSend('AUTH LOGIN', '334|503');
  124. if ($authRequired == '334') {
  125. if (!$this->_smtpSend(base64_encode($this->_config['username']), '334')) {
  126. throw new SocketException(__d('cake_dev', 'SMTP server did not accept the username.'));
  127. }
  128. if (!$this->_smtpSend(base64_encode($this->_config['password']), '235')) {
  129. throw new SocketException(__d('cake_dev', 'SMTP server did not accept the password.'));
  130. }
  131. } elseif ($authRequired == '504') {
  132. throw new SocketException(__d('cake_dev', 'SMTP authentication method not allowed, check if SMTP server requires TLS'));
  133. } elseif ($authRequired != '503') {
  134. throw new SocketException(__d('cake_dev', 'SMTP does not require authentication.'));
  135. }
  136. }
  137. }
  138. /**
  139. * Send emails
  140. *
  141. * @return void
  142. * @throws SocketException
  143. */
  144. protected function _sendRcpt() {
  145. $from = $this->_cakeEmail->from();
  146. $this->_smtpSend('MAIL FROM:<' . key($from) . '>');
  147. $to = $this->_cakeEmail->to();
  148. $cc = $this->_cakeEmail->cc();
  149. $bcc = $this->_cakeEmail->bcc();
  150. $emails = array_merge(array_keys($to), array_keys($cc), array_keys($bcc));
  151. foreach ($emails as $email) {
  152. $this->_smtpSend('RCPT TO:<' . $email . '>');
  153. }
  154. }
  155. /**
  156. * Send Data
  157. *
  158. * @return void
  159. * @throws SocketException
  160. */
  161. protected function _sendData() {
  162. $this->_smtpSend('DATA', '354');
  163. $headers = $this->_cakeEmail->getHeaders(array('from', 'sender', 'replyTo', 'readReceipt', 'returnPath', 'to', 'cc', 'subject'));
  164. $headers = $this->_headersToString($headers);
  165. $lines = $this->_cakeEmail->message();
  166. $messages = array();
  167. foreach ($lines as $line) {
  168. if ((!empty($line)) && ($line[0] === '.')) {
  169. $messages[] = '.' . $line;
  170. } else {
  171. $messages[] = $line;
  172. }
  173. }
  174. $message = implode("\r\n", $messages);
  175. $this->_smtpSend($headers . "\r\n\r\n" . $message . "\r\n\r\n\r\n.");
  176. $this->_content = array('headers' => $headers, 'message' => $message);
  177. }
  178. /**
  179. * Disconnect
  180. *
  181. * @return void
  182. * @throws SocketException
  183. */
  184. protected function _disconnect() {
  185. $this->_smtpSend('QUIT', false);
  186. $this->_socket->disconnect();
  187. }
  188. /**
  189. * Helper method to generate socket
  190. *
  191. * @return void
  192. * @throws SocketException
  193. */
  194. protected function _generateSocket() {
  195. $this->_socket = new CakeSocket($this->_config);
  196. }
  197. /**
  198. * Protected method for sending data to SMTP connection
  199. *
  200. * @param string $data data to be sent to SMTP server
  201. * @param string|boolean $checkCode code to check for in server response, false to skip
  202. * @return void
  203. * @throws SocketException
  204. */
  205. protected function _smtpSend($data, $checkCode = '250') {
  206. if (!is_null($data)) {
  207. $this->_socket->write($data . "\r\n");
  208. }
  209. while ($checkCode !== false) {
  210. $response = '';
  211. $startTime = time();
  212. while (substr($response, -2) !== "\r\n" && ((time() - $startTime) < $this->_config['timeout'])) {
  213. $response .= $this->_socket->read();
  214. }
  215. if (substr($response, -2) !== "\r\n") {
  216. throw new SocketException(__d('cake_dev', 'SMTP timeout.'));
  217. }
  218. $responseLines = explode("\r\n", rtrim($response, "\r\n"));
  219. $response = end($responseLines);
  220. if (preg_match('/^(' . $checkCode . ')(.)/', $response, $code)) {
  221. if ($code[2] === '-') {
  222. continue;
  223. }
  224. return $code[1];
  225. }
  226. throw new SocketException(__d('cake_dev', 'SMTP Error: %s', $response));
  227. }
  228. }
  229. }