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: );
123: $this->_config = array_merge($default, $this->_config, $config);
124: return $this->_config;
125: }
126:
127: 128: 129: 130: 131: 132:
133: protected function _bufferResponseLines(array $responseLines) {
134: $response = array();
135: foreach ($responseLines as $responseLine) {
136: if (preg_match('/^(\d{3})(?:[ -]+(.*))?$/', $responseLine, $match)) {
137: $response[] = array(
138: 'code' => $match[1],
139: 'message' => isset($match[2]) ? $match[2] : null
140: );
141: }
142: }
143: $this->_lastResponse = array_merge($this->_lastResponse, $response);
144: }
145:
146: 147: 148: 149: 150: 151:
152: protected function _connect() {
153: $this->_generateSocket();
154: if (!$this->_socket->connect()) {
155: throw new SocketException(__d('cake_dev', 'Unable to connect to SMTP server.'));
156: }
157: $this->_smtpSend(null, '220');
158:
159: if (isset($this->_config['client'])) {
160: $host = $this->_config['client'];
161: } elseif ($httpHost = env('HTTP_HOST')) {
162: list($host) = explode(':', $httpHost);
163: } else {
164: $host = 'localhost';
165: }
166:
167: try {
168: $this->_smtpSend("EHLO {$host}", '250');
169: if ($this->_config['tls']) {
170: $this->_smtpSend("STARTTLS", '220');
171: $this->_socket->enableCrypto('tls');
172: $this->_smtpSend("EHLO {$host}", '250');
173: }
174: } catch (SocketException $e) {
175: if ($this->_config['tls']) {
176: throw new SocketException(__d('cake_dev', 'SMTP server did not accept the connection or trying to connect to non TLS SMTP server using TLS.'));
177: }
178: try {
179: $this->_smtpSend("HELO {$host}", '250');
180: } catch (SocketException $e2) {
181: throw new SocketException(__d('cake_dev', 'SMTP server did not accept the connection.'));
182: }
183: }
184: }
185:
186: 187: 188: 189: 190: 191:
192: protected function _auth() {
193: if (isset($this->_config['username']) && isset($this->_config['password'])) {
194: $replyCode = $this->_smtpSend('AUTH LOGIN', '334|500|502|504');
195: if ($replyCode == '334') {
196: try {
197: $this->_smtpSend(base64_encode($this->_config['username']), '334');
198: } catch (SocketException $e) {
199: throw new SocketException(__d('cake_dev', 'SMTP server did not accept the username.'));
200: }
201: try {
202: $this->_smtpSend(base64_encode($this->_config['password']), '235');
203: } catch (SocketException $e) {
204: throw new SocketException(__d('cake_dev', 'SMTP server did not accept the password.'));
205: }
206: } elseif ($replyCode == '504') {
207: throw new SocketException(__d('cake_dev', 'SMTP authentication method not allowed, check if SMTP server requires TLS.'));
208: } else {
209: throw new SocketException(__d('cake_dev', 'AUTH command not recognized or not implemented, SMTP server may not require authentication.'));
210: }
211: }
212: }
213:
214: 215: 216: 217: 218: 219:
220: protected function _prepareFromCmd($email) {
221: return 'MAIL FROM:<' . $email . '>';
222: }
223:
224: 225: 226: 227: 228: 229:
230: protected function _prepareRcptCmd($email) {
231: return 'RCPT TO:<' . $email . '>';
232: }
233:
234: 235: 236: 237: 238:
239: protected function _prepareFromAddress() {
240: $from = $this->_cakeEmail->returnPath();
241: if (empty($from)) {
242: $from = $this->_cakeEmail->from();
243: }
244: return $from;
245: }
246:
247: 248: 249: 250: 251:
252: protected function _prepareRecipientAddresses() {
253: $to = $this->_cakeEmail->to();
254: $cc = $this->_cakeEmail->cc();
255: $bcc = $this->_cakeEmail->bcc();
256: return array_merge(array_keys($to), array_keys($cc), array_keys($bcc));
257: }
258:
259: 260: 261: 262: 263:
264: protected function _prepareMessageHeaders() {
265: return $this->_cakeEmail->getHeaders(array('from', 'sender', 'replyTo', 'readReceipt', 'to', 'cc', 'subject'));
266: }
267:
268: 269: 270: 271: 272:
273: protected function _prepareMessage() {
274: $lines = $this->_cakeEmail->message();
275: $messages = array();
276: foreach ($lines as $line) {
277: if ((!empty($line)) && ($line[0] === '.')) {
278: $messages[] = '.' . $line;
279: } else {
280: $messages[] = $line;
281: }
282: }
283: return implode("\r\n", $messages);
284: }
285:
286: 287: 288: 289: 290: 291:
292: protected function _sendRcpt() {
293: $from = $this->_prepareFromAddress();
294: $this->_smtpSend($this->_prepareFromCmd(key($from)));
295:
296: $emails = $this->_prepareRecipientAddresses();
297: foreach ($emails as $email) {
298: $this->_smtpSend($this->_prepareRcptCmd($email));
299: }
300: }
301:
302: 303: 304: 305: 306: 307:
308: protected function _sendData() {
309: $this->_smtpSend('DATA', '354');
310:
311: $headers = $this->_headersToString($this->_prepareMessageHeaders());
312: $message = $this->_prepareMessage();
313:
314: $this->_smtpSend($headers . "\r\n\r\n" . $message . "\r\n\r\n\r\n.");
315: $this->_content = array('headers' => $headers, 'message' => $message);
316: }
317:
318: 319: 320: 321: 322: 323:
324: protected function _disconnect() {
325: $this->_smtpSend('QUIT', false);
326: $this->_socket->disconnect();
327: }
328:
329: 330: 331: 332: 333: 334:
335: protected function _generateSocket() {
336: $this->_socket = new CakeSocket($this->_config);
337: }
338:
339: 340: 341: 342: 343: 344: 345: 346:
347: protected function _smtpSend($data, $checkCode = '250') {
348: $this->_lastResponse = array();
349:
350: if ($data !== null) {
351: $this->_socket->write($data . "\r\n");
352: }
353: while ($checkCode !== false) {
354: $response = '';
355: $startTime = time();
356: while (substr($response, -2) !== "\r\n" && ((time() - $startTime) < $this->_config['timeout'])) {
357: $response .= $this->_socket->read();
358: }
359: if (substr($response, -2) !== "\r\n") {
360: throw new SocketException(__d('cake_dev', 'SMTP timeout.'));
361: }
362: $responseLines = explode("\r\n", rtrim($response, "\r\n"));
363: $response = end($responseLines);
364:
365: $this->_bufferResponseLines($responseLines);
366:
367: if (preg_match('/^(' . $checkCode . ')(.)/', $response, $code)) {
368: if ($code[2] === '-') {
369: continue;
370: }
371: return $code[1];
372: }
373: throw new SocketException(__d('cake_dev', 'SMTP Error: %s', $response));
374: }
375: }
376:
377: }
378: