Completed
Push — development ( 664040...c5cb76 )
by Nils
06:39
created

SMTP::getLastTransactionID()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 8
nc 4
nop 0
dl 0
loc 16
rs 9.2
c 0
b 0
f 0
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.23';
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.23';
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
    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 gmdate('Y-m-d H:i:s') . ' ' . htmlentities(
231
                    preg_replace('/[\r\n]+/', '', $str),
232
                    ENT_QUOTES,
233
                    'UTF-8'
234
                ) . "<br>\n";
235
                break;
236
            case 'echo':
237
            default:
238
                //Normalize line breaks
239
                $str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str);
240
                echo gmdate('Y-m-d H:i:s') . "\t" . str_replace(
241
                    "\n",
242
                    "\n                   \t                  ",
243
                    trim($str)
244
                ) . "\n";
245
        }
246
    }
247
248
    /**
249
     * Connect to an SMTP server.
250
     * @param string $host SMTP server IP or host name
251
     * @param integer $port The port number to connect to
252
     * @param integer $timeout How long to wait for the connection to open
253
     * @param array $options An array of options for stream_context_create()
254
     * @access public
255
     * @return boolean
256
     */
257
    public function connect($host, $port = null, $timeout = 30, $options = array())
258
    {
259
        static $streamok;
260
        //This is enabled by default since 5.0.0 but some providers disable it
261
        //Check this once and cache the result
262
        if (is_null($streamok)) {
263
            $streamok = function_exists('stream_socket_client');
264
        }
265
        // Clear errors to avoid confusion
266
        $this->setError('');
267
        // Make sure we are __not__ connected
268
        if ($this->connected()) {
269
            // Already connected, generate error
270
            $this->setError('Already connected to a server');
271
            return false;
272
        }
273
        if (empty($port)) {
274
            $port = self::DEFAULT_SMTP_PORT;
275
        }
276
        // Connect to the SMTP server
277
        $this->edebug(
278
            "Connection: opening to $host:$port, timeout=$timeout, options=" .
279
            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
        set_error_handler(array($this, 'errorHandler'));
366
        $crypto_ok = stream_socket_enable_crypto(
367
            $this->smtp_conn,
368
            true,
369
            $crypto_method
370
        );
371
        restore_error_handler();
372
        return $crypto_ok;
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, NTLM, CRAM-MD5, XOAUTH2)
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 = '',
392
        $workstation = '',
393
        $OAuth = null
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; try to find a proper authentication method
402
            if (!array_key_exists('AUTH', $this->server_caps)) {
403
                $this->setError('Authentication is not allowed at this stage');
404
                // 'at this stage' means that auth may be allowed after the stage changes
405
                // e.g. after STARTTLS
406
                return false;
407
            }
408
409
            self::edebug('Auth method requested: ' . ($authtype ? $authtype : 'UNKNOWN'), self::DEBUG_LOWLEVEL);
410
            self::edebug(
411
                'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']),
412
                self::DEBUG_LOWLEVEL
413
            );
414
415
            if (empty($authtype)) {
416
                foreach (array('CRAM-MD5', 'LOGIN', 'PLAIN', 'NTLM', 'XOAUTH2') as $method) {
417
                    if (in_array($method, $this->server_caps['AUTH'])) {
418
                        $authtype = $method;
419
                        break;
420
                    }
421
                }
422
                if (empty($authtype)) {
423
                    $this->setError('No supported authentication methods found');
424
                    return false;
425
                }
426
                self::edebug('Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL);
427
            }
428
429
            if (!in_array($authtype, $this->server_caps['AUTH'])) {
430
                $this->setError("The requested authentication method \"$authtype\" is not supported by the server");
431
                return false;
432
            }
433
        } elseif (empty($authtype)) {
434
            $authtype = 'LOGIN';
435
        }
436
        switch ($authtype) {
437
            case 'PLAIN':
438
                // Start authentication
439
                if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) {
440
                    return false;
441
                }
442
                // Send encoded username and password
443
                if (!$this->sendCommand(
444
                    'User & Password',
445
                    base64_encode("\0" . $username . "\0" . $password),
446
                    235
447
                )
448
                ) {
449
                    return false;
450
                }
451
                break;
452
            case 'LOGIN':
453
                // Start authentication
454
                if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) {
455
                    return false;
456
                }
457
                if (!$this->sendCommand("Username", base64_encode($username), 334)) {
458
                    return false;
459
                }
460
                if (!$this->sendCommand("Password", base64_encode($password), 235)) {
461
                    return false;
462
                }
463
                break;
464
            case 'XOAUTH2':
465
                //If the OAuth Instance is not set. Can be a case when PHPMailer is used
466
                //instead of PHPMailerOAuth
467
                if (is_null($OAuth)) {
468
                    return false;
469
                }
470
                $oauth = $OAuth->getOauth64();
