1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\Mail;
9:
10: use Nette;
11:
12:
13: 14: 15:
16: class SmtpMailer implements IMailer
17: {
18: use Nette\SmartObject;
19:
20:
21: private $connection;
22:
23:
24: private $host;
25:
26:
27: private $port;
28:
29:
30: private $username;
31:
32:
33: private $password;
34:
35:
36: private $secure;
37:
38:
39: private $timeout;
40:
41:
42: private $context;
43:
44:
45: private $persistent;
46:
47:
48: private $clientHost;
49:
50:
51: public function __construct(array $options = [])
52: {
53: if (isset($options['host'])) {
54: $this->host = $options['host'];
55: $this->port = isset($options['port']) ? (int) $options['port'] : null;
56: } else {
57: $this->host = ini_get('SMTP');
58: $this->port = (int) ini_get('smtp_port');
59: }
60: $this->username = isset($options['username']) ? $options['username'] : '';
61: $this->password = isset($options['password']) ? $options['password'] : '';
62: $this->secure = isset($options['secure']) ? $options['secure'] : '';
63: $this->timeout = isset($options['timeout']) ? (int) $options['timeout'] : 20;
64: $this->context = isset($options['context']) ? stream_context_create($options['context']) : stream_context_get_default();
65: if (!$this->port) {
66: $this->port = $this->secure === 'ssl' ? 465 : 25;
67: }
68: $this->persistent = !empty($options['persistent']);
69: if (isset($options['clientHost'])) {
70: $this->clientHost = $options['clientHost'];
71: } else {
72: $this->clientHost = isset($_SERVER['HTTP_HOST']) && preg_match('#^[\w.-]+\z#', $_SERVER['HTTP_HOST'])
73: ? $_SERVER['HTTP_HOST']
74: : 'localhost';
75: }
76: }
77:
78:
79: 80: 81: 82: 83:
84: public function send(Message $mail)
85: {
86: $tmp = clone $mail;
87: $tmp->setHeader('Bcc', null);
88: $data = $tmp->generateMessage();
89:
90: try {
91: if (!$this->connection) {
92: $this->connect();
93: }
94:
95: if (($from = $mail->getHeader('Return-Path'))
96: || ($from = key($mail->getHeader('From')))
97: ) {
98: $this->write("MAIL FROM:<$from>", 250);
99: }
100:
101: foreach (array_merge(
102: (array) $mail->getHeader('To'),
103: (array) $mail->getHeader('Cc'),
104: (array) $mail->getHeader('Bcc')
105: ) as $email => $name) {
106: $this->write("RCPT TO:<$email>", [250, 251]);
107: }
108:
109: $this->write('DATA', 354);
110: $data = preg_replace('#^\.#m', '..', $data);
111: $this->write($data);
112: $this->write('.', 250);
113:
114: if (!$this->persistent) {
115: $this->write('QUIT', 221);
116: $this->disconnect();
117: }
118: } catch (SmtpException $e) {
119: if ($this->connection) {
120: $this->disconnect();
121: }
122: throw $e;
123: }
124: }
125:
126:
127: 128: 129: 130:
131: protected function connect()
132: {
133: $this->connection = @stream_socket_client(
134: ($this->secure === 'ssl' ? 'ssl://' : '') . $this->host . ':' . $this->port,
135: $errno, $error, $this->timeout, STREAM_CLIENT_CONNECT, $this->context
136: );
137: if (!$this->connection) {
138: throw new SmtpException($error ?: error_get_last()['message'], $errno);
139: }
140: stream_set_timeout($this->connection, $this->timeout, 0);
141: $this->read();
142:
143: $this->write("EHLO $this->clientHost");
144: $ehloResponse = $this->read();
145: if ((int) $ehloResponse !== 250) {
146: $this->write("HELO $this->clientHost", 250);
147: }
148:
149: if ($this->secure === 'tls') {
150: $this->write('STARTTLS', 220);
151: if (!stream_socket_enable_crypto($this->connection, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
152: throw new SmtpException('Unable to connect via TLS.');
153: }
154: $this->write("EHLO $this->clientHost", 250);
155: }
156:
157: if ($this->username != null && $this->password != null) {
158: $authMechanisms = [];
159: if (preg_match('~^250[ -]AUTH (.*)$~im', $ehloResponse, $matches)) {
160: $authMechanisms = explode(' ', trim($matches[1]));
161: }
162:
163: if (in_array('PLAIN', $authMechanisms, true)) {
164: $credentials = $this->username . "\0" . $this->username . "\0" . $this->password;
165: $this->write('AUTH PLAIN ' . base64_encode($credentials), 235, 'PLAIN credentials');
166: } else {
167: $this->write('AUTH LOGIN', 334);
168: $this->write(base64_encode($this->username), 334, 'username');
169: $this->write(base64_encode($this->password), 235, 'password');
170: }
171: }
172: }
173:
174:
175: 176: 177: 178:
179: protected function disconnect()
180: {
181: fclose($this->connection);
182: $this->connection = null;
183: }
184:
185:
186: 187: 188: 189: 190: 191: 192:
193: protected function write($line, $expectedCode = null, $message = null)
194: {
195: fwrite($this->connection, $line . Message::EOL);
196: if ($expectedCode) {
197: $response = $this->read();
198: if (!in_array((int) $response, (array) $expectedCode, true)) {
199: throw new SmtpException('SMTP server did not accept ' . ($message ? $message : $line) . ' with error: ' . trim($response));
200: }
201: }
202: }
203:
204:
205: 206: 207: 208:
209: protected function read()
210: {
211: $s = '';
212: while (($line = fgets($this->connection, 1000)) != null) {
213: $s .= $line;
214: if (substr($line, 3, 1) === ' ') {
215: break;
216: }
217: }
218: return $s;
219: }
220: }
221: