Issues (1919)

Security Analysis    not enabled

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

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

lib/Ajde/Mailer/class.smtp.php (11 issues)

Upgrade to new PHP Analysis Engine

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

1
<?php
2
/**
3
 * PHPMailer RFC821 SMTP email transport class.
4
 * Version 5.2.7
5
 * PHP version 5.0.0.
6
 *
7
 * @category  PHP
8
 *
9
 * @link      https://github.com/PHPMailer/PHPMailer/
10
 *
11
 * @author    Marcus Bointon (coolbru) <[email protected]>
12
 * @author    Jim Jagielski (jimjag) <[email protected]>
13
 * @author    Andy Prevost (codeworxtech) <[email protected]>
14
 * @copyright 2013 Marcus Bointon
15
 * @copyright 2004 - 2008 Andy Prevost
16
 * @copyright 2010 - 2012 Jim Jagielski
17
 * @license   http://www.gnu.org/copyleft/lesser.html Distributed under the Lesser General Public License (LGPL)
18
 */
19
20
/**
21
 * PHPMailer RFC821 SMTP email transport class.
22
 *
23
 * Implements RFC 821 SMTP commands
24
 * and provides some utility methods for sending mail to an SMTP server.
25
 *
26
 * PHP Version 5.0.0
27
 *
28
 * @category PHP
29
 *
30
 * @link     https://github.com/PHPMailer/PHPMailer/blob/master/class.smtp.php
31
 *
32
 * @author   Chris Ryan <[email protected]>
33
 * @author   Marcus Bointon <[email protected]>
34
 * @license  http://www.gnu.org/copyleft/lesser.html Distributed under the Lesser General Public License (LGPL)
35
 */