471
472
                // Start authentication
473
                if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
474
                    return false;
475
                }
476
                break;
477
            case 'NTLM':
478
                /*
479
                 * ntlm_sasl_client.php
480
                 * Bundled with Permission
481
                 *
482
                 * How to telnet in windows:
483
                 * http://technet.microsoft.com/en-us/library/aa995718%28EXCHG.65%29.aspx
484
                 * PROTOCOL Docs http://curl.haxx.se/rfc/ntlm.html#ntlmSmtpAuthentication
485
                 */
486
                require_once 'extras/ntlm_sasl_client.php';
487
                $temp = new stdClass;
488
                $ntlm_client = new ntlm_sasl_client_class;
489
                //Check that functions are available
490
                if (!$ntlm_client->initialize($temp)) {
491
                    $this->setError($temp->error);
492
                    $this->edebug(
493
                        'You need to enable some modules in your php.ini file: '
494
                        . $this->error['error'],
495
                        self::DEBUG_CLIENT
496
                    );
497
                    return false;
498
                }
499
                //msg1
500
                $msg1 = $ntlm_client->typeMsg1($realm, $workstation); //msg1
501
502
                if (!$this->sendCommand(
503
                    'AUTH NTLM',
504
                    'AUTH NTLM ' . base64_encode($msg1),
505
                    334
506
                )
507
                ) {
508
                    return false;
509
                }
510
                //Though 0 based, there is a white space after the 3 digit number
511
                //msg2
512
                $challenge = substr($this->last_reply, 3);
513
                $challenge = base64_decode($challenge);
514
                $ntlm_res = $ntlm_client->NTLMResponse(
515
                    substr($challenge, 24, 8),
516
                    $password
517
                );
518
                //msg3
519
                $msg3 = $ntlm_client->typeMsg3(
520
                    $ntlm_res,
521
                    $username,
522
                    $realm,
523
                    $workstation
524
                );
525
                // send encoded username
526
                return $this->sendCommand('Username', base64_encode($msg3), 235);
527
            case 'CRAM-MD5':
528
                // Start authentication
529
                if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) {
530
                    return false;
531
                }
532
                // Get the challenge
533
                $challenge = base64_decode(substr($this->last_reply, 4));
534
535
                // Build the response
536
                $response = $username . ' ' . $this->hmac($challenge, $password);
537
538
                // send encoded credentials
539
                return $this->sendCommand('Username', base64_encode($response), 235);
540
            default:
541
                $this->setError("Authentication method \"$authtype\" is not supported");
542
                return false;
543
        }
544
        return true;
545
    }
546
547
    /**
548
     * Calculate an MD5 HMAC hash.
549
     * Works like hash_hmac('md5', $data, $key)
550
     * in case that function is not available
551
     * @param string $data The data to hash
552
     * @param string $key The key to hash with
553
     * @access protected
554
     * @return string
555
     */
556
    protected function hmac($data, $key)
557
    {
558
        if (function_exists('hash_hmac')) {
559
            return hash_hmac('md5', $data, $key);
560
        }
561
562
        // The following borrowed from
563
        // http://php.net/manual/en/function.mhash.php#27225
564
565
        // RFC 2104 HMAC implementation for php.
566
        // Creates an md5 HMAC.
567
        // Eliminates the need to install mhash to compute a HMAC
568
        // by Lance Rushing
569
570
        $bytelen = 64; // byte length for md5
571
        if (strlen($key) > $bytelen) {
572
            $key = pack('H*', md5($key));
573
        }
574
        $key = str_pad($key, $bytelen, chr(0x00));
575
        $ipad = str_pad('', $bytelen, chr(0x36));
576
        $opad = str_pad('', $bytelen, chr(0x5c));
577
        $k_ipad = $key ^ $ipad;
578
        $k_opad = $key ^ $opad;
579
580
        return md5($k_opad . pack('H*', md5($k_ipad . $data)));
581
    }
582
583
    /**
584
     * Check connection state.
585
     * @access public
586
     * @return boolean True if connected.
587
     */
588
    public function connected()
