Issues (4967)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/wp-includes/class-smtp.php (8 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
 * @package PHPMailer
6
 * @link https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
7
 * @author Marcus Bointon (Synchro/coolbru) <[email protected]>
8
 * @author Jim Jagielski (jimjag) <[email protected]>
9
 * @author Andy Prevost (codeworxtech) <[email protected]>
10
 * @author Brent R. Matzelle (original founder)
11
 * @copyright 2014 Marcus Bointon
12
 * @copyright 2010 - 2012 Jim Jagielski
13
 * @copyright 2004 - 2009 Andy Prevost
14
 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
15
 * @note This program is distributed in the hope that it will be useful - WITHOUT
16
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17
 * FITNESS FOR A PARTICULAR PURPOSE.
18
 */
19
20
/**
21
 * PHPMailer RFC821 SMTP email transport class.
22
 * Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server.
23
 * @package PHPMailer
24
 * @author Chris Ryan
25
 * @author Marcus Bointon <[email protected]>
26
 */
27
class SMTP
28
{
29
    /**
30
     * The PHPMailer SMTP version number.
31
     * @var string
32
     */
33
    const VERSION = '5.2.22';
34
35
    /**
36
     * SMTP line break constant.
37
     * @var string
38
     */
39
    const CRLF = "\r\n";
40
41
    /**
42
     * The SMTP port to use if one is not specified.
43
     * @var integer
44
     */
45
    const DEFAULT_SMTP_PORT = 25;
46
47
    /**
48
     * The maximum line length allowed by RFC 2822 section 2.1.1
49
     * @var integer
50
     */
51
    const MAX_LINE_LENGTH = 998;
52
53
    /**
54
     * Debug level for no output
55
     */
56
    const DEBUG_OFF = 0;
57
58
    /**
59
     * Debug level to show client -> server messages
60
     */
61
    const DEBUG_CLIENT = 1;
62
63
    /**
64
     * Debug level to show client -> server and server -> client messages
65
     */
66
    const DEBUG_SERVER = 2;
67
68
    /**
69
     * Debug level to show connection status, client -> server and server -> client messages
70
     */
71
    const DEBUG_CONNECTION = 3;
72
73
    /**
74
     * Debug level to show all messages
75
     */
76
    const DEBUG_LOWLEVEL = 4;
77
78
    /**
79
     * The PHPMailer SMTP Version number.
80
     * @var string
81
     * @deprecated Use the `VERSION` constant instead
82
     * @see SMTP::VERSION
83
     */
84
    public $Version = '5.2.22';
85
86
    /**
87
     * SMTP server port number.
88
     * @var integer
89
     * @deprecated This is only ever used as a default value, so use the `DEFAULT_SMTP_PORT` constant instead
90
     * @see SMTP::DEFAULT_SMTP_PORT
91
     */
92
    public $SMTP_PORT = 25;
93
94
    /**
95
     * SMTP reply line ending.
96
     * @var string
97
     * @deprecated Use the `CRLF` constant instead
98
     * @see SMTP::CRLF
99
     */
100
    public $CRLF = "\r\n";
101
102
    /**
103
     * Debug output level.
104
     * Options:
105
     * * self::DEBUG_OFF (`0`) No debug output, default
106
     * * self::DEBUG_CLIENT (`1`) Client commands
107
     * * self::DEBUG_SERVER (`2`) Client commands and server responses
108
     * * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status
109
     * * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages
110
     * @var integer
111
     */
112
    public $do_debug = self::DEBUG_OFF;
113
114
    /**
115
     * How to handle debug output.
116
     * Options:
117
     * * `echo` Output plain-text as-is, appropriate for CLI
118
     * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output
119
     * * `error_log` Output to error log as configured in php.ini
120
     *
121
     * Alternatively, you can provide a callable expecting two params: a message string and the debug level:
122
     * <code>
123
     * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
124
     * </code>
125
     * @var string|callable
126
     */
127
    public $Debugoutput = 'echo';
128
129
    /**
130
     * Whether to use VERP.
131
     * @link http://en.wikipedia.org/wiki/Variable_envelope_return_path
132
     * @link http://www.postfix.org/VERP_README.html Info on VERP
133
     * @var boolean
134
     */
135
    public $do_verp = false;
136
137
    /**
138
     * The timeout value for connection, in seconds.
139
     * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2
140
     * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure.
141
     * @link http://tools.ietf.org/html/rfc2821#section-4.5.3.2
142
     * @var integer
143
     */
144
    public $Timeout = 300;
145
146
    /**
147
     * How long to wait for commands to complete, in seconds.
148
     * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2
149
     * @var integer
150
     */
151
    public $Timelimit = 300;
152
153
	/**
154
	 * @var array patterns to extract smtp transaction id from smtp reply
155
	 * Only first capture group will be use, use non-capturing group to deal with it
156
	 * Extend this class to override this property to fulfil your needs.
157
	 */
158
	protected $smtp_transaction_id_patterns = array(
159
		'exim' => '/[0-9]{3} OK id=(.*)/',
160
		'sendmail' => '/[0-9]{3} 2.0.0 (.*) Message/',
161
		'postfix' => '/[0-9]{3} 2.0.0 Ok: queued as (.*)/'
162
	);
163
164
    /**
165
     * The socket for the server connection.
166
     * @var resource
167
     */
168
    protected $smtp_conn;
169
170
    /**
171
     * Error information, if any, for the last SMTP command.
172
     * @var array
173
     */
174
    protected $error = array(
175
        'error' => '',
176
        'detail' => '',
177
        'smtp_code' => '',
178
        'smtp_code_ex' => ''
179
    );
180
181
    /**
182
     * The reply the server sent to us for HELO.
183
     * If null, no HELO string has yet been received.
184
     * @var string|null
185
     */
186
    protected $helo_rply = null;
187
188
    /**
189
     * The set of SMTP extensions sent in reply to EHLO command.
190
     * Indexes of the array are extension names.
191
     * Value at index 'HELO' or 'EHLO' (according to command that was sent)
192
     * represents the server name. In case of HELO it is the only element of the array.
193
     * Other values can be boolean TRUE or an array containing extension options.
194
     * If null, no HELO/EHLO string has yet been received.
195
     * @var array|null
196
     */
197
    protected $server_caps = null;
198
199
    /**
200
     * The most recent reply received from the server.
201
     * @var string
202
     */
203
    protected $last_reply = '';
204
205
    /**
206
     * Output debugging info via a user-selected method.
207
     * @see SMTP::$Debugoutput
208
     * @see SMTP::$do_debug
209
     * @param string $str Debug string to output
210
     * @param integer $level The debug level of this message; see DEBUG_* constants
211
     * @return void
212
     */
213 View Code Duplication
    protected function edebug($str, $level = 0)
214
    {
215
        if ($level > $this->do_debug) {
216
            return;
217
        }
218
        //Avoid clash with built-in function names
219
        if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) {
220
            call_user_func($this->Debugoutput, $str, $level);
221
            return;
222
        }
223
        switch ($this->Debugoutput) {
224
            case 'error_log':
225
                //Don't output, just log
226
                error_log($str);
227
                break;
228
            case 'html':
229
                //Cleans up output a bit for a better looking, HTML-safe output
230
                echo htmlentities(
231
                    preg_replace('/[\r\n]+/', '', $str),
232
                    ENT_QUOTES,
233
                    'UTF-8'
234
                )
235
                . "<br>\n";
236
                break;
237
            case 'echo':
238
            default:
239
                //Normalize line breaks
240
                $str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str);
241
                echo gmdate('Y-m-d H:i:s') . "\t" . str_replace(
242
                    "\n",
243
                    "\n                   \t                  ",
244
                    trim($str)
245
                )."\n";
246
        }
247
    }