36
class SMTP
37
{
38
    /**
39
     * The PHPMailer SMTP Version number.
40
     */
41
    const VERSION = '5.2.7';
42
43
    /**
44
     * SMTP line break constant.
45
     */
46
    const CRLF = "\r\n";
47
48
    /**
49
     * The SMTP port to use if one is not specified.
50
     */
51
    const DEFAULT_SMTP_PORT = 25;
52
53
    /**
54
     * The PHPMailer SMTP Version number.
55
     *
56
     * @var string
57
     *
58
     * @deprecated This should be a constant
59
     * @see        SMTP::VERSION
60
     */
61
    public $Version = '5.2.7';
62
63
    /**
64
     * SMTP server port number.
65
     *
66
     * @var int
67
     *
68
     * @deprecated This is only ever ued as default value, so should be a constant
69
     * @see        SMTP::DEFAULT_SMTP_PORT
70
     */
71
    public $SMTP_PORT = 25;
72
73
    /**
74
     * SMTP reply line ending.
75
     *
76
     * @var string
77
     *
78
     * @deprecated Use the class constant instead
79
     * @see        SMTP::CRLF
80
     */
81
    public $CRLF = "\r\n";
82
83
    /**
84
     * Debug output level.
85
     * Options:
86
     *   0: no output
87
     *   1: commands
88
     *   2: data and commands
89
     *   3: as 2 plus connection status
90
     *   4: low level data output.
91
     *
92
     * @var int
93
     */
94
    public $do_debug = 0;
95
96
    /**
97
     * The function/method to use for debugging output.
98
     * Options: 'echo', 'html' or 'error_log'.
99
     *
100
     * @var string
101
     */
102
    public $Debugoutput = 'echo';
103
104
    /**
105
     * Whether to use VERP.
106
     *
107
     * @var bool
108
     */
109
    public $do_verp = false;
110
111
    /**
112
     * The timeout value for connection, in seconds.
113
     * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2.
114
     *
115
     * @var int
116
     */
117
    public $Timeout = 300;
118
119
    /**
120
     * The SMTP timelimit value for reads, in seconds.
121
     *
122
     * @var int
123
     */
124
    public $Timelimit = 30;
125
126
    /**
127
     * The socket for the server connection.
128
     *
129
     * @var resource
130
     */
131
    protected $smtp_conn;
132
133
    /**
134
     * Error message, if any, for the last call.
135
     *
136
     * @var string
137
     */
138
    protected $error = '';
139
140
    /**
141
     * The reply the server sent to us for HELO.
142
     *
143
     * @var string
144
     */
145
    protected $helo_rply = '';
146
147
    /**
148
     * The most recent reply received from the server.
149
     *
150
     * @var string
151
     */
152
    protected $last_reply = '';
153
154
    /**
155
     * Constructor.
156
     */
157
    public function __construct()
158
    {
159
        $this->smtp_conn = 0;
0 ignored issues
show
Documentation Bug introduced by
It seems like 0 of type integer is incompatible with the declared type resource of property $smtp_conn.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
160
        $this->error = null;
161
        $this->helo_rply = null;
162
163
        $this->do_debug = 0;
164
    }
165
166
    /**
167
     * Output debugging info via a user-selected method.
168
     *
169
     * @param string $str Debug string to output
170
     *
171
     * @return void
172
     */
173
    protected function edebug($str)
174
    {
175
        switch ($this->Debugoutput) {
176
            case 'error_log':
177
                //Don't output, just log
178
                error_log($str);
179
                break;
180
            case 'html':
181
                //Cleans up output a bit for a better looking, HTML-safe output
182
                echo htmlentities(
183
                        preg_replace('/[\r\n]+/', '', $str),
184
                        ENT_QUOTES,
185
                        'UTF-8'
186
                    )
187
                    ."<br>\n";
188
                break;
189
            case 'echo':
190
            default:
191
                echo gmdate('Y-m-d H:i:s')."\t".trim($str)."\n";
192
        }
193
    }
194
195
    /**
196
     * Connect to an SMTP server.
197
     *
198
     * @param string $host    SMTP server IP or host name
199
     * @param int    $port    The port number to connect to
200
     * @param int    $timeout How long to wait for the connection to open
201
     * @param array  $options An array of options for stream_context_create()
202
     *
203
     * @return bool
204
     */
205
    public function connect($host, $port = null, $timeout = 30, $options = [])
206
    {
207
        // Clear errors to avoid confusion
208
        $this->error = null;
209
210
        // Make sure we are __not__ connected
211
        if ($this->connected()) {
212
            // Already connected, generate error
213
            $this->error = ['error' => 'Already connected to a server'];
0 ignored issues
show
Documentation Bug introduced by
It seems like array('error' => 'Already connected to a server') of type array<string,string,{"error":"string"}> is incompatible with the declared type string of property $error.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
214
215
            return false;
216
        }
217
218
        if (empty($port)) {
219
            $port = self::DEFAULT_SMTP_PORT;
220
        }
221
222
        // Connect to the SMTP server
223
        if ($this->do_debug >= 3) {
224
            $this->edebug('Connection: opening');
225
        }
226
227
        $errno = 0;
228
        $errstr = '';
229
        $socket_context = stream_context_create($options);
230
        //Suppress errors; connection failures are handled at a higher level
231
        $this->smtp_conn = @stream_socket_client(
232
            $host.':'.$port,
233
            $errno,
234
            $errstr,
235
            $timeout,
236
            STREAM_CLIENT_CONNECT,
237
            $socket_context
238
        );
239
240
        // Verify we connected properly
241
        if (empty($this->smtp_conn)) {
242
            $this->error = [
0 ignored issues
show
Documentation Bug introduced by
It seems like array('error' => 'Failed...o, 'errstr' => $errstr) of type array<string,string|inte...errstr":"string|null"}> is incompatible with the declared type string of property $error.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
243
                'error'  => 'Failed to connect to server',
244
                'errno'  => $errno,
245
                'errstr' => $errstr,
246
            ];
247
            if ($this->do_debug >= 1) {
248
                $this->edebug(
249
                    'SMTP ERROR: '.$this->error['error']
250
                    .": $errstr ($errno)"
251
                );
252
            }
253
254
            return false;
255
        }
256
        if ($this->do_debug >= 3) {
257
            $this->edebug('Connection: opened');
258
        }
259
260
        // SMTP server can take longer to respond, give longer timeout for first read
261
        // Windows does not have support for this timeout function
262
        if (substr(PHP_OS, 0, 3) != 'WIN') {
263
            $max = ini_get('max_execution_time');
264
            if ($max != 0 && $timeout > $max) { // Don't bother if unlimited
265
                @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...
266
            }
267
            stream_set_timeout($this->smtp_conn, $timeout, 0);
268
        }
269
270
        // Get any announcement
271
        $announce = $this->get_lines();
272
273
        if ($this->do_debug >= 2) {
274
            $this->edebug('SERVER -> CLIENT: '.$announce);
275
        }
276
277
        return true;
278
    }
279
280
    /**
281
     * Initiate a TLS (encrypted) session.
282
     *
283
     * @return bool
284
     */
285
    public function startTLS()
286
    {
287
        if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) {
288
            return false;
289
        }
290
        // Begin encrypted connection
291
        if (!stream_socket_enable_crypto(
292
            $this->smtp_conn,
293
            true,
294
            STREAM_CRYPTO_METHOD_TLS_CLIENT
295
        )
296
        ) {
297
            return false;
298
        }
299
300
        return true;
301
    }
302
303
    /**
304
     * Perform SMTP authentication.
305
     * Must be run after hello().
306
     *
307
     * @see    hello()
308
     *
309
     * @param string $username    The user name
310
     * @param string $password    The password
311
     * @param string $authtype    The auth type (PLAIN, LOGIN, NTLM, CRAM-MD5)
312
     * @param string $realm       The auth realm for NTLM
313
     * @param string $workstation The auth workstation for NTLM
314
     *
315
     * @return bool True if successfully authenticated.
316
     */
317
    public function authenticate(
318
        $username,
319
        $password,
320
        $authtype = 'LOGIN',
321
        $realm = '',
322
        $workstation = ''
323
    ) {
324
        if (empty($authtype)) {
325
            $authtype = 'LOGIN';
326
        }
327
328
        switch ($authtype) {
329
            case 'PLAIN':
330
                // Start authentication
331
                if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) {
332
                    return false;
333
                }
334
                // Send encoded username and password
335
                if (!$this->sendCommand(
336
                    'User & Password',
337
                    base64_encode("\0".$username."\0".$password),
338
                    235
339
                )
340
                ) {
341
                    return false;
342
                }
343
                break;
344
            case 'LOGIN':
345
                // Start authentication
346
                if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) {
347
                    return false;
348
                }
349
                if (!$this->sendCommand('Username', base64_encode($username), 334)) {
350
                    return false;
351
                }
352
                if (!$this->sendCommand('Password', base64_encode($password), 235)) {
353
                    return false;
354
                }
355
                break;
356
            case 'NTLM':
357
                /*
358
                 * ntlm_sasl_client.php
359
                 * Bundled with Permission
360
                 *
361
                 * How to telnet in windows:
362
                 * http://technet.microsoft.com/en-us/library/aa995718%28EXCHG.65%29.aspx
363
                 * PROTOCOL Docs http://curl.haxx.se/rfc/ntlm.html#ntlmSmtpAuthentication
364
                 */
365
                require_once 'extras/ntlm_sasl_client.php';
366
                $temp = new stdClass();
367
                $ntlm_client = new ntlm_sasl_client_class();
368
                //Check that functions are available
369
                if (!$ntlm_client->Initialize($temp)) {
370
                    $this->error = ['error' => $temp->error];
0 ignored issues
show
Documentation Bug introduced by
It seems like array('error' => $temp->error) of type array<string,?,{"error":"?"}> is incompatible with the declared type string of property $error.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
371
                    if ($this->do_debug >= 1) {
372
                        $this->edebug(
373
                            'You need to enable some modules in your php.ini file: '
374
                            .$this->error['error']
375
                        );
376
                    }
377
378
                    return false;
379
                }
380
                //msg1
381
                $msg1 = $ntlm_client->TypeMsg1($realm, $workstation); //msg1
382
383
                if (!$this->sendCommand(
384
                    'AUTH NTLM',
385
                    'AUTH NTLM '.base64_encode($msg1),
386
                    334
387
                )
388
                ) {
389
                    return false;
390
                }
391
392
                //Though 0 based, there is a white space after the 3 digit number
393
                //msg2
394
                $challenge = substr($this->last_reply, 3);
395
                $challenge = base64_decode($challenge);
396
                $ntlm_res = $ntlm_client->NTLMResponse(
397
                    substr($challenge, 24, 8),
398
                    $password
399
                );
400
                //msg3
401
                $msg3 = $ntlm_client->TypeMsg3(
402
                    $ntlm_res,
403
                    $username,
404
                    $realm,
405
                    $workstation
406
                );
407
408
                // send encoded username
409
                return $this->sendCommand('Username', base64_encode($msg3), 235);
410
                break;
0 ignored issues
show
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
411
            case 'CRAM-MD5':
412
                // Start authentication
413
                if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) {
414
                    return false;
415
                }
