Completed
Push — master ( 4f7967...d94127 )
by Marcus
09:41
created

SMTP::errorHandler()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 9
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 13
ccs 0
cts 0
cp 0
crap 2
rs 9.4285
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
0 ignored issues
show
Complexity introduced by
This class has a complexity of 129 which exceeds the configured maximum of 50.

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

Some resources for further reading:

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

Loading history...
Complexity introduced by
This class has 1188 lines of code which exceeds the configured maximum of 1000.

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

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

Loading history...
28
{
29
    /**
30
     * The PHPMailer SMTP version number.
31
     * @var string
32
     */
33
    const VERSION = '5.2.16';
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.16';
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
     * The socket for the server connection.
155
     * @var resource
156
     */
157
    protected $smtp_conn;
158
159
    /**
160
     * Error information, if any, for the last SMTP command.
161
     * @var array
162
     */
163
    protected $error = array(
164
        'error' => '',
165
        'detail' => '',
166
        'smtp_code' => '',
167
        'smtp_code_ex' => ''
168
    );
169
170
    /**
171
     * The reply the server sent to us for HELO.
172
     * If null, no HELO string has yet been received.
173
     * @var string|null
174
     */
175
    protected $helo_rply = null;
176
177
    /**
178
     * The set of SMTP extensions sent in reply to EHLO command.
179
     * Indexes of the array are extension names.
180
     * Value at index 'HELO' or 'EHLO' (according to command that was sent)
181
     * represents the server name. In case of HELO it is the only element of the array.
182
     * Other values can be boolean TRUE or an array containing extension options.
183
     * If null, no HELO/EHLO string has yet been received.
184
     * @var array|null
185
     */
186
    protected $server_caps = null;
187
188
    /**
189
     * The most recent reply received from the server.
190
     * @var string
191
     */
192
    protected $last_reply = '';
193
194
    /**
195
     * Output debugging info via a user-selected method.
196
     * @see SMTP::$Debugoutput
197
     * @see SMTP::$do_debug
198
     * @param string $str Debug string to output
199
     * @param integer $level The debug level of this message; see DEBUG_* constants
200
     * @return void
201
     */
202 35
    protected function edebug($str, $level = 0)
203
    {
204 35
        if ($level > $this->do_debug) {
205 34
            return;
206
        }
207
        //Avoid clash with built-in function names
208 35
        if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) {
209
            call_user_func($this->Debugoutput, $str, $this->do_debug);
210
            return;
211
        }
212 35
        switch ($this->Debugoutput) {
213 35
            case 'error_log':
214
                //Don't output, just log
215
                error_log($str);
216
                break;
217 35
            case 'html':
218
                //Cleans up output a bit for a better looking, HTML-safe output
219
                echo htmlentities(
220
                    preg_replace('/[\r\n]+/', '', $str),
221
                    ENT_QUOTES,
222
                    'UTF-8'
223
                )
224
                . "<br>\n";
225
                break;
226 35
            case 'echo':
227 35
            default:
228
                //Normalize line breaks
229 35
                $str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str);
230 35
                echo gmdate('Y-m-d H:i:s') . "\t" . str_replace(
231 35
                    "\n",
232 35
                    "\n                   \t                  ",
233 35
                    trim($str)
234 35
                )."\n";
235 35
        }
236 35
    }
237
238
    /**
239
     * Connect to an SMTP server.
240
     * @param string $host SMTP server IP or host name
241
     * @param integer $port The port number to connect to
242
     * @param integer $timeout How long to wait for the connection to open
243
     * @param array $options An array of options for stream_context_create()
244
     * @access public
245
     * @return boolean
246
     */
247 35
    public function connect($host, $port = null, $timeout = 30, $options = array())