248
249
    /**
250
     * Connect to an SMTP server.
251
     * @param string $host SMTP server IP or host name
252
     * @param integer $port The port number to connect to
253
     * @param integer $timeout How long to wait for the connection to open
254
     * @param array $options An array of options for stream_context_create()
255
     * @access public
256
     * @return boolean
257
     */
258
    public function connect($host, $port = null, $timeout = 30, $options = array())
259
    {
260
        static $streamok;
261
        //This is enabled by default since 5.0.0 but some providers disable it
262
        //Check this once and cache the result
263
        if (is_null($streamok)) {
264
            $streamok = function_exists('stream_socket_client');
265
        }
266
        // Clear errors to avoid confusion
267
        $this->setError('');
268
        // Make sure we are __not__ connected
269
        if ($this->connected()) {
270
            // Already connected, generate error
271
            $this->setError('Already connected to a server');
272
            return false;
273
        }
274
        if (empty($port)) {
275
            $port = self::DEFAULT_SMTP_PORT;
276
        }
277
        // Connect to the SMTP server
278
        $this->edebug(
279
            "Connection: opening to $host:$port, timeout=$timeout, options=".var_export($options, true),
280
            self::DEBUG_CONNECTION
281
        );
282
        $errno = 0;
283
        $errstr = '';
284
        if ($streamok) {
285
            $socket_context = stream_context_create($options);
286
            set_error_handler(array($this, 'errorHandler'));
287
            $this->smtp_conn = stream_socket_client(
288
                $host . ":" . $port,
289
                $errno,
290
                $errstr,
291
                $timeout,
292
                STREAM_CLIENT_CONNECT,
293
                $socket_context
294
            );
295
            restore_error_handler();
296
        } else {
297
            //Fall back to fsockopen which should work in more places, but is missing some features
298
            $this->edebug(
299
                "Connection: stream_socket_client not available, falling back to fsockopen",
300
                self::DEBUG_CONNECTION
301
            );
302
            set_error_handler(array($this, 'errorHandler'));
303
            $this->smtp_conn = fsockopen(
304
                $host,
305
                $port,
306
                $errno,
307
                $errstr,
308
                $timeout
309
            );
310
            restore_error_handler();
311
        }
312
        // Verify we connected properly
313
        if (!is_resource($this->smtp_conn)) {
314
            $this->setError(
315
                'Failed to connect to server',
316
                $errno,
317
                $errstr
318
            );
319
            $this->edebug(
320
                'SMTP ERROR: ' . $this->error['error']
321
                . ": $errstr ($errno)",
322
                self::DEBUG_CLIENT
323
            );
324
            return false;
325
        }
326
        $this->edebug('Connection: opened', self::DEBUG_CONNECTION);
327
        // SMTP server can take longer to respond, give longer timeout for first read
328
        // Windows does not have support for this timeout function
329
        if (substr(PHP_OS, 0, 3) != 'WIN') {
330
            $max = ini_get('max_execution_time');
331
            // Don't bother if unlimited
332
            if ($max != 0 && $timeout > $max) {
333
                @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...
334
            }
335
            stream_set_timeout($this->smtp_conn, $timeout, 0);
336
        }
337
        // Get any announcement
338
        $announce = $this->get_lines();
339
        $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER);
