MDL-60194 lib: Update to phpmailer 6.0.1
[moodle.git] / lib / phpmailer / src / SMTP.php
1 <?php
2 /**
3  * PHPMailer RFC821 SMTP email transport class.
4  * PHP Version 5.5.
5  *
6  * @see       https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
7  *
8  * @author    Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
9  * @author    Jim Jagielski (jimjag) <jimjag@gmail.com>
10  * @author    Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
11  * @author    Brent R. Matzelle (original founder)
12  * @copyright 2012 - 2017 Marcus Bointon
13  * @copyright 2010 - 2012 Jim Jagielski
14  * @copyright 2004 - 2009 Andy Prevost
15  * @license   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
16  * @note      This program is distributed in the hope that it will be useful - WITHOUT
17  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
18  * FITNESS FOR A PARTICULAR PURPOSE.
19  */
21 namespace PHPMailer\PHPMailer;
23 /**
24  * PHPMailer RFC821 SMTP email transport class.
25  * Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server.
26  *
27  * @author  Chris Ryan
28  * @author  Marcus Bointon <phpmailer@synchromedia.co.uk>
29  */
30 class SMTP
31 {
32     /**
33      * The PHPMailer SMTP version number.
34      *
35      * @var string
36      */
37     const VERSION = '6.0.1';
39     /**
40      * SMTP line break constant.
41      *
42      * @var string
43      */
44     const LE = "\r\n";
46     /**
47      * The SMTP port to use if one is not specified.
48      *
49      * @var int
50      */
51     const DEFAULT_PORT = 25;
53     /**
54      * The maximum line length allowed by RFC 2822 section 2.1.1.
55      *
56      * @var int
57      */
58     const MAX_LINE_LENGTH = 998;
60     /**
61      * Debug level for no output.
62      */
63     const DEBUG_OFF = 0;
65     /**
66      * Debug level to show client -> server messages.
67      */
68     const DEBUG_CLIENT = 1;
70     /**
71      * Debug level to show client -> server and server -> client messages.
72      */
73     const DEBUG_SERVER = 2;
75     /**
76      * Debug level to show connection status, client -> server and server -> client messages.
77      */
78     const DEBUG_CONNECTION = 3;
80     /**
81      * Debug level to show all messages.
82      */
83     const DEBUG_LOWLEVEL = 4;
85     /**
86      * Debug output level.
87      * Options:
88      * * self::DEBUG_OFF (`0`) No debug output, default
89      * * self::DEBUG_CLIENT (`1`) Client commands
90      * * self::DEBUG_SERVER (`2`) Client commands and server responses
91      * * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status
92      * * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages.
93      *
94      * @var int
95      */
96     public $do_debug = self::DEBUG_OFF;
98     /**
99      * How to handle debug output.
100      * Options:
101      * * `echo` Output plain-text as-is, appropriate for CLI
102      * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output
103      * * `error_log` Output to error log as configured in php.ini
104      * Alternatively, you can provide a callable expecting two params: a message string and the debug level:
105      *
106      * ```php
107      * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
108      * ```
109      *
110      * Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug`
111      * level output is used:
112      *
113      * ```php
114      * $mail->Debugoutput = new myPsr3Logger;
115      * ```
116      *
117      * @var string|callable|\Psr\Log\LoggerInterface
118      */
119     public $Debugoutput = 'echo';
121     /**
122      * Whether to use VERP.
123      *
124      * @see http://en.wikipedia.org/wiki/Variable_envelope_return_path
125      * @see http://www.postfix.org/VERP_README.html Info on VERP
126      *
127      * @var bool
128      */
129     public $do_verp = false;
131     /**
132      * The timeout value for connection, in seconds.
133      * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2.
134      * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure.
135      *
136      * @see http://tools.ietf.org/html/rfc2821#section-4.5.3.2
137      *
138      * @var int
139      */
140     public $Timeout = 300;
142     /**
143      * How long to wait for commands to complete, in seconds.
144      * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2.
145      *
146      * @var int
147      */
148     public $Timelimit = 300;
150     /**
151      * Patterns to extract an SMTP transaction id from reply to a DATA command.
152      * The first capture group in each regex will be used as the ID.
153      * MS ESMTP returns the message ID, which may not be correct for internal tracking.
154      *
155      * @var string[]
156      */
157     protected $smtp_transaction_id_patterns = [
158         'exim' => '/[0-9]{3} OK id=(.*)/',
159         'sendmail' => '/[0-9]{3} 2.0.0 (.*) Message/',
160         'postfix' => '/[0-9]{3} 2.0.0 Ok: queued as (.*)/',
161         'Microsoft_ESMTP' => '/[0-9]{3} 2.[0-9].0 (.*)@(?:.*) Queued mail for delivery/',
162         'Amazon_SES' => '/[0-9]{3} Ok (.*)/',
163     ];
165     /**
166      * The last transaction ID issued in response to a DATA command,
167      * if one was detected.
168      *
169      * @var string|bool|null
170      */
171     protected $last_smtp_transaction_id;
173     /**
174      * The socket for the server connection.
175      *
176      * @var ?resource
177      */
178     protected $smtp_conn;
180     /**
181      * Error information, if any, for the last SMTP command.
182      *
183      * @var array
184      */
185     protected $error = [
186         'error' => '',
187         'detail' => '',
188         'smtp_code' => '',
189         'smtp_code_ex' => '',
190     ];
192     /**
193      * The reply the server sent to us for HELO.
194      * If null, no HELO string has yet been received.
195      *
196      * @var string|null
197      */
198     protected $helo_rply = null;
200     /**
201      * The set of SMTP extensions sent in reply to EHLO command.
202      * Indexes of the array are extension names.
203      * Value at index 'HELO' or 'EHLO' (according to command that was sent)
204      * represents the server name. In case of HELO it is the only element of the array.
205      * Other values can be boolean TRUE or an array containing extension options.
206      * If null, no HELO/EHLO string has yet been received.
207      *
208      * @var array|null
209      */
210     protected $server_caps = null;
212     /**
213      * The most recent reply received from the server.
214      *
215      * @var string
216      */
217     protected $last_reply = '';
219     /**
220      * Output debugging info via a user-selected method.
221      *
222      * @param string $str   Debug string to output
223      * @param int    $level The debug level of this message; see DEBUG_* constants
224      *
225      * @see SMTP::$Debugoutput
226      * @see SMTP::$do_debug
227      */
228     protected function edebug($str, $level = 0)
229     {
230         if ($level > $this->do_debug) {
231             return;
232         }
233         //Is this a PSR-3 logger?
234         if (is_a($this->Debugoutput, 'Psr\Log\LoggerInterface')) {
235             $this->Debugoutput->debug($str);
237             return;
238         }
239         //Avoid clash with built-in function names
240         if (!in_array($this->Debugoutput, ['error_log', 'html', 'echo']) and is_callable($this->Debugoutput)) {
241             call_user_func($this->Debugoutput, $str, $level);
243             return;
244         }
245         switch ($this->Debugoutput) {
246             case 'error_log':
247                 //Don't output, just log
248                 error_log($str);
249                 break;
250             case 'html':
251                 //Cleans up output a bit for a better looking, HTML-safe output
252                 echo gmdate('Y-m-d H:i:s'), ' ', htmlentities(
253                     preg_replace('/[\r\n]+/', '', $str),
254                     ENT_QUOTES,
255                     'UTF-8'
256                 ), "<br>\n";
257                 break;
258             case 'echo':
259             default:
260                 //Normalize line breaks
261                 $str = preg_replace('/\r\n|\r/ms', "\n", $str);
262                 echo gmdate('Y-m-d H:i:s'),
263                 "\t",
264                     //Trim trailing space
265                 trim(
266                 //Indent for readability, except for trailing break
267                     str_replace(
268                         "\n",
269                         "\n                   \t                  ",
270                         trim($str)
271                     )
272                 ),
273                 "\n";
274         }
275     }
277     /**
278      * Connect to an SMTP server.
279      *
280      * @param string $host    SMTP server IP or host name
281      * @param int    $port    The port number to connect to
282      * @param int    $timeout How long to wait for the connection to open
283      * @param array  $options An array of options for stream_context_create()
284      *
285      * @return bool
286      */
287     public function connect($host, $port = null, $timeout = 30, $options = [])
288     {
289         static $streamok;
290         //This is enabled by default since 5.0.0 but some providers disable it
291         //Check this once and cache the result
292         if (null === $streamok) {
293             $streamok = function_exists('stream_socket_client');
294         }
295         // Clear errors to avoid confusion
296         $this->setError('');
297         // Make sure we are __not__ connected
298         if ($this->connected()) {
299             // Already connected, generate error
300             $this->setError('Already connected to a server');
302             return false;
303         }
304         if (empty($port)) {
305             $port = self::DEFAULT_PORT;
306         }
307         // Connect to the SMTP server
308         $this->edebug(
309             "Connection: opening to $host:$port, timeout=$timeout, options=" .
310             (count($options) > 0 ? var_export($options, true) : 'array()'),
311             self::DEBUG_CONNECTION
312         );
313         $errno = 0;
314         $errstr = '';
315         if ($streamok) {
316             $socket_context = stream_context_create($options);
317             set_error_handler([$this, 'errorHandler']);
318             $this->smtp_conn = stream_socket_client(
319                 $host . ':' . $port,
320                 $errno,
321                 $errstr,
322                 $timeout,
323                 STREAM_CLIENT_CONNECT,
324                 $socket_context
325             );
326             restore_error_handler();
327         } else {
328             //Fall back to fsockopen which should work in more places, but is missing some features
329             $this->edebug(
330                 'Connection: stream_socket_client not available, falling back to fsockopen',
331                 self::DEBUG_CONNECTION
332             );
333             set_error_handler([$this, 'errorHandler']);
334             $this->smtp_conn = fsockopen(
335                 $host,
336                 $port,
337                 $errno,
338                 $errstr,
339                 $timeout
340             );
341             restore_error_handler();
342         }
343         // Verify we connected properly
344         if (!is_resource($this->smtp_conn)) {
345             $this->setError(
346                 'Failed to connect to server',
347                 '',
348                 (string) $errno,
349                 (string) $errstr
350             );
351             $this->edebug(
352                 'SMTP ERROR: ' . $this->error['error']
353                 . ": $errstr ($errno)",
354                 self::DEBUG_CLIENT
355             );
357             return false;
358         }
359         $this->edebug('Connection: opened', self::DEBUG_CONNECTION);
360         // SMTP server can take longer to respond, give longer timeout for first read
361         // Windows does not have support for this timeout function
362         if (substr(PHP_OS, 0, 3) != 'WIN') {
363             $max = ini_get('max_execution_time');
364             // Don't bother if unlimited
365             if (0 != $max and $timeout > $max) {
366                 @set_time_limit($timeout);
367             }
368             stream_set_timeout($this->smtp_conn, $timeout, 0);
369         }
370         // Get any announcement
371         $announce = $this->get_lines();
372         $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER);
374         return true;
375     }
377     /**
378      * Initiate a TLS (encrypted) session.
379      *
380      * @return bool
381      */
382     public function startTLS()
383     {
384         if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) {
385             return false;
386         }
388         //Allow the best TLS version(s) we can
389         $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
391         //PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT
392         //so add them back in manually if we can
393         if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
394             $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
395             $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
396         }
398         // Begin encrypted connection
399         set_error_handler([$this, 'errorHandler']);
400         $crypto_ok = stream_socket_enable_crypto(
401             $this->smtp_conn,
402             true,
403             $crypto_method
404         );
405         restore_error_handler();
407         return (bool) $crypto_ok;
408     }
410     /**
411      * Perform SMTP authentication.
412      * Must be run after hello().
413      *
414      * @see    hello()
415      *
416      * @param string $username The user name
417      * @param string $password The password
418      * @param string $authtype The auth type (CRAM-MD5, PLAIN, LOGIN, XOAUTH2)
419      * @param OAuth  $OAuth    An optional OAuth instance for XOAUTH2 authentication
420      *
421      * @return bool True if successfully authenticated
422      */
423     public function authenticate(
424         $username,
425         $password,
426         $authtype = null,
427         $OAuth = null
428     ) {
429         if (!$this->server_caps) {
430             $this->setError('Authentication is not allowed before HELO/EHLO');
432             return false;
433         }
435         if (array_key_exists('EHLO', $this->server_caps)) {
436             // SMTP extensions are available; try to find a proper authentication method
437             if (!array_key_exists('AUTH', $this->server_caps)) {
438                 $this->setError('Authentication is not allowed at this stage');
439                 // 'at this stage' means that auth may be allowed after the stage changes
440                 // e.g. after STARTTLS
442                 return false;
443             }
445             $this->edebug('Auth method requested: ' . ($authtype ? $authtype : 'UNKNOWN'), self::DEBUG_LOWLEVEL);
446             $this->edebug(
447                 'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']),
448                 self::DEBUG_LOWLEVEL
449             );
451             //If we have requested a specific auth type, check the server supports it before trying others
452             if (!in_array($authtype, $this->server_caps['AUTH'])) {
453                 $this->edebug('Requested auth method not available: ' . $authtype, self::DEBUG_LOWLEVEL);
454                 $authtype = null;
455             }
457             if (empty($authtype)) {
458                 //If no auth mechanism is specified, attempt to use these, in this order
459                 //Try CRAM-MD5 first as it's more secure than the others
460                 foreach (['CRAM-MD5', 'LOGIN', 'PLAIN', 'XOAUTH2'] as $method) {
461                     if (in_array($method, $this->server_caps['AUTH'])) {
462                         $authtype = $method;
463                         break;
464                     }
465                 }
466                 if (empty($authtype)) {
467                     $this->setError('No supported authentication methods found');
469                     return false;
470                 }
471                 self::edebug('Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL);
472             }
474             if (!in_array($authtype, $this->server_caps['AUTH'])) {
475                 $this->setError("The requested authentication method \"$authtype\" is not supported by the server");
477                 return false;
478             }
479         } elseif (empty($authtype)) {
480             $authtype = 'LOGIN';
481         }
482         switch ($authtype) {
483             case 'PLAIN':
484                 // Start authentication
485                 if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) {
486                     return false;
487                 }
488                 // Send encoded username and password
489                 if (!$this->sendCommand(
490                     'User & Password',
491                     base64_encode("\0" . $username . "\0" . $password),
492                     235
493                 )
494                 ) {
495                     return false;
496                 }
497                 break;
498             case 'LOGIN':
499                 // Start authentication
500                 if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) {
501                     return false;
502                 }
503                 if (!$this->sendCommand('Username', base64_encode($username), 334)) {
504                     return false;
505                 }
506                 if (!$this->sendCommand('Password', base64_encode($password), 235)) {
507                     return false;
508                 }
509                 break;
510             case 'CRAM-MD5':
511                 // Start authentication
512                 if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) {
513                     return false;
514                 }
515                 // Get the challenge
516                 $challenge = base64_decode(substr($this->last_reply, 4));
518                 // Build the response
519                 $response = $username . ' ' . $this->hmac($challenge, $password);
521                 // send encoded credentials
522                 return $this->sendCommand('Username', base64_encode($response), 235);
523             case 'XOAUTH2':
524                 //The OAuth instance must be set up prior to requesting auth.
525                 if (null === $OAuth) {
526                     return false;
527                 }
528                 $oauth = $OAuth->getOauth64();
530                 // Start authentication
531                 if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
532                     return false;
533                 }
534                 break;
535             default:
536                 $this->setError("Authentication method \"$authtype\" is not supported");
538                 return false;
539         }
541         return true;
542     }
544     /**
545      * Calculate an MD5 HMAC hash.
546      * Works like hash_hmac('md5', $data, $key)
547      * in case that function is not available.
548      *
549      * @param string $data The data to hash
550      * @param string $key  The key to hash with
551      *
552      * @return string
553      */
554     protected function hmac($data, $key)
555     {
556         if (function_exists('hash_hmac')) {
557             return hash_hmac('md5', $data, $key);
558         }
560         // The following borrowed from
561         // http://php.net/manual/en/function.mhash.php#27225
563         // RFC 2104 HMAC implementation for php.
564         // Creates an md5 HMAC.
565         // Eliminates the need to install mhash to compute a HMAC
566         // by Lance Rushing
568         $bytelen = 64; // byte length for md5
569         if (strlen($key) > $bytelen) {
570             $key = pack('H*', md5($key));
571         }
572         $key = str_pad($key, $bytelen, chr(0x00));
573         $ipad = str_pad('', $bytelen, chr(0x36));
574         $opad = str_pad('', $bytelen, chr(0x5c));
575         $k_ipad = $key ^ $ipad;
576         $k_opad = $key ^ $opad;
578         return md5($k_opad . pack('H*', md5($k_ipad . $data)));
579     }
581     /**
582      * Check connection state.
583      *
584      * @return bool True if connected
585      */
586     public function connected()
587     {
588         if (is_resource($this->smtp_conn)) {
589             $sock_status = stream_get_meta_data($this->smtp_conn);
590             if ($sock_status['eof']) {
591                 // The socket is valid but we are not connected
592                 $this->edebug(
593                     'SMTP NOTICE: EOF caught while checking if connected',
594                     self::DEBUG_CLIENT
595                 );
596                 $this->close();
598                 return false;
599             }
601             return true; // everything looks good
602         }
604         return false;
605     }
607     /**
608      * Close the socket and clean up the state of the class.
609      * Don't use this function without first trying to use QUIT.
610      *
611      * @see quit()
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     }
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 an additional <CRLF>.
633      * Implements RFC 821: DATA <CRLF>.
634      *
635      * @param string $msg_data Message data to send
636      *
637      * @return bool
638      */
639     public function data($msg_data)
640     {
641         //This will use the standard timelimit
642         if (!$this->sendCommand('DATA', 'DATA', 354)) {
643             return false;
644         }
646         /* The server is ready to accept data!
647          * According to rfc821 we should not send more than 1000 characters on a single line (including the LE)
648          * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into
649          * smaller lines to fit within the limit.
650          * We will also look for lines that start with a '.' and prepend an additional '.'.
651          * NOTE: this does not count towards line-length limit.
652          */
654         // Normalize line breaks before exploding
655         $lines = explode("\n", str_replace(["\r\n", "\r"], "\n", $msg_data));
657         /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field
658          * of the first line (':' separated) does not contain a space then it _should_ be a header and we will
659          * process all lines before a blank line as headers.
660          */
662         $field = substr($lines[0], 0, strpos($lines[0], ':'));
663         $in_headers = false;
664         if (!empty($field) and strpos($field, ' ') === false) {
665             $in_headers = true;
666         }
668         foreach ($lines as $line) {
669             $lines_out = [];
670             if ($in_headers and $line == '') {
671                 $in_headers = false;
672             }
673             //Break this line up into several smaller lines if it's too long
674             //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len),
675             while (isset($line[self::MAX_LINE_LENGTH])) {
676                 //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on
677                 //so as to avoid breaking in the middle of a word
678                 $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' ');
679                 //Deliberately matches both false and 0
680                 if (!$pos) {
681                     //No nice break found, add a hard break
682                     $pos = self::MAX_LINE_LENGTH - 1;
683                     $lines_out[] = substr($line, 0, $pos);
684                     $line = substr($line, $pos);
685                 } else {
686                     //Break at the found point
687                     $lines_out[] = substr($line, 0, $pos);
688                     //Move along by the amount we dealt with
689                     $line = substr($line, $pos + 1);
690                 }
691                 //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1
692                 if ($in_headers) {
693                     $line = "\t" . $line;
694                 }
695             }
696             $lines_out[] = $line;
698             //Send the lines to the server
699             foreach ($lines_out as $line_out) {
700                 //RFC2821 section 4.5.2
701                 if (!empty($line_out) and $line_out[0] == '.') {
702                     $line_out = '.' . $line_out;
703                 }
704                 $this->client_send($line_out . static::LE);
705             }
706         }
708         //Message data has been sent, complete the command
709         //Increase timelimit for end of DATA command
710         $savetimelimit = $this->Timelimit;
711         $this->Timelimit = $this->Timelimit * 2;
712         $result = $this->sendCommand('DATA END', '.', 250);
713         $this->recordLastTransactionID();
714         //Restore timelimit
715         $this->Timelimit = $savetimelimit;
717         return $result;
718     }
720     /**
721      * Send an SMTP HELO or EHLO command.
722      * Used to identify the sending server to the receiving server.
723      * This makes sure that client and server are in a known state.
724      * Implements RFC 821: HELO <SP> <domain> <CRLF>
725      * and RFC 2821 EHLO.
726      *
727      * @param string $host The host name or IP to connect to
728      *
729      * @return bool
730      */
731     public function hello($host = '')
732     {
733         //Try extended hello first (RFC 2821)
734         return (bool) ($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host));
735     }
737     /**
738      * Send an SMTP HELO or EHLO command.
739      * Low-level implementation used by hello().
740      *
741      * @param string $hello The HELO string
742      * @param string $host  The hostname to say we are
743      *
744      * @return bool
745      *
746      * @see    hello()
747      */
748     protected function sendHello($hello, $host)
749     {
750         $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250);
751         $this->helo_rply = $this->last_reply;
752         if ($noerror) {
753             $this->parseHelloFields($hello);
754         } else {
755             $this->server_caps = null;
756         }
758         return $noerror;
759     }
761     /**
762      * Parse a reply to HELO/EHLO command to discover server extensions.
763      * In case of HELO, the only parameter that can be discovered is a server name.
764      *
765      * @param string $type `HELO` or `EHLO`
766      */
767     protected function parseHelloFields($type)
768     {
769         $this->server_caps = [];
770         $lines = explode("\n", $this->helo_rply);
772         foreach ($lines as $n => $s) {
773             //First 4 chars contain response code followed by - or space
774             $s = trim(substr($s, 4));
775             if (empty($s)) {
776                 continue;
777             }
778             $fields = explode(' ', $s);
779             if (!empty($fields)) {
780                 if (!$n) {
781                     $name = $type;
782                     $fields = $fields[0];
783                 } else {
784                     $name = array_shift($fields);
785                     switch ($name) {
786                         case 'SIZE':
787                             $fields = ($fields ? $fields[0] : 0);
788                             break;
789                         case 'AUTH':
790                             if (!is_array($fields)) {
791                                 $fields = [];
792                             }
793                             break;
794                         default:
795                             $fields = true;
796                     }
797                 }
798                 $this->server_caps[$name] = $fields;
799             }
800         }
801     }
803     /**
804      * Send an SMTP MAIL command.
805      * Starts a mail transaction from the email address specified in
806      * $from. Returns true if successful or false otherwise. If True
807      * the mail transaction is started and then one or more recipient
808      * commands may be called followed by a data command.
809      * Implements RFC 821: MAIL <SP> FROM:<reverse-path> <CRLF>.
810      *
811      * @param string $from Source address of this message
812      *
813      * @return bool
814      */
815     public function mail($from)
816     {
817         $useVerp = ($this->do_verp ? ' XVERP' : '');
819         return $this->sendCommand(
820             'MAIL FROM',
821             'MAIL FROM:<' . $from . '>' . $useVerp,
822             250
823         );
824     }
826     /**
827      * Send an SMTP QUIT command.
828      * Closes the socket if there is no error or the $close_on_error argument is true.
829      * Implements from RFC 821: QUIT <CRLF>.
830      *
831      * @param bool $close_on_error Should the connection close if an error occurs?
832      *
833      * @return bool
834      */
835     public function quit($close_on_error = true)
836     {
837         $noerror = $this->sendCommand('QUIT', 'QUIT', 221);
838         $err = $this->error; //Save any error
839         if ($noerror or $close_on_error) {
840             $this->close();
841             $this->error = $err; //Restore any error from the quit command
842         }
844         return $noerror;
845     }
847     /**
848      * Send an SMTP RCPT command.
849      * Sets the TO argument to $toaddr.
850      * Returns true if the recipient was accepted false if it was rejected.
851      * Implements from RFC 821: RCPT <SP> TO:<forward-path> <CRLF>.
852      *
853      * @param string $address The address the message is being sent to
854      *
855      * @return bool
856      */
857     public function recipient($address)
858     {
859         return $this->sendCommand(
860             'RCPT TO',
861             'RCPT TO:<' . $address . '>',
862             [250, 251]
863         );
864     }
866     /**
867      * Send an SMTP RSET command.
868      * Abort any transaction that is currently in progress.
869      * Implements RFC 821: RSET <CRLF>.
870      *
871      * @return bool True on success
872      */
873     public function reset()
874     {
875         return $this->sendCommand('RSET', 'RSET', 250);
876     }
878     /**
879      * Send a command to an SMTP server and check its return code.
880      *
881      * @param string    $command       The command name - not sent to the server
882      * @param string    $commandstring The actual command to send
883      * @param int|array $expect        One or more expected integer success codes
884      *
885      * @return bool True on success
886      */
887     protected function sendCommand($command, $commandstring, $expect)
888     {
889         if (!$this->connected()) {
890             $this->setError("Called $command without being connected");
892             return false;
893         }
894         //Reject line breaks in all commands
895         if (strpos($commandstring, "\n") !== false or strpos($commandstring, "\r") !== false) {
896             $this->setError("Command '$command' contained line breaks");
898             return false;
899         }
900         $this->client_send($commandstring . static::LE);
902         $this->last_reply = $this->get_lines();
903         // Fetch SMTP code and possible error code explanation
904         $matches = [];
905         if (preg_match('/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]) )?/', $this->last_reply, $matches)) {
906             $code = $matches[1];
907             $code_ex = (count($matches) > 2 ? $matches[2] : null);
908             // Cut off error code from each response line
909             $detail = preg_replace(
910                 "/{$code}[ -]" .
911                 ($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . '/m',
912                 '',
913                 $this->last_reply
914             );
915         } else {
916             // Fall back to simple parsing if regex fails
917             $code = substr($this->last_reply, 0, 3);
918             $code_ex = null;
919             $detail = substr($this->last_reply, 4);
920         }
922         $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
924         if (!in_array($code, (array) $expect)) {
925             $this->setError(
926                 "$command command failed",
927                 $detail,
928                 $code,
929                 $code_ex
930             );
931             $this->edebug(
932                 'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply,
933                 self::DEBUG_CLIENT
934             );
936             return false;
937         }
939         $this->setError('');
941         return true;
942     }
944     /**
945      * Send an SMTP SAML command.
946      * Starts a mail transaction from the email address specified in $from.
947      * Returns true if successful or false otherwise. If True
948      * the mail transaction is started and then one or more recipient
949      * commands may be called followed by a data command. This command
950      * will send the message to the users terminal if they are logged
951      * in and send them an email.
952      * Implements RFC 821: SAML <SP> FROM:<reverse-path> <CRLF>.
953      *
954      * @param string $from The address the message is from
955      *
956      * @return bool
957      */
958     public function sendAndMail($from)
959     {
960         return $this->sendCommand('SAML', "SAML FROM:$from", 250);
961     }
963     /**
964      * Send an SMTP VRFY command.
965      *
966      * @param string $name The name to verify
967      *
968      * @return bool
969      */
970     public function verify($name)
971     {
972         return $this->sendCommand('VRFY', "VRFY $name", [250, 251]);
973     }
975     /**
976      * Send an SMTP NOOP command.
977      * Used to keep keep-alives alive, doesn't actually do anything.
978      *
979      * @return bool
980      */
981     public function noop()
982     {
983         return $this->sendCommand('NOOP', 'NOOP', 250);
984     }
986     /**
987      * Send an SMTP TURN command.
988      * This is an optional command for SMTP that this class does not support.
989      * This method is here to make the RFC821 Definition complete for this class
990      * and _may_ be implemented in future.
991      * Implements from RFC 821: TURN <CRLF>.
992      *
993      * @return bool
994      */
995     public function turn()
996     {
997         $this->setError('The SMTP TURN command is not implemented');
998         $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT);
1000         return false;
1001     }
1003     /**
1004      * Send raw data to the server.
1005      *
1006      * @param string $data The data to send
1007      *
1008      * @return int|bool The number of bytes sent to the server or false on error
1009      */
1010     public function client_send($data)
1011     {
1012         $this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT);
1013         set_error_handler([$this, 'errorHandler']);
1014         $result = fwrite($this->smtp_conn, $data);
1015         restore_error_handler();
1017         return $result;
1018     }
1020     /**
1021      * Get the latest error.
1022      *
1023      * @return array
1024      */
1025     public function getError()
1026     {
1027         return $this->error;
1028     }
1030     /**
1031      * Get SMTP extensions available on the server.
1032      *
1033      * @return array|null
1034      */
1035     public function getServerExtList()
1036     {
1037         return $this->server_caps;
1038     }
1040     /**
1041      * Get metadata about the SMTP server from its HELO/EHLO response.
1042      * The method works in three ways, dependent on argument value and current state:
1043      *   1. HELO/EHLO has not been sent - returns null and populates $this->error.
1044      *   2. HELO has been sent -
1045      *     $name == 'HELO': returns server name
1046      *     $name == 'EHLO': returns boolean false
1047      *     $name == any other string: returns null and populates $this->error
1048      *   3. EHLO has been sent -
1049      *     $name == 'HELO'|'EHLO': returns the server name
1050      *     $name == any other string: if extension $name exists, returns True
1051      *       or its options (e.g. AUTH mechanisms supported). Otherwise returns False.
1052      *
1053      * @param string $name Name of SMTP extension or 'HELO'|'EHLO'
1054      *
1055      * @return mixed
1056      */
1057     public function getServerExt($name)
1058     {
1059         if (!$this->server_caps) {
1060             $this->setError('No HELO/EHLO was sent');
1062             return;
1063         }
1065         if (!array_key_exists($name, $this->server_caps)) {
1066             if ('HELO' == $name) {
1067                 return $this->server_caps['EHLO'];
1068             }
1069             if ('EHLO' == $name || array_key_exists('EHLO', $this->server_caps)) {
1070                 return false;
1071             }
1072             $this->setError('HELO handshake was used; No information about server extensions available');
1074             return;
1075         }
1077         return $this->server_caps[$name];
1078     }
1080     /**
1081      * Get the last reply from the server.
1082      *
1083      * @return string
1084      */
1085     public function getLastReply()
1086     {
1087         return $this->last_reply;
1088     }
1090     /**
1091      * Read the SMTP server's response.
1092      * Either before eof or socket timeout occurs on the operation.
1093      * With SMTP we can tell if we have more lines to read if the
1094      * 4th character is '-' symbol. If it is a space then we don't
1095      * need to read anything else.
1096      *
1097      * @return string
1098      */
1099     protected function get_lines()
1100     {
1101         // If the connection is bad, give up straight away
1102         if (!is_resource($this->smtp_conn)) {
1103             return '';
1104         }
1105         $data = '';
1106         $endtime = 0;
1107         stream_set_timeout($this->smtp_conn, $this->Timeout);
1108         if ($this->Timelimit > 0) {
1109             $endtime = time() + $this->Timelimit;
1110         }
1111         $selR = [$this->smtp_conn];
1112         $selW = null;
1113         while (is_resource($this->smtp_conn) and !feof($this->smtp_conn)) {
1114             //Must pass vars in here as params are by reference
1115             if (!stream_select($selR, $selW, $selW, $this->Timelimit)) {
1116                 $this->edebug(
1117                     'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)',
1118                     self::DEBUG_LOWLEVEL
1119                 );
1120                 break;
1121             }
1122             //Deliberate noise suppression - errors are handled afterwards
1123             $str = @fgets($this->smtp_conn, 515);
1124             $this->edebug('SMTP INBOUND: "' . trim($str) . '"', self::DEBUG_LOWLEVEL);
1125             $data .= $str;
1126             // If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled),
1127             // or 4th character is a space, we are done reading, break the loop,
1128             // string array access is a micro-optimisation over strlen
1129             if (!isset($str[3]) or (isset($str[3]) and $str[3] == ' ')) {
1130                 break;
1131             }
1132             // Timed-out? Log and break
1133             $info = stream_get_meta_data($this->smtp_conn);
1134             if ($info['timed_out']) {
1135                 $this->edebug(
1136                     'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)',
1137                     self::DEBUG_LOWLEVEL
1138                 );
1139                 break;
1140             }
1141             // Now check if reads took too long
1142             if ($endtime and time() > $endtime) {
1143                 $this->edebug(
1144                     'SMTP -> get_lines(): timelimit reached (' .
1145                     $this->Timelimit . ' sec)',
1146                     self::DEBUG_LOWLEVEL
1147                 );
1148                 break;
1149             }
1150         }
1152         return $data;
1153     }
1155     /**
1156      * Enable or disable VERP address generation.
1157      *
1158      * @param bool $enabled
1159      */
1160     public function setVerp($enabled = false)
1161     {
1162         $this->do_verp = $enabled;
1163     }
1165     /**
1166      * Get VERP address generation mode.
1167      *
1168      * @return bool
1169      */
1170     public function getVerp()
1171     {
1172         return $this->do_verp;
1173     }
1175     /**
1176      * Set error messages and codes.
1177      *
1178      * @param string $message      The error message
1179      * @param string $detail       Further detail on the error
1180      * @param string $smtp_code    An associated SMTP error code
1181      * @param string $smtp_code_ex Extended SMTP code
1182      */
1183     protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '')
1184     {
1185         $this->error = [
1186             'error' => $message,
1187             'detail' => $detail,
1188             'smtp_code' => $smtp_code,
1189             'smtp_code_ex' => $smtp_code_ex,
1190         ];
1191     }
1193     /**
1194      * Set debug output method.
1195      *
1196      * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it
1197      */
1198     public function setDebugOutput($method = 'echo')
1199     {
1200         $this->Debugoutput = $method;
1201     }
1203     /**
1204      * Get debug output method.
1205      *
1206      * @return string
1207      */
1208     public function getDebugOutput()
1209     {
1210         return $this->Debugoutput;
1211     }
1213     /**
1214      * Set debug output level.
1215      *
1216      * @param int $level
1217      */
1218     public function setDebugLevel($level = 0)
1219     {
1220         $this->do_debug = $level;
1221     }
1223     /**
1224      * Get debug output level.
1225      *
1226      * @return int
1227      */
1228     public function getDebugLevel()
1229     {
1230         return $this->do_debug;
1231     }
1233     /**
1234      * Set SMTP timeout.
1235      *
1236      * @param int $timeout The timeout duration in seconds
1237      */
1238     public function setTimeout($timeout = 0)
1239     {
1240         $this->Timeout = $timeout;
1241     }
1243     /**
1244      * Get SMTP timeout.
1245      *
1246      * @return int
1247      */
1248     public function getTimeout()
1249     {
1250         return $this->Timeout;
1251     }
1253     /**
1254      * Reports an error number and string.
1255      *
1256      * @param int    $errno   The error number returned by PHP
1257      * @param string $errmsg  The error message returned by PHP
1258      * @param string $errfile The file the error occurred in
1259      * @param int    $errline The line number the error occurred on
1260      */
1261     protected function errorHandler($errno, $errmsg, $errfile = '', $errline = 0)
1262     {
1263         $notice = 'Connection failed.';
1264         $this->setError(
1265             $notice,
1266             $errmsg,
1267             (string) $errno
1268         );
1269         $this->edebug(
1270             "$notice Error #$errno: $errmsg [$errfile line $errline]",
1271             self::DEBUG_CONNECTION
1272         );
1273     }
1275     /**
1276      * Extract and return the ID of the last SMTP transaction based on
1277      * a list of patterns provided in SMTP::$smtp_transaction_id_patterns.
1278      * Relies on the host providing the ID in response to a DATA command.
1279      * If no reply has been received yet, it will return null.
1280      * If no pattern was matched, it will return false.
1281      *
1282      * @return bool|null|string
1283      */
1284     protected function recordLastTransactionID()
1285     {
1286         $reply = $this->getLastReply();
1288         if (empty($reply)) {
1289             $this->last_smtp_transaction_id = null;
1290         } else {
1291             $this->last_smtp_transaction_id = false;
1292             foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
1293                 if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) {
1294                     $this->last_smtp_transaction_id = $matches[1];
1295                     break;
1296                 }
1297             }
1298         }
1300         return $this->last_smtp_transaction_id;
1301     }
1303     /**
1304      * Get the queue/transaction ID of the last SMTP transaction
1305      * If no reply has been received yet, it will return null.
1306      * If no pattern was matched, it will return false.
1307      *
1308      * @return bool|null|string
1309      *
1310      * @see recordLastTransactionID()
1311      */
1312     public function getLastTransactionID()
1313     {
1314         return $this->last_smtp_transaction_id;
1315     }