Passed
Push — master ( 31493b...700ea0 )
by Marcus
06:04
created

src/SMTP.php (12 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
0 ignored issues
show
This class has 1285 lines of code which exceeds the configured maximum of 1000.

Really long classes often contain too much logic and violate the single responsibility principle.

We suggest to take a look at the “Code” section for options on how to refactor this code.

Loading history...
This class has a complexity of 135 which exceeds the configured maximum of 50.

The class complexity is the sum of the complexity of all methods. A very high value is usually an indication that your class does not follow the single reponsibility principle and does more than one job.

Some resources for further reading:

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

Loading history...
31
{
32
    /**
33
     * The PHPMailer SMTP version number.
34
     *
35
     * @var string
36
     */
37
    const VERSION = '6.0.0';
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 = [
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $smtp_transaction_id_patterns exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
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
    ];
163
164
    /**
165
     * The last transaction ID issued in response to a DATA command,
166
     * if one was detected.
167
     *
168
     * @var string|bool|null
169
     */
170
    protected $last_smtp_transaction_id;
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $last_smtp_transaction_id exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
171
172
    /**
173
     * The socket for the server connection.
174
     *
175
     * @var ?resource
176
     */
177
    protected $smtp_conn;
178
179
    /**
180
     * Error information, if any, for the last SMTP command.
181
     *
182
     * @var array
183
     */
184
    protected $error = [
185
        'error' => '',
186
        'detail' => '',
187
        'smtp_code' => '',
188
        'smtp_code_ex' => '',
189
    ];
190
191
    /**
192
     * The reply the server sent to us for HELO.
193
     * If null, no HELO string has yet been received.
194
     *
195
     * @var string|null
196
     */
197
    protected $helo_rply = null;
198
199
    /**
200
     * The set of SMTP extensions sent in reply to EHLO command.
201
     * Indexes of the array are extension names.
202
     * Value at index 'HELO' or 'EHLO' (according to command that was sent)
203
     * represents the server name. In case of HELO it is the only element of the array.
204
     * Other values can be boolean TRUE or an array containing extension options.
205
     * If null, no HELO/EHLO string has yet been received.
206
     *
207
     * @var array|null
208
     */
209
    protected $server_caps = null;
210
211
    /**
212
     * The most recent reply received from the server.
213
     *
214
     * @var string
215
     */
216
    protected $last_reply = '';
217
218
    /**
219
     * Output debugging info via a user-selected method.
220
     *
221
     * @param string $str   Debug string to output
222
     * @param int    $level The debug level of this message; see DEBUG_* constants
223
     *
224
     * @see SMTP::$Debugoutput
225
     * @see SMTP::$do_debug
226
     */
227
    protected function edebug($str, $level = 0)
228
    {
229
        if ($level > $this->do_debug) {
230
            return;
231
        }
232
        //Is this a PSR-3 logger?
233
        if (is_a($this->Debugoutput, 'Psr\Log\LoggerInterface')) {
234
            $this->Debugoutput->debug($str);
0 ignored issues
show
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...
235
236
            return;
237
        }
238
        //Avoid clash with built-in function names
239
        if (!in_array($this->Debugoutput, ['error_log', 'html', 'echo']) and is_callable($this->Debugoutput)) {
240
            call_user_func($this->Debugoutput, $str, $level);
241
242
            return;
243
        }
244
        switch ($this->Debugoutput) {
245
            case 'error_log':
246
                //Don't output, just log
247
                error_log($str);
248
                break;
249
            case 'html':
250
                //Cleans up output a bit for a better looking, HTML-safe output
251
                echo gmdate('Y-m-d H:i:s'), ' ', htmlentities(
252
                    preg_replace('/[\r\n]+/', '', $str),
253
                    ENT_QUOTES,
254
                    'UTF-8'
255
                ), "<br>\n";
256
                break;
257
            case 'echo':
258
            default:
259
                //Normalize line breaks
260
                $str = preg_replace('/\r\n|\r/ms', "\n", $str);
261
                echo gmdate('Y-m-d H:i:s'),
262
                "\t",
263
                    //Trim trailing space
264
                trim(
265
                //Indent for readability, except for trailing break
266
                    str_replace(
267
                        "\n",
268
                        "\n                   \t                  ",
269
                        trim($str)
270
                    )
271
                ),
272
                "\n";
273
        }
274
    }
275
276
    /**
277
     * Connect to an SMTP server.
278
     *
279
     * @param string $host    SMTP server IP or host name
280
     * @param int    $port    The port number to connect to
281
     * @param int    $timeout How long to wait for the connection to open
282
     * @param array  $options An array of options for stream_context_create()
283
     *
284
     * @return bool
285
     */
286
    public function connect($host, $port = null, $timeout = 30, $options = [])
0 ignored issues
show
This operation has 640 execution paths which exceeds the configured maximum of 200.

A high number of execution paths generally suggests many nested conditional statements and make the code less readible. This can usually be fixed by splitting the method into several smaller methods.

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

Loading history...
287
    {
288
        static $streamok;
289
        //This is enabled by default since 5.0.0 but some providers disable it
290
        //Check this once and cache the result
291
        if (null === $streamok) {
292
            $streamok = function_exists('stream_socket_client');
293
        }
294
        // Clear errors to avoid confusion
295
        $this->setError('');
296
        // Make sure we are __not__ connected
297
        if ($this->connected()) {
298
            // Already connected, generate error
299
            $this->setError('Already connected to a server');
300
301
            return false;
302
        }
303
        if (empty($port)) {
304
            $port = self::DEFAULT_PORT;
305
        }
306
        // Connect to the SMTP server
307
        $this->edebug(
308
            "Connection: opening to $host:$port, timeout=$timeout, options=" .
309
            (count($options) > 0 ? var_export($options, true) : 'array()'),
310
            self::DEBUG_CONNECTION
311
        );
312
        $errno = 0;
313
        $errstr = '';
314
        if ($streamok) {
315
            $socket_context = stream_context_create($options);
316
            set_error_handler([$this, 'errorHandler']);
317
            $this->smtp_conn = stream_socket_client(
318
                $host . ':' . $port,
319
                $errno,
320
                $errstr,
321
                $timeout,
322
                STREAM_CLIENT_CONNECT,
323
                $socket_context
324
            );
325
            restore_error_handler();
326
        } else {
327
            //Fall back to fsockopen which should work in more places, but is missing some features
328
            $this->edebug(
329
                'Connection: stream_socket_client not available, falling back to fsockopen',
330
                self::DEBUG_CONNECTION
331
            );
332
            set_error_handler([$this, 'errorHandler']);
333
            $this->smtp_conn = fsockopen(
334
                $host,
335
                $port,
336
                $errno,
337
                $errstr,
338
                $timeout
339
            );
340
            restore_error_handler();
341
        }
342
        // Verify we connected properly
343
        if (!is_resource($this->smtp_conn)) {
344
            $this->setError(
345
                'Failed to connect to server',
346
                '',
347
                (string) $errno,
348
                (string) $errstr
349
            );
350
            $this->edebug(
351
                'SMTP ERROR: ' . $this->error['error']
352
                . ": $errstr ($errno)",
353
                self::DEBUG_CLIENT
354
            );
355
356
            return false;
357
        }
358
        $this->edebug('Connection: opened', self::DEBUG_CONNECTION);
359
        // SMTP server can take longer to respond, give longer timeout for first read
360
        // Windows does not have support for this timeout function
361
        if (substr(PHP_OS, 0, 3) != 'WIN') {
362
            $max = ini_get('max_execution_time');
363
            // Don't bother if unlimited
364
            if (0 != $max and $timeout > $max) {
365
                @set_time_limit($timeout);
366
            }
367
            stream_set_timeout($this->smtp_conn, $timeout, 0);
368
        }
369
        // Get any announcement
370
        $announce = $this->get_lines();
371
        $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER);
372
373
        return true;
374
    }
375
376
    /**
377
     * Initiate a TLS (encrypted) session.
378
     *
379
     * @return bool
380
     */
381
    public function startTLS()
382
    {
383
        if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) {
384
            return false;
385
        }
386
387
        //Allow the best TLS version(s) we can
388
        $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
389
390
        //PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT
391
        //so add them back in manually if we can
392
        if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
393
            $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
394
            $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
395
        }