416
                // Get the challenge
417
                $challenge = base64_decode(substr($this->last_reply, 4));
418
419
                // Build the response
420
                $response = $username.' '.$this->hmac($challenge, $password);
421
422
                // send encoded credentials
423
                return $this->sendCommand('Username', base64_encode($response), 235);
424
                break;
0 ignored issues
show
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
425
        }
426
427
        return true;
428
    }
429
430
    /**
431
     * Calculate an MD5 HMAC hash.
432
     * Works like hash_hmac('md5', $data, $key)
433
     * in case that function is not available.
434
     *
435
     * @param string $data The data to hash
436
     * @param string $key  The key to hash with
437
     *
438
     * @return string
439
     */
440
    protected function hmac($data, $key)
441
    {
442
        if (function_exists('hash_hmac')) {
443
            return hash_hmac('md5', $data, $key);
444
        }
445
446
        // The following borrowed from
447
        // http://php.net/manual/en/function.mhash.php#27225
448
449
        // RFC 2104 HMAC implementation for php.
450
        // Creates an md5 HMAC.
451
        // Eliminates the need to install mhash to compute a HMAC
452
        // Hacked by Lance Rushing
453
454
        $b = 64; // byte length for md5
455
        if (strlen($key) > $b) {
456
            $key = pack('H*', md5($key));
457
        }
458
        $key = str_pad($key, $b, chr(0x00));
459
        $ipad = str_pad('', $b, chr(0x36));
460
        $opad = str_pad('', $b, chr(0x5c));
461
        $k_ipad = $key ^ $ipad;
462
        $k_opad = $key ^ $opad;
463
464
        return md5($k_opad.pack('H*', md5($k_ipad.$data)));
465
    }
