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