396
397
        // Begin encrypted connection
398
        set_error_handler([$this, 'errorHandler']);
399
        $crypto_ok = stream_socket_enable_crypto(
400
            $this->smtp_conn,
401
            true,
402
            $crypto_method
403
        );
404
        restore_error_handler();
405
406
        return (bool) $crypto_ok;
407
    }
408
409
    /**
410
     * Perform SMTP authentication.
411
     * Must be run after hello().
412
     *
413
     * @see    hello()
414
     *
415
     * @param string $username The user name
416
     * @param string $password The password
417
     * @param string $authtype The auth type (CRAM-MD5, PLAIN, LOGIN, XOAUTH2)
418
     * @param OAuth  $OAuth    An optional OAuth instance for XOAUTH2 authentication
419
     *
420
     * @return bool True if successfully authenticated
421
     */
422
    public function authenticate(
0 ignored issues
show
This operation has 10716 execution paths which exceeds the configured maximum of 200.

A high number of execution paths generally suggests many nested conditional statements and make the code less readible. This can usually be fixed by splitting the method into several smaller methods.

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

Loading history...
423
        $username,
424
        $password,
425
        $authtype = null,
426
        $OAuth = null
427
    ) {
428
        if (!$this->server_caps) {
429
            $this->setError('Authentication is not allowed before HELO/EHLO');
430
431
            return false;
432
        }
433
434
        if (array_key_exists('EHLO', $this->server_caps)) {
435
            // SMTP extensions are available; try to find a proper authentication method
436
            if (!array_key_exists('AUTH', $this->server_caps)) {
437
                $this->setError('Authentication is not allowed at this stage');
438
                // 'at this stage' means that auth may be allowed after the stage changes
439
                // e.g. after STARTTLS
440
441
                return false;
442
            }
443
444
            $this->edebug('Auth method requested: ' . ($authtype ? $authtype : 'UNKNOWN'), self::DEBUG_LOWLEVEL);
445
            $this->edebug(
446
                'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']),
447
                self::DEBUG_LOWLEVEL
448
            );