589
    {
590
        if (is_resource($this->smtp_conn)) {
591
            $sock_status = stream_get_meta_data($this->smtp_conn);
592
            if ($sock_status['eof']) {
593
                // The socket is valid but we are not connected
594
                $this->edebug(
595
                    'SMTP NOTICE: EOF caught while checking if connected',
596
                    self::DEBUG_CLIENT
597
                );
598
                $this->close();
599
                return false;
600
            }
601
            return true; // everything looks good
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
     * @see quit()
610
     * @access public
611
     * @return void
612
     */
613
    public function close()
614
    {
615
        $this->setError('');
616
        $this->server_caps = null;
617
        $this->helo_rply = null;
618
        if (is_resource($this->smtp_conn)) {
619
            // close the connection and cleanup
620
            fclose($this->smtp_conn);
621
            $this->smtp_conn = null; //Makes for cleaner serialization
622
            $this->edebug('Connection: closed', self::DEBUG_CONNECTION);
623
        }
624
    }
625
626
    /**
627
     * Send an SMTP DATA command.
628
     * Issues a data command and sends the msg_data to the server,
629
     * finializing the mail transaction. $msg_data is the message
630
     * that is to be send with the headers. Each header needs to be
631
     * on a single line followed by a <CRLF> with the message headers
632
     * and the message body being separated by and additional <CRLF>.
633
     * Implements rfc 821: DATA <CRLF>
634
     * @param string $msg_data Message data to send
635
     * @access public
636
     * @return boolean
637
     */
638
    public function data($msg_data)
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 CRLF)
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(array("\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) && strpos($field, ' ') === false) {
664
            $in_headers = true;
665
        }
666
667
        foreach ($lines as $line) {
668
            $lines_out = array();
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),
0 ignored issues
show
Unused Code Comprehensibility introduced by
49% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
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 . self::CRLF);
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
        //Restore timelimit
713
        $this->Timelimit = $savetimelimit;
714
        return $result;
715
    }
716
717
    /**
718
     * Send an SMTP HELO or EHLO command.
719
     * Used to identify the sending server to the receiving server.
720
     * This makes sure that client and server are in a known state.
721
     * Implements RFC 821: HELO <SP> <domain> <CRLF>
722
     * and RFC 2821 EHLO.
723
     * @param string $host The host name or IP to connect to
724
     * @access public
725
     * @return boolean
726
     */
727
    public function hello($host = '')
728
    {
729
        //Try extended hello first (RFC 2821)
730
        return (boolean)($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host));
731
    }
732
733
    /**
734
     * Send an SMTP HELO or EHLO command.
735
     * Low-level implementation used by hello()
736
     * @see hello()
737
     * @param string $hello The HELO string
738
     * @param string $host The hostname to say we are
739
     * @access protected
740
     * @return boolean
741
     */
742
    protected function sendHello($hello, $host)
743
    {
744
        $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250);
745
        $this->helo_rply = $this->last_reply;
746
        if ($noerror) {
747
            $this->parseHelloFields($hello);
748
        } else {
749
            $this->server_caps = null;
750
        }
751
        return $noerror;
752
    }
753
754
    /**
755
     * Parse a reply to HELO/EHLO command to discover server extensions.
756
     * In case of HELO, the only parameter that can be discovered is a server name.
757
     * @access protected
758
     * @param string $type - 'HELO' or 'EHLO'
759
     */
760
    protected function parseHelloFields($type)
761
    {
762
        $this->server_caps = array();
763
        $lines = explode("\n", $this->helo_rply);
764
765
        foreach ($lines as $n => $s) {
766
            //First 4 chars contain response code followed by - or space
767
            $s = trim(substr($s, 4));
768
            if (empty($s)) {
769
                continue;
770
            }
771
            $fields = explode(' ', $s);
772
            if (!empty($fields)) {
773
                if (!$n) {
774
                    $name = $type;
775
                    $fields = $fields[0];
776
                } else {
777
                    $name = array_shift($fields);
778
                    switch ($name) {
779
                        case 'SIZE':
780
                            $fields = ($fields ? $fields[0] : 0);
781
                            break;
782
                        case 'AUTH':
783
                            if (!is_array($fields)) {
784
                                $fields = array();
785
                            }
786
                            break;
787
                        default:
788
                            $fields = true;
789
                    }
790
                }
791
                $this->server_caps[$name] = $fields;
792
            }
793
        }
794
    }
795
796
    /**
797
     * Send an SMTP MAIL command.
798
     * Starts a mail transaction from the email address specified in
799
     * $from. Returns true if successful or false otherwise. If True
800
     * the mail transaction is started and then one or more recipient
801
     * commands may be called followed by a data command.
802
     * Implements rfc 821: MAIL <SP> FROM:<reverse-path> <CRLF>
803
     * @param string $from Source address of this message
804
     * @access public
805
     * @return boolean
806
     */
807
    public function mail($from)
808
    {
809
        $useVerp = ($this->do_verp ? ' XVERP' : '');
810
        return $this->sendCommand(
811
            'MAIL FROM',
812
            'MAIL FROM:<' . $from . '>' . $useVerp,
813
            250
814
        );
815
    }
816
817
    /**
818
     * Send an SMTP QUIT command.
819
     * Closes the socket if there is no error or the $close_on_error argument is true.
820
     * Implements from rfc 821: QUIT <CRLF>
821
     * @param boolean $close_on_error Should the connection close if an error occurs?
822
     * @access public
823
     * @return boolean
824
     */
825
    public function quit($close_on_error = true)