340
        return true;
341
    }
342
343
    /**
344
     * Initiate a TLS (encrypted) session.
345
     * @access public
346
     * @return boolean
347
     */
348
    public function startTLS()
349
    {
350
        if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) {
351
            return false;
352
        }
353
354
        //Allow the best TLS version(s) we can
355
        $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
356
357
        //PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT
358
        //so add them back in manually if we can
359
        if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
360
            $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
361
            $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
362
        }
363
364
        // Begin encrypted connection
365
        if (!stream_socket_enable_crypto(
0 ignored issues
show
This if statement, and the following return statement can be replaced with return (bool) stream_soc... true, $crypto_method);.
Loading history...
366
            $this->smtp_conn,
367
            true,
368
            $crypto_method
369
        )) {
370
            return false;
371
        }
372
        return true;
373
    }
374
375
    /**
376
     * Perform SMTP authentication.
377
     * Must be run after hello().
378
     * @see hello()
379
     * @param string $username The user name
380
     * @param string $password The password
381
     * @param string $authtype The auth type (PLAIN, LOGIN, CRAM-MD5)
382
     * @param string $realm The auth realm for NTLM
383
     * @param string $workstation The auth workstation for NTLM
384
     * @param null|OAuth $OAuth An optional OAuth instance (@see PHPMailerOAuth)
385
     * @return bool True if successfully authenticated.* @access public
386
     */
387
    public function authenticate(
388
        $username,
389
        $password,
390
        $authtype = null,
391
        $realm = '',
0 ignored issues
show
The parameter $realm is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
392
        $workstation = '',
0 ignored issues
show
The parameter $workstation is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
393
        $OAuth = null
0 ignored issues
show
The parameter $OAuth is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
394
    ) {
395
        if (!$this->server_caps) {
396
            $this->setError('Authentication is not allowed before HELO/EHLO');
397
            return false;
398
        }
399
400
        if (array_key_exists('EHLO', $this->server_caps)) {
401
        // SMTP extensions are available. Let's try to find a proper authentication method
402
403
            if (!array_key_exists('AUTH', $this->server_caps)) {
404
                $this->setError('Authentication is not allowed at this stage');
405
                // 'at this stage' means that auth may be allowed after the stage changes
406
                // e.g. after STARTTLS
407
                return false;
408
            }
409
410
            self::edebug('Auth method requested: ' . ($authtype ? $authtype : 'UNKNOWN'), self::DEBUG_LOWLEVEL);
411
            self::edebug(
412
                'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']),
413
                self::DEBUG_LOWLEVEL
414
            );
415
416
            if (empty($authtype)) {
417
                foreach (array('CRAM-MD5', 'LOGIN', 'PLAIN') as $method) {
418
                    if (in_array($method, $this->server_caps['AUTH'])) {
419
                        $authtype = $method;
420
                        break;
421
                    }
422
                }
423
                if (empty($authtype)) {
424
                    $this->setError('No supported authentication methods found');
425
                    return false;
426
                }
427
                self::edebug('Auth method selected: '.$authtype, self::DEBUG_LOWLEVEL);
428
            }
429
430
            if (!in_array($authtype, $this->server_caps['AUTH'])) {
431
                $this->setError("The requested authentication method \"$authtype\" is not supported by the server");
432
                return false;
433
            }
434
        } elseif (empty($authtype)) {
435
            $authtype = 'LOGIN';
436
        }
437
        switch ($authtype) {
438
            case 'PLAIN':
439
                // Start authentication
440
                if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) {
441
                    return false;
442
                }