449
450
            //If we have requested a specific auth type, check the server supports it before trying others
451
            if (!in_array($authtype, $this->server_caps['AUTH'])) {
452
                $this->edebug('Requested auth method not available: ' . $authtype, self::DEBUG_LOWLEVEL);
453
                $authtype = null;
454
            }
455
456
            if (empty($authtype)) {
457
                //If no auth mechanism is specified, attempt to use these, in this order
458
                //Try CRAM-MD5 first as it's more secure than the others
459
                foreach (['CRAM-MD5', 'LOGIN', 'PLAIN', 'XOAUTH2'] as $method) {
460
                    if (in_array($method, $this->server_caps['AUTH'])) {
461
                        $authtype = $method;
462
                        break;
463
                    }
464
                }
465
                if (empty($authtype)) {
466
                    $this->setError('No supported authentication methods found');
467
468
                    return false;
469
                }
470
                self::edebug('Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL);
471
            }
472
473
            if (!in_array($authtype, $this->server_caps['AUTH'])) {
474
                $this->setError("The requested authentication method \"$authtype\" is not supported by the server");
475
476
                return false;
477
            }
478
        } elseif (empty($authtype)) {
479
            $authtype = 'LOGIN';
480
        }
481
        switch ($authtype) {
482
            case 'PLAIN':
483
                // Start authentication
484
                if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) {
485
                    return false;
486
                }
487
                // Send encoded username and password
488
                if (!$this->sendCommand(
489
                    'User & Password',
490
                    base64_encode("\0" . $username . "\0" . $password),
491
                    235
492
                )
493
                ) {
494
                    return false;
495
                }
496
                break;
497
            case 'LOGIN':
498
                // Start authentication
499
                if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) {
500
                    return false;
501
                }
502
                if (!$this->sendCommand('Username', base64_encode($username), 334)) {
503
                    return false;
504
                }
505
                if (!$this->sendCommand('Password', base64_encode($password), 235)) {
506
                    return false;
507
                }
508
                break;
509
            case 'CRAM-MD5':
510
                // Start authentication
511
                if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) {
512
                    return false;
513
                }
514
                // Get the challenge
515
                $challenge = base64_decode(substr($this->last_reply, 4));
516
517
                // Build the response
518
                $response = $username . ' ' . $this->hmac($challenge, $password);
519
520
                // send encoded credentials
521
                return $this->sendCommand('Username', base64_encode($response), 235);
522
            case 'XOAUTH2':
523
                //The OAuth instance must be set up prior to requesting auth.
524
                if (null === $OAuth) {
525
                    return false;
526
                }
527
                $oauth = $OAuth->getOauth64();
528
529
                // Start authentication
530
                if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
531
                    return false;
532
                }
533
                break;
534
            default:
535
                $this->setError("Authentication method \"$authtype\" is not supported");
536
537
                return false;
538
        }
539
540
        return true;
541
    }
542
543
    /**
544
     * Calculate an MD5 HMAC hash.
545
     * Works like hash_hmac('md5', $data, $key)
546
     * in case that function is not available.
547
     *
548
     * @param string $data The data to hash
549
     * @param string $key  The key to hash with
550
     *
551
     * @return string
552
     */
553
    protected function hmac($data, $key)
554
    {
555
        if (function_exists('hash_hmac')) {
556
            return hash_hmac('md5', $data, $key);
557
        }
558
559
        // The following borrowed from
560
        // http://php.net/manual/en/function.mhash.php#27225
561
562
        // RFC 2104 HMAC implementation for php.
563
        // Creates an md5 HMAC.
564
        // Eliminates the need to install mhash to compute a HMAC
565
        // by Lance Rushing
566
567
        $bytelen = 64; // byte length for md5
568
        if (strlen($key) > $bytelen) {
569
            $key = pack('H*', md5($key));
570
        }
571
        $key = str_pad($key, $bytelen, chr(0x00));
572
        $ipad = str_pad('', $bytelen, chr(0x36));
573
        $opad = str_pad('', $bytelen, chr(0x5c));
574
        $k_ipad = $key ^ $ipad;
575
        $k_opad = $key ^ $opad;
576
577
        return md5($k_opad . pack('H*', md5($k_ipad . $data)));
578
    }