826
    {
827
        $noerror = $this->sendCommand('QUIT', 'QUIT', 221);
828
        $err = $this->error; //Save any error
829
        if ($noerror or $close_on_error) {
830
            $this->close();
831
            $this->error = $err; //Restore any error from the quit command
832
        }
833
        return $noerror;
834
    }
835
836
    /**
837
     * Send an SMTP RCPT command.
838
     * Sets the TO argument to $toaddr.
839
     * Returns true if the recipient was accepted false if it was rejected.
840
     * Implements from rfc 821: RCPT <SP> TO:<forward-path> <CRLF>
841
     * @param string $address The address the message is being sent to
842
     * @access public
843
     * @return boolean
844
     */
845
    public function recipient($address)
846
    {
847
        return $this->sendCommand(
848
            'RCPT TO',
849
            'RCPT TO:<' . $address . '>',
850
            array(250, 251)
851
        );
852
    }
853
854
    /**
855
     * Send an SMTP RSET command.
856
     * Abort any transaction that is currently in progress.
857
     * Implements rfc 821: RSET <CRLF>
858
     * @access public
859
     * @return boolean True on success.
860
     */
861
    public function reset()
862
    {
863
        return $this->sendCommand('RSET', 'RSET', 250);
864
    }
865
866
    /**
867
     * Send a command to an SMTP server and check its return code.
868
     * @param string $command The command name - not sent to the server
869
     * @param string $commandstring The actual command to send
870
     * @param integer|array $expect One or more expected integer success codes
871
     * @access protected
872
     * @return boolean True on success.
873
     */
874
    protected function sendCommand($command, $commandstring, $expect)
875
    {
876
        if (!$this->connected()) {
877
            $this->setError("Called $command without being connected");
878
            return false;
879
        }
880
        //Reject line breaks in all commands
881
        if (strpos($commandstring, "\n") !== false or strpos($commandstring, "\r") !== false) {
882
            $this->setError("Command '$command' contained line breaks");
883
            return false;
884
        }
885
        $this->client_send($commandstring . self::CRLF);
886
887
        $this->last_reply = $this->get_lines();
888
        // Fetch SMTP code and possible error code explanation
889
        $matches = array();
890
        if (preg_match("/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]) )?/", $this->last_reply, $matches)) {
891
            $code = $matches[1];
892
            $code_ex = (count($matches) > 2 ? $matches[2] : null);
893
            // Cut off error code from each response line
894
            $detail = preg_replace(
895
                "/{$code}[ -]" .
896
                ($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . "/m",
897
                '',
898
                $this->last_reply
899
            );
900
        } else {
901
            // Fall back to simple parsing if regex fails
902
            $code = substr($this->last_reply, 0, 3);
903
            $code_ex = null;
904
            $detail = substr($this->last_reply, 4);
905
        }
906
907
        $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
908
909
        if (!in_array($code, (array)$expect)) {
910
            $this->setError(
911
                "$command command failed",
912
                $detail,
913
                $code,
914
                $code_ex
915
            );
916
            $this->edebug(
917
                'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply,
918
                self::DEBUG_CLIENT
919
            );
920
            return false;
921
        }
922
923
        $this->setError('');
924
        return true;
925
    }
926
927
    /**
928
     * Send an SMTP SAML command.
929
     * Starts a mail transaction from the email address specified in $from.
930
     * Returns true if successful or false otherwise. If True
931
     * the mail transaction is started and then one or more recipient
932
     * commands may be called followed by a data command. This command
933
     * will send the message to the users terminal if they are logged
934
     * in and send them an email.
935
     * Implements rfc 821: SAML <SP> FROM:<reverse-path> <CRLF>
936
     * @param string $from The address the message is from
937
     * @access public
938
     * @return boolean
939
     */
940
    public function sendAndMail($from)
941
    {
942
        return $this->sendCommand('SAML', "SAML FROM:$from", 250);
943
    }
944
945
    /**
946
     * Send an SMTP VRFY command.
947
     * @param string $name The name to verify
948
     * @access public
949
     * @return boolean
950
     */
951
    public function verify($name)
952
    {
953
        return $this->sendCommand('VRFY', "VRFY $name", array(250, 251));
954
    }
955
956
    /**
957
     * Send an SMTP NOOP command.
958
     * Used to keep keep-alives alive, doesn't actually do anything
959
     * @access public
960
     * @return boolean
961
     */
962
    public function noop()
963
    {
964
        return $this->sendCommand('NOOP', 'NOOP', 250);
965
    }
966
967
    /**
968
     * Send an SMTP TURN command.
969
     * This is an optional command for SMTP that this class does not support.
970
     * This method is here to make the RFC821 Definition complete for this class
971
     * and _may_ be implemented in future
972
     * Implements from rfc 821: TURN <CRLF>
973
     * @access public
974
     * @return boolean
975
     */
976
    public function turn()
977
    {
978
        $this->setError('The SMTP TURN command is not implemented');
979
        $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT);
980
        return false;
981
    }