443
                // Send encoded username and password
444
                if (!$this->sendCommand(
445
                    'User & Password',
446
                    base64_encode("\0" . $username . "\0" . $password),
447
                    235
448
                )
449
                ) {
450
                    return false;
451
                }
452
                break;
453
            case 'LOGIN':
454
                // Start authentication
455
                if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) {
456
                    return false;
457
                }
458
                if (!$this->sendCommand("Username", base64_encode($username), 334)) {
459
                    return false;
460
                }
461
                if (!$this->sendCommand("Password", base64_encode($password), 235)) {
462
                    return false;
463
                }
464
                break;
465
            case 'CRAM-MD5':
466
                // Start authentication
467
                if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) {
468
                    return false;
469
                }
470
                // Get the challenge
471
                $challenge = base64_decode(substr($this->last_reply, 4));
472
473
                // Build the response
474
                $response = $username . ' ' . $this->hmac($challenge, $password);
475
476
                // send encoded credentials
477
                return $this->sendCommand('Username', base64_encode($response), 235);
478
            default:
479
                $this->setError("Authentication method \"$authtype\" is not supported");
480
                return false;
481
        }
482
        return true;
483
    }
484
485
    /**
486
     * Calculate an MD5 HMAC hash.
487
     * Works like hash_hmac('md5', $data, $key)
488
     * in case that function is not available
489
     * @param string $data The data to hash
490
     * @param string $key  The key to hash with
491
     * @access protected
492
     * @return string
0 ignored issues
show
Should the return type not be string|false?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
493
     */
494
    protected function hmac($data, $key)
495
    {
496
        if (function_exists('hash_hmac')) {
497
            return hash_hmac('md5', $data, $key);
498
        }
499
500
        // The following borrowed from
501
        // http://php.net/manual/en/function.mhash.php#27225
502
503
        // RFC 2104 HMAC implementation for php.
504
        // Creates an md5 HMAC.
505
        // Eliminates the need to install mhash to compute a HMAC
506
        // by Lance Rushing
507
508
        $bytelen = 64; // byte length for md5
509
        if (strlen($key) > $bytelen) {
510
            $key = pack('H*', md5($key));
511
        }
512
        $key = str_pad($key, $bytelen, chr(0x00));
513
        $ipad = str_pad('', $bytelen, chr(0x36));
514
        $opad = str_pad('', $bytelen, chr(0x5c));
515
        $k_ipad = $key ^ $ipad;
516
        $k_opad = $key ^ $opad;
517
518
        return md5($k_opad . pack('H*', md5($k_ipad . $data)));
519
    }
520
521
    /**
522
     * Check connection state.
523
     * @access public
524
     * @return boolean True if connected.
525
     */
526
    public function connected()
527
    {
528
        if (is_resource($this->smtp_conn)) {
529
            $sock_status = stream_get_meta_data($this->smtp_conn);
530
            if ($sock_status['eof']) {
531
                // The socket is valid but we are not connected
532
                $this->edebug(
533
                    'SMTP NOTICE: EOF caught while checking if connected',
534
                    self::DEBUG_CLIENT
535
                );
536
                $this->close();
537
                return false;
538
            }
539
            return true; // everything looks good
540
        }
541
        return false;
542
    }
543
544
    /**
545
     * Close the socket and clean up the state of the class.
546
     * Don't use this function without first trying to use QUIT.
547
     * @see quit()
548
     * @access public
549
     * @return void
550
     */
551
    public function close()
552
    {
553
        $this->setError('');
554
        $this->server_caps = null;
555
        $this->helo_rply = null;
556
        if (is_resource($this->smtp_conn)) {
557
            // close the connection and cleanup
558
            fclose($this->smtp_conn);
559
            $this->smtp_conn = null; //Makes for cleaner serialization
560
            $this->edebug('Connection: closed', self::DEBUG_CONNECTION);
561
        }
562
    }
563
564
    /**
565
     * Send an SMTP DATA command.
566
     * Issues a data command and sends the msg_data to the server,
567
     * finializing the mail transaction. $msg_data is the message
568
     * that is to be send with the headers. Each header needs to be
569
     * on a single line followed by a <CRLF> with the message headers
570
     * and the message body being separated by and additional <CRLF>.
571
     * Implements rfc 821: DATA <CRLF>
572
     * @param string $msg_data Message data to send
573
     * @access public
574
     * @return boolean
575
     */
576
    public function data($msg_data)