248
    {
249 35
        static $streamok;
250
        //This is enabled by default since 5.0.0 but some providers disable it
251
        //Check this once and cache the result
252 35
        if (is_null($streamok)) {
253 1
            $streamok = function_exists('stream_socket_client');
254 1
        }
255
        // Clear errors to avoid confusion
256 35
        $this->setError('');
257
        // Make sure we are __not__ connected
258 35
        if ($this->connected()) {
259
            // Already connected, generate error
260
            $this->setError('Already connected to a server');
261
            return false;
262
        }
263 35
        if (empty($port)) {
264
            $port = self::DEFAULT_SMTP_PORT;
265
        }
266
        // Connect to the SMTP server
267 35
        $this->edebug(
268 35
            "Connection: opening to $host:$port, timeout=$timeout, options=".var_export($options, true),
269
            self::DEBUG_CONNECTION
270 35
        );
271 35
        $errno = 0;
272 35
        $errstr = '';
273 35
        if ($streamok) {
274 35
            $socket_context = stream_context_create($options);
275
            set_error_handler(array('self', 'errorHandler'));
276 35
            $this->smtp_conn = stream_socket_client(
277 35
                $host . ":" . $port,
278
                $errno,
279
                $errstr,
280 35
                $timeout,
281 35
                STREAM_CLIENT_CONNECT,
282
                $socket_context
283 35
            );
284 35
            restore_error_handler();
285
        } else {
286
            //Fall back to fsockopen which should work in more places, but is missing some features
287
            $this->edebug(
288
                "Connection: stream_socket_client not available, falling back to fsockopen",
289
                self::DEBUG_CONNECTION
290
            );
291
            set_error_handler(array('self', 'errorHandler'));
292
            $this->smtp_conn = fsockopen(
293
                $host,
294
                $port,
295
                $errno,
296
                $errstr,
297
                $timeout
298
            );
299 35
            restore_error_handler();
300 1
        }
301 1
        // Verify we connected properly
302 1
        if (!is_resource($this->smtp_conn)) {
303
            $this->setError(
304 1
                'Failed to connect to server',
305 1
                $errno,
306 1
                $errstr
307 1
            );
308
            $this->edebug(
309 1
                'SMTP ERROR: ' . $this->error['error']
310 1
                . ": $errstr ($errno)",
311
                self::DEBUG_CLIENT
312 35
            );
313
            return false;
314
        }
315 35
        $this->edebug('Connection: opened', self::DEBUG_CONNECTION);
316 35
        // SMTP server can take longer to respond, give longer timeout for first read
317
        // Windows does not have support for this timeout function
318 35
        if (substr(PHP_OS, 0, 3) != 'WIN') {
319
            $max = ini_get('max_execution_time');
320
            // Don't bother if unlimited
321 35
            if ($max != 0 && $timeout > $max) {
322 35
                @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...
323
            }
324 35
            stream_set_timeout($this->smtp_conn, $timeout, 0);
325 35
        }
326 35
        // Get any announcement
327
        $announce = $this->get_lines();
328
        $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER);
329
        return true;
330
    }
331
332
    /**
333
     * Initiate a TLS (encrypted) session.
334
     * @access public
335
     * @return boolean
336
     */
337
    public function startTLS()
338
    {
339
        if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) {
340
            return false;
341
        }
342
343
        //Allow the best TLS version(s) we can
344
        $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
345
346
        //PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT
347
        //so add them back in manually if we can
348
        if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
349
            $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
350
            $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
351
        }
352
353
        // Begin encrypted connection
354
        if (!stream_socket_enable_crypto(
355
            $this->smtp_conn,
356
            true,
357
            $crypto_method
358
        )) {
359
            return false;
360
        }
361
        return true;
362
    }
363
364
    /**
365
     * Perform SMTP authentication.
366
     * Must be run after hello().
367
     * @see hello()
368
     * @param string $username The user name
369
     * @param string $password The password
370
     * @param string $authtype The auth type (PLAIN, LOGIN, NTLM, CRAM-MD5, XOAUTH2)
371
     * @param string $realm The auth realm for NTLM
372
     * @param string $workstation The auth workstation for NTLM
373
     * @param null|OAuth $OAuth An optional OAuth instance (@see PHPMailerOAuth)
374
     * @return bool True if successfully authenticated.* @access public
375
     */
