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