Bitrix-D7
23.9
Загрузка...
Поиск...
Не найдено
smtp.php
1
<?php
2
3
namespace
Bitrix\Mail
;
4
5
use
Bitrix\Main
;
6
use
Bitrix\Main\Text\BinaryString
;
7
use
Bitrix\Main\Text\Encoding
;
8
use
Bitrix\Main\Localization\Loc
;
9
10
Loc::loadMessages
(__FILE__);
11
12
class
Smtp
13
{
14
const
ERR_CONNECT
= 101;
15
const
ERR_REJECTED
= 102;
16
const
ERR_COMMUNICATE
= 103;
17
const
ERR_EMPTY_RESPONSE
= 104;
18
19
const
ERR_STARTTLS
= 201;
20
const
ERR_COMMAND_REJECTED
= 202;
21
const
ERR_CAPABILITY
= 203;
22
const
ERR_AUTH
= 204;
23
const
ERR_AUTH_MECH
= 205;
24
25
protected
$stream
,
$errors
;
26
protected
$sessCapability
;
27
28
protected
$options
= array();
29
35
protected
bool
$isOauth
=
false
;
36
48
public
function
__construct
($host, $port, $tls, $strict, $login, $password, $encoding =
null
)
49
{
50
$this->
reset
();
51
52
$this->options = array(
53
'host'
=> $host,
54
'port'
=> $port,
55
'tls'
=> $tls,
56
'socket'
=> sprintf(
'%s://%s:%s'
, ($tls ?
'ssl'
:
'tcp'
), $host, $port),
57
'timeout'
=> \COption::getOptionInt(
'mail'
,
'connect_timeout'
, B_MAIL_TIMEOUT),
58
'context'
=> stream_context_create(array(
59
'ssl'
=> array(
60
'verify_peer'
=> (
bool
) $strict,
61
'verify_peer_name'
=> (
bool
) $strict,
62
'crypto_method'
=> STREAM_CRYPTO_METHOD_ANY_CLIENT,
63
)
64
)),
65
'login'
=> $login,
66
'password'
=> $password,
67
'encoding'
=> $encoding ?: LANG_CHARSET,
68
);
69
}
70
76
public
function
__destruct
()
77
{
78
$this->
disconnect
();
79
}
80
86
protected
function
disconnect
()
87
{
88
if
(!is_null($this->stream))
89
{
90
@fclose($this->stream);
91
}
92
93
unset($this->stream);
94
}
95
96
protected
function
reset
()
97
{
98
$this->
disconnect
();
99
100
$this->errors =
new
Main\ErrorCollection
();
101
}
102
109
public
function
connect
(&$error)
110
{
111
$error =
null
;
112
113
if
($this->stream)
114
{
115
return
true
;
116
}
117
118
$resource = @stream_socket_client(
119
$this->options[
'socket'
], $errno, $errstr, $this->options[
'timeout'
],
120
STREAM_CLIENT_CONNECT, $this->options[
'context'
]
121
);
122
123
if
($resource ===
false
)
124
{
125
$error = $this->
errorMessage
(
Smtp::ERR_CONNECT
, $errno ?:
null
);
126
return
false
;
127
}
128
129
$this->stream = $resource;
130
131
if
($this->options[
'timeout'
] > 0)
132
{
133
stream_set_timeout($this->stream, $this->options[
'timeout'
]);
134
}
135
136
$prompt = $this->
readResponse
();
137
138
if
(
false
=== $prompt)
139
{
140
$error = $this->
errorMessage
(array(
Smtp::ERR_CONNECT
,
Smtp::ERR_COMMUNICATE
));
141
}
142
else
if
(!preg_match(
'/^ 220 ( \r\n | \x20 ) /x'
, end($prompt)))
143
{
144
$error = $this->
errorMessage
(array(
Smtp::ERR_CONNECT
,
Smtp::ERR_REJECTED
), trim(end($prompt)));
145
}
146
147
if
($error)
148
{
149
return
false
;
150
}
151
152
if
(!$this->
capability
($error))
153
{
154
return
false
;
155
}
156
157
if
(!$this->options[
'tls'
] && preg_grep(
'/^ STARTTLS $/ix'
, $this->sessCapability))
158
{
159
if
(!$this->
starttls
($error))
160
{
161
return
false
;
162
}
163
}
164
165
return
true
;
166
}
167
168
protected
function
starttls
(&$error)
169
{
170
$error =
null
;
171
172
if
(!$this->stream)
173
{
174
$error = $this->
errorMessage
(
Smtp::ERR_STARTTLS
);
175
return
false
;
176
}
177
178
$response = $this->
executeCommand
(
'STARTTLS'
, $error);
179
180
if
($error)
181
{
182
$error = $error ==
Smtp::ERR_COMMAND_REJECTED
? null : $error;
183
$error = $this->
errorMessage
(array(
Smtp::ERR_STARTTLS
, $error), $response ? trim(end($response)) :
null
);
184
185
return
false
;
186
}
187
188
if
(stream_socket_enable_crypto($this->stream,
true
, STREAM_CRYPTO_METHOD_ANY_CLIENT))
189
{
190
if
(!$this->
capability
($error))
191
{
192
return
false
;
193
}
194
}
195
else
196
{
197
$this->
reset
();
198
199
$error = $this->
errorMessage
(
Smtp::ERR_STARTTLS
);
200
return
false
;
201
}
202
203
return
true
;
204
}
205
206
protected
function
capability
(&$error)
207
{
208
$error =
null
;
209
210
if
(!$this->stream)
211
{
212
$error = $this->
errorMessage
(
Smtp::ERR_CAPABILITY
);
213
return
false
;
214
}
215
216
$response = $this->
executeCommand
(
217
sprintf(
218
'EHLO %s'
,
219
Main\
Context::getCurrent
()->getRequest()->getHttpHost() ?:
'localhost'
220
),
221
$error
222
);
223
224
if
($error || !is_array($response))
225
{
226
$error = $error ==
Smtp::ERR_COMMAND_REJECTED
? null : $error;
227
$error = $this->
errorMessage
(array(
Smtp::ERR_CAPABILITY
, $error), $response ? trim(end($response)) :
null
);
228
229
return
false
;
230
}
231
232
$this->sessCapability = array_map(
233
function
($line)
234
{
235
return
trim(mb_substr($line, 4));
236
},
237
$response
238
);
239
240
return
true
;
241
}
242
249
public
function
authenticate
(&$error)
250
{
251
$error =
null
;
252
253
if
(!$this->
connect
($error))
254
{
255
return
false
;
256
}
257
258
$mech =
false
;
259
260
if
($capabilities = preg_grep(
'/^ AUTH \x20 /ix'
, $this->sessCapability))
261
{
262
if
($this->isOauth)
263
{
264
$mech =
'oauth'
;
265
}
266
else
if
(preg_grep(
'/ \x20 PLAIN ( \x20 | $ ) /ix'
, $capabilities))
267
{
268
$mech =
'plain'
;
269
}
270
else
if
(preg_grep(
'/ \x20 LOGIN ( \x20 | $ ) /ix'
, $capabilities))
271
{
272
$mech =
'login'
;
273
}
274
}
275
276
if
(!$mech)
277
{
278
$error = $this->
errorMessage
(array(
Smtp::ERR_AUTH
,
Smtp::ERR_AUTH_MECH
));
279
return
false
;
280
}
281
282
if
($mech ===
'oauth'
)
283
{
284
$token = Helper\OAuth::getTokenByMeta($this->options[
'password'
]);
285
if
(empty($token))
286
{
287
$error = $this->
errorMessage
(array(
Smtp::ERR_AUTH
,
Smtp::ERR_AUTH_MECH
));
288
return
false
;
289
}
290
$formatted = sprintf(
"user=%s\x01auth=Bearer %s\x01\x01"
, $this->options[
'login'
], $token);
291
$response = $this->
executeCommand
(sprintf(
"AUTH XOAUTH2\x00%s"
, base64_encode($formatted)), $error);
292
}
293
else
if
($mech ===
'plain'
)
294
{
295
$response = $this->
executeCommand
(
296
sprintf(
297
"AUTH PLAIN\x00%s"
,
298
base64_encode(sprintf(
299
"\x00%s\x00%s"
,
300
Encoding::convertEncoding($this->options[
'login'
], $this->options[
'encoding'
],
'UTF-8'
),
301
Encoding::convertEncoding($this->options[
'password'
], $this->options[
'encoding'
],
'UTF-8'
)
302
))
303
),
304
$error
305
);
306
}
307
else
308
{
309
$response = $this->
executeCommand
(sprintf(
310
"AUTH LOGIN\x00%s\x00%s"
,
311
base64_encode($this->options[
'login'
]),
312
base64_encode($this->options[
'password'
])
313
), $error);
314
}
315
316
if
($error)
317
{
318
$error = $error ==
Smtp::ERR_COMMAND_REJECTED
? null : $error;
319
$error = $this->
errorMessage
(array(
Smtp::ERR_AUTH
, $error), $response ? trim(end($response)) :
null
);
320
321
return
false
;
322
}
323
324
return
true
;
325
}
326
327
protected
function
executeCommand
($command, &$error)
328
{
329
$error =
null
;
330
$response =
false
;
331
332
$chunks = explode(
"\x00"
, $command);
333
334
$k = count($chunks);
335
foreach
($chunks as $chunk)
336
{
337
$k--;
338
339
$response = (array) $this->
exchange
($chunk, $error);
340
341
if
($k > 0 && mb_strpos(end($response),
'3'
) !== 0)
342
{
343
break
;
344
}
345
}
346
347
return
$response;
348
}
349
350
protected
function
exchange
($data, &$error)
351
{
352
$error =
null
;
353
354
if
($this->
sendData
(sprintf(
"%s\r\n"
, $data)) ===
false
)
355
{
356
$error =
Smtp::ERR_COMMUNICATE
;
357
return
false
;
358
}
359
360
$response = $this->
readResponse
();
361
362
if
($response ===
false
)
363
{
364
$error =
Smtp::ERR_COMMUNICATE
;
365
return
false
;
366
}
367
368
if
(!preg_match(
'/^ [23] \d{2} /ix'
, end($response)))
369
{
370
$error =
Smtp::ERR_COMMAND_REJECTED
;
371
}
372
373
return
$response;
374
}
375
376
protected
function
sendData
($data)
377
{
378
$fails = 0;
379
while
(BinaryString::getLength($data) > 0 && !feof($this->stream))
380
{
381
$bytes = @fputs($this->stream, $data);
382
383
if
(
false
== $bytes)
384
{
385
if
(
false
=== $bytes || ++$fails >= 3)
386
{
387
break
;
388
}
389
390
continue
;
391
}
392
393
$fails = 0;
394
395
$data = BinaryString::getSubstring($data, $bytes);
396
}
397
398
if
(BinaryString::getLength($data) > 0)
399
{
400
$this->
reset
();
401
return
false
;
402
}
403
404
return
true
;
405
}
406
407
protected
function
readLine
()
408
{
409
$line =
''
;
410
411
while
(!feof($this->stream))
412
{
413
$buffer = @fgets($this->stream, 4096);
414
if
($buffer ===
false
)
415
{
416
break
;
417
}
418
419
$meta = ($this->options[
'timeout'
] > 0 ? stream_get_meta_data($this->stream) : array(
'timed_out'
=>
false
));
420
421
$line .= $buffer;
422
423
if
(preg_match(
'/\r\n$/'
, $buffer, $matches) || $meta[
'timed_out'
])
424
{
425
break
;
426
}
427
}
428
429
if
(!preg_match(
'/\r\n$/'
, $line, $matches))
430
{
431
$this->
reset
();
432
433
return
false
;
434
}
435
436
return
$line;
437
}
438
444
protected
function
readResponse
()
445
{
446
$response = array();
447
448
do
449
{
450
$line = $this->
readLine
();
451
if
($line ===
false
)
452
{
453
return
false
;
454
}
455
456
$response[] = $line;
457
}
458
while
(!preg_match(
'/^ \d{3} ( \r\n | \x20 ) /x'
, $line));
459
460
return
$response;
461
}
462
463
protected
function
errorMessage
(
$errors
, $details =
null
)
464
{
465
$errors
= array_filter((array)
$errors
);
466
$details = array_filter((array) $details);
467
468
foreach
(
$errors
as $i => $error)
469
{
470
$errors
[$i] = static::decodeError($error);
471
$this->errors->setError(
new
Main\
Error
((
string
)
$errors
[$i], $error > 0 ? $error : 0));
472
}
473
474
$error = join(
': '
,
$errors
);
475
if
($details)
476
{
477
$error .= sprintf(
' (SMTP: %s)'
, join(
': '
, $details));
478
479
$this->errors->setError(
new
Main\
Error
(
'SMTP'
, -1));
480
foreach
($details as $item)
481
{
482
$this->errors->setError(
new
Main\
Error
((
string
) $item, -1));
483
}
484
}
485
486
return
$error;
487
}
488
494
public
function
getErrors
()
495
{
496
return
$this->errors
;
497
}
498
505
public
static
function
decodeError
($code)
506
{
507
switch
($code)
508
{
509
case
self::ERR_CONNECT
:
510
return
Loc::getMessage
(
'MAIL_SMTP_ERR_CONNECT'
);
511
case
self::ERR_REJECTED
:
512
return
Loc::getMessage
(
'MAIL_SMTP_ERR_REJECTED'
);
513
case
self::ERR_COMMUNICATE
:
514
return
Loc::getMessage
(
'MAIL_SMTP_ERR_COMMUNICATE'
);
515
case
self::ERR_EMPTY_RESPONSE
:
516
return
Loc::getMessage
(
'MAIL_SMTP_ERR_EMPTY_RESPONSE'
);
517
518
case
self::ERR_STARTTLS
:
519
return
Loc::getMessage
(
'MAIL_SMTP_ERR_STARTTLS'
);
520
case
self::ERR_COMMAND_REJECTED
:
521
return
Loc::getMessage
(
'MAIL_SMTP_ERR_COMMAND_REJECTED'
);
522
case
self::ERR_CAPABILITY
:
523
return
Loc::getMessage
(
'MAIL_SMTP_ERR_CAPABILITY'
);
524
case
self::ERR_AUTH
:
525
return
Loc::getMessage
(
'MAIL_SMTP_ERR_AUTH'
);
526
case
self::ERR_AUTH_MECH
:
527
return
Loc::getMessage
(
'MAIL_SMTP_ERR_AUTH_MECH'
);
528
529
default
:
530
return
Loc::getMessage
(
'MAIL_SMTP_ERR_DEFAULT'
);
531
}
532
}
533
541
public
function
setIsOauth
(
bool
$value): self
542
{
543
$this->isOauth = $value;
544
return
$this;
545
}
546
547
}
Bitrix\Mail\Smtp
Definition
smtp.php:13
Bitrix\Mail\Smtp\$options
$options
Definition
smtp.php:28
Bitrix\Mail\Smtp\ERR_REJECTED
const ERR_REJECTED
Definition
smtp.php:15
Bitrix\Mail\Smtp\capability
capability(&$error)
Definition
smtp.php:206
Bitrix\Mail\Smtp\ERR_COMMAND_REJECTED
const ERR_COMMAND_REJECTED
Definition
smtp.php:20
Bitrix\Mail\Smtp\ERR_CONNECT
const ERR_CONNECT
Definition
smtp.php:14
Bitrix\Mail\Smtp\__destruct
__destruct()
Definition
smtp.php:76
Bitrix\Mail\Smtp\reset
reset()
Definition
smtp.php:96
Bitrix\Mail\Smtp\$isOauth
bool $isOauth
Definition
smtp.php:35
Bitrix\Mail\Smtp\ERR_AUTH_MECH
const ERR_AUTH_MECH
Definition
smtp.php:23
Bitrix\Mail\Smtp\errorMessage
errorMessage($errors, $details=null)
Definition
smtp.php:463
Bitrix\Mail\Smtp\ERR_EMPTY_RESPONSE
const ERR_EMPTY_RESPONSE
Definition
smtp.php:17
Bitrix\Mail\Smtp\readLine
readLine()
Definition
smtp.php:407
Bitrix\Mail\Smtp\getErrors
getErrors()
Definition
smtp.php:494
Bitrix\Mail\Smtp\ERR_AUTH
const ERR_AUTH
Definition
smtp.php:22
Bitrix\Mail\Smtp\starttls
starttls(&$error)
Definition
smtp.php:168
Bitrix\Mail\Smtp\connect
connect(&$error)
Definition
smtp.php:109
Bitrix\Mail\Smtp\$sessCapability
$sessCapability
Definition
smtp.php:26
Bitrix\Mail\Smtp\ERR_CAPABILITY
const ERR_CAPABILITY
Definition
smtp.php:21
Bitrix\Mail\Smtp\$stream
$stream
Definition
smtp.php:25
Bitrix\Mail\Smtp\authenticate
authenticate(&$error)
Definition
smtp.php:249
Bitrix\Mail\Smtp\exchange
exchange($data, &$error)
Definition
smtp.php:350
Bitrix\Mail\Smtp\$errors
$errors
Definition
smtp.php:25
Bitrix\Mail\Smtp\executeCommand
executeCommand($command, &$error)
Definition
smtp.php:327
Bitrix\Mail\Smtp\disconnect
disconnect()
Definition
smtp.php:86
Bitrix\Mail\Smtp\decodeError
static decodeError($code)
Definition
smtp.php:505
Bitrix\Mail\Smtp\sendData
sendData($data)
Definition
smtp.php:376
Bitrix\Mail\Smtp\ERR_STARTTLS
const ERR_STARTTLS
Definition
smtp.php:19
Bitrix\Mail\Smtp\setIsOauth
setIsOauth(bool $value)
Definition
smtp.php:541
Bitrix\Mail\Smtp\__construct
__construct($host, $port, $tls, $strict, $login, $password, $encoding=null)
Definition
smtp.php:48
Bitrix\Mail\Smtp\ERR_COMMUNICATE
const ERR_COMMUNICATE
Definition
smtp.php:16
Bitrix\Mail\Smtp\readResponse
readResponse()
Definition
smtp.php:444
Bitrix\Main\Context\getCurrent
static getCurrent()
Definition
context.php:241
Bitrix\Main\ErrorCollection
Definition
errorcollection.php:14
Bitrix\Main\Error
Definition
error.php:14
Bitrix\Main\Localization\Loc
Definition
loc.php:11
Bitrix\Main\Localization\Loc\loadMessages
static loadMessages($file)
Definition
loc.php:64
Bitrix\Main\Localization\Loc\getMessage
static getMessage($code, $replace=null, $language=null)
Definition
loc.php:29
Bitrix\Main\Text\BinaryString
Definition
binarystring.php:14
Bitrix\Main\Text\Encoding
Definition
encoding.php:8
Bitrix\Mail
Definition
blacklist.php:3
Bitrix\Main
modules
mail
lib
smtp.php
Создано системой
1.10.0