376
    public function authenticate(
0 ignored issues
show
Complexity introduced by
This operation has 6532 execution paths which exceeds the configured maximum of 200.

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

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

Loading history...
377
        $username,
378
        $password,
379
        $authtype = null,
380
        $realm = '',
381
        $workstation = '',
382
        $OAuth = null
383
    ) {
384
        if (!$this->server_caps) {
385
            $this->setError('Authentication is not allowed before HELO/EHLO');
386
            return false;
387
        }
388
389
        if (array_key_exists('EHLO', $this->server_caps)) {
390
        // SMTP extensions are available. Let's try to find a proper authentication method
391
392
            if (!array_key_exists('AUTH', $this->server_caps)) {
393
                $this->setError('Authentication is not allowed at this stage');
394
                // 'at this stage' means that auth may be allowed after the stage changes
395
                // e.g. after STARTTLS
396
                return false;
397
            }
398
399
            self::edebug('Auth method requested: ' . ($authtype ? $authtype : 'UNKNOWN'), self::DEBUG_LOWLEVEL);
400
            self::edebug(
401
                'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']),
402
                self::DEBUG_LOWLEVEL
403
            );
404
405
            if (empty($authtype)) {
406
                foreach (array('CRAM-MD5', 'LOGIN', 'PLAIN', 'NTLM', 'XOAUTH2') as $method) {
407
                    if (in_array($method, $this->server_caps['AUTH'])) {
408
                        $authtype = $method;
409
                        break;
410
                    }
411
                }
412
                if (empty($authtype)) {
413
                    $this->setError('No supported authentication methods found');
414
                    return false;
415
                }
416
                self::edebug('Auth method selected: '.$authtype, self::DEBUG_LOWLEVEL);
417
            }
418
419
            if (!in_array($authtype, $this->server_caps['AUTH'])) {
420
                $this->setError("The requested authentication method \"$authtype\" is not supported by the server");
421
                return false;
422
            }
423
        } elseif (empty($authtype)) {
424
            $authtype = 'LOGIN';
425
        }
426
        switch ($authtype) {
427
            case 'PLAIN':
428
                // Start authentication
429
                if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) {
430
                    return false;
431
                }
432
                // Send encoded username and password
433
                if (!$this->sendCommand(
434
                    'User & Password',
435
                    base64_encode("\0" . $username . "\0" . $password),
436
                    235
437
                )
438
                ) {
439
                    return false;
440
                }
441
                break;
442
            case 'LOGIN':
443
                // Start authentication
444
                if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) {
445
                    return false;
446
                }
447
                if (!$this->sendCommand("Username", base64_encode($username), 334)) {
448
                    return false;
449
                }
450
                if (!$this->sendCommand("Password", base64_encode($password), 235)) {
451
                    return false;
452
                }
453
                break;
454
            case 'XOAUTH2':
455
                //If the OAuth Instance is not set. Can be a case when PHPMailer is used
456
                //instead of PHPMailerOAuth
457
                if (is_null($OAuth)) {
458
                    return false;
459
                }
460
                $oauth = $OAuth->getOauth64();
461
462
                // Start authentication
463
                if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
464
                    return false;
465
                }
466
                break;
467
            case 'NTLM':
468
                /*
469
                 * ntlm_sasl_client.php
470
                 * Bundled with Permission
471
                 *
472
                 * How to telnet in windows:
473
                 * http://technet.microsoft.com/en-us/library/aa995718%28EXCHG.65%29.aspx
474
                 * PROTOCOL Docs http://curl.haxx.se/rfc/ntlm.html#ntlmSmtpAuthentication
475
                 */
476
                require_once 'extras/ntlm_sasl_client.php';
477
                $temp = new stdClass;
478
                $ntlm_client = new ntlm_sasl_client_class;
479
                //Check that functions are available
480
                if (!$ntlm_client->Initialize($temp)) {
481
                    $this->setError($temp->error);
482
                    $this->edebug(
483
                        'You need to enable some modules in your php.ini file: '
484
                        . $this->error['error'],
485
                        self::DEBUG_CLIENT
486
                    );
487
                    return false;
488
                }
489
                //msg1
490
                $msg1 = $ntlm_client->TypeMsg1($realm, $workstation); //msg1
491
492
                if (!$this->sendCommand(
493
                    'AUTH NTLM',
494
                    'AUTH NTLM ' . base64_encode($msg1),
495
                    334
496
                )
497
                ) {
498
                    return false;
499
                }