579
580
    /**
581
     * Check connection state.
582
     *
583
     * @return bool True if connected
584
     */
585
    public function connected()
586
    {
587
        if (is_resource($this->smtp_conn)) {
588
            $sock_status = stream_get_meta_data($this->smtp_conn);
589
            if ($sock_status['eof']) {
590
                // The socket is valid but we are not connected
591
                $this->edebug(
592
                    'SMTP NOTICE: EOF caught while checking if connected',
593
                    self::DEBUG_CLIENT
594
                );
595
                $this->close();
596
597
                return false;
598
            }
599
600
            return true; // everything looks good
601
        }
602
603
        return false;
604
    }
605
606
    /**
607
     * Close the socket and clean up the state of the class.
608
     * Don't use this function without first trying to use QUIT.
609
     *
610
     * @see quit()
611
     */
612
    public function close()
613
    {
614
        $this->setError('');
615
        $this->server_caps = null;
616
        $this->helo_rply = null;
617
        if (is_resource($this->smtp_conn)) {
618
            // close the connection and cleanup
619
            fclose($this->smtp_conn);
620
            $this->smtp_conn = null; //Makes for cleaner serialization
621
            $this->edebug('Connection: closed', self::DEBUG_CONNECTION);
622
        }
623
    }
624
625
    /**
626
     * Send an SMTP DATA command.
627
     * Issues a data command and sends the msg_data to the server,
628
     * finializing the mail transaction. $msg_data is the message
629
     * that is to be send with the headers. Each header needs to be
630
     * on a single line followed by a <CRLF> with the message headers
631
     * and the message body being separated by an additional <CRLF>.
632
     * Implements RFC 821: DATA <CRLF>.
633
     *
634
     * @param string $msg_data Message data to send
635
     *
636
     * @return bool
637
     */
638
    public function data($msg_data)
0 ignored issues
show
This operation has 366 execution paths which exceeds the configured maximum of 200.

A high number of execution paths generally suggests many nested conditional statements and make the code less readible. This can usually be fixed by splitting the method into several smaller methods.

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

Loading history...
639
    {
640
        //This will use the standard timelimit
641
        if (!$this->sendCommand('DATA', 'DATA', 354)) {
642
            return false;
643
        }
644
645
        /* The server is ready to accept data!
646
         * According to rfc821 we should not send more than 1000 characters on a single line (including the LE)
647
         * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into
648
         * smaller lines to fit within the limit.
649
         * We will also look for lines that start with a '.' and prepend an additional '.'.
650
         * NOTE: this does not count towards line-length limit.
651
         */
652
653
        // Normalize line breaks before exploding
654
        $lines = explode("\n", str_replace(["\r\n", "\r"], "\n", $msg_data));
655
656
        /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field
657
         * of the first line (':' separated) does not contain a space then it _should_ be a header and we will
658
         * process all lines before a blank line as headers.
659
         */
660
661
        $field = substr($lines[0], 0, strpos($lines[0], ':'));
662
        $in_headers = false;
663
        if (!empty($field) and strpos($field, ' ') === false) {
664
            $in_headers = true;
665
        }
666
667
        foreach ($lines as $line) {
668
            $lines_out = [];
669
            if ($in_headers and $line == '') {
670
                $in_headers = false;
671
            }
672
            //Break this line up into several smaller lines if it's too long
673
            //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len),
674
            while (isset($line[self::MAX_LINE_LENGTH])) {
675
                //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on
676
                //so as to avoid breaking in the middle of a word
677
                $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' ');
678
                //Deliberately matches both false and 0
679
                if (!$pos) {
680
                    //No nice break found, add a hard break
681
                    $pos = self::MAX_LINE_LENGTH - 1;
682
                    $lines_out[] = substr($line, 0, $pos);
683
                    $line = substr($line, $pos);
684
                } else {
685
                    //Break at the found point
686
                    $lines_out[] = substr($line, 0, $pos);
687
                    //Move along by the amount we dealt with
688
                    $line = substr($line, $pos + 1);
689
                }
690
                //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1
691
                if ($in_headers) {
692
                    $line = "\t" . $line;
693
                }
694
            }
695
            $lines_out[] = $line;
696
697
            //Send the lines to the server
698
            foreach ($lines_out as $line_out) {
699
                //RFC2821 section 4.5.2
700
                if (!empty($line_out) and $line_out[0] == '.') {
701
                    $line_out = '.' . $line_out;
702
                }
703
                $this->client_send($line_out . static::LE);
704
            }
705
        }
706
707
        //Message data has been sent, complete the command
708
        //Increase timelimit for end of DATA command
709
        $savetimelimit = $this->Timelimit;
710
        $this->Timelimit = $this->Timelimit * 2;