577
    {
578
        //This will use the standard timelimit
579
        if (!$this->sendCommand('DATA', 'DATA', 354)) {
580
            return false;
581
        }
582
583
        /* The server is ready to accept data!
584
         * According to rfc821 we should not send more than 1000 characters on a single line (including the CRLF)
585
         * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into
586
         * smaller lines to fit within the limit.
587
         * We will also look for lines that start with a '.' and prepend an additional '.'.
588
         * NOTE: this does not count towards line-length limit.
589
         */
590
591
        // Normalize line breaks before exploding
592
        $lines = explode("\n", str_replace(array("\r\n", "\r"), "\n", $msg_data));
593
594
        /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field
595
         * of the first line (':' separated) does not contain a space then it _should_ be a header and we will
596
         * process all lines before a blank line as headers.
597
         */
598
599
        $field = substr($lines[0], 0, strpos($lines[0], ':'));
600
        $in_headers = false;
601
        if (!empty($field) && strpos($field, ' ') === false) {
602
            $in_headers = true;
603
        }
604
605
        foreach ($lines as $line) {
606
            $lines_out = array();
607
            if ($in_headers and $line == '') {
608
                $in_headers = false;
609
            }
610
            //Break this line up into several smaller lines if it's too long
611
            //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len),
612
            while (isset($line[self::MAX_LINE_LENGTH])) {
613
                //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on
614
                //so as to avoid breaking in the middle of a word
615
                $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' ');
616
                //Deliberately matches both false and 0
617
                if (!$pos) {
618
                    //No nice break found, add a hard break
619
                    $pos = self::MAX_LINE_LENGTH - 1;
620
                    $lines_out[] = substr($line, 0, $pos);
621
                    $line = substr($line, $pos);
622
                } else {
623
                    //Break at the found point
624
                    $lines_out[] = substr($line, 0, $pos);
625
                    //Move along by the amount we dealt with
626
                    $line = substr($line, $pos + 1);
627
                }
628
                //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1
629
                if ($in_headers) {
630
                    $line = "\t" . $line;
631
                }
632
            }
633
            $lines_out[] = $line;
634
635
            //Send the lines to the server
636
            foreach ($lines_out as $line_out) {
637
                //RFC2821 section 4.5.2
638
                if (!empty($line_out) and $line_out[0] == '.') {
639
                    $line_out = '.' . $line_out;
640
                }
641
                $this->client_send($line_out . self::CRLF);
642
            }
643
        }
644
645
        //Message data has been sent, complete the command
646
        //Increase timelimit for end of DATA command
647
        $savetimelimit = $this->Timelimit;
648
        $this->Timelimit = $this->Timelimit * 2;
649
        $result = $this->sendCommand('DATA END', '.', 250);
650
        //Restore timelimit
651
        $this->Timelimit = $savetimelimit;
652
        return $result;
653
    }
654
655
    /**
656
     * Send an SMTP HELO or EHLO command.
657
     * Used to identify the sending server to the receiving server.
658
     * This makes sure that client and server are in a known state.
659
     * Implements RFC 821: HELO <SP> <domain> <CRLF>
660
     * and RFC 2821 EHLO.
661
     * @param string $host The host name or IP to connect to
662
     * @access public
663
     * @return boolean
664
     */
665
    public function hello($host = '')
666
    {
667
        //Try extended hello first (RFC 2821)
668
        return (boolean)($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host));
669
    }
670
671
    /**
672
     * Send an SMTP HELO or EHLO command.
673
     * Low-level implementation used by hello()
674
     * @see hello()
675
     * @param string $hello The HELO string
676
     * @param string $host The hostname to say we are
677
     * @access protected
678
     * @return boolean
679
     */
680
    protected function sendHello($hello, $host)
681
    {
682
        $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250);
683
        $this->helo_rply = $this->last_reply;
684
        if ($noerror) {
685
            $this->parseHelloFields($hello);
686
        } else {
687
            $this->server_caps = null;
688
        }
689
        return $noerror;
690
    }
691
692
    /**
693
     * Parse a reply to HELO/EHLO command to discover server extensions.
694
     * In case of HELO, the only parameter that can be discovered is a server name.
695
     * @access protected
696
     * @param string $type - 'HELO' or 'EHLO'
697
     */
698
    protected function parseHelloFields($type)
699
    {
700
        $this->server_caps = array();
701
        $lines = explode("\n", $this->helo_rply);
702
703
        foreach ($lines as $n => $s) {
704
            //First 4 chars contain response code followed by - or space
705
            $s = trim(substr($s, 4));
706
            if (empty($s)) {
707
                continue;
708
            }
709
            $fields = explode(' ', $s);
710
            if (!empty($fields)) {
711
                if (!$n) {
712
                    $name = $type;
713
                    $fields = $fields[0];
714
                } else {
715
                    $name = array_shift($fields);
716
                    switch ($name) {
717
                        case 'SIZE':
718
                            $fields = ($fields ? $fields[0] : 0);
719
                            break;
720
                        case 'AUTH':
721
                            if (!is_array($fields)) {
722
                                $fields = array();
723
                            }
724
                            break;
725
                        default:
726
                            $fields = true;
727
                    }
728
                }
729
                $this->server_caps[$name] = $fields;
730
            }
731
        }
732
    }