500
                //Though 0 based, there is a white space after the 3 digit number
501
                //msg2
502
                $challenge = substr($this->last_reply, 3);
503
                $challenge = base64_decode($challenge);
504
                $ntlm_res = $ntlm_client->NTLMResponse(
505
                    substr($challenge, 24, 8),
506
                    $password
507
                );
508
                //msg3
509
                $msg3 = $ntlm_client->TypeMsg3(
510
                    $ntlm_res,
511
                    $username,
512
                    $realm,
513
                    $workstation
514
                );
515
                // send encoded username
516
                return $this->sendCommand('Username', base64_encode($msg3), 235);
517
            case 'CRAM-MD5':
518
                // Start authentication
519
                if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) {
520
                    return false;
521
                }
522
                // Get the challenge
523
                $challenge = base64_decode(substr($this->last_reply, 4));
524
525
                // Build the response
526
                $response = $username . ' ' . $this->hmac($challenge, $password);
527
528
                // send encoded credentials
529
                return $this->sendCommand('Username', base64_encode($response), 235);
530
            default:
531
                $this->setError("Authentication method \"$authtype\" is not supported");
532
                return false;
533
        }
534
        return true;
535
    }
536
537
    /**
538
     * Calculate an MD5 HMAC hash.
539
     * Works like hash_hmac('md5', $data, $key)
540
     * in case that function is not available
541
     * @param string $data The data to hash
542
     * @param string $key  The key to hash with
543
     * @access protected
544
     * @return string
545
     */
546
    protected function hmac($data, $key)
547
    {
548
        if (function_exists('hash_hmac')) {
549
            return hash_hmac('md5', $data, $key);
550
        }
551
552
        // The following borrowed from
553
        // http://php.net/manual/en/function.mhash.php#27225
554
555
        // RFC 2104 HMAC implementation for php.
556
        // Creates an md5 HMAC.
557
        // Eliminates the need to install mhash to compute a HMAC
558
        // by Lance Rushing
559
560
        $bytelen = 64; // byte length for md5
561
        if (strlen($key) > $bytelen) {
562
            $key = pack('H*', md5($key));
563
        }
564
        $key = str_pad($key, $bytelen, chr(0x00));
565
        $ipad = str_pad('', $bytelen, chr(0x36));
566
        $opad = str_pad('', $bytelen, chr(0x5c));
567
        $k_ipad = $key ^ $ipad;
568
        $k_opad = $key ^ $opad;
569
570
        return md5($k_opad . pack('H*', md5($k_ipad . $data)));
571
    }
572
573
    /**
574
     * Check connection state.
575 35
     * @access public
576
     * @return boolean True if connected.
577 35
     */
578 35
    public function connected()
579 35
    {
580
        if (is_resource($this->smtp_conn)) {
581
            $sock_status = stream_get_meta_data($this->smtp_conn);
582
            if ($sock_status['eof']) {
583
                // The socket is valid but we are not connected
584
                $this->edebug(
585
                    'SMTP NOTICE: EOF caught while checking if connected',
586
                    self::DEBUG_CLIENT
587
                );
588 35
                $this->close();
589
                return false;
590 35
            }
591
            return true; // everything looks good
592
        }
593
        return false;
594
    }
595
596
    /**
597
     * Close the socket and clean up the state of the class.
598
     * Don't use this function without first trying to use QUIT.
599
     * @see quit()
600 35
     * @access public
601
     * @return void
602 35
     */
603 35
    public function close()
604 35
    {
605 35
        $this->setError('');
606
        $this->server_caps = null;
607 35
        $this->helo_rply = null;
608 35
        if (is_resource($this->smtp_conn)) {
609 35
            // close the connection and cleanup
610 35
            fclose($this->smtp_conn);
611 35
            $this->smtp_conn = null; //Makes for cleaner serialization
612
            $this->edebug('Connection: closed', self::DEBUG_CONNECTION);
613
        }
614
    }
615
616
    /**
617
     * Send an SMTP DATA command.
618
     * Issues a data command and sends the msg_data to the server,
619
     * finializing the mail transaction. $msg_data is the message
620
     * that is to be send with the headers. Each header needs to be
621
     * on a single line followed by a <CRLF> with the message headers
622
     * and the message body being separated by and additional <CRLF>.
623
     * Implements rfc 821: DATA <CRLF>
624
     * @param string $msg_data Message data to send
625 33
     * @access public
626
     * @return boolean
627
     */
