This repository has been archived by the owner on Jan 30, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 111
/
Copy pathSmtp.php
412 lines (360 loc) · 11.1 KB
/
Smtp.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\Mail\Protocol;
/**
* SMTP implementation of Zend\Mail\Protocol\AbstractProtocol
*
* Minimum implementation according to RFC2821: EHLO, MAIL FROM, RCPT TO, DATA,
* RSET, NOOP, QUIT
*/
class Smtp extends AbstractProtocol
{
/**
* The transport method for the socket
*
* @var string
*/
protected $transport = 'tcp';
/**
* Indicates that a session is requested to be secure
*
* @var string
*/
protected $secure;
/**
* Indicates an smtp session has been started by the HELO command
*
* @var bool
*/
protected $sess = false;
/**
* Indicates an smtp AUTH has been issued and authenticated
*
* @var bool
*/
protected $auth = false;
/**
* Indicates a MAIL command has been issued
*
* @var bool
*/
protected $mail = false;
/**
* Indicates one or more RCTP commands have been issued
*
* @var bool
*/
protected $rcpt = false;
/**
* Indicates that DATA has been issued and sent
*
* @var bool
*/
protected $data = null;
/**
* Constructor.
*
* The first argument may be an array of all options. If so, it must include
* the 'host' and 'port' keys in order to ensure that all required values
* are present.
*
* @param string|array $host
* @param null|int $port
* @param null|array $config
* @throws Exception\InvalidArgumentException
*/
public function __construct($host = '127.0.0.1', $port = null, array $config = null)
{
// Did we receive a configuration array?
if (is_array($host)) {
// Merge config array with principal array, if provided
if (is_array($config)) {
$config = array_replace_recursive($host, $config);
} else {
$config = $host;
}
// Look for a host key; if none found, use default value
if (isset($config['host'])) {
$host = $config['host'];
} else {
$host = '127.0.0.1';
}
// Look for a port key; if none found, use default value
if (isset($config['port'])) {
$port = $config['port'];
} else {
$port = null;
}
}
// If we don't have a config array, initialize it
if (null === $config) {
$config = [];
}
if (isset($config['ssl'])) {
switch (strtolower($config['ssl'])) {
case 'tls':
$this->secure = 'tls';
break;
case 'ssl':
$this->transport = 'ssl';
$this->secure = 'ssl';
if ($port === null) {
$port = 465;
}
break;
default:
throw new Exception\InvalidArgumentException($config['ssl'] . ' is unsupported SSL type');
}
}
// If no port has been specified then check the master PHP ini file. Defaults to 25 if the ini setting is null.
if ($port === null) {
if (($port = ini_get('smtp_port')) == '') {
$port = 25;
}
}
parent::__construct($host, $port);
}
/**
* Connect to the server with the parameters given in the constructor.
*
* @return bool
*/
public function connect()
{
return $this->_connect($this->transport . '://' . $this->host . ':' . $this->port);
}
/**
* Initiate HELO/EHLO sequence and set flag to indicate valid smtp session
*
* @param string $host The client hostname or IP address (default: 127.0.0.1)
* @throws Exception\RuntimeException
*/
public function helo($host = '127.0.0.1')
{
// Respect RFC 2821 and disallow HELO attempts if session is already initiated.
if ($this->sess === true) {
throw new Exception\RuntimeException('Cannot issue HELO to existing session');
}
// Validate client hostname
if (! $this->validHost->isValid($host)) {
throw new Exception\RuntimeException(implode(', ', $this->validHost->getMessages()));
}
// Initiate helo sequence
$this->_expect(220, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
$this->ehlo($host);
// If a TLS session is required, commence negotiation
if ($this->secure == 'tls') {
$this->_send('STARTTLS');
$this->_expect(220, 180);
if (! stream_socket_enable_crypto($this->socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
throw new Exception\RuntimeException('Unable to connect via TLS');
}
$this->ehlo($host);
}
$this->startSession();
$this->auth();
}
/**
* Returns the perceived session status
*
* @return bool
*/
public function hasSession()
{
return $this->sess;
}
/**
* Send EHLO or HELO depending on capabilities of smtp host
*
* @param string $host The client hostname or IP address (default: 127.0.0.1)
* @throws \Exception|Exception\ExceptionInterface
*/
protected function ehlo($host)
{
// Support for older, less-compliant remote servers. Tries multiple attempts of EHLO or HELO.
try {
$this->_send('EHLO ' . $host);
$this->_expect(250, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
} catch (Exception\ExceptionInterface $e) {
$this->_send('HELO ' . $host);
$this->_expect(250, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
} catch (\Exception $e) {
throw $e;
}
}
/**
* Issues MAIL command
*
* @param string $from Sender mailbox
* @throws Exception\RuntimeException
*/
public function mail($from)
{
if ($this->sess !== true) {
throw new Exception\RuntimeException('A valid session has not been started');
}
$this->_send('MAIL FROM:<' . $from . '>');
$this->_expect(250, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
// Set mail to true, clear recipients and any existing data flags as per 4.1.1.2 of RFC 2821
$this->mail = true;
$this->rcpt = false;
$this->data = false;
}
/**
* Issues RCPT command
*
* @param string $to Receiver(s) mailbox
* @throws Exception\RuntimeException
*/
public function rcpt($to)
{
if ($this->mail !== true) {
throw new Exception\RuntimeException('No sender reverse path has been supplied');
}
// Set rcpt to true, as per 4.1.1.3 of RFC 2821
$this->_send('RCPT TO:<' . $to . '>');
$this->_expect([250, 251], 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
$this->rcpt = true;
}
/**
* Issues DATA command
*
* @param string $data
* @throws Exception\RuntimeException
*/
public function data($data)
{
// Ensure recipients have been set
if ($this->rcpt !== true) { // Per RFC 2821 3.3 (page 18)
throw new Exception\RuntimeException('No recipient forward path has been supplied');
}
$this->_send('DATA');
$this->_expect(354, 120); // Timeout set for 2 minutes as per RFC 2821 4.5.3.2
if (($fp = fopen("php://temp", "r+")) === false) {
throw new Exception\RuntimeException('cannot fopen');
}
if (fwrite($fp, $data) === false) {
throw new Exception\RuntimeException('cannot fwrite');
}
unset($data);
rewind($fp);
// max line length is 998 char + \r\n = 1000
while (($line = stream_get_line($fp, 1000, "\n")) !== false) {
$line = rtrim($line, "\r");
if (isset($line[0]) && $line[0] === '.') {
// Escape lines prefixed with a '.'
$line = '.' . $line;
}
$this->_send($line);
}
fclose($fp);
$this->_send('.');
$this->_expect(250, 600); // Timeout set for 10 minutes as per RFC 2821 4.5.3.2
$this->data = true;
}
/**
* Issues the RSET command end validates answer
*
* Can be used to restore a clean smtp communication state when a
* transaction has been cancelled or commencing a new transaction.
*/
public function rset()
{
$this->_send('RSET');
// MS ESMTP doesn't follow RFC, see [ZF-1377]
$this->_expect([250, 220]);
$this->mail = false;
$this->rcpt = false;
$this->data = false;
}
/**
* Issues the NOOP command end validates answer
*
* Not used by Zend\Mail, could be used to keep a connection alive or check if it is still open.
*
*/
public function noop()
{
$this->_send('NOOP');
$this->_expect(250, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
}
/**
* Issues the VRFY command end validates answer
*
* Not used by Zend\Mail.
*
* @param string $user User Name or eMail to verify
*/
public function vrfy($user)
{
$this->_send('VRFY ' . $user);
$this->_expect([250, 251, 252], 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
}
/**
* Issues the QUIT command and clears the current session
*
*/
public function quit()
{
if ($this->sess) {
$this->auth = false;
$this->_send('QUIT');
$this->_expect(221, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
$this->stopSession();
}
}
/**
* Default authentication method
*
* This default method is implemented by AUTH adapters to properly authenticate to a remote host.
*
* @throws Exception\RuntimeException
*/
public function auth()
{
if ($this->auth === true) {
throw new Exception\RuntimeException('Already authenticated for this session');
}
}
/**
* Closes connection
*
*/
public function disconnect()
{
$this->_disconnect();
}
// @codingStandardsIgnoreStart
/**
* Disconnect from remote host and free resource
*/
protected function _disconnect()
{
// @codingStandardsIgnoreEnd
// Make sure the session gets closed
$this->quit();
parent::_disconnect();
}
/**
* Start mail session
*
*/
protected function startSession()
{
$this->sess = true;
}
/**
* Stop mail session
*
*/
protected function stopSession()
{
$this->sess = false;
}
}