1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17:
18:
19: App::uses('CakeSocket', 'Network');
20:
21: 22: 23: 24: 25:
26: class SmtpTransport extends AbstractTransport {
27:
28: 29: 30: 31: 32:
33: protected $_socket;
34:
35: 36: 37: 38: 39:
40: protected $_cakeEmail;
41:
42: 43: 44: 45: 46:
47: protected $_content;
48:
49: 50: 51: 52: 53:
54: protected $_lastResponse = array();
55:
56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80:
81: public function getLastResponse() {
82: return $this->_lastResponse;
83: }
84:
85: 86: 87: 88: 89: 90: 91:
92: public function send(CakeEmail $email) {
93: $this->_cakeEmail = $email;
94:
95: $this->_connect();
96: $this->_auth();
97: $this->_sendRcpt();
98: $this->_sendData();
99: $this->_disconnect();
100:
101: return $this->_content;
102: }
103:
104: 105: 106: 107: 108: 109:
110: public function config($config = null) {
111: if ($config === null) {
112: return $this->_config;
113: }
114: $default = array(
115: 'host' => 'localhost',
116: 'port' => 25,
117: 'timeout' => 30,
118: 'username' => null,
119: 'password' => null,
120: 'client' => null,
121: 'tls' => false,
122: 'ssl_allow_self_signed' => false
123: );
124: $this->_config = array_merge($default, $this->_config, $config);
125: return $this->_config;
126: }
127:
128: 129: 130: 131: 132: 133:
134: protected function _bufferResponseLines(array $responseLines) {
135: $response = array();
136: foreach ($responseLines as $responseLine) {
137: if (preg_match('/^(\d{3})(?:[ -]+(.*))?$/', $responseLine, $match)) {
138: $response[] = array(
139: 'code' => $match[1],
140: 'message' => isset($match[2]) ? $match[2] : null
141: );
142: }
143: }
144: $this->_lastResponse = array_merge($this->_lastResponse, $response);
145: }
146:
147: 148: 149: 150: 151: 152:
153: protected function _connect() {
154: $this->_generateSocket();
155: if (!$this->_socket->connect()) {
156: throw new SocketException(__d('cake_dev', 'Unable to connect to SMTP server.'));
157: }
158: $this->_smtpSend(null, '220');
159:
160: if (isset($this->_config['client'])) {
161: $host = $this->_config['client'];
162: } elseif ($httpHost = env('HTTP_HOST')) {
163: list($host) = explode(':', $httpHost);
164: } else {
165: $host = 'localhost';
166: }
167:
168: try {
169: $this->_smtpSend("EHLO {$host}", '250');
170: if ($this->_config['tls']) {
171: $this->_smtpSend("STARTTLS", '220');
172: $this->_socket->enableCrypto('tls');
173: $this->_smtpSend("EHLO {$host}", '250');
174: }
175: } catch (SocketException $e) {
176: if ($this->_config['tls']) {
177: throw new SocketException(__d('cake_dev', 'SMTP server did not accept the connection or trying to connect to non TLS SMTP server using TLS.'));
178: }
179: try {
180: $this->_smtpSend("HELO {$host}", '250');
181: } catch (SocketException $e2) {
182: throw new SocketException(__d('cake_dev', 'SMTP server did not accept the connection.'));
183: }
184: }
185: }
186:
187: 188: 189: 190: 191: 192:
193: protected function _auth() {
194: if (isset($this->_config['username']) && isset($this->_config['password'])) {
195: $replyCode = $this->_smtpSend('AUTH LOGIN', '334|500|502|504');
196: if ($replyCode == '334') {
197: try {
198: $this->_smtpSend(base64_encode($this->_config['username']), '334');
199: } catch (SocketException $e) {
200: throw new SocketException(__d('cake_dev', 'SMTP server did not accept the username.'));
201: }
202: try {
203: $this->_smtpSend(base64_encode($this->_config['password']), '235');
204: } catch (SocketException $e) {
205: throw new SocketException(__d('cake_dev', 'SMTP server did not accept the password.'));
206: }
207: } elseif ($replyCode == '504') {
208: throw new SocketException(__d('cake_dev', 'SMTP authentication method not allowed, check if SMTP server requires TLS.'));
209: } else {
210: throw new SocketException(__d('cake_dev', 'AUTH command not recognized or not implemented, SMTP server may not require authentication.'));
211: }
212: }
213: }
214:
215: 216: 217: 218: 219: 220:
221: protected function _prepareFromCmd($email) {
222: return 'MAIL FROM:<' . $email . '>';
223: }
224:
225: 226: 227: 228: 229: 230:
231: protected function _prepareRcptCmd($email) {
232: return 'RCPT TO:<' . $email . '>';
233: }
234:
235: 236: 237: 238: 239:
240: protected function _prepareFromAddress() {
241: $from = $this->_cakeEmail->returnPath();
242: if (empty($from)) {
243: $from = $this->_cakeEmail->from();
244: }
245: return $from;
246: }
247:
248: 249: 250: 251: 252:
253: protected function _prepareRecipientAddresses() {
254: $to = $this->_cakeEmail->to();
255: $cc = $this->_cakeEmail->cc();
256: $bcc = $this->_cakeEmail->bcc();
257: return array_merge(array_keys($to), array_keys($cc), array_keys($bcc));
258: }
259:
260: 261: 262: 263: 264:
265: protected function _prepareMessageHeaders() {
266: return $this->_cakeEmail->getHeaders(array('from', 'sender', 'replyTo', 'readReceipt', 'to', 'cc', 'subject'));
267: }
268:
269: 270: 271: 272: 273:
274: protected function _prepareMessage() {
275: $lines = $this->_cakeEmail->message();
276: $messages = array();
277: foreach ($lines as $line) {
278: if ((!empty($line)) && ($line[0] === '.')) {
279: $messages[] = '.' . $line;
280: } else {
281: $messages[] = $line;
282: }
283: }
284: return implode("\r\n", $messages);
285: }
286:
287: 288: 289: 290: 291: 292:
293: protected function _sendRcpt() {
294: $from = $this->_prepareFromAddress();
295: $this->_smtpSend($this->_prepareFromCmd(key($from)));
296:
297: $emails = $this->_prepareRecipientAddresses();
298: foreach ($emails as $email) {
299: $this->_smtpSend($this->_prepareRcptCmd($email));
300: }
301: }
302:
303: 304: 305: 306: 307: 308:
309: protected function _sendData() {
310: $this->_smtpSend('DATA', '354');
311:
312: $headers = $this->_headersToString($this->_prepareMessageHeaders());
313: $message = $this->_prepareMessage();
314:
315: $this->_smtpSend($headers . "\r\n\r\n" . $message . "\r\n\r\n\r\n.");
316: $this->_content = array('headers' => $headers, 'message' => $message);
317: }
318:
319: 320: 321: 322: 323: 324:
325: protected function _disconnect() {
326: $this->_smtpSend('QUIT', false);
327: $this->_socket->disconnect();
328: }
329:
330: 331: 332: 333: 334: 335:
336: protected function _generateSocket() {
337: $this->_socket = new CakeSocket($this->_config);
338: }
339:
340: 341: 342: 343: 344: 345: 346: 347:
348: protected function _smtpSend($data, $checkCode = '250') {
349: $this->_lastResponse = array();
350:
351: if ($data !== null) {
352: $this->_socket->write($data . "\r\n");
353: }
354: while ($checkCode !== false) {
355: $response = '';
356: $startTime = time();
357: while (substr($response, -2) !== "\r\n" && ((time() - $startTime) < $this->_config['timeout'])) {
358: $response .= $this->_socket->read();
359: }
360: if (substr($response, -2) !== "\r\n") {
361: throw new SocketException(__d('cake_dev', 'SMTP timeout.'));
362: }
363: $responseLines = explode("\r\n", rtrim($response, "\r\n"));
364: $response = end($responseLines);
365:
366: $this->_bufferResponseLines($responseLines);
367:
368: if (preg_match('/^(' . $checkCode . ')(.)/', $response, $code)) {
369: if ($code[2] === '-') {
370: continue;
371: }
372: return $code[1];
373: }
374: throw new SocketException(__d('cake_dev', 'SMTP Error: %s', $response));
375: }
376: }
377:
378: }
379: