Completed
Push — master ( a81350...e6a872 )
by Wanderson
05:44
created

SMTP::getServerExt()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 12
nc 5
nop 1
dl 0
loc 22
rs 8.6737
c 0
b 0
f 0
1
<?php
2
/**
3
 * PHPMailer RFC821 SMTP email transport class.
4
 * PHP Version 5.5.
5
 *
6
 * @see       https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
7
 *
8
 * @author    Marcus Bointon (Synchro/coolbru) <[email protected]>
9
 * @author    Jim Jagielski (jimjag) <[email protected]>
10
 * @author    Andy Prevost (codeworxtech) <[email protected]>
11
 * @author    Brent R. Matzelle (original founder)
12
 * @copyright 2012 - 2017 Marcus Bointon
13
 * @copyright 2010 - 2012 Jim Jagielski
14
 * @copyright 2004 - 2009 Andy Prevost
15
 * @license   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
16
 * @note      This program is distributed in the hope that it will be useful - WITHOUT
17
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
18
 * FITNESS FOR A PARTICULAR PURPOSE.
19
 */
20
21
namespace PHPMailer\PHPMailer;
22
23
/**
24
 * PHPMailer RFC821 SMTP email transport class.
25
 * Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server.
26
 *
27
 * @author  Chris Ryan
28
 * @author  Marcus Bointon <[email protected]>
29
 */
30
class SMTP
31
{
32
    /**
33
     * The PHPMailer SMTP version number.
34
     *
35
     * @var string
36
     */
37
    const VERSION = '6.0.1';
38
39
    /**
40
     * SMTP line break constant.
41
     *
42
     * @var string
43
     */
44
    const LE = "\r\n";
45
46
    /**
47
     * The SMTP port to use if one is not specified.
48
     *
49
     * @var int
50
     */
51
    const DEFAULT_PORT = 25;
52
53
    /**
54
     * The maximum line length allowed by RFC 2822 section 2.1.1.
55
     *
56
     * @var int
57
     */
58
    const MAX_LINE_LENGTH = 998;
59
60
    /**
61
     * Debug level for no output.
62
     */
63
    const DEBUG_OFF = 0;
64
65
    /**
66
     * Debug level to show client -> server messages.
67
     */
68
    const DEBUG_CLIENT = 1;
69
70
    /**
71
     * Debug level to show client -> server and server -> client messages.
72
     */
73
    const DEBUG_SERVER = 2;
74
75
    /**
76
     * Debug level to show connection status, client -> server and server -> client messages.
77
     */
78
    const DEBUG_CONNECTION = 3;
79
80
    /**
81
     * Debug level to show all messages.
82
     */
83
    const DEBUG_LOWLEVEL = 4;
84
85
    /**
86
     * Debug output level.
87
     * Options:
88
     * * self::DEBUG_OFF (`0`) No debug output, default
89
     * * self::DEBUG_CLIENT (`1`) Client commands
90
     * * self::DEBUG_SERVER (`2`) Client commands and server responses
91
     * * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status
92
     * * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages.
93
     *
94
     * @var int
95
     */
96
    public $do_debug = self::DEBUG_OFF;
97
98
    /**
99
     * How to handle debug output.
100
     * Options:
101
     * * `echo` Output plain-text as-is, appropriate for CLI
102
     * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output
103
     * * `error_log` Output to error log as configured in php.ini
104
     * Alternatively, you can provide a callable expecting two params: a message string and the debug level:
105
     *
106
     * ```php
107
     * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
108
     * ```
109
     *
110
     * Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug`
111
     * level output is used:
112
     *
113
     * ```php
114
     * $mail->Debugoutput = new myPsr3Logger;
115
     * ```
116
     *
117
     * @var string|callable|\Psr\Log\LoggerInterface
118
     */
119
    public $Debugoutput = 'echo';
120
121
    /**
122
     * Whether to use VERP.
123
     *
124
     * @see http://en.wikipedia.org/wiki/Variable_envelope_return_path
125
     * @see http://www.postfix.org/VERP_README.html Info on VERP
126
     *
127
     * @var bool
128
     */
129
    public $do_verp = false;
130
131
    /**
132
     * The timeout value for connection, in seconds.
133
     * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2.
134
     * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure.
135
     *
136
     * @see http://tools.ietf.org/html/rfc2821#section-4.5.3.2
137
     *
138
     * @var int
139
     */
140
    public $Timeout = 300;
141
142
    /**
143
     * How long to wait for commands to complete, in seconds.
144
     * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2.
145
     *
146
     * @var int
147
     */
148
    public $Timelimit = 300;
149
150
    /**
151
     * Patterns to extract an SMTP transaction id from reply to a DATA command.
152
     * The first capture group in each regex will be used as the ID.
153
     * MS ESMTP returns the message ID, which may not be correct for internal tracking.
154
     *
155
     * @var string[]
156
     */
157
    protected $smtp_transaction_id_patterns = [
158
        'exim' => '/[0-9]{3} OK id=(.*)/',
159
        'sendmail' => '/[0-9]{3} 2.0.0 (.*) Message/',
160
        'postfix' => '/[0-9]{3} 2.0.0 Ok: queued as (.*)/',
161
        'Microsoft_ESMTP' => '/[0-9]{3} 2.[0-9].0 (.*)@(?:.*) Queued mail for delivery/',
162
        'Amazon_SES' => '/[0-9]{3} Ok (.*)/',
163
    ];
164
165
    /**
166
     * The last transaction ID issued in response to a DATA command,
167
     * if one was detected.
168
     *
169
     * @var string|bool|null
170
     */
171
    protected $last_smtp_transaction_id;
172
173
    /**
174
     * The socket for the server connection.
175
     *
176
     * @var ?resource
177
     */
178
    protected $smtp_conn;
179
180
    /**
181
     * Error information, if any, for the last SMTP command.
182
     *
183
     * @var array
184
     */
185
    protected $error = [
186
        'error' => '',
187
        'detail' => '',
188
        'smtp_code' => '',
189
        'smtp_code_ex' => '',
190
    ];
191
192
    /**
193
     * The reply the server sent to us for HELO.
194
     * If null, no HELO string has yet been received.
195
     *
196
     * @var string|null
197
     */
198
    protected $helo_rply = null;
199
200
    /**
201
     * The set of SMTP extensions sent in reply to EHLO command.
202
     * Indexes of the array are extension names.
203
     * Value at index 'HELO' or 'EHLO' (according to command that was sent)
204
     * represents the server name. In case of HELO it is the only element of the array.
205
     * Other values can be boolean TRUE or an array containing extension options.
206
     * If null, no HELO/EHLO string has yet been received.
207
     *
208
     * @var array|null
209
     */
210
    protected $server_caps = null;
211
212
    /**
213
     * The most recent reply received from the server.
214
     *
215
     * @var string
216
     */
217
    protected $last_reply = '';
218
219
    /**
220
     * Output debugging info via a user-selected method.
221
     *
222
     * @param string $str   Debug string to output
223
     * @param int    $level The debug level of this message; see DEBUG_* constants
224
     *
225
     * @see SMTP::$Debugoutput
226
     * @see SMTP::$do_debug
227
     */
228
    protected function edebug($str, $level = 0)
229
    {
230
        if ($level > $this->do_debug) {
231
            return;
232
        }
233
        //Is this a PSR-3 logger?
234
        if (is_a($this->Debugoutput, 'Psr\Log\LoggerInterface')) {
235
            $this->Debugoutput->debug($str);
0 ignored issues
show
Bug introduced by
The method debug cannot be called on $this->Debugoutput (of type callable).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
236
237
            return;
238
        }
239
        //Avoid clash with built-in function names
240 View Code Duplication
        if (!in_array($this->Debugoutput, ['error_log', 'html', 'echo']) and is_callable($this->Debugoutput)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
241
            call_user_func($this->Debugoutput, $str, $level);
242
243
            return;
244
        }
245 View Code Duplication
        switch ($this->Debugoutput) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
246
            case 'error_log':
247
                //Don't output, just log
248
                error_log($str);
249
                break;
250
            case 'html':
251
                //Cleans up output a bit for a better looking, HTML-safe output
252
                echo gmdate('Y-m-d H:i:s'), ' ', htmlentities(
253
                    preg_replace('/[\r\n]+/', '', $str),
254
                    ENT_QUOTES,
255
                    'UTF-8'
256
                ), "<br>\n";
257
                break;
258
            case 'echo':
259
            default:
260
                //Normalize line breaks
261
                $str = preg_replace('/\r\n|\r/ms', "\n", $str);
262
                echo gmdate('Y-m-d H:i:s'),
263
                "\t",
264
                    //Trim trailing space
265
                trim(
266
                //Indent for readability, except for trailing break
267
                    str_replace(
268
                        "\n",
269
                        "\n                   \t                  ",
270
                        trim($str)
271
                    )
272
                ),
273
                "\n";
274
        }
275
    }
276
277
    /**
278
     * Connect to an SMTP server.
279
     *
280
     * @param string $host    SMTP server IP or host name
281
     * @param int    $port    The port number to connect to
282
     * @param int    $timeout How long to wait for the connection to open
283
     * @param array  $options An array of options for stream_context_create()
284
     *
285
     * @return bool
286
     */
287
    public function connect($host, $port = null, $timeout = 30, $options = [])
288
    {
289
        static $streamok;
290
        //This is enabled by default since 5.0.0 but some providers disable it
291
        //Check this once and cache the result
292
        if (null === $streamok) {
293
            $streamok = function_exists('stream_socket_client');
294
        }
295
        // Clear errors to avoid confusion
296
        $this->setError('');
297
        // Make sure we are __not__ connected
298
        if ($this->connected()) {
299
            // Already connected, generate error
300
            $this->setError('Already connected to a server');
301
302
            return false;
303
        }
304
        if (empty($port)) {
305
            $port = self::DEFAULT_PORT;
306
        }
307
        // Connect to the SMTP server
308
        $this->edebug(
309
            "Connection: opening to $host:$port, timeout=$timeout, options=" .
310
            (count($options) > 0 ? var_export($options, true) : 'array()'),
311
            self::DEBUG_CONNECTION
312
        );
313
        $errno = 0;
314
        $errstr = '';
315
        if ($streamok) {
316
            $socket_context = stream_context_create($options);
317
            set_error_handler([$this, 'errorHandler']);
318
            $this->smtp_conn = stream_socket_client(
319
                $host . ':' . $port,
320
                $errno,
321
                $errstr,
322
                $timeout,
323
                STREAM_CLIENT_CONNECT,
324
                $socket_context
325
            );
326
            restore_error_handler();
327
        } else {
328
            //Fall back to fsockopen which should work in more places, but is missing some features
329
            $this->edebug(
330
                'Connection: stream_socket_client not available, falling back to fsockopen',
331
                self::DEBUG_CONNECTION
332
            );
333
            set_error_handler([$this, 'errorHandler']);
334
            $this->smtp_conn = fsockopen(
335
                $host,
336
                $port,
337
                $errno,
338
                $errstr,
339
                $timeout
340
            );
341
            restore_error_handler();
342
        }
343
        // Verify we connected properly
344
        if (!is_resource($this->smtp_conn)) {
345
            $this->setError(
346
                'Failed to connect to server',
347
                '',
348
                (string) $errno,
349
                (string) $errstr
350
            );
351
            $this->edebug(
352
                'SMTP ERROR: ' . $this->error['error']
353
                . ": $errstr ($errno)",
354
                self::DEBUG_CLIENT
355
            );
356
357
            return false;
358
        }
359
        $this->edebug('Connection: opened', self::DEBUG_CONNECTION);
360
        // SMTP server can take longer to respond, give longer timeout for first read
361
        // Windows does not have support for this timeout function
362
        if (substr(PHP_OS, 0, 3) != 'WIN') {
363
            $max = ini_get('max_execution_time');
364
            // Don't bother if unlimited
365
            if (0 != $max and $timeout > $max) {
366
                @set_time_limit($timeout);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
367
            }
368
            stream_set_timeout($this->smtp_conn, $timeout, 0);
369
        }
370
        // Get any announcement
371
        $announce = $this->get_lines();
372
        $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER);
373
374
        return true;
375
    }
376
377
    /**
378
     * Initiate a TLS (encrypted) session.
379
     *
380
     * @return bool
381
     */
382
    public function startTLS()
383
    {
384
        if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) {
385
            return false;
386
        }
387
388
        //Allow the best TLS version(s) we can
389
        $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
390
391
        //PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT
392
        //so add them back in manually if we can
393
        if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
394
            $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
395
            $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
396
        }
397
398
        // Begin encrypted connection
399
        set_error_handler([$this, 'errorHandler']);
400
        $crypto_ok = stream_socket_enable_crypto(
401
            $this->smtp_conn,
402
            true,
403
            $crypto_method
404
        );
405
        restore_error_handler();
406
407
        return (bool) $crypto_ok;
408
    }
409
410
    /**
411
     * Perform SMTP authentication.
412
     * Must be run after hello().
413
     *
414
     * @see    hello()
415
     *
416
     * @param string $username The user name
417
     * @param string $password The password
418
     * @param string $authtype The auth type (CRAM-MD5, PLAIN, LOGIN, XOAUTH2)
419
     * @param OAuth  $OAuth    An optional OAuth instance for XOAUTH2 authentication
420
     *
421
     * @return bool True if successfully authenticated
422
     */
423
    public function authenticate(
424
        $username,
425
        $password,
426
        $authtype = null,
427
        $OAuth = null
428
    ) {
429
        if (!$this->server_caps) {
430
            $this->setError('Authentication is not allowed before HELO/EHLO');
431
432
            return false;
433
        }
434
435
        if (array_key_exists('EHLO', $this->server_caps)) {
436
            // SMTP extensions are available; try to find a proper authentication method
437
            if (!array_key_exists('AUTH', $this->server_caps)) {
438
                $this->setError('Authentication is not allowed at this stage');
439
                // 'at this stage' means that auth may be allowed after the stage changes
440
                // e.g. after STARTTLS
441
442
                return false;
443
            }
444
445
            $this->edebug('Auth method requested: ' . ($authtype ? $authtype : 'UNKNOWN'), self::DEBUG_LOWLEVEL);
446
            $this->edebug(
447
                'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']),
448
                self::DEBUG_LOWLEVEL
449
            );
450
451
            //If we have requested a specific auth type, check the server supports it before trying others
452
            if (!in_array($authtype, $this->server_caps['AUTH'])) {
453
                $this->edebug('Requested auth method not available: ' . $authtype, self::DEBUG_LOWLEVEL);
454
                $authtype = null;
455
            }
456
457
            if (empty($authtype)) {
458
                //If no auth mechanism is specified, attempt to use these, in this order
459
                //Try CRAM-MD5 first as it's more secure than the others
460
                foreach (['CRAM-MD5', 'LOGIN', 'PLAIN', 'XOAUTH2'] as $method) {
461
                    if (in_array($method, $this->server_caps['AUTH'])) {
462
                        $authtype = $method;
463
                        break;
464
                    }
465
                }
466
                if (empty($authtype)) {
467
                    $this->setError('No supported authentication methods found');
468
469
                    return false;
470
                }
471
                self::edebug('Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL);
472
            }
473
474
            if (!in_array($authtype, $this->server_caps['AUTH'])) {
475
                $this->setError("The requested authentication method \"$authtype\" is not supported by the server");
476
477
                return false;
478
            }
479
        } elseif (empty($authtype)) {
480
            $authtype = 'LOGIN';
481
        }
482
        switch ($authtype) {
483
            case 'PLAIN':
484
                // Start authentication
485
                if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) {
486
                    return false;
487
                }
488
                // Send encoded username and password
489
                if (!$this->sendCommand(
490
                    'User & Password',
491
                    base64_encode("\0" . $username . "\0" . $password),
492
                    235
493
                )
494
                ) {
495
                    return false;
496
                }
497
                break;
498
            case 'LOGIN':
499
                // Start authentication
500
                if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) {
501
                    return false;
502
                }
503
                if (!$this->sendCommand('Username', base64_encode($username), 334)) {
504
                    return false;
505
                }
506
                if (!$this->sendCommand('Password', base64_encode($password), 235)) {
507
                    return false;
508
                }
509
                break;
510
            case 'CRAM-MD5':
511
                // Start authentication
512
                if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) {
513
                    return false;
514
                }
515
                // Get the challenge
516
                $challenge = base64_decode(substr($this->last_reply, 4));
517
518
                // Build the response
519
                $response = $username . ' ' . $this->hmac($challenge, $password);
520
521
                // send encoded credentials
522
                return $this->sendCommand('Username', base64_encode($response), 235);
523
            case 'XOAUTH2':
524
                //The OAuth instance must be set up prior to requesting auth.
525
                if (null === $OAuth) {
526
                    return false;
527
                }
528
                $oauth = $OAuth->getOauth64();
529
530
                // Start authentication
531
                if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
532
                    return false;
533
                }
534
                break;
535
            default:
536
                $this->setError("Authentication method \"$authtype\" is not supported");
537
538
                return false;
539
        }
540
541
        return true;
542
    }
543
544
    /**
545
     * Calculate an MD5 HMAC hash.
546
     * Works like hash_hmac('md5', $data, $key)
547
     * in case that function is not available.
548
     *
549
     * @param string $data The data to hash
550
     * @param string $key  The key to hash with
551
     *
552
     * @return string
553
     */
554
    protected function hmac($data, $key)
555
    {
556
        if (function_exists('hash_hmac')) {
557
            return hash_hmac('md5', $data, $key);
558
        }
559
560
        // The following borrowed from
561
        // http://php.net/manual/en/function.mhash.php#27225
562
563
        // RFC 2104 HMAC implementation for php.
564
        // Creates an md5 HMAC.
565
        // Eliminates the need to install mhash to compute a HMAC
566
        // by Lance Rushing
567
568
        $bytelen = 64; // byte length for md5
569
        if (strlen($key) > $bytelen) {
570
            $key = pack('H*', md5($key));
571
        }
572
        $key = str_pad($key, $bytelen, chr(0x00));
573
        $ipad = str_pad('', $bytelen, chr(0x36));
574
        $opad = str_pad('', $bytelen, chr(0x5c));
575
        $k_ipad = $key ^ $ipad;
576
        $k_opad = $key ^ $opad;
577
578
        return md5($k_opad . pack('H*', md5($k_ipad . $data)));
579
    }
580
581
    /**
582
     * Check connection state.
583
     *
584
     * @return bool True if connected
585
     */
586
    public function connected()
587
    {
588
        if (is_resource($this->smtp_conn)) {
589
            $sock_status = stream_get_meta_data($this->smtp_conn);
590
            if ($sock_status['eof']) {
591
                // The socket is valid but we are not connected
592
                $this->edebug(
593
                    'SMTP NOTICE: EOF caught while checking if connected',
594
                    self::DEBUG_CLIENT
595
                );
596
                $this->close();
597
598
                return false;
599
            }
600
601
            return true; // everything looks good
602
        }
603
604
        return false;
605
    }
606
607
    /**
608
     * Close the socket and clean up the state of the class.
609
     * Don't use this function without first trying to use QUIT.
610
     *
611
     * @see quit()
612
     */
613
    public function close()
614
    {
615
        $this->setError('');
616
        $this->server_caps = null;
617
        $this->helo_rply = null;
618
        if (is_resource($this->smtp_conn)) {
619
            // close the connection and cleanup
620
            fclose($this->smtp_conn);
621
            $this->smtp_conn = null; //Makes for cleaner serialization
622
            $this->edebug('Connection: closed', self::DEBUG_CONNECTION);
623
        }
624
    }
625
626
    /**
627
     * Send an SMTP DATA command.
628
     * Issues a data command and sends the msg_data to the server,
629
     * finializing the mail transaction. $msg_data is the message
630
     * that is to be send with the headers. Each header needs to be
631
     * on a single line followed by a <CRLF> with the message headers
632
     * and the message body being separated by an additional <CRLF>.
633
     * Implements RFC 821: DATA <CRLF>.
634
     *
635
     * @param string $msg_data Message data to send
636
     *
637
     * @return bool
638
     */
639
    public function data($msg_data)
640
    {
641
        //This will use the standard timelimit
642
        if (!$this->sendCommand('DATA', 'DATA', 354)) {
643
            return false;
644
        }
645
646
        /* The server is ready to accept data!
647
         * According to rfc821 we should not send more than 1000 characters on a single line (including the LE)
648
         * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into
649
         * smaller lines to fit within the limit.
650
         * We will also look for lines that start with a '.' and prepend an additional '.'.
651
         * NOTE: this does not count towards line-length limit.
652
         */
653
654
        // Normalize line breaks before exploding
655
        $lines = explode("\n", str_replace(["\r\n", "\r"], "\n", $msg_data));
656
657
        /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field
658
         * of the first line (':' separated) does not contain a space then it _should_ be a header and we will
659
         * process all lines before a blank line as headers.
660
         */
661
662
        $field = substr($lines[0], 0, strpos($lines[0], ':'));
663
        $in_headers = false;
664
        if (!empty($field) and strpos($field, ' ') === false) {
665
            $in_headers = true;
666
        }
667
668
        foreach ($lines as $line) {
669
            $lines_out = [];
670
            if ($in_headers and $line == '') {
671
                $in_headers = false;
672
            }
673
            //Break this line up into several smaller lines if it's too long
674
            //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len),
0 ignored issues
show
Unused Code Comprehensibility introduced by
49% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
675
            while (isset($line[self::MAX_LINE_LENGTH])) {
676
                //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on
677
                //so as to avoid breaking in the middle of a word
678
                $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' ');
679
                //Deliberately matches both false and 0
680
                if (!$pos) {
681
                    //No nice break found, add a hard break
682
                    $pos = self::MAX_LINE_LENGTH - 1;
683
                    $lines_out[] = substr($line, 0, $pos);
684
                    $line = substr($line, $pos);
685
                } else {
686
                    //Break at the found point
687
                    $lines_out[] = substr($line, 0, $pos);
688
                    //Move along by the amount we dealt with
689
                    $line = substr($line, $pos + 1);
690
                }
691
                //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1
692
                if ($in_headers) {
693
                    $line = "\t" . $line;
694
                }
695
            }
696
            $lines_out[] = $line;
697
698
            //Send the lines to the server
699
            foreach ($lines_out as $line_out) {
700
                //RFC2821 section 4.5.2
701
                if (!empty($line_out) and $line_out[0] == '.') {
702
                    $line_out = '.' . $line_out;
703
                }
704
                $this->client_send($line_out . static::LE);
705
            }
706
        }
707
708
        //Message data has been sent, complete the command
709
        //Increase timelimit for end of DATA command
710
        $savetimelimit = $this->Timelimit;
711
        $this->Timelimit = $this->Timelimit * 2;
712
        $result = $this->sendCommand('DATA END', '.', 250);
713
        $this->recordLastTransactionID();
714
        //Restore timelimit
715
        $this->Timelimit = $savetimelimit;
716
717
        return $result;
718
    }
719
720
    /**
721
     * Send an SMTP HELO or EHLO command.
722
     * Used to identify the sending server to the receiving server.
723
     * This makes sure that client and server are in a known state.
724
     * Implements RFC 821: HELO <SP> <domain> <CRLF>
725
     * and RFC 2821 EHLO.
726
     *
727
     * @param string $host The host name or IP to connect to
728
     *
729
     * @return bool
730
     */
731
    public function hello($host = '')
732
    {
733
        //Try extended hello first (RFC 2821)
734
        return (bool) ($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host));
735
    }
736
737
    /**
738
     * Send an SMTP HELO or EHLO command.
739
     * Low-level implementation used by hello().
740
     *
741
     * @param string $hello The HELO string
742
     * @param string $host  The hostname to say we are
743
     *
744
     * @return bool
745
     *
746
     * @see    hello()
747
     */
748
    protected function sendHello($hello, $host)
749
    {
750
        $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250);
751
        $this->helo_rply = $this->last_reply;
752
        if ($noerror) {
753
            $this->parseHelloFields($hello);
754
        } else {
755
            $this->server_caps = null;
756
        }
757
758
        return $noerror;
759
    }
760
761
    /**
762
     * Parse a reply to HELO/EHLO command to discover server extensions.
763
     * In case of HELO, the only parameter that can be discovered is a server name.
764
     *
765
     * @param string $type `HELO` or `EHLO`
766
     */
767
    protected function parseHelloFields($type)
768
    {
769
        $this->server_caps = [];
770
        $lines = explode("\n", $this->helo_rply);
771
772
        foreach ($lines as $n => $s) {
773
            //First 4 chars contain response code followed by - or space
774
            $s = trim(substr($s, 4));
775
            if (empty($s)) {
776
                continue;
777
            }
778
            $fields = explode(' ', $s);
779
            if (!empty($fields)) {
780
                if (!$n) {
781
                    $name = $type;
782
                    $fields = $fields[0];
783
                } else {
784
                    $name = array_shift($fields);
785
                    switch ($name) {
786
                        case 'SIZE':
787
                            $fields = ($fields ? $fields[0] : 0);
788
                            break;
789
                        case 'AUTH':
790
                            if (!is_array($fields)) {
791
                                $fields = [];
792
                            }
793
                            break;
794
                        default:
795
                            $fields = true;
796
                    }
797
                }
798
                $this->server_caps[$name] = $fields;
799
            }
800
        }
801
    }
802
803
    /**
804
     * Send an SMTP MAIL command.
805
     * Starts a mail transaction from the email address specified in
806
     * $from. Returns true if successful or false otherwise. If True
807
     * the mail transaction is started and then one or more recipient
808
     * commands may be called followed by a data command.
809
     * Implements RFC 821: MAIL <SP> FROM:<reverse-path> <CRLF>.
810
     *
811
     * @param string $from Source address of this message
812
     *
813
     * @return bool
814
     */
815
    public function mail($from)
816
    {
817
        $useVerp = ($this->do_verp ? ' XVERP' : '');
818
819
        return $this->sendCommand(
820
            'MAIL FROM',
821
            'MAIL FROM:<' . $from . '>' . $useVerp,
822
            250
823
        );
824
    }
825
826
    /**
827
     * Send an SMTP QUIT command.
828
     * Closes the socket if there is no error or the $close_on_error argument is true.
829
     * Implements from RFC 821: QUIT <CRLF>.
830
     *
831
     * @param bool $close_on_error Should the connection close if an error occurs?
832
     *
833
     * @return bool
834
     */
835
    public function quit($close_on_error = true)
836
    {
837
        $noerror = $this->sendCommand('QUIT', 'QUIT', 221);
838
        $err = $this->error; //Save any error
839
        if ($noerror or $close_on_error) {
840
            $this->close();
841
            $this->error = $err; //Restore any error from the quit command
842
        }
843
844
        return $noerror;
845
    }
846
847
    /**
848
     * Send an SMTP RCPT command.
849
     * Sets the TO argument to $toaddr.
850
     * Returns true if the recipient was accepted false if it was rejected.
851
     * Implements from RFC 821: RCPT <SP> TO:<forward-path> <CRLF>.
852
     *
853
     * @param string $address The address the message is being sent to
854
     *
855
     * @return bool
856
     */
857
    public function recipient($address)
858
    {
859
        return $this->sendCommand(
860
            'RCPT TO',
861
            'RCPT TO:<' . $address . '>',
862
            [250, 251]
863
        );
864
    }
865
866
    /**
867
     * Send an SMTP RSET command.
868
     * Abort any transaction that is currently in progress.
869
     * Implements RFC 821: RSET <CRLF>.
870
     *
871
     * @return bool True on success
872
     */
873
    public function reset()
874
    {
875
        return $this->sendCommand('RSET', 'RSET', 250);
876
    }
877
878
    /**
879
     * Send a command to an SMTP server and check its return code.
880
     *
881
     * @param string    $command       The command name - not sent to the server
882
     * @param string    $commandstring The actual command to send
883
     * @param int|array $expect        One or more expected integer success codes
884
     *
885
     * @return bool True on success
886
     */
887
    protected function sendCommand($command, $commandstring, $expect)
888
    {
889
        if (!$this->connected()) {
890
            $this->setError("Called $command without being connected");
891
892
            return false;
893
        }
894
        //Reject line breaks in all commands
895
        if (strpos($commandstring, "\n") !== false or strpos($commandstring, "\r") !== false) {
896
            $this->setError("Command '$command' contained line breaks");
897
898
            return false;
899
        }
900
        $this->client_send($commandstring . static::LE);
901
902
        $this->last_reply = $this->get_lines();
903
        // Fetch SMTP code and possible error code explanation
904
        $matches = [];
905
        if (preg_match('/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]) )?/', $this->last_reply, $matches)) {
906
            $code = $matches[1];
907
            $code_ex = (count($matches) > 2 ? $matches[2] : null);
908
            // Cut off error code from each response line
909
            $detail = preg_replace(
910
                "/{$code}[ -]" .
911
                ($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . '/m',
912
                '',
913
                $this->last_reply
914
            );
915
        } else {
916
            // Fall back to simple parsing if regex fails
917
            $code = substr($this->last_reply, 0, 3);
918
            $code_ex = null;
919
            $detail = substr($this->last_reply, 4);
920
        }
921
922
        $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
923
924
        if (!in_array($code, (array) $expect)) {
925
            $this->setError(
926
                "$command command failed",
927
                $detail,
928
                $code,
929
                $code_ex
930
            );
931
            $this->edebug(
932
                'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply,
933
                self::DEBUG_CLIENT
934
            );
935
936
            return false;
937
        }
938
939
        $this->setError('');
940
941
        return true;
942
    }
943
944
    /**
945
     * Send an SMTP SAML command.
946
     * Starts a mail transaction from the email address specified in $from.
947
     * Returns true if successful or false otherwise. If True
948
     * the mail transaction is started and then one or more recipient
949
     * commands may be called followed by a data command. This command
950
     * will send the message to the users terminal if they are logged
951
     * in and send them an email.
952
     * Implements RFC 821: SAML <SP> FROM:<reverse-path> <CRLF>.
953
     *
954
     * @param string $from The address the message is from
955
     *
956
     * @return bool
957
     */
958
    public function sendAndMail($from)
959
    {
960
        return $this->sendCommand('SAML', "SAML FROM:$from", 250);
961
    }
962
963
    /**
964
     * Send an SMTP VRFY command.
965
     *
966
     * @param string $name The name to verify
967
     *
968
     * @return bool
969
     */
970
    public function verify($name)
971
    {
972
        return $this->sendCommand('VRFY', "VRFY $name", [250, 251]);
973
    }
974
975
    /**
976
     * Send an SMTP NOOP command.
977
     * Used to keep keep-alives alive, doesn't actually do anything.
978
     *
979
     * @return bool
980
     */
981
    public function noop()
982
    {
983
        return $this->sendCommand('NOOP', 'NOOP', 250);
984
    }
985
986
    /**
987
     * Send an SMTP TURN command.
988
     * This is an optional command for SMTP that this class does not support.
989
     * This method is here to make the RFC821 Definition complete for this class
990
     * and _may_ be implemented in future.
991
     * Implements from RFC 821: TURN <CRLF>.
992
     *
993
     * @return bool
994
     */
995
    public function turn()
996
    {
997
        $this->setError('The SMTP TURN command is not implemented');
998
        $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT);
999
1000
        return false;
1001
    }
1002
1003
    /**
1004
     * Send raw data to the server.
1005
     *
1006
     * @param string $data The data to send
1007
     *
1008
     * @return int|bool The number of bytes sent to the server or false on error
1009
     */
1010
    public function client_send($data)
1011
    {
1012
        $this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT);
1013
        set_error_handler([$this, 'errorHandler']);
1014
        $result = fwrite($this->smtp_conn, $data);
1015
        restore_error_handler();
1016
1017
        return $result;
1018
    }
1019
1020
    /**
1021
     * Get the latest error.
1022
     *
1023
     * @return array
1024
     */
1025
    public function getError()
1026
    {
1027
        return $this->error;
1028
    }
1029
1030
    /**
1031
     * Get SMTP extensions available on the server.
1032
     *
1033
     * @return array|null
1034
     */
1035
    public function getServerExtList()
1036
    {
1037
        return $this->server_caps;
1038
    }
1039
1040
    /**
1041
     * Get metadata about the SMTP server from its HELO/EHLO response.
1042
     * The method works in three ways, dependent on argument value and current state:
1043
     *   1. HELO/EHLO has not been sent - returns null and populates $this->error.
1044
     *   2. HELO has been sent -
1045
     *     $name == 'HELO': returns server name
1046
     *     $name == 'EHLO': returns boolean false
1047
     *     $name == any other string: returns null and populates $this->error
1048
     *   3. EHLO has been sent -
1049
     *     $name == 'HELO'|'EHLO': returns the server name
1050
     *     $name == any other string: if extension $name exists, returns True
1051
     *       or its options (e.g. AUTH mechanisms supported). Otherwise returns False.
1052
     *
1053
     * @param string $name Name of SMTP extension or 'HELO'|'EHLO'
1054
     *
1055
     * @return mixed
1056
     */
1057
    public function getServerExt($name)
1058
    {
1059
        if (!$this->server_caps) {
1060
            $this->setError('No HELO/EHLO was sent');
1061
1062
            return;
1063
        }
1064
1065
        if (!array_key_exists($name, $this->server_caps)) {
1066
            if ('HELO' == $name) {
1067
                return $this->server_caps['EHLO'];
1068
            }
1069
            if ('EHLO' == $name || array_key_exists('EHLO', $this->server_caps)) {
1070
                return false;
1071
            }
1072
            $this->setError('HELO handshake was used; No information about server extensions available');
1073
1074
            return;
1075
        }
1076
1077
        return $this->server_caps[$name];
1078
    }
1079
1080
    /**
1081
     * Get the last reply from the server.
1082
     *
1083
     * @return string
1084
     */
1085
    public function getLastReply()
1086
    {
1087
        return $this->last_reply;
1088
    }
1089
1090
    /**
1091
     * Read the SMTP server's response.
1092
     * Either before eof or socket timeout occurs on the operation.
1093
     * With SMTP we can tell if we have more lines to read if the
1094
     * 4th character is '-' symbol. If it is a space then we don't
1095
     * need to read anything else.
1096
     *
1097
     * @return string
1098
     */
1099
    protected function get_lines()
1100
    {
1101
        // If the connection is bad, give up straight away
1102
        if (!is_resource($this->smtp_conn)) {
1103
            return '';
1104
        }
1105
        $data = '';
1106
        $endtime = 0;
1107
        stream_set_timeout($this->smtp_conn, $this->Timeout);
1108
        if ($this->Timelimit > 0) {
1109
            $endtime = time() + $this->Timelimit;
1110
        }
1111
        $selR = [$this->smtp_conn];
1112
        $selW = null;
1113
        while (is_resource($this->smtp_conn) and !feof($this->smtp_conn)) {
1114
            //Must pass vars in here as params are by reference
1115
            if (!stream_select($selR, $selW, $selW, $this->Timelimit)) {
1116
                $this->edebug(
1117
                    'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)',
1118
                    self::DEBUG_LOWLEVEL
1119
                );
1120
                break;
1121
            }
1122
            //Deliberate noise suppression - errors are handled afterwards
1123
            $str = @fgets($this->smtp_conn, 515);
1124
            $this->edebug('SMTP INBOUND: "' . trim($str) . '"', self::DEBUG_LOWLEVEL);
1125
            $data .= $str;
1126
            // If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled),
1127
            // or 4th character is a space, we are done reading, break the loop,
1128
            // string array access is a micro-optimisation over strlen
1129
            if (!isset($str[3]) or (isset($str[3]) and $str[3] == ' ')) {
1130
                break;
1131
            }
1132
            // Timed-out? Log and break
1133
            $info = stream_get_meta_data($this->smtp_conn);
1134
            if ($info['timed_out']) {
1135
                $this->edebug(
1136
                    'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)',
1137
                    self::DEBUG_LOWLEVEL
1138
                );
1139
                break;
1140
            }
1141
            // Now check if reads took too long
1142
            if ($endtime and time() > $endtime) {
1143
                $this->edebug(
1144
                    'SMTP -> get_lines(): timelimit reached (' .
1145
                    $this->Timelimit . ' sec)',
1146
                    self::DEBUG_LOWLEVEL
1147
                );
1148
                break;
1149
            }
1150
        }
1151
1152
        return $data;
1153
    }
1154
1155
    /**
1156
     * Enable or disable VERP address generation.
1157
     *
1158
     * @param bool $enabled
1159
     */
1160
    public function setVerp($enabled = false)
1161
    {
1162
        $this->do_verp = $enabled;
1163
    }
1164
1165
    /**
1166
     * Get VERP address generation mode.
1167
     *
1168
     * @return bool
1169
     */
1170
    public function getVerp()
1171
    {
1172
        return $this->do_verp;
1173
    }
1174
1175
    /**
1176
     * Set error messages and codes.
1177
     *
1178
     * @param string $message      The error message
1179
     * @param string $detail       Further detail on the error
1180
     * @param string $smtp_code    An associated SMTP error code
1181
     * @param string $smtp_code_ex Extended SMTP code
1182
     */
1183
    protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '')
1184
    {
1185
        $this->error = [
1186
            'error' => $message,
1187
            'detail' => $detail,
1188
            'smtp_code' => $smtp_code,
1189
            'smtp_code_ex' => $smtp_code_ex,
1190
        ];
1191
    }
1192
1193
    /**
1194
     * Set debug output method.
1195
     *
1196
     * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it
1197
     */
1198
    public function setDebugOutput($method = 'echo')
1199
    {
1200
        $this->Debugoutput = $method;
1201
    }
1202
1203
    /**
1204
     * Get debug output method.
1205
     *
1206
     * @return string
1207
     */
1208
    public function getDebugOutput()
1209
    {
1210
        return $this->Debugoutput;
1211
    }
1212
1213
    /**
1214
     * Set debug output level.
1215
     *
1216
     * @param int $level
1217
     */
1218
    public function setDebugLevel($level = 0)
1219
    {
1220
        $this->do_debug = $level;
1221
    }
1222
1223
    /**
1224
     * Get debug output level.
1225
     *
1226
     * @return int
1227
     */
1228
    public function getDebugLevel()
1229
    {
1230
        return $this->do_debug;
1231
    }
1232
1233
    /**
1234
     * Set SMTP timeout.
1235
     *
1236
     * @param int $timeout The timeout duration in seconds
1237
     */
1238
    public function setTimeout($timeout = 0)
1239
    {
1240
        $this->Timeout = $timeout;
1241
    }
1242
1243
    /**
1244
     * Get SMTP timeout.
1245
     *
1246
     * @return int
1247
     */
1248
    public function getTimeout()
1249
    {
1250
        return $this->Timeout;
1251
    }
1252
1253
    /**
1254
     * Reports an error number and string.
1255
     *
1256
     * @param int    $errno   The error number returned by PHP
1257
     * @param string $errmsg  The error message returned by PHP
1258
     * @param string $errfile The file the error occurred in
1259
     * @param int    $errline The line number the error occurred on
1260
     */
1261
    protected function errorHandler($errno, $errmsg, $errfile = '', $errline = 0)
1262
    {
1263
        $notice = 'Connection failed.';
1264
        $this->setError(
1265
            $notice,
1266
            $errmsg,
1267
            (string) $errno
1268
        );
1269
        $this->edebug(
1270
            "$notice Error #$errno: $errmsg [$errfile line $errline]",
1271
            self::DEBUG_CONNECTION
1272
        );
1273
    }
1274
1275
    /**
1276
     * Extract and return the ID of the last SMTP transaction based on
1277
     * a list of patterns provided in SMTP::$smtp_transaction_id_patterns.
1278
     * Relies on the host providing the ID in response to a DATA command.
1279
     * If no reply has been received yet, it will return null.
1280
     * If no pattern was matched, it will return false.
1281
     *
1282
     * @return bool|null|string
1283
     */
1284
    protected function recordLastTransactionID()
1285
    {
1286
        $reply = $this->getLastReply();
1287
1288
        if (empty($reply)) {
1289
            $this->last_smtp_transaction_id = null;
1290
        } else {
1291
            $this->last_smtp_transaction_id = false;
1292
            foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
1293
                if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) {
1294
                    $this->last_smtp_transaction_id = $matches[1];
1295
                    break;
1296
                }
1297
            }
1298
        }
1299
1300
        return $this->last_smtp_transaction_id;
1301
    }
1302
1303
    /**
1304
     * Get the queue/transaction ID of the last SMTP transaction
1305
     * If no reply has been received yet, it will return null.
1306
     * If no pattern was matched, it will return false.
1307
     *
1308
     * @return bool|null|string
1309
     *
1310
     * @see recordLastTransactionID()
1311
     */
1312
    public function getLastTransactionID()
1313
    {
1314
        return $this->last_smtp_transaction_id;
1315
    }
1316
}
1317