982
983
    /**
984
     * Send raw data to the server.
985
     * @param string $data The data to send
986
     * @access public
987
     * @return integer|boolean The number of bytes sent to the server or false on error
988
     */
989
    public function client_send($data)
990
    {
991
        $this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT);
992
        set_error_handler(array($this, 'errorHandler'));
993
        $result = fwrite($this->smtp_conn, $data);
0 ignored issues
show
Security File Manipulation introduced by
$data can contain request data and is used in file manipulation context(s) leading to a potential security vulnerability.

5 paths for user data to reach this point

  1. Path: Read from $_POST in sources/main.queries.php on line 86
  1. Read from $_POST
    in sources/main.queries.php on line 86
  2. Data is decoded by json_decode()
    in vendor/sources/main.functions.php on line 1261
  3. $dataReceived is assigned
    in sources/main.queries.php on line 86
  4. $dataReceived['new_pw'] is passed through htmlspecialchars_decode(), and $LANG['forgot_pw_email_body'] . ' ' . htmlspecialchars_decode($dataReceived['new_pw']) is passed to sendEmail()
    in sources/main.queries.php on line 227
  5. PHPMailer::$Body is assigned
    in sources/main.functions.php on line 1155
  6. Tainted property PHPMailer::$Body is read, and $body is assigned
    in includes/libraries/Email/Phpmailer/class.phpmailer.php on line 2241
  7. PHPMailer::createBody() returns tainted data, and PHPMailer::$MIMEBody is assigned
    in includes/libraries/Email/Phpmailer/class.phpmailer.php on line 1275
  8. Tainted property PHPMailer::$MIMEBody is read, and $this->MIMEBody is passed to PHPMailer::smtpSend()
    in includes/libraries/Email/Phpmailer/class.phpmailer.php on line 1335
  9. $header . $body is passed to SMTP::data()
    in includes/libraries/Email/Phpmailer/class.phpmailer.php on line 1568
  10. $msg_data is passed through str_replace(), and ``str_replace(array(' ', ' '), ' ', $msg_data)`` is passed through explode(), and $lines is assigned
    in includes/libraries/Email/Phpmailer/class.smtp.php on line 654
  11. $line is assigned
    in includes/libraries/Email/Phpmailer/class.smtp.php on line 667
  12. $lines_out is assigned
    in includes/libraries/Email/Phpmailer/class.smtp.php on line 695
  13. $line_out is assigned
    in includes/libraries/Email/Phpmailer/class.smtp.php on line 698
  14. $line_out . self::CRLF is passed to SMTP::client_send()
    in includes/libraries/Email/Phpmailer/class.smtp.php on line 703
  2. Path: Read from $_POST, and $_POST['login'] is escaped by mysqli_escape_string() for sql context(s) in sources/main.queries.php on line 388
  1. Read from $_POST, and $_POST['login'] is escaped by mysqli_escape_string() for sql context(s)
    in sources/main.queries.php on line 388
  2. $textMail is assigned
    in sources/main.queries.php on line 386
  3. $textMail is passed to sendEmail()
    in sources/main.queries.php on line 422
  4. PHPMailer::$Body is assigned
    in sources/main.functions.php on line 1155
  5. Tainted property PHPMailer::$Body is read, and $body is assigned
    in includes/libraries/Email/Phpmailer/class.phpmailer.php on line 2241
  6. PHPMailer::createBody() returns tainted data, and PHPMailer::$MIMEBody is assigned
    in includes/libraries/Email/Phpmailer/class.phpmailer.php on line 1275
  7. Tainted property PHPMailer::$MIMEBody is read, and $this->MIMEBody is passed to PHPMailer::smtpSend()
    in includes/libraries/Email/Phpmailer/class.phpmailer.php on line 1335
  8. $header . $body is passed to SMTP::data()
    in includes/libraries/Email/Phpmailer/class.phpmailer.php on line 1568
  9. $msg_data is passed through str_replace(), and ``str_replace(array(' ', ' '), ' ', $msg_data)`` is passed through explode(), and $lines is assigned
    in includes/libraries/Email/Phpmailer/class.smtp.php on line 654
  10. $line is assigned
    in includes/libraries/Email/Phpmailer/class.smtp.php on line 667
  11. $lines_out is assigned
    in includes/libraries/Email/Phpmailer/class.smtp.php on line 695
  12. $line_out is assigned
    in includes/libraries/Email/Phpmailer/class.smtp.php on line 698
  13. $line_out . self::CRLF is passed to SMTP::client_send()
    in includes/libraries/Email/Phpmailer/class.smtp.php on line 703
  3. Path: Read from $_POST, and $_POST['login'] is escaped by mysqli_escape_string() for sql context(s) in sources/main.queries.php on line 389
  1. Read from $_POST, and $_POST['login'] is escaped by mysqli_escape_string() for sql context(s)
    in sources/main.queries.php on line 389
  2. $textMail is assigned
    in sources/main.queries.php on line 386
  3. $textMail is passed to sendEmail()
    in sources/main.queries.php on line 422
  4. PHPMailer::$Body is assigned
    in sources/main.functions.php on line 1155
  5. Tainted property PHPMailer::$Body is read, and $body is assigned
    in includes/libraries/Email/Phpmailer/class.phpmailer.php on line 2241
  6. PHPMailer::createBody() returns tainted data, and PHPMailer::$MIMEBody is assigned
    in includes/libraries/Email/Phpmailer/class.phpmailer.php on line 1275
  7. Tainted property PHPMailer::$MIMEBody is read, and $this->MIMEBody is passed to PHPMailer::smtpSend()
    in includes/libraries/Email/Phpmailer/class.phpmailer.php on line 1335
  8. $header . $body is passed to SMTP::data()
    in includes/libraries/Email/Phpmailer/class.phpmailer.php on line 1568
  9. $msg_data is passed through str_replace(), and ``str_replace(array(' ', ' '), ' ', $msg_data)`` is passed through explode(), and $lines is assigned
    in includes/libraries/Email/Phpmailer/class.smtp.php on line 654
  10. $line is assigned
    in includes/libraries/Email/Phpmailer/class.smtp.php on line 667
  11. $lines_out is assigned
    in includes/libraries/Email/Phpmailer/class.smtp.php on line 695
  12. $line_out is assigned
    in includes/libraries/Email/Phpmailer/class.smtp.php on line 698
  13. $line_out . self::CRLF is passed to SMTP::client_send()
    in includes/libraries/Email/Phpmailer/class.smtp.php on line 703
  4. Path: Read from $_POST, and $_POST['login'] is escaped by mysqli_escape_string() for sql context(s), and $textMailAlt is assigned in sources/main.queries.php on line 390
  1. Read from $_POST, and $_POST['login'] is escaped by mysqli_escape_string() for sql context(s), and $textMailAlt is assigned
    in sources/main.queries.php on line 390
  2. $textMailAlt is passed to sendEmail()
    in sources/main.queries.php on line 422
  3. PHPMailer::$AltBody is assigned
    in sources/main.functions.php on line 1156
  4. Tainted property PHPMailer::$AltBody is read, and $body is assigned
    in includes/libraries/Email/Phpmailer/class.phpmailer.php on line 2268
  5. $body is assigned
    in includes/libraries/Email/Phpmailer/class.phpmailer.php on line 2271
  6. PHPMailer::createBody() returns tainted data, and PHPMailer::$MIMEBody is assigned
    in includes/libraries/Email/Phpmailer/class.phpmailer.php on line 1275
  7. Tainted property PHPMailer::$MIMEBody is read, and $this->MIMEBody is passed to PHPMailer::smtpSend()
    in includes/libraries/Email/Phpmailer/class.phpmailer.php on line 1335
  8. $header . $body is passed to SMTP::data()
    in includes/libraries/Email/Phpmailer/class.phpmailer.php on line 1568
  9. $msg_data is passed through str_replace(), and ``str_replace(array(' ', ' '), ' ', $msg_data)`` is passed through explode(), and $lines is assigned
    in includes/libraries/Email/Phpmailer/class.smtp.php on line 654
  10. $line is assigned
    in includes/libraries/Email/Phpmailer/class.smtp.php on line 667
  11. $lines_out is assigned
    in includes/libraries/Email/Phpmailer/class.smtp.php on line 695
  12. $line_out is assigned
    in includes/libraries/Email/Phpmailer/class.smtp.php on line 698
  13. $line_out . self::CRLF is passed to SMTP::client_send()
    in includes/libraries/Email/Phpmailer/class.smtp.php on line 703
  5. Path: Read from $_POST in sources/items.queries.php on line 415
  1. Read from $_POST
    in sources/items.queries.php on line 415
  2. Data is decoded by json_decode()
    in vendor/sources/main.functions.php on line 1261
  3. $dataReceived is assigned
    in sources/items.queries.php on line 415
  4. Data is escaped by htmlspecialchars() for html context(s)
    in vendor/sources/main.functions.php on line 1483
  5. $label is assigned
    in sources/items.queries.php on line 419
  6. $label is passed through str_replace(), and str_replace('#item_label#', $label, $LANG['email_bodyalt_item_updated']) is passed to sendEmail()
    in sources/items.queries.php on line 916
  7. PHPMailer::$AltBody is assigned
    in sources/main.functions.php on line 1156
  8. Tainted property PHPMailer::$AltBody is read, and $body is assigned
    in includes/libraries/Email/Phpmailer/class.phpmailer.php on line 2268
  9. $body is assigned
    in includes/libraries/Email/Phpmailer/class.phpmailer.php on line 2271
  10. PHPMailer::createBody() returns tainted data, and PHPMailer::$MIMEBody is assigned
    in includes/libraries/Email/Phpmailer/class.phpmailer.php on line 1275
  11. Tainted property PHPMailer::$MIMEBody is read, and $this->MIMEBody is passed to PHPMailer::smtpSend()
    in includes/libraries/Email/Phpmailer/class.phpmailer.php on line 1335
  12. $header . $body is passed to SMTP::data()
    in includes/libraries/Email/Phpmailer/class.phpmailer.php on line 1568
  13. $msg_data is passed through str_replace(), and ``str_replace(array(' ', ' '), ' ', $msg_data)`` is passed through explode(), and $lines is assigned
    in includes/libraries/Email/Phpmailer/class.smtp.php on line 654
  14. $line is assigned
    in includes/libraries/Email/Phpmailer/class.smtp.php on line 667
  15. $lines_out is assigned
    in includes/libraries/Email/Phpmailer/class.smtp.php on line 695
  16. $line_out is assigned
    in includes/libraries/Email/Phpmailer/class.smtp.php on line 698
  17. $line_out . self::CRLF is passed to SMTP::client_send()
    in includes/libraries/Email/Phpmailer/class.smtp.php on line 703

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
994
        restore_error_handler();