733
734
    /**
735
     * Send an SMTP MAIL command.
736
     * Starts a mail transaction from the email address specified in
737
     * $from. Returns true if successful or false otherwise. If True
738
     * the mail transaction is started and then one or more recipient
739
     * commands may be called followed by a data command.
740
     * Implements rfc 821: MAIL <SP> FROM:<reverse-path> <CRLF>
741
     * @param string $from Source address of this message
742
     * @access public
743
     * @return boolean
744
     */
745
    public function mail($from)
746
    {
747
        $useVerp = ($this->do_verp ? ' XVERP' : '');
748
        return $this->sendCommand(
749
            'MAIL FROM',
750
            'MAIL FROM:<' . $from . '>' . $useVerp,
751
            250
752
        );
753
    }
754
755
    /**
756
     * Send an SMTP QUIT command.
757
     * Closes the socket if there is no error or the $close_on_error argument is true.
758
     * Implements from rfc 821: QUIT <CRLF>
759
     * @param boolean $close_on_error Should the connection close if an error occurs?
760
     * @access public
761
     * @return boolean
762
     */
763
    public function quit($close_on_error = true)
764
    {
765
        $noerror = $this->sendCommand('QUIT', 'QUIT', 221);
766
        $err = $this->error; //Save any error
767
        if ($noerror or $close_on_error) {
768
            $this->close();
769
            $this->error = $err; //Restore any error from the quit command
770
        }
771
        return $noerror;
772
    }
773
774
    /**
775
     * Send an SMTP RCPT command.
776
     * Sets the TO argument to $toaddr.
777
     * Returns true if the recipient was accepted false if it was rejected.
778
     * Implements from rfc 821: RCPT <SP> TO:<forward-path> <CRLF>
779
     * @param string $address The address the message is being sent to
780
     * @access public
781
     * @return boolean
782
     */
783
    public function recipient($address)
784
    {
785
        return $this->sendCommand(
786
            'RCPT TO',
787
            'RCPT TO:<' . $address . '>',
788
            array(250, 251)
789
        );
790
    }
791
792
    /**
793
     * Send an SMTP RSET command.
794
     * Abort any transaction that is currently in progress.
795
     * Implements rfc 821: RSET <CRLF>
796
     * @access public
797
     * @return boolean True on success.
798
     */
799
    public function reset()
800
    {
801
        return $this->sendCommand('RSET', 'RSET', 250);
802
    }
803
804
    /**
805
     * Send a command to an SMTP server and check its return code.
806
     * @param string $command The command name - not sent to the server
807
     * @param string $commandstring The actual command to send
808
     * @param integer|array $expect One or more expected integer success codes
809
     * @access protected
810
     * @return boolean True on success.
811
     */
812
    protected function sendCommand($command, $commandstring, $expect)
813
    {
814
        if (!$this->connected()) {
815
            $this->setError("Called $command without being connected");
816
            return false;
817
        }
818
        //Reject line breaks in all commands
819
        if (strpos($commandstring, "\n") !== false or strpos($commandstring, "\r") !== false) {
820
            $this->setError("Command '$command' contained line breaks");
821
            return false;
822
        }
823
        $this->client_send($commandstring . self::CRLF);
824
825
        $this->last_reply = $this->get_lines();
826
        // Fetch SMTP code and possible error code explanation
827
        $matches = array();
828
        if (preg_match("/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]) )?/", $this->last_reply, $matches)) {
829
            $code = $matches[1];
830
            $code_ex = (count($matches) > 2 ? $matches[2] : null);
831
            // Cut off error code from each response line
832
            $detail = preg_replace(
833
                "/{$code}[ -]".($code_ex ? str_replace('.', '\\.', $code_ex).' ' : '')."/m",
834
                '',
835
                $this->last_reply
836
            );
837
        } else {
838
            // Fall back to simple parsing if regex fails
839
            $code = substr($this->last_reply, 0, 3);
840
            $code_ex = null;
841
            $detail = substr($this->last_reply, 4);
842
        }
843
844
        $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
845
846
        if (!in_array($code, (array)$expect)) {
847
            $this->setError(
848
                "$command command failed",
849
                $detail,
850
                $code,
851
                $code_ex
852
            );
853
            $this->edebug(
854
                'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply,
855
                self::DEBUG_CLIENT
856
            );
857
            return false;
858
        }
859
860
        $this->setError('');
861
        return true;
862
    }
863
864
    /**
865
     * Send an SMTP SAML command.
866
     * Starts a mail transaction from the email address specified in $from.
867
     * Returns true if successful or false otherwise. If True
868
     * the mail transaction is started and then one or more recipient
869
     * commands may be called followed by a data command. This command
870
     * will send the message to the users terminal if they are logged
871
     * in and send them an email.
872
     * Implements rfc 821: SAML <SP> FROM:<reverse-path> <CRLF>
873
     * @param string $from The address the message is from
874
     * @access public
875
     * @return boolean
876
     */