711
        $result = $this->sendCommand('DATA END', '.', 250);
712
        $this->recordLastTransactionID();
713
        //Restore timelimit
714
        $this->Timelimit = $savetimelimit;
715
716
        return $result;
717
    }
718
719
    /**
720
     * Send an SMTP HELO or EHLO command.
721
     * Used to identify the sending server to the receiving server.
722
     * This makes sure that client and server are in a known state.
723
     * Implements RFC 821: HELO <SP> <domain> <CRLF>
724
     * and RFC 2821 EHLO.
725
     *
726
     * @param string $host The host name or IP to connect to
727
     *
728
     * @return bool
729
     */
730
    public function hello($host = '')
731
    {
732
        //Try extended hello first (RFC 2821)
733
        return (bool) ($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host));
734
    }
735
736
    /**
737
     * Send an SMTP HELO or EHLO command.
738
     * Low-level implementation used by hello().
739
     *
740
     * @param string $hello The HELO string
741
     * @param string $host  The hostname to say we are
742
     *
743
     * @return bool
744
     *
745
     * @see    hello()
746
     */
747
    protected function sendHello($hello, $host)
748
    {
749
        $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250);
750
        $this->helo_rply = $this->last_reply;
751
        if ($noerror) {
752
            $this->parseHelloFields($hello);
753
        } else {
754
            $this->server_caps = null;
755
        }
756
757
        return $noerror;
758
    }
759
760
    /**
761
     * Parse a reply to HELO/EHLO command to discover server extensions.
762
     * In case of HELO, the only parameter that can be discovered is a server name.
763
     *
764
     * @param string $type `HELO` or `EHLO`
765
     */
766
    protected function parseHelloFields($type)
767
    {
768
        $this->server_caps = [];
769
        $lines = explode("\n", $this->helo_rply);
770
771
        foreach ($lines as $n => $s) {
772
            //First 4 chars contain response code followed by - or space
773
            $s = trim(substr($s, 4));
774
            if (empty($s)) {
775
                continue;
776
            }
777
            $fields = explode(' ', $s);
778
            if (!empty($fields)) {
779
                if (!$n) {
780
                    $name = $type;
781
                    $fields = $fields[0];
782
                } else {
783
                    $name = array_shift($fields);
784
                    switch ($name) {
785
                        case 'SIZE':
786
                            $fields = ($fields ? $fields[0] : 0);
787
                            break;
788
                        case 'AUTH':
789
                            if (!is_array($fields)) {
790
                                $fields = [];
791
                            }
792
                            break;
793
                        default:
794
                            $fields = true;
795
                    }
796
                }
797
                $this->server_caps[$name] = $fields;
798
            }
799
        }
800
    }
801
802
    /**
803
     * Send an SMTP MAIL command.
804
     * Starts a mail transaction from the email address specified in
805
     * $from. Returns true if successful or false otherwise. If True
806
     * the mail transaction is started and then one or more recipient
807
     * commands may be called followed by a data command.
808
     * Implements RFC 821: MAIL <SP> FROM:<reverse-path> <CRLF>.
809
     *
810
     * @param string $from Source address of this message
811
     *
812
     * @return bool
813
     */
814
    public function mail($from)
815
    {
816
        $useVerp = ($this->do_verp ? ' XVERP' : '');
817
818
        return $this->sendCommand(
819
            'MAIL FROM',
820
            'MAIL FROM:<' . $from . '>' . $useVerp,
821
            250
822
        );
823
    }
824
825
    /**
826
     * Send an SMTP QUIT command.
827
     * Closes the socket if there is no error or the $close_on_error argument is true.
828
     * Implements from RFC 821: QUIT <CRLF>.
829
     *
830
     * @param bool $close_on_error Should the connection close if an error occurs?
831
     *
832
     * @return bool
833
     */
834
    public function quit($close_on_error = true)
835
    {
836
        $noerror = $this->sendCommand('QUIT', 'QUIT', 221);
837
        $err = $this->error; //Save any error
838
        if ($noerror or $close_on_error) {
839
            $this->close();
840
            $this->error = $err; //Restore any error from the quit command
841
        }
842
843
        return $noerror;
844
    }
845
846
    /**
847
     * Send an SMTP RCPT command.
848
     * Sets the TO argument to $toaddr.
849
     * Returns true if the recipient was accepted false if it was rejected.
850
     * Implements from RFC 821: RCPT <SP> TO:<forward-path> <CRLF>.
851
     *
852
     * @param string $address The address the message is being sent to
853
     *
854
     * @return bool
855
     */
856
    public function recipient($address)
857
    {
858
        return $this->sendCommand(
859
            'RCPT TO',
860
            'RCPT TO:<' . $address . '>',
861
            [250, 251]
862
        );
863
    }
864
865
    /**
866
     * Send an SMTP RSET command.
867
     * Abort any transaction that is currently in progress.
868
     * Implements RFC 821: RSET <CRLF>.
869
     *
870
     * @return bool True on success
871
     */
872
    public function reset()
873
    {
874
        return $this->sendCommand('RSET', 'RSET', 250);
875
    }
876
877
    /**
878
     * Send a command to an SMTP server and check its return code.
879
     *
880
     * @param string    $command       The command name - not sent to the server
881
     * @param string    $commandstring The actual command to send
882
     * @param int|array $expect        One or more expected integer success codes
883
     *
884
     * @return bool True on success
885
     */
886
    protected function sendCommand($command, $commandstring, $expect)
0 ignored issues
show
This operation has 312 execution paths which exceeds the configured maximum of 200.

A high number of execution paths generally suggests many nested conditional statements and make the code less readible. This can usually be fixed by splitting the method into several smaller methods.

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

Loading history...
887
    {
888
        if (!$this->connected()) {
889
            $this->setError("Called $command without being connected");
890
891
            return false;
892
        }
893
        //Reject line breaks in all commands
894
        if (strpos($commandstring, "\n") !== false or strpos($commandstring, "\r") !== false) {
895
            $this->setError("Command '$command' contained line breaks");
896
897
            return false;
898
        }
899
        $this->client_send($commandstring . static::LE);
900
901
        $this->last_reply = $this->get_lines();
902
        // Fetch SMTP code and possible error code explanation
903
        $matches = [];
904
        if (preg_match('/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]) )?/', $this->last_reply, $matches)) {
905
            $code = $matches[1];
906
            $code_ex = (count($matches) > 2 ? $matches[2] : null);
907
            // Cut off error code from each response line
908
            $detail = preg_replace(
909
                "/{$code}[ -]" .
910
                ($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . '/m',
911
                '',
912
                $this->last_reply
913
            );
914
        } else {
915
            // Fall back to simple parsing if regex fails
916
            $code = substr($this->last_reply, 0, 3);
917
            $code_ex = null;
918
            $detail = substr($this->last_reply, 4);
919
        }
920
921
        $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
922
923
        if (!in_array($code, (array) $expect)) {
924
            $this->setError(
925
                "$command command failed",
926
                $detail,
927
                $code,
928
                $code_ex
929
            );
930
            $this->edebug(
931
                'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply,
932
                self::DEBUG_CLIENT
933
            );
934
935
            return false;
936
        }
937
938
        $this->setError('');
939
940
        return true;
941
    }
942
943
    /**
944
     * Send an SMTP SAML command.
945
     * Starts a mail transaction from the email address specified in $from.
946
     * Returns true if successful or false otherwise. If True
947
     * the mail transaction is started and then one or more recipient
948
     * commands may be called followed by a data command. This command
949
     * will send the message to the users terminal if they are logged
950
     * in and send them an email.
951
     * Implements RFC 821: SAML <SP> FROM:<reverse-path> <CRLF>.
952
     *
953
     * @param string $from The address the message is from
954
     *
955
     * @return bool
956
     */
957
    public function sendAndMail($from)
958
    {
959
        return $this->sendCommand('SAML', "SAML FROM:$from", 250);
960
    }
961
962
    /**
963
     * Send an SMTP VRFY command.
964
     *
965
     * @param string $name The name to verify
966
     *
967
     * @return bool
968
     */
969
    public function verify($name)
970
    {
971
        return $this->sendCommand('VRFY', "VRFY $name", [250, 251]);
972
    }
973
974
    /**
975
     * Send an SMTP NOOP command.
976
     * Used to keep keep-alives alive, doesn't actually do anything.
977
     *
978
     * @return bool
979
     */
980
    public function noop()
981
    {
982
        return $this->sendCommand('NOOP', 'NOOP', 250);
983
    }
984
985
    /**
986
     * Send an SMTP TURN command.
987
     * This is an optional command for SMTP that this class does not support.
988
     * This method is here to make the RFC821 Definition complete for this class
989
     * and _may_ be implemented in future.
990
     * Implements from RFC 821: TURN <CRLF>.
991
     *
992
     * @return bool
993
     */
994
    public function turn()
995
    {
996
        $this->setError('The SMTP TURN command is not implemented');
997
        $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT);
998
999
        return false;
1000
    }
1001
1002
    /**
1003
     * Send raw data to the server.
1004
     *
1005
     * @param string $data The data to send
1006
     *
1007
     * @return int|bool The number of bytes sent to the server or false on error
1008
     */
1009
    public function client_send($data)
1010
    {
1011
        $this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT);
1012
        set_error_handler([$this, 'errorHandler']);
1013
        $result = fwrite($this->smtp_conn, $data);
1014
        restore_error_handler();
1015
1016
        return $result;
1017
    }
1018
1019
    /**
1020
     * Get the latest error.
1021
     *
1022
     * @return array
1023
     */
1024
    public function getError()
1025
    {
1026
        return $this->error;
1027
    }
1028
1029
    /**
1030
     * Get SMTP extensions available on the server.
1031
     *
1032
     * @return array|null
1033
     */
1034
    public function getServerExtList()
1035
    {
1036
        return $this->server_caps;
1037
    }
1038
1039
    /**
1040
     * Get metadata about the SMTP server from its HELO/EHLO response.
1041
     * The method works in three ways, dependent on argument value and current state:
1042
     *   1. HELO/EHLO has not been sent - returns null and populates $this->error.
1043
     *   2. HELO has been sent -
1044
     *     $name == 'HELO': returns server name
1045
     *     $name == 'EHLO': returns boolean false
1046
     *     $name == any other string: returns null and populates $this->error
1047
     *   3. EHLO has been sent -
1048
     *     $name == 'HELO'|'EHLO': returns the server name
1049
     *     $name == any other string: if extension $name exists, returns True
1050
     *       or its options (e.g. AUTH mechanisms supported). Otherwise returns False.
1051
     *
1052
     * @param string $name Name of SMTP extension or 'HELO'|'EHLO'
1053
     *
1054
     * @return mixed
1055
     */
1056
    public function getServerExt($name)
1057
    {
1058
        if (!$this->server_caps) {
1059
            $this->setError('No HELO/EHLO was sent');
1060
1061
            return;
1062
        }
1063
1064
        if (!array_key_exists($name, $this->server_caps)) {
1065
            if ('HELO' == $name) {
1066
                return $this->server_caps['EHLO'];
1067
            }
1068
            if ('EHLO' == $name || array_key_exists('EHLO', $this->server_caps)) {
1069
                return false;
1070
            }
1071
            $this->setError('HELO handshake was used; No information about server extensions available');
1072
1073
            return;
1074
        }
1075
1076
        return $this->server_caps[$name];
1077
    }
1078
1079
    /**
1080
     * Get the last reply from the server.
1081
     *
1082
     * @return string
1083
     */
1084
    public function getLastReply()
1085
    {
1086
        return $this->last_reply;
1087
    }
1088
1089
    /**
1090
     * Read the SMTP server's response.
1091
     * Either before eof or socket timeout occurs on the operation.
1092
     * With SMTP we can tell if we have more lines to read if the
1093
     * 4th character is '-' symbol. If it is a space then we don't
1094
     * need to read anything else.
1095
     *
1096
     * @return string
1097
     */
1098
    protected function get_lines()
0 ignored issues
show
This operation has 200 execution paths which exceeds the configured maximum of 200.

A high number of execution paths generally suggests many nested conditional statements and make the code less readible. This can usually be fixed by splitting the method into several smaller methods.

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

Loading history...
1099
    {
1100
        // If the connection is bad, give up straight away
1101
        if (!is_resource($this->smtp_conn)) {
1102
            return '';
1103
        }
1104
        $data = '';
1105
        $endtime = 0;
1106
        stream_set_timeout($this->smtp_conn, $this->Timeout);
1107
        if ($this->Timelimit > 0) {
1108
            $endtime = time() + $this->Timelimit;
1109
        }
1110
        $selR = [$this->smtp_conn];
1111
        $selW = null;
1112
        while (is_resource($this->smtp_conn) and !feof($this->smtp_conn)) {
1113
            //Must pass vars in here as params are by reference
1114
            if (!stream_select($selR, $selW, $selW, $this->Timelimit)) {
1115
                $this->edebug(
1116
                    'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)',
1117
                    self::DEBUG_LOWLEVEL
1118
                );
1119
                break;
1120
            }
1121
            //Deliberate noise suppression - errors are handled afterwards
1122
            $str = @fgets($this->smtp_conn, 515);
1123
            $this->edebug('SMTP INBOUND: "' . trim($str) . '"', self::DEBUG_LOWLEVEL);
1124
            $data .= $str;
1125
            // If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled),
1126
            // or 4th character is a space, we are done reading, break the loop,
1127
            // string array access is a micro-optimisation over strlen
1128
            if (!isset($str[3]) or (isset($str[3]) and $str[3] == ' ')) {
1129
                break;
1130
            }
1131
            // Timed-out? Log and break
1132
            $info = stream_get_meta_data($this->smtp_conn);
1133
            if ($info['timed_out']) {
1134
                $this->edebug(
1135
                    'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)',
1136
                    self::DEBUG_LOWLEVEL
1137
                );
1138
                break;
1139
            }
1140
            // Now check if reads took too long
1141
            if ($endtime and time() > $endtime) {
1142
                $this->edebug(
1143
                    'SMTP -> get_lines(): timelimit reached (' .
1144
                    $this->Timelimit . ' sec)',
1145
                    self::DEBUG_LOWLEVEL
1146
                );
1147
                break;
1148
            }
1149
        }
1150
1151
        return $data;
1152
    }