628 33
    public function data($msg_data)
0 ignored issues
show
Complexity introduced by
This operation has 366 execution paths which exceeds the configured maximum of 200.

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

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

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

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

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

Loading history...
865
    {
866
        if (!$this->connected()) {
867
            $this->setError("Called $command without being connected");
868 35
            return false;
869 1
        }
870 1
        //Reject line breaks in all commands
871
        if (strpos($commandstring, "\n") !== false or strpos($commandstring, "\r") !== false) {
872 35
            $this->setError("Command '$command' contained line breaks");
873
            return false;
874 35
        }
875
        $this->client_send($commandstring . self::CRLF);
876 35
877 35
        $this->last_reply = $this->get_lines();
878 35
        // Fetch SMTP code and possible error code explanation
879 35
        $matches = array();
880
        if (preg_match("/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]) )?/", $this->last_reply, $matches)) {
881 35
            $code = $matches[1];
882 35
            $code_ex = (count($matches) > 2 ? $matches[2] : null);
883 35
            // Cut off error code from each response line
884 35
            $detail = preg_replace(
885 35
                "/{$code}[ -]".($code_ex ? str_replace('.', '\\.', $code_ex).' ' : '')."/m",
886 35
                '',
887
                $this->last_reply
888
            );
889
        } else {
890
            // Fall back to simple parsing if regex fails
891
            $code = substr($this->last_reply, 0, 3);
892
            $code_ex = null;
893 35
            $detail = substr($this->last_reply, 4);
894
        }
895 35
896
        $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
897
898
        if (!in_array($code, (array)$expect)) {
899
            $this->setError(
900
                "$command command failed",
901
                $detail,
902
                $code,
903
                $code_ex
904
            );
905
            $this->edebug(
906
                'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply,
907
                self::DEBUG_CLIENT
908
            );
909 35
            return false;
910 35
        }
911
912
        $this->setError('');
913
        return true;
914
    }
915
916
    /**
917
     * Send an SMTP SAML command.
918
     * Starts a mail transaction from the email address specified in $from.
919
     * Returns true if successful or false otherwise. If True
920
     * the mail transaction is started and then one or more recipient
921
     * commands may be called followed by a data command. This command
922
     * will send the message to the users terminal if they are logged
923
     * in and send them an email.
924
     * Implements rfc 821: SAML <SP> FROM:<reverse-path> <CRLF>
925
     * @param string $from The address the message is from
926
     * @access public
927
     * @return boolean
928
     */
929
    public function sendAndMail($from)
930
    {
931
        return $this->sendCommand('SAML', "SAML FROM:$from", 250);
932
    }
933
934
    /**
935
     * Send an SMTP VRFY command.
936
     * @param string $name The name to verify
937
     * @access public
938
     * @return boolean
939
     */
940
    public function verify($name)
941
    {
942
        return $this->sendCommand('VRFY', "VRFY $name", array(250, 251));
943
    }
944
945
    /**
946
     * Send an SMTP NOOP command.
947
     * Used to keep keep-alives alive, doesn't actually do anything
948
     * @access public
949
     * @return boolean
950
     */
951
    public function noop()
952
    {
953
        return $this->sendCommand('NOOP', 'NOOP', 250);
954
    }
955
956
    /**
957
     * Send an SMTP TURN command.
958
     * This is an optional command for SMTP that this class does not support.
959
     * This method is here to make the RFC821 Definition complete for this class
960
     * and _may_ be implemented in future
961
     * Implements from rfc 821: TURN <CRLF>
962
     * @access public
963
     * @return boolean
964
     */
965
    public function turn()
966
    {
967
        $this->setError('The SMTP TURN command is not implemented');
968
        $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT);
969
        return false;
970
    }
971
972
    /**
973
     * Send raw data to the server.
974
     * @param string $data The data to send
975 35
     * @access public
976
     * @return integer|boolean The number of bytes sent to the server or false on error
977 35
     */