877
    public function sendAndMail($from)
878
    {
879
        return $this->sendCommand('SAML', "SAML FROM:$from", 250);
880
    }
881
882
    /**
883
     * Send an SMTP VRFY command.
884
     * @param string $name The name to verify
885
     * @access public
886
     * @return boolean
887
     */
888
    public function verify($name)
889
    {
890
        return $this->sendCommand('VRFY', "VRFY $name", array(250, 251));
891
    }
892
893
    /**
894
     * Send an SMTP NOOP command.
895
     * Used to keep keep-alives alive, doesn't actually do anything
896
     * @access public
897
     * @return boolean
898
     */
899
    public function noop()
900
    {
901
        return $this->sendCommand('NOOP', 'NOOP', 250);
902
    }
903
904
    /**
905
     * Send an SMTP TURN command.
906
     * This is an optional command for SMTP that this class does not support.
907
     * This method is here to make the RFC821 Definition complete for this class
908
     * and _may_ be implemented in future
909
     * Implements from rfc 821: TURN <CRLF>
910
     * @access public
911
     * @return boolean
912
     */
913
    public function turn()
914
    {
915
        $this->setError('The SMTP TURN command is not implemented');
916
        $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT);
917
        return false;
918
    }
919
920
    /**
921
     * Send raw data to the server.
922
     * @param string $data The data to send
923
     * @access public
924
     * @return integer|boolean The number of bytes sent to the server or false on error
925
     */
926
    public function client_send($data)
927
    {
928
        $this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT);
929
        return fwrite($this->smtp_conn, $data);
930
    }
931
932
    /**
933
     * Get the latest error.
934
     * @access public
935
     * @return array
936
     */
937
    public function getError()
938
    {
939
        return $this->error;
940
    }
941
942
    /**
943
     * Get SMTP extensions available on the server
944
     * @access public
945
     * @return array|null
946
     */
947
    public function getServerExtList()
948
    {
949
        return $this->server_caps;
950
    }
951
952
    /**
953
     * A multipurpose method
954
     * The method works in three ways, dependent on argument value and current state
955
     *   1. HELO/EHLO was not sent - returns null and set up $this->error
956
     *   2. HELO was sent
957
     *     $name = 'HELO': returns server name
958
     *     $name = 'EHLO': returns boolean false
959
     *     $name = any string: returns null and set up $this->error
960
     *   3. EHLO was sent
961
     *     $name = 'HELO'|'EHLO': returns server name
962
     *     $name = any string: if extension $name exists, returns boolean True
963
     *       or its options. Otherwise returns boolean False
964
     * In other words, one can use this method to detect 3 conditions:
965
     *  - null returned: handshake was not or we don't know about ext (refer to $this->error)
966
     *  - false returned: the requested feature exactly not exists
967
     *  - positive value returned: the requested feature exists
968
     * @param string $name Name of SMTP extension or 'HELO'|'EHLO'
969
     * @return mixed
970
     */
971
    public function getServerExt($name)
972
    {
973
        if (!$this->server_caps) {
974
            $this->setError('No HELO/EHLO was sent');
975
            return null;
976
        }
977
978
        // the tight logic knot ;)
979
        if (!array_key_exists($name, $this->server_caps)) {
980
            if ($name == 'HELO') {
981
                return $this->server_caps['EHLO'];
982
            }
983
            if ($name == 'EHLO' || array_key_exists('EHLO', $this->server_caps)) {
984
                return false;
985
            }
986
            $this->setError('HELO handshake was used. Client knows nothing about server extensions');
987
            return null;
988
        }
989
990
        return $this->server_caps[$name];
991
    }
992
993
    /**
994
     * Get the last reply from the server.
995
     * @access public
996
     * @return string
997
     */
998
    public function getLastReply()
999
    {
1000
        return $this->last_reply;
1001
    }
1002
1003
    /**
1004
     * Read the SMTP server's response.
1005
     * Either before eof or socket timeout occurs on the operation.
1006
     * With SMTP we can tell if we have more lines to read if the
1007
     * 4th character is '-' symbol. If it is a space then we don't
1008
     * need to read anything else.
1009
     * @access protected
1010
     * @return string
1011
     */
1012
    protected function get_lines()