1153
1154
    /**
1155
     * Enable or disable VERP address generation.
1156
     *
1157
     * @param bool $enabled
1158
     */
1159
    public function setVerp($enabled = false)
1160
    {
1161
        $this->do_verp = $enabled;
1162
    }
1163
1164
    /**
1165
     * Get VERP address generation mode.
1166
     *
1167
     * @return bool
1168
     */
1169
    public function getVerp()
0 ignored issues
show
Coding Style Naming introduced by
The 'getVerp()' method which returns a boolean should be named 'is...()' or 'has...()'
Loading history...
1170
    {
1171
        return $this->do_verp;
1172
    }
1173
1174
    /**
1175
     * Set error messages and codes.
1176
     *
1177
     * @param string $message      The error message
1178
     * @param string $detail       Further detail on the error
1179
     * @param string $smtp_code    An associated SMTP error code
1180
     * @param string $smtp_code_ex Extended SMTP code
1181
     */
1182
    protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '')
1183
    {
1184
        $this->error = [
1185
            'error' => $message,
1186
            'detail' => $detail,
1187
            'smtp_code' => $smtp_code,
1188
            'smtp_code_ex' => $smtp_code_ex,
1189
        ];
1190
    }
1191
1192
    /**
1193
     * Set debug output method.
1194
     *
1195
     * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it
1196
     */
1197
    public function setDebugOutput($method = 'echo')
1198
    {
1199
        $this->Debugoutput = $method;
1200
    }
1201
1202
    /**
1203
     * Get debug output method.
1204
     *
1205
     * @return string
1206
     */
1207
    public function getDebugOutput()
1208
    {
1209
        return $this->Debugoutput;
1210
    }
1211
1212
    /**
1213
     * Set debug output level.
1214
     *
1215
     * @param int $level
1216
     */
1217
    public function setDebugLevel($level = 0)
1218
    {
1219
        $this->do_debug = $level;
1220
    }
1221
1222
    /**
1223
     * Get debug output level.
1224
     *
1225
     * @return int
1226
     */
1227
    public function getDebugLevel()
1228
    {
1229
        return $this->do_debug;
1230
    }
1231
1232
    /**
1233
     * Set SMTP timeout.
1234
     *
1235
     * @param int $timeout The timeout duration in seconds
1236
     */
1237
    public function setTimeout($timeout = 0)
1238
    {
1239
        $this->Timeout = $timeout;
1240
    }
1241
1242
    /**
1243
     * Get SMTP timeout.
1244
     *
1245
     * @return int
1246
     */
1247
    public function getTimeout()
1248
    {
1249
        return $this->Timeout;
1250
    }
1251
1252
    /**
1253
     * Reports an error number and string.
1254
     *
1255
     * @param int    $errno   The error number returned by PHP
1256
     * @param string $errmsg  The error message returned by PHP
1257
     * @param string $errfile The file the error occurred in
1258
     * @param int    $errline The line number the error occurred on
1259
     */
1260
    protected function errorHandler($errno, $errmsg, $errfile = '', $errline = 0)
1261
    {
1262
        $notice = 'Connection failed.';
1263
        $this->setError(
1264
            $notice,
1265
            $errmsg,
1266
            (string) $errno
1267
        );
1268
        $this->edebug(
1269
            "$notice Error #$errno: $errmsg [$errfile line $errline]",
1270
            self::DEBUG_CONNECTION
1271
        );
1272
    }
1273
1274
    /**
1275
     * Extract and return the ID of the last SMTP transaction based on
1276
     * a list of patterns provided in SMTP::$smtp_transaction_id_patterns.
1277
     * Relies on the host providing the ID in response to a DATA command.
1278
     * If no reply has been received yet, it will return null.
1279
     * If no pattern was matched, it will return false.
1280
     *
1281
     * @return bool|null|string
1282
     */
1283
    protected function recordLastTransactionID()
1284
    {
1285
        $reply = $this->getLastReply();
1286
1287
        if (empty($reply)) {
1288
            $this->last_smtp_transaction_id = null;
1289
        } else {
1290
            $this->last_smtp_transaction_id = false;
1291
            foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $smtp_transaction_id_pattern exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
1292
                if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) {
1293
                    $this->last_smtp_transaction_id = $matches[1];
1294
                }
1295
            }
1296
        }
1297
1298
        return $this->last_smtp_transaction_id;
1299
    }
1300
1301
    /**
1302
     * Get the queue/transaction ID of the last SMTP transaction
1303
     * If no reply has been received yet, it will return null.
1304
     * If no pattern was matched, it will return false.
1305
     *
1306
     * @return bool|null|string
1307
     *
1308
     * @see recordLastTransactionID()
1309
     */
1310
    public function getLastTransactionID()
1311
    {
1312
        return $this->last_smtp_transaction_id;
1313
    }
1314
}
1315