995
        return $result;
996
    }
997
998
    /**
999
     * Get the latest error.
1000
     * @access public
1001
     * @return array
1002
     */
1003
    public function getError()
1004
    {
1005
        return $this->error;
1006
    }
1007
1008
    /**
1009
     * Get SMTP extensions available on the server
1010
     * @access public
1011
     * @return array|null
1012
     */
1013
    public function getServerExtList()
1014
    {
1015
        return $this->server_caps;
1016
    }
1017
1018
    /**
1019
     * A multipurpose method
1020
     * The method works in three ways, dependent on argument value and current state
1021
     *   1. HELO/EHLO was not sent - returns null and set up $this->error
1022
     *   2. HELO was sent
1023
     *     $name = 'HELO': returns server name
1024
     *     $name = 'EHLO': returns boolean false
1025
     *     $name = any string: returns null and set up $this->error
1026
     *   3. EHLO was sent
1027
     *     $name = 'HELO'|'EHLO': returns server name
1028
     *     $name = any string: if extension $name exists, returns boolean True
1029
     *       or its options. Otherwise returns boolean False
1030
     * In other words, one can use this method to detect 3 conditions:
1031
     *  - null returned: handshake was not or we don't know about ext (refer to $this->error)
1032
     *  - false returned: the requested feature exactly not exists
1033
     *  - positive value returned: the requested feature exists
1034
     * @param string $name Name of SMTP extension or 'HELO'|'EHLO'
1035
     * @return mixed
1036
     */