466
467
    /**
468
     * Check connection state.
469
     *
470
     * @return bool True if connected.
471
     */
472
    public function connected()
473
    {
474
        if (!empty($this->smtp_conn)) {
475
            $sock_status = stream_get_meta_data($this->smtp_conn);
476
            if ($sock_status['eof']) {
477
                // the socket is valid but we are not connected
478
                if ($this->do_debug >= 1) {
479
                    $this->edebug(
480
                        'SMTP NOTICE: EOF caught while checking if connected'
481
                    );
482
                }
483
                $this->close();
484
485
                return false;
486
            }
487
488
            return true; // everything looks good
489
        }
490
491
        return false;
492
    }
493
494
    /**
495
     * Close the socket and clean up the state of the class.
496
     * Don't use this function without first trying to use QUIT.
497
     *
498
     * @see    quit()
499
     *
500
     * @return void
501
     */
502
    public function close()
503
    {
504
        $this->error = null; // so there is no confusion
505
        $this->helo_rply = null;
506
        if (!empty($this->smtp_conn)) {
507
            // close the connection and cleanup
508
            fclose($this->smtp_conn);
509
            if ($this->do_debug >= 3) {
510
                $this->edebug('Connection: closed');
511
            }
512
            $this->smtp_conn = 0;
0 ignored issues
show
Documentation Bug introduced by
It seems like 0 of type integer is incompatible with the declared type resource of property $smtp_conn.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
513
        }
514
    }