978 35
    public function client_send($data)
979
    {
980
        $this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT);
981
        return fwrite($this->smtp_conn, $data);
982
    }
983
984
    /**
985
     * Get the latest error.
986 1
     * @access public
987
     * @return array
988 1
     */
989
    public function getError()
990
    {
991
        return $this->error;
992
    }
993
994
    /**
995
     * Get SMTP extensions available on the server
996
     * @access public
997
     * @return array|null
998
     */
999
    public function getServerExtList()
1000
    {
1001
        return $this->server_caps;
1002
    }
1003
1004
    /**
1005
     * A multipurpose method
1006
     * The method works in three ways, dependent on argument value and current state
1007
     *   1. HELO/EHLO was not sent - returns null and set up $this->error
1008
     *   2. HELO was sent
1009
     *     $name = 'HELO': returns server name
1010
     *     $name = 'EHLO': returns boolean false
1011
     *     $name = any string: returns null and set up $this->error
1012
     *   3. EHLO was sent
1013
     *     $name = 'HELO'|'EHLO': returns server name
1014
     *     $name = any string: if extension $name exists, returns boolean True
1015
     *       or its options. Otherwise returns boolean False
1016
     * In other words, one can use this method to detect 3 conditions:
1017
     *  - null returned: handshake was not or we don't know about ext (refer to $this->error)
1018
     *  - false returned: the requested feature exactly not exists
1019
     *  - positive value returned: the requested feature exists
1020 35
     * @param string $name Name of SMTP extension or 'HELO'|'EHLO'
1021
     * @return mixed
1022 35
     */
1023
    public function getServerExt($name)
1024
    {
1025
        if (!$this->server_caps) {
1026
            $this->setError('No HELO/EHLO was sent');
1027
            return null;
1028 35
        }
1029 35
1030
        // the tight logic knot ;)
1031
        if (!array_key_exists($name, $this->server_caps)) {
1032 35
            if ($name == 'HELO') {
1033 35
                return $this->server_caps['EHLO'];
1034
            }
1035
            if ($name == 'EHLO' || array_key_exists('EHLO', $this->server_caps)) {
1036
                return false;
1037
            }
1038
            $this->setError('HELO handshake was used. Client knows nothing about server extensions');
1039
            return null;
1040
        }
1041
1042
        return $this->server_caps[$name];
1043
    }
1044
1045
    /**
1046
     * Get the last reply from the server.
1047
     * @access public
1048
     * @return string
1049
     */
1050
    public function getLastReply()
1051
    {
1052
        return $this->last_reply;
1053
    }
1054
1055
    /**
1056
     * Read the SMTP server's response.
1057
     * Either before eof or socket timeout occurs on the operation.
1058
     * With SMTP we can tell if we have more lines to read if the
1059
     * 4th character is '-' symbol. If it is a space then we don't
1060
     * need to read anything else.
1061 35
     * @access protected
1062
     * @return string
1063
     */
1064 35
    protected function get_lines()
1065
    {
1066
        // If the connection is bad, give up straight away
1067 35
        if (!is_resource($this->smtp_conn)) {
1068 35
            return '';
1069 35
        }
1070 35
        $data = '';
1071 35
        $endtime = 0;
1072 35
        stream_set_timeout($this->smtp_conn, $this->Timeout);
1073 35
        if ($this->Timelimit > 0) {
1074 35
            $endtime = time() + $this->Timelimit;
1075 35
        }
1076 35
        while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
1077 35
            $str = @fgets($this->smtp_conn, 515);
1078
            $this->edebug("SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL);
1079 35
            $this->edebug("SMTP -> get_lines(): \$str is  \"$str\"", self::DEBUG_LOWLEVEL);
1080 35
            $data .= $str;
1081
            // If 4th character is a space, we are done reading, break the loop, micro-optimisation over strlen
1082
            if ((isset($str[3]) and $str[3] == ' ')) {
1083 35
                break;
1084 35
            }
1085
            // Timed-out? Log and break
1086
            $info = stream_get_meta_data($this->smtp_conn);
1087
            if ($info['timed_out']) {
1088
                $this->edebug(
1089
                    'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)',
1090
                    self::DEBUG_LOWLEVEL
1091
                );
1092 35
                break;
1093
            }
1094
            // Now check if reads took too long
1095
            if ($endtime and time() > $endtime) {
1096
                $this->edebug(
1097
                    'SMTP -> get_lines(): timelimit reached ('.
1098
                    $this->Timelimit . ' sec)',
1099
                    self::DEBUG_LOWLEVEL
1100 35
                );
1101 35
                break;
1102
            }
1103
        }
1104
        return $data;
1105
    }