1037
    public function getServerExt($name)
1038
    {
1039
        if (!$this->server_caps) {
1040
            $this->setError('No HELO/EHLO was sent');
1041
            return null;
1042
        }
1043
1044
        // the tight logic knot ;)
1045
        if (!array_key_exists($name, $this->server_caps)) {
1046
            if ($name == 'HELO') {
1047
                return $this->server_caps['EHLO'];
1048
            }
1049
            if ($name == 'EHLO' || array_key_exists('EHLO', $this->server_caps)) {
1050
                return false;
1051
            }
1052
            $this->setError('HELO handshake was used. Client knows nothing about server extensions');
1053
            return null;
1054
        }
1055
1056
        return $this->server_caps[$name];
1057
    }
1058
1059
    /**
1060
     * Get the last reply from the server.
1061
     * @access public
1062
     * @return string
1063
     */
1064
    public function getLastReply()
1065
    {
1066
        return $this->last_reply;
1067
    }
1068
1069
    /**
1070
     * Read the SMTP server's response.
1071
     * Either before eof or socket timeout occurs on the operation.
1072
     * With SMTP we can tell if we have more lines to read if the
1073
     * 4th character is '-' symbol. If it is a space then we don't
1074
     * need to read anything else.
1075
     * @access protected
1076
     * @return string
1077
     */
1078
    protected function get_lines()