515
516
    /**
517
     * Send an SMTP DATA command.
518
     * Issues a data command and sends the msg_data to the server,
519
     * finializing the mail transaction. $msg_data is the message
520
     * that is to be send with the headers. Each header needs to be
521
     * on a single line followed by a <CRLF> with the message headers
522
     * and the message body being separated by and additional <CRLF>.
523
     * Implements rfc 821: DATA <CRLF>.
524
     *
525
     * @param string $msg_data Message data to send
526
     *
527
     * @return bool
528
     */
529
    public function data($msg_data)
530
    {
531
        if (!$this->sendCommand('DATA', 'DATA', 354)) {
532
            return false;
533
        }
534
535
        /* The server is ready to accept data!
536
         * according to rfc821 we should not send more than 1000
537
         * including the CRLF
538
         * characters on a single line so we will break the data up
539
         * into lines by \r and/or \n then if needed we will break
540
         * each of those into smaller lines to fit within the limit.
541
         * in addition we will be looking for lines that start with
542
         * a period '.' and append and additional period '.' to that
543
         * line. NOTE: this does not count towards limit.
544
         */
545
546
        // Normalize the line breaks before exploding
547
        $msg_data = str_replace("\r\n", "\n", $msg_data);
548
        $msg_data = str_replace("\r", "\n", $msg_data);
549
        $lines = explode("\n", $msg_data);
550
551
        /* We need to find a good way to determine if headers are
552
         * in the msg_data or if it is a straight msg body
553
         * currently I am assuming rfc822 definitions of msg headers
554
         * and if the first field of the first line (':' separated)
555
         * does not contain a space then it _should_ be a header
556
         * and we can process all lines before a blank "" line as
557
         * headers.
558
         */
559
560
        $field = substr($lines[0], 0, strpos($lines[0], ':'));
561
        $in_headers = false;
562
        if (!empty($field) && !strstr($field, ' ')) {
563
            $in_headers = true;
564
        }
565
566
        //RFC 2822 section 2.1.1 limit
567
        $max_line_length = 998;
568
569
        foreach ($lines as $line) {
570
            $lines_out = null;
571
            if ($line == '' && $in_headers) {
572
                $in_headers = false;
573
            }
574
            // ok we need to break this line up into several smaller lines
575
            while (strlen($line) > $max_line_length) {
576
                $pos = strrpos(substr($line, 0, $max_line_length), ' ');
577
578
                // Patch to fix DOS attack
579
                if (!$pos) {
580
                    $pos = $max_line_length - 1;
581
                    $lines_out[] = substr($line, 0, $pos);
582
                    $line = substr($line, $pos);
583
                } else {
584
                    $lines_out[] = substr($line, 0, $pos);
585
                    $line = substr($line, $pos + 1);
586
                }
587
588
                /* If processing headers add a LWSP-char to the front of new line
589
                 * rfc822 on long msg headers
590
                 */
591
                if ($in_headers) {
592
                    $line = "\t".$line;
593
                }
594
            }
595
            $lines_out[] = $line;
596
597
            // send the lines to the server
598
            while (list(, $line_out) = @each($lines_out)) {
599
                if (strlen($line_out) > 0) {
600
                    if (substr($line_out, 0, 1) == '.') {
601
                        $line_out = '.'.$line_out;
602
                    }
603
                }
604
                $this->client_send($line_out.self::CRLF);
605
            }
606
        }
607
608
        // Message data has been sent, complete the command
609
        return $this->sendCommand('DATA END', '.', 250);
610
    }
611
612
    /**
613
     * Send an SMTP HELO or EHLO command.
614
     * Used to identify the sending server to the receiving server.
615
     * This makes sure that client and server are in a known state.
616
     * Implements from RFC 821: HELO <SP> <domain> <CRLF>
617
     * and RFC 2821 EHLO.
618
     *
619
     * @param string $host The host name or IP to connect to
620
     *
621
     * @return bool
622
     */