1106
1107
    /**
1108 35
     * Enable or disable VERP address generation.
1109
     * @param boolean $enabled
1110 35
     */
1111 35
    public function setVerp($enabled = false)
1112
    {
1113
        $this->do_verp = $enabled;
1114
    }
1115
1116
    /**
1117
     * Get VERP address generation mode.
1118
     * @return boolean
1119
     */
1120
    public function getVerp()
0 ignored issues
show
Coding Style Naming introduced by
The 'getVerp()' method which returns a boolean should be named 'is...()' or 'has...()'
Loading history...
1121
    {
1122
        return $this->do_verp;
1123
    }
1124
1125
    /**
1126
     * Set error messages and codes.
1127
     * @param string $message The error message
1128
     * @param string $detail Further detail on the error
1129 35
     * @param string $smtp_code An associated SMTP error code
1130
     * @param string $smtp_code_ex Extended SMTP code
1131 35
     */
1132 35
    protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '')
1133 35
    {
1134 35
        $this->error = array(
1135
            'error' => $message,
1136 35
            'detail' => $detail,
1137 35
            'smtp_code' => $smtp_code,
1138
            'smtp_code_ex' => $smtp_code_ex
1139
        );
1140
    }
1141
1142
    /**
1143 35
     * Set debug output method.
1144
     * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it.
1145 35
     */
1146 35
    public function setDebugOutput($method = 'echo')
1147
    {
1148
        $this->Debugoutput = $method;
1149
    }
1150
1151
    /**
1152
     * Get debug output method.
1153
     * @return string
1154
     */
1155
    public function getDebugOutput()
1156
    {
1157
        return $this->Debugoutput;
1158
    }
1159
1160
    /**
1161 35
     * Set debug output level.
1162
     * @param integer $level
1163 35
     */
1164 35
    public function setDebugLevel($level = 0)
1165
    {
1166
        $this->do_debug = $level;
1167
    }
1168
1169
    /**
1170
     * Get debug output level.
1171
     * @return integer
1172
     */
1173
    public function getDebugLevel()
1174
    {
1175
        return $this->do_debug;
1176
    }
1177
1178
    /**
1179 35
     * Set SMTP timeout.
1180
     * @param integer $timeout
1181 35
     */
1182 35
    public function setTimeout($timeout = 0)
1183
    {
1184
        $this->Timeout = $timeout;
1185
    }
1186
1187
    /**
1188
     * Get SMTP timeout.
1189
     * @return integer
1190
     */
1191
    public function getTimeout()
1192
    {
1193
        return $this->Timeout;
1194
    }
1195
1196
    /**
1197
     * Reports an error number and string.
1198
     * @param integer $errno The error number returned by PHP.
1199
     * @param string $errmsg The error message returned by PHP.
1200
     */
1201
    function errorHandler($errno, $errmsg)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
Comprehensibility Best Practice introduced by
It is recommend to declare an explicit visibility for errorHandler.

Generally, we recommend to declare visibility for all methods in your source code. This has the advantage of clearly communication to other developers, and also yourself, how this method should be consumed.

If you are not sure which visibility to choose, it is a good idea to start with the most restrictive visibility, and then raise visibility as needed, i.e. start with private, and only raise it to protected if a sub-class needs to have access, or public if an external class needs access.

Loading history...
1202
    {
1203
        $notice = 'Connection: Failed to connect to server.';
1204
        $this->setError(
1205
            $notice,
1206
            $errno,
1207
            $errmsg
1208
        );
1209
        $this->edebug(
1210
            $notice . ' Error number ' . $errno . '. "Error notice: ' . $errmsg,
1211
            self::DEBUG_CONNECTION
1212
        );
1213
    }
1214
}
1215