1079
    {
1080
        // If the connection is bad, give up straight away
1081
        if (!is_resource($this->smtp_conn)) {
1082
            return '';
1083
        }
1084
        $data = '';
1085
        $endtime = 0;
1086
        stream_set_timeout($this->smtp_conn, $this->Timeout);
1087
        if ($this->Timelimit > 0) {
1088
            $endtime = time() + $this->Timelimit;
1089
        }
1090
        while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
1091
            $str = @fgets($this->smtp_conn, 515);
1092
            $this->edebug("SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL);
1093
            $this->edebug("SMTP -> get_lines(): \$str is  \"$str\"", self::DEBUG_LOWLEVEL);
1094
            $data .= $str;
1095
            // If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled),
1096
            // or 4th character is a space, we are done reading, break the loop,
1097
            // string array access is a micro-optimisation over strlen
1098
            if (!isset($str[3]) or (isset($str[3]) and $str[3] == ' ')) {
1099
                break;
1100
            }
1101
            // Timed-out? Log and break
1102
            $info = stream_get_meta_data($this->smtp_conn);
1103
            if ($info['timed_out']) {
1104
                $this->edebug(
1105
                    'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)',
1106
                    self::DEBUG_LOWLEVEL
1107
                );
1108
                break;
1109
            }
1110
            // Now check if reads took too long
1111
            if ($endtime and time() > $endtime) {
1112
                $this->edebug(
1113
                    'SMTP -> get_lines(): timelimit reached (' .
1114
                    $this->Timelimit . ' sec)',
1115
                    self::DEBUG_LOWLEVEL
1116
                );
1117
                break;
1118
            }
1119
        }
1120
        return $data;
1121
    }
1122
1123
    /**
1124
     * Enable or disable VERP address generation.
1125
     * @param boolean $enabled
1126
     */
1127
    public function setVerp($enabled = false)
1128
    {
1129
        $this->do_verp = $enabled;
1130
    }
1131
1132
    /**
1133
     * Get VERP address generation mode.
1134
     * @return boolean
1135
     */
1136
    public function getVerp()
1137
    {
1138
        return $this->do_verp;
1139
    }
1140
1141
    /**
1142
     * Set error messages and codes.
1143
     * @param string $message The error message
1144
     * @param string $detail Further detail on the error
1145
     * @param string $smtp_code An associated SMTP error code
1146
     * @param string $smtp_code_ex Extended SMTP code
1147
     */
1148
    protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '')
1149
    {
1150
        $this->error = array(
1151
            'error' => $message,
1152
            'detail' => $detail,
1153
            'smtp_code' => $smtp_code,
1154
            'smtp_code_ex' => $smtp_code_ex
1155
        );
1156
    }
1157
1158
    /**
1159
     * Set debug output method.
1160
     * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it.
1161
     */
1162
    public function setDebugOutput($method = 'echo')
1163
    {
1164
        $this->Debugoutput = $method;
1165
    }
1166
1167
    /**
1168
     * Get debug output method.
1169
     * @return string
1170
     */
1171
    public function getDebugOutput()
1172
    {
1173
        return $this->Debugoutput;
1174
    }
1175
1176
    /**
1177
     * Set debug output level.
1178
     * @param integer $level
1179
     */
1180
    public function setDebugLevel($level = 0)
1181
    {
1182
        $this->do_debug = $level;
1183
    }
1184
1185
    /**
1186
     * Get debug output level.
1187
     * @return integer
1188
     */
1189
    public function getDebugLevel()
1190
    {
1191
        return $this->do_debug;
1192
    }
1193
1194
    /**
1195
     * Set SMTP timeout.
1196
     * @param integer $timeout
1197
     */
1198
    public function setTimeout($timeout = 0)
1199
    {
1200
        $this->Timeout = $timeout;
1201
    }
1202
1203
    /**
1204
     * Get SMTP timeout.
1205
     * @return integer
1206
     */
1207
    public function getTimeout()
1208
    {
1209
        return $this->Timeout;
1210
    }
1211
1212
    /**
1213
     * Reports an error number and string.
1214
     * @param integer $errno The error number returned by PHP.
1215
     * @param string $errmsg The error message returned by PHP.
1216
     * @param string $errfile The file the error occurred in
1217
     * @param integer $errline The line number the error occurred on
1218
     */
1219
    protected function errorHandler($errno, $errmsg, $errfile = '', $errline = 0)
1220
    {
1221
        $notice = 'Connection failed.';
1222
        $this->setError(
1223
            $notice,
1224
            $errno,
1225
            $errmsg
1226
        );
1227
        $this->edebug(
1228
            $notice . ' Error #' . $errno . ': ' . $errmsg . " [$errfile line $errline]",
1229
            self::DEBUG_CONNECTION
1230
        );
1231
    }
1232
1233
    /**
1234
     * Will return the ID of the last smtp transaction based on a list of patterns provided
1235
     * in SMTP::$smtp_transaction_id_patterns.
1236
     * If no reply has been received yet, it will return null.
1237
     * If no pattern has been matched, it will return false.
1238
     * @return bool|null|string
1239
     */
1240
    public function getLastTransactionID()
1241
    {
1242
        $reply = $this->getLastReply();
1243
1244
        if (empty($reply)) {
1245
            return null;
1246
        }
1247
1248
        foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
1249
            if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) {
1250
                return $matches[1];
1251
            }
1252
        }
1253
1254
        return false;
1255
    }
1256
}
1257