623
    public function hello($host = '')
624
    {
625
        // Try extended hello first (RFC 2821)
626
        if (!$this->sendHello('EHLO', $host)) {
627
            if (!$this->sendHello('HELO', $host)) {
628
                return false;
629
            }
630
        }
631
632
        return true;
633
    }
634
635
    /**
636
     * Send an SMTP HELO or EHLO command.
637
     * Low-level implementation used by hello().
638
     *
639
     * @see    hello()
640
     *
641
     * @param string $hello The HELO string
642
     * @param string $host  The hostname to say we are
643
     *
644
     * @return bool
645
     */
646
    protected function sendHello($hello, $host)
647
    {
648
        $noerror = $this->sendCommand($hello, $hello.' '.$host, 250);
649
        $this->helo_rply = $this->last_reply;
650
651
        return $noerror;
652
    }
653
654
    /**
655
     * Send an SMTP MAIL command.
656
     * Starts a mail transaction from the email address specified in
657
     * $from. Returns true if successful or false otherwise. If True
658
     * the mail transaction is started and then one or more recipient
659
     * commands may be called followed by a data command.
660
     * Implements rfc 821: MAIL <SP> FROM:<reverse-path> <CRLF>.
661
     *
662
     * @param string $from Source address of this message
663
     *
664
     * @return bool
665
     */
666
    public function mail($from)
667
    {
668
        $useVerp = ($this->do_verp ? ' XVERP' : '');
669
670
        return $this->sendCommand(
671
            'MAIL FROM',
672
            'MAIL FROM:<'.$from.'>'.$useVerp,
673
            250
674
        );
675
    }
676
677
    /**
678
     * Send an SMTP QUIT command.
679
     * Closes the socket if there is no error or the $close_on_error argument is true.
680
     * Implements from rfc 821: QUIT <CRLF>.
681
     *
682
     * @param bool $close_on_error Should the connection close if an error occurs?
683
     *
684
     * @return bool
685
     */
686
    public function quit($close_on_error = true)
687
    {
688
        $noerror = $this->sendCommand('QUIT', 'QUIT', 221);
689
        $e = $this->error; //Save any error
690
        if ($noerror or $close_on_error) {
691
            $this->close();
692
            $this->error = $e; //Restore any error from the quit command
693
        }
694
695
        return $noerror;
696
    }
697
698
    /**
699
     * Send an SMTP RCPT command.
700
     * Sets the TO argument to $to.
701
     * Returns true if the recipient was accepted false if it was rejected.
702
     * Implements from rfc 821: RCPT <SP> TO:<forward-path> <CRLF>.
703
     *
704
     * @param string $to The address the message is being sent to
705
     *
706
     * @return bool
707
     */
708
    public function recipient($to)
709
    {
710
        return $this->sendCommand(
711
            'RCPT TO ',
712
            'RCPT TO:<'.$to.'>',
713
            [250, 251]
714
        );
715
    }
716
717
    /**
718
     * Send an SMTP RSET command.
719
     * Abort any transaction that is currently in progress.
720
     * Implements rfc 821: RSET <CRLF>.
721
     *
722
     * @return bool True on success.
723
     */
724
    public function reset()
725
    {
726
        return $this->sendCommand('RSET', 'RSET', 250);
727
    }
728
729
    /**
730
     * Send a command to an SMTP server and check its return code.
731
     *
732
     * @param string    $command       The command name - not sent to the server
733
     * @param string    $commandstring The actual command to send
734
     * @param int|array $expect        One or more expected integer success codes
735
     *
736
     * @return bool True on success.
737
     */
738
    protected function sendCommand($command, $commandstring, $expect)