1013
    {
1014
        // If the connection is bad, give up straight away
1015
        if (!is_resource($this->smtp_conn)) {
1016
            return '';
1017
        }
1018
        $data = '';
1019
        $endtime = 0;
1020
        stream_set_timeout($this->smtp_conn, $this->Timeout);
1021
        if ($this->Timelimit > 0) {
1022
            $endtime = time() + $this->Timelimit;
1023
        }
1024
        while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
1025
            $str = @fgets($this->smtp_conn, 515);
1026
            $this->edebug("SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL);
1027
            $this->edebug("SMTP -> get_lines(): \$str is  \"$str\"", self::DEBUG_LOWLEVEL);
1028
            $data .= $str;
1029
            // If 4th character is a space, we are done reading, break the loop, micro-optimisation over strlen
1030
            if ((isset($str[3]) and $str[3] == ' ')) {
1031
                break;
1032
            }
1033
            // Timed-out? Log and break
1034
            $info = stream_get_meta_data($this->smtp_conn);
1035
            if ($info['timed_out']) {
1036
                $this->edebug(
1037
                    'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)',
1038
                    self::DEBUG_LOWLEVEL
1039
                );
1040
                break;
1041
            }
1042
            // Now check if reads took too long
1043
            if ($endtime and time() > $endtime) {
1044
                $this->edebug(
1045
                    'SMTP -> get_lines(): timelimit reached ('.
1046
                    $this->Timelimit . ' sec)',
1047
                    self::DEBUG_LOWLEVEL
1048
                );
1049
                break;
1050
            }
1051
        }
1052
        return $data;
1053
    }
1054
1055
    /**
1056
     * Enable or disable VERP address generation.
1057
     * @param boolean $enabled
1058
     */
1059
    public function setVerp($enabled = false)
1060
    {
1061
        $this->do_verp = $enabled;
1062
    }
1063
1064
    /**
1065
     * Get VERP address generation mode.
1066
     * @return boolean
1067
     */
1068
    public function getVerp()
1069
    {
1070
        return $this->do_verp;
1071
    }
1072
1073
    /**
1074
     * Set error messages and codes.
1075
     * @param string $message The error message
1076
     * @param string $detail Further detail on the error
1077
     * @param string $smtp_code An associated SMTP error code
1078
     * @param string $smtp_code_ex Extended SMTP code
1079
     */
1080
    protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '')
1081
    {
1082
        $this->error = array(
1083
            'error' => $message,
1084
            'detail' => $detail,
1085
            'smtp_code' => $smtp_code,
1086
            'smtp_code_ex' => $smtp_code_ex
1087
        );
1088
    }
1089
1090
    /**
1091
     * Set debug output method.
1092
     * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it.
1093
     */
1094
    public function setDebugOutput($method = 'echo')
1095
    {
1096
        $this->Debugoutput = $method;
1097
    }
1098
1099
    /**
1100
     * Get debug output method.
1101
     * @return string
0 ignored issues
show
Should the return type not be callable? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
1102
     */
1103
    public function getDebugOutput()
1104
    {
1105
        return $this->Debugoutput;
1106
    }
1107
1108
    /**
1109
     * Set debug output level.
1110
     * @param integer $level
1111
     */
1112
    public function setDebugLevel($level = 0)
1113
    {
1114
        $this->do_debug = $level;
1115
    }
1116
1117
    /**
1118
     * Get debug output level.
1119
     * @return integer
1120
     */
1121
    public function getDebugLevel()
1122
    {
1123
        return $this->do_debug;
1124
    }
1125
1126
    /**
1127
     * Set SMTP timeout.
1128
     * @param integer $timeout
1129
     */
1130
    public function setTimeout($timeout = 0)
1131
    {
1132
        $this->Timeout = $timeout;
1133
    }
1134
1135
    /**
1136
     * Get SMTP timeout.
1137
     * @return integer
1138
     */
1139
    public function getTimeout()
1140
    {
1141
        return $this->Timeout;
1142
    }
1143
1144
    /**
1145
     * Reports an error number and string.
1146
     * @param integer $errno The error number returned by PHP.
1147
     * @param string $errmsg The error message returned by PHP.
1148
     */
1149
    protected function errorHandler($errno, $errmsg)
1150
    {
1151
        $notice = 'Connection: Failed to connect to server.';
1152
        $this->setError(
1153
            $notice,
1154
            $errno,
1155
            $errmsg
1156
        );
1157
        $this->edebug(
1158
            $notice . ' Error number ' . $errno . '. "Error notice: ' . $errmsg,
1159
            self::DEBUG_CONNECTION
1160
        );
1161
    }
1162
1163
	/**
1164
	 * Will return the ID of the last smtp transaction based on a list of patterns provided
1165
	 * in SMTP::$smtp_transaction_id_patterns.
1166
	 * If no reply has been received yet, it will return null.
1167
	 * If no pattern has been matched, it will return false.
1168
	 * @return bool|null|string
0 ignored issues
show
Consider making the return type a bit more specific; maybe use null|string|false.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
1169
	 */
1170
	public function getLastTransactionID()
1171
	{
1172
		$reply = $this->getLastReply();
1173
1174
		if (empty($reply)) {
1175
			return null;
1176
		}
1177
1178
		foreach($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
1179
			if(preg_match($smtp_transaction_id_pattern, $reply, $matches)) {
1180
				return $matches[1];
1181
			}
1182
		}
1183
1184
		return false;
1185
    }
1186
}
1187