739
    {
740
        if (!$this->connected()) {
741
            $this->error = [
0 ignored issues
show
Documentation Bug introduced by
It seems like array('error' => "Called...thout being connected") of type array<string,?> is incompatible with the declared type string of property $error.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
742
                'error' => "Called $command without being connected",
743
            ];
744
745
            return false;
746
        }
747
        $this->client_send($commandstring.self::CRLF);
748
749
        $reply = $this->get_lines();
750
        $code = substr($reply, 0, 3);
751
752
        if ($this->do_debug >= 2) {
753
            $this->edebug('SERVER -> CLIENT: '.$reply);
754
        }
755
756
        if (!in_array($code, (array) $expect)) {
757
            $this->last_reply = null;
758
            $this->error = [
0 ignored issues
show
Documentation Bug introduced by
It seems like array('error' => "{$comm...' => substr($reply, 4)) of type array<string,string,{"sm...ng","detail":"string"}> is incompatible with the declared type string of property $error.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
759
                'error'     => "$command command failed",
760
                'smtp_code' => $code,
761
                'detail'    => substr($reply, 4),
762
            ];
763
            if ($this->do_debug >= 1) {
764
                $this->edebug(
765
                    'SMTP ERROR: '.$this->error['error'].': '.$reply
766
                );
767
            }
768
769
            return false;
770
        }
771
772
        $this->last_reply = $reply;
773
        $this->error = null;
774
775
        return true;
776
    }
777
778
    /**
779
     * Send an SMTP SAML command.
780
     * Starts a mail transaction from the email address specified in $from.
781
     * Returns true if successful or false otherwise. If True
782
     * the mail transaction is started and then one or more recipient
783
     * commands may be called followed by a data command. This command
784
     * will send the message to the users terminal if they are logged
785
     * in and send them an email.
786
     * Implements rfc 821: SAML <SP> FROM:<reverse-path> <CRLF>.
787
     *
788
     * @param string $from The address the message is from
789
     *
790
     * @return bool
791
     */
792
    public function sendAndMail($from)
793
    {
794
        return $this->sendCommand('SAML', "SAML FROM:$from", 250);
795
    }
796
797
    /**
798
     * Send an SMTP VRFY command.
799
     *
800
     * @param string $name The name to verify
801
     *
802
     * @return bool
803
     */
804
    public function verify($name)
805
    {
806
        return $this->sendCommand('VRFY', "VRFY $name", [250, 251]);
807
    }
808
809
    /**
810
     * Send an SMTP NOOP command.
811
     * Used to keep keep-alives alive, doesn't actually do anything.
812
     *
813
     * @return bool
814
     */
815
    public function noop()
816
    {
817
        return $this->sendCommand('NOOP', 'NOOP', 250);
818
    }
819
820
    /**
821
     * Send an SMTP TURN command.
822
     * This is an optional command for SMTP that this class does not support.
823
     * This method is here to make the RFC821 Definition
824
     * complete for this class and __may__ be implemented in future
825
     * Implements from rfc 821: TURN <CRLF>.
826
     *
827
     * @return bool
828
     */
829
    public function turn()
830
    {
831
        $this->error = [
0 ignored issues
show
Documentation Bug introduced by
It seems like array('error' => 'The SM...nd is not implemented') of type array<string,string,{"error":"string"}> is incompatible with the declared type string of property $error.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
832
            'error' => 'The SMTP TURN command is not implemented',
833
        ];
834
        if ($this->do_debug >= 1) {
835
            $this->edebug('SMTP NOTICE: '.$this->error['error']);
836
        }
837
838
        return false;
839
    }
840
841
    /**
842
     * Send raw data to the server.
843
     *
844
     * @param string $data The data to send
845
     *
846
     * @return int|bool The number of bytes sent to the server or FALSE on error
847
     */
848
    public function client_send($data)
849
    {
850
        if ($this->do_debug >= 1) {
851
            $this->edebug("CLIENT -> SERVER: $data");
852
        }
853
854
        return fwrite($this->smtp_conn, $data);
855
    }
856
857
    /**
858
     * Get the latest error.
859
     *
860
     * @return array
861
     */
862
    public function getError()
863
    {
864
        return $this->error;
865
    }
866
867
    /**
868
     * Get the last reply from the server.
869
     *
870
     * @return string
871
     */
872
    public function getLastReply()
873
    {
874
        return $this->last_reply;
875
    }
876
877
    /**
878
     * Read the SMTP server's response.
879
     * Either before eof or socket timeout occurs on the operation.
880
     * With SMTP we can tell if we have more lines to read if the
881
     * 4th character is '-' symbol. If it is a space then we don't
882
     * need to read anything else.
883
     *
884
     * @return string
885
     */
886
    protected function get_lines()
887
    {
888
        $data = '';
889
        $endtime = 0;
890
        // If the connection is bad, give up now
891
        if (!is_resource($this->smtp_conn)) {
892
            return $data;
893
        }
894
        stream_set_timeout($this->smtp_conn, $this->Timeout);
895
        if ($this->Timelimit > 0) {
896
            $endtime = time() + $this->Timelimit;
897
        }
898
        while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
899
            $str = @fgets($this->smtp_conn, 515);
900
            if ($this->do_debug >= 4) {
901
                $this->edebug("SMTP -> get_lines(): \$data was \"$data\"");
902
                $this->edebug("SMTP -> get_lines(): \$str is \"$str\"");
903
            }
904
            $data .= $str;
905
            if ($this->do_debug >= 4) {
906
                $this->edebug("SMTP -> get_lines(): \$data is \"$data\"");
907
            }
908
            // if 4th character is a space, we are done reading, break the loop
909
            if (substr($str, 3, 1) == ' ') {
910
                break;
911
            }
912
            // Timed-out? Log and break
913
            $info = stream_get_meta_data($this->smtp_conn);
914
            if ($info['timed_out']) {
915
                if ($this->do_debug >= 4) {
916
                    $this->edebug(
917
                        'SMTP -> get_lines(): timed-out ('.$this->Timeout.' sec)'
918
                    );
919
                }
920
                break;
921
            }
922
            // Now check if reads took too long
923
            if ($endtime) {
924
                if (time() > $endtime) {
925
                    if ($this->do_debug >= 4) {
926
                        $this->edebug(
927
                            'SMTP -> get_lines(): timelimit reached ('
928
                            .$this->Timelimit.' sec)'
929
                        );
930
                    }
931
                    break;
932
                }
933
            }
934
        }
935
936
        return $data;
937
    }
938
939
    /**
940
     * Enable or disable VERP address generation.
941
     *
942
     * @param bool $enabled
943
     */
944
    public function setVerp($enabled = false)
945
    {
946
        $this->do_verp = $enabled;
947
    }
948
949
    /**
950
     * Get VERP address generation mode.
951
     *
952
     * @return bool
953
     */
954
    public function getVerp()
955
    {
956
        return $this->do_verp;
957
    }
958
959
    /**
960
     * Set debug output method.
961
     *
962
     * @param string $method The function/method to use for debugging output.
963
     */
964
    public function setDebugOutput($method = 'echo')
965
    {
966
        $this->Debugoutput = $method;
967
    }
968
969
    /**
970
     * Get debug output method.
971
     *
972
     * @return string
973
     */
974
    public function getDebugOutput()
975
    {
976
        return $this->Debugoutput;
977
    }
978
979
    /**
980
     * Set debug output level.
981
     *
982
     * @param int $level
983
     */
984
    public function setDebugLevel($level = 0)
985
    {
986
        $this->do_debug = $level;
987
    }
988
989
    /**
990
     * Get debug output level.
991
     *
992
     * @return int
993
     */
994
    public function getDebugLevel()
995
    {
996
        return $this->do_debug;
997
    }
998
999
    /**
1000
     * Set SMTP timeout.
1001
     *
1002
     * @param int $timeout
1003
     */
1004
    public function setTimeout($timeout = 0)
1005
    {
1006
        $this->Timeout = $timeout;
1007
    }
1008
1009
    /**
1010
     * Get SMTP timeout.
1011
     *
1012
     * @return int
1013
     */
1014
    public function getTimeout()
1015
    {
1016
        return $this->Timeout;
1017
    }
1018
}
1019