wp/wp-includes/PHPMailer/SMTP.php
changeset 18 be944660c56a
parent 16 a86126ab1dd4
child 19 3d72ae0968f4
equal deleted inserted replaced
17:34716fd837a4 18:be944660c56a
     1 <?php
     1 <?php
       
     2 
     2 /**
     3 /**
     3  * PHPMailer RFC821 SMTP email transport class.
     4  * PHPMailer RFC821 SMTP email transport class.
     4  * PHP Version 5.5.
     5  * PHP Version 5.5.
     5  *
     6  *
     6  * @see       https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
     7  * @see       https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
     7  *
     8  *
     8  * @author    Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
     9  * @author    Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
     9  * @author    Jim Jagielski (jimjag) <jimjag@gmail.com>
    10  * @author    Jim Jagielski (jimjag) <jimjag@gmail.com>
    10  * @author    Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
    11  * @author    Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
    11  * @author    Brent R. Matzelle (original founder)
    12  * @author    Brent R. Matzelle (original founder)
    12  * @copyright 2012 - 2019 Marcus Bointon
    13  * @copyright 2012 - 2020 Marcus Bointon
    13  * @copyright 2010 - 2012 Jim Jagielski
    14  * @copyright 2010 - 2012 Jim Jagielski
    14  * @copyright 2004 - 2009 Andy Prevost
    15  * @copyright 2004 - 2009 Andy Prevost
    15  * @license   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
    16  * @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  * @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  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    32     /**
    33     /**
    33      * The PHPMailer SMTP version number.
    34      * The PHPMailer SMTP version number.
    34      *
    35      *
    35      * @var string
    36      * @var string
    36      */
    37      */
    37     const VERSION = '6.1.6';
    38     const VERSION = '6.5.0';
    38 
    39 
    39     /**
    40     /**
    40      * SMTP line break constant.
    41      * SMTP line break constant.
    41      *
    42      *
    42      * @var string
    43      * @var string
   183         'postfix' => '/[\d]{3} 2.0.0 Ok: queued as (.*)/',
   184         'postfix' => '/[\d]{3} 2.0.0 Ok: queued as (.*)/',
   184         'Microsoft_ESMTP' => '/[0-9]{3} 2.[\d].0 (.*)@(?:.*) Queued mail for delivery/',
   185         'Microsoft_ESMTP' => '/[0-9]{3} 2.[\d].0 (.*)@(?:.*) Queued mail for delivery/',
   185         'Amazon_SES' => '/[\d]{3} Ok (.*)/',
   186         'Amazon_SES' => '/[\d]{3} Ok (.*)/',
   186         'SendGrid' => '/[\d]{3} Ok: queued as (.*)/',
   187         'SendGrid' => '/[\d]{3} Ok: queued as (.*)/',
   187         'CampaignMonitor' => '/[\d]{3} 2.0.0 OK:([a-zA-Z\d]{48})/',
   188         'CampaignMonitor' => '/[\d]{3} 2.0.0 OK:([a-zA-Z\d]{48})/',
       
   189         'Haraka' => '/[\d]{3} Message Queued \((.*)\)/',
   188     ];
   190     ];
   189 
   191 
   190     /**
   192     /**
   191      * The last transaction ID issued in response to a DATA command,
   193      * The last transaction ID issued in response to a DATA command,
   192      * if one was detected.
   194      * if one was detected.
   309      *
   311      *
   310      * @return bool
   312      * @return bool
   311      */
   313      */
   312     public function connect($host, $port = null, $timeout = 30, $options = [])
   314     public function connect($host, $port = null, $timeout = 30, $options = [])
   313     {
   315     {
       
   316         //Clear errors to avoid confusion
       
   317         $this->setError('');
       
   318         //Make sure we are __not__ connected
       
   319         if ($this->connected()) {
       
   320             //Already connected, generate error
       
   321             $this->setError('Already connected to a server');
       
   322 
       
   323             return false;
       
   324         }
       
   325         if (empty($port)) {
       
   326             $port = self::DEFAULT_PORT;
       
   327         }
       
   328         //Connect to the SMTP server
       
   329         $this->edebug(
       
   330             "Connection: opening to $host:$port, timeout=$timeout, options=" .
       
   331             (count($options) > 0 ? var_export($options, true) : 'array()'),
       
   332             self::DEBUG_CONNECTION
       
   333         );
       
   334 
       
   335         $this->smtp_conn = $this->getSMTPConnection($host, $port, $timeout, $options);
       
   336 
       
   337         if ($this->smtp_conn === false) {
       
   338             //Error info already set inside `getSMTPConnection()`
       
   339             return false;
       
   340         }
       
   341 
       
   342         $this->edebug('Connection: opened', self::DEBUG_CONNECTION);
       
   343 
       
   344         //Get any announcement
       
   345         $this->last_reply = $this->get_lines();
       
   346         $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
       
   347         $responseCode = (int)substr($this->last_reply, 0, 3);
       
   348         if ($responseCode === 220) {
       
   349             return true;
       
   350         }
       
   351         //Anything other than a 220 response means something went wrong
       
   352         //RFC 5321 says the server will wait for us to send a QUIT in response to a 554 error
       
   353         //https://tools.ietf.org/html/rfc5321#section-3.1
       
   354         if ($responseCode === 554) {
       
   355             $this->quit();
       
   356         }
       
   357         //This will handle 421 responses which may not wait for a QUIT (e.g. if the server is being shut down)
       
   358         $this->edebug('Connection: closing due to error', self::DEBUG_CONNECTION);
       
   359         $this->close();
       
   360         return false;
       
   361     }
       
   362 
       
   363     /**
       
   364      * Create connection to the SMTP server.
       
   365      *
       
   366      * @param string $host    SMTP server IP or host name
       
   367      * @param int    $port    The port number to connect to
       
   368      * @param int    $timeout How long to wait for the connection to open
       
   369      * @param array  $options An array of options for stream_context_create()
       
   370      *
       
   371      * @return false|resource
       
   372      */
       
   373     protected function getSMTPConnection($host, $port = null, $timeout = 30, $options = [])
       
   374     {
   314         static $streamok;
   375         static $streamok;
   315         //This is enabled by default since 5.0.0 but some providers disable it
   376         //This is enabled by default since 5.0.0 but some providers disable it
   316         //Check this once and cache the result
   377         //Check this once and cache the result
   317         if (null === $streamok) {
   378         if (null === $streamok) {
   318             $streamok = function_exists('stream_socket_client');
   379             $streamok = function_exists('stream_socket_client');
   319         }
   380         }
   320         // Clear errors to avoid confusion
   381 
   321         $this->setError('');
       
   322         // Make sure we are __not__ connected
       
   323         if ($this->connected()) {
       
   324             // Already connected, generate error
       
   325             $this->setError('Already connected to a server');
       
   326 
       
   327             return false;
       
   328         }
       
   329         if (empty($port)) {
       
   330             $port = self::DEFAULT_PORT;
       
   331         }
       
   332         // Connect to the SMTP server
       
   333         $this->edebug(
       
   334             "Connection: opening to $host:$port, timeout=$timeout, options=" .
       
   335             (count($options) > 0 ? var_export($options, true) : 'array()'),
       
   336             self::DEBUG_CONNECTION
       
   337         );
       
   338         $errno = 0;
   382         $errno = 0;
   339         $errstr = '';
   383         $errstr = '';
   340         if ($streamok) {
   384         if ($streamok) {
   341             $socket_context = stream_context_create($options);
   385             $socket_context = stream_context_create($options);
   342             set_error_handler([$this, 'errorHandler']);
   386             set_error_handler([$this, 'errorHandler']);
   343             $this->smtp_conn = stream_socket_client(
   387             $connection = stream_socket_client(
   344                 $host . ':' . $port,
   388                 $host . ':' . $port,
   345                 $errno,
   389                 $errno,
   346                 $errstr,
   390                 $errstr,
   347                 $timeout,
   391                 $timeout,
   348                 STREAM_CLIENT_CONNECT,
   392                 STREAM_CLIENT_CONNECT,
   354             $this->edebug(
   398             $this->edebug(
   355                 'Connection: stream_socket_client not available, falling back to fsockopen',
   399                 'Connection: stream_socket_client not available, falling back to fsockopen',
   356                 self::DEBUG_CONNECTION
   400                 self::DEBUG_CONNECTION
   357             );
   401             );
   358             set_error_handler([$this, 'errorHandler']);
   402             set_error_handler([$this, 'errorHandler']);
   359             $this->smtp_conn = fsockopen(
   403             $connection = fsockopen(
   360                 $host,
   404                 $host,
   361                 $port,
   405                 $port,
   362                 $errno,
   406                 $errno,
   363                 $errstr,
   407                 $errstr,
   364                 $timeout
   408                 $timeout
   365             );
   409             );
   366             restore_error_handler();
   410             restore_error_handler();
   367         }
   411         }
   368         // Verify we connected properly
   412 
   369         if (!is_resource($this->smtp_conn)) {
   413         //Verify we connected properly
       
   414         if (!is_resource($connection)) {
   370             $this->setError(
   415             $this->setError(
   371                 'Failed to connect to server',
   416                 'Failed to connect to server',
   372                 '',
   417                 '',
   373                 (string) $errno,
   418                 (string) $errno,
   374                 $errstr
   419                 $errstr
   379                 self::DEBUG_CLIENT
   424                 self::DEBUG_CLIENT
   380             );
   425             );
   381 
   426 
   382             return false;
   427             return false;
   383         }
   428         }
   384         $this->edebug('Connection: opened', self::DEBUG_CONNECTION);
   429 
   385         // SMTP server can take longer to respond, give longer timeout for first read
   430         //SMTP server can take longer to respond, give longer timeout for first read
   386         // Windows does not have support for this timeout function
   431         //Windows does not have support for this timeout function
   387         if (strpos(PHP_OS, 'WIN') !== 0) {
   432         if (strpos(PHP_OS, 'WIN') !== 0) {
   388             $max = (int) ini_get('max_execution_time');
   433             $max = (int)ini_get('max_execution_time');
   389             // Don't bother if unlimited
   434             //Don't bother if unlimited, or if set_time_limit is disabled
   390             if (0 !== $max && $timeout > $max) {
   435             if (0 !== $max && $timeout > $max && strpos(ini_get('disable_functions'), 'set_time_limit') === false) {
   391                 @set_time_limit($timeout);
   436                 @set_time_limit($timeout);
   392             }
   437             }
   393             stream_set_timeout($this->smtp_conn, $timeout, 0);
   438             stream_set_timeout($connection, $timeout, 0);
   394         }
   439         }
   395         // Get any announcement
   440 
   396         $announce = $this->get_lines();
   441         return $connection;
   397         $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER);
       
   398 
       
   399         return true;
       
   400     }
   442     }
   401 
   443 
   402     /**
   444     /**
   403      * Initiate a TLS (encrypted) session.
   445      * Initiate a TLS (encrypted) session.
   404      *
   446      *
   418         if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
   460         if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
   419             $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
   461             $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
   420             $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
   462             $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
   421         }
   463         }
   422 
   464 
   423         // Begin encrypted connection
   465         //Begin encrypted connection
   424         set_error_handler([$this, 'errorHandler']);
   466         set_error_handler([$this, 'errorHandler']);
   425         $crypto_ok = stream_socket_enable_crypto(
   467         $crypto_ok = stream_socket_enable_crypto(
   426             $this->smtp_conn,
   468             $this->smtp_conn,
   427             true,
   469             true,
   428             $crypto_method
   470             $crypto_method
   456 
   498 
   457             return false;
   499             return false;
   458         }
   500         }
   459 
   501 
   460         if (array_key_exists('EHLO', $this->server_caps)) {
   502         if (array_key_exists('EHLO', $this->server_caps)) {
   461             // SMTP extensions are available; try to find a proper authentication method
   503             //SMTP extensions are available; try to find a proper authentication method
   462             if (!array_key_exists('AUTH', $this->server_caps)) {
   504             if (!array_key_exists('AUTH', $this->server_caps)) {
   463                 $this->setError('Authentication is not allowed at this stage');
   505                 $this->setError('Authentication is not allowed at this stage');
   464                 // 'at this stage' means that auth may be allowed after the stage changes
   506                 //'at this stage' means that auth may be allowed after the stage changes
   465                 // e.g. after STARTTLS
   507                 //e.g. after STARTTLS
   466 
   508 
   467                 return false;
   509                 return false;
   468             }
   510             }
   469 
   511 
   470             $this->edebug('Auth method requested: ' . ($authtype ?: 'UNSPECIFIED'), self::DEBUG_LOWLEVEL);
   512             $this->edebug('Auth method requested: ' . ($authtype ?: 'UNSPECIFIED'), self::DEBUG_LOWLEVEL);
   504         } elseif (empty($authtype)) {
   546         } elseif (empty($authtype)) {
   505             $authtype = 'LOGIN';
   547             $authtype = 'LOGIN';
   506         }
   548         }
   507         switch ($authtype) {
   549         switch ($authtype) {
   508             case 'PLAIN':
   550             case 'PLAIN':
   509                 // Start authentication
   551                 //Start authentication
   510                 if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) {
   552                 if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) {
   511                     return false;
   553                     return false;
   512                 }
   554                 }
   513                 // Send encoded username and password
   555                 //Send encoded username and password
   514                 if (!$this->sendCommand(
   556                 if (
   515                     'User & Password',
   557                     //Format from https://tools.ietf.org/html/rfc4616#section-2
   516                     base64_encode("\0" . $username . "\0" . $password),
   558                     //We skip the first field (it's forgery), so the string starts with a null byte
   517                     235
   559                     !$this->sendCommand(
   518                 )
   560                         'User & Password',
       
   561                         base64_encode("\0" . $username . "\0" . $password),
       
   562                         235
       
   563                     )
   519                 ) {
   564                 ) {
   520                     return false;
   565                     return false;
   521                 }
   566                 }
   522                 break;
   567                 break;
   523             case 'LOGIN':
   568             case 'LOGIN':
   524                 // Start authentication
   569                 //Start authentication
   525                 if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) {
   570                 if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) {
   526                     return false;
   571                     return false;
   527                 }
   572                 }
   528                 if (!$this->sendCommand('Username', base64_encode($username), 334)) {
   573                 if (!$this->sendCommand('Username', base64_encode($username), 334)) {
   529                     return false;
   574                     return false;
   531                 if (!$this->sendCommand('Password', base64_encode($password), 235)) {
   576                 if (!$this->sendCommand('Password', base64_encode($password), 235)) {
   532                     return false;
   577                     return false;
   533                 }
   578                 }
   534                 break;
   579                 break;
   535             case 'CRAM-MD5':
   580             case 'CRAM-MD5':
   536                 // Start authentication
   581                 //Start authentication
   537                 if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) {
   582                 if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) {
   538                     return false;
   583                     return false;
   539                 }
   584                 }
   540                 // Get the challenge
   585                 //Get the challenge
   541                 $challenge = base64_decode(substr($this->last_reply, 4));
   586                 $challenge = base64_decode(substr($this->last_reply, 4));
   542 
   587 
   543                 // Build the response
   588                 //Build the response
   544                 $response = $username . ' ' . $this->hmac($challenge, $password);
   589                 $response = $username . ' ' . $this->hmac($challenge, $password);
   545 
   590 
   546                 // send encoded credentials
   591                 //send encoded credentials
   547                 return $this->sendCommand('Username', base64_encode($response), 235);
   592                 return $this->sendCommand('Username', base64_encode($response), 235);
   548             case 'XOAUTH2':
   593             case 'XOAUTH2':
   549                 //The OAuth instance must be set up prior to requesting auth.
   594                 //The OAuth instance must be set up prior to requesting auth.
   550                 if (null === $OAuth) {
   595                 if (null === $OAuth) {
   551                     return false;
   596                     return false;
   552                 }
   597                 }
   553                 $oauth = $OAuth->getOauth64();
   598                 $oauth = $OAuth->getOauth64();
   554 
   599 
   555                 // Start authentication
   600                 //Start authentication
   556                 if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
   601                 if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
   557                     return false;
   602                     return false;
   558                 }
   603                 }
   559                 break;
   604                 break;
   560             default:
   605             default:
   580     {
   625     {
   581         if (function_exists('hash_hmac')) {
   626         if (function_exists('hash_hmac')) {
   582             return hash_hmac('md5', $data, $key);
   627             return hash_hmac('md5', $data, $key);
   583         }
   628         }
   584 
   629 
   585         // The following borrowed from
   630         //The following borrowed from
   586         // http://php.net/manual/en/function.mhash.php#27225
   631         //http://php.net/manual/en/function.mhash.php#27225
   587 
   632 
   588         // RFC 2104 HMAC implementation for php.
   633         //RFC 2104 HMAC implementation for php.
   589         // Creates an md5 HMAC.
   634         //Creates an md5 HMAC.
   590         // Eliminates the need to install mhash to compute a HMAC
   635         //Eliminates the need to install mhash to compute a HMAC
   591         // by Lance Rushing
   636         //by Lance Rushing
   592 
   637 
   593         $bytelen = 64; // byte length for md5
   638         $bytelen = 64; //byte length for md5
   594         if (strlen($key) > $bytelen) {
   639         if (strlen($key) > $bytelen) {
   595             $key = pack('H*', md5($key));
   640             $key = pack('H*', md5($key));
   596         }
   641         }
   597         $key = str_pad($key, $bytelen, chr(0x00));
   642         $key = str_pad($key, $bytelen, chr(0x00));
   598         $ipad = str_pad('', $bytelen, chr(0x36));
   643         $ipad = str_pad('', $bytelen, chr(0x36));
   611     public function connected()
   656     public function connected()
   612     {
   657     {
   613         if (is_resource($this->smtp_conn)) {
   658         if (is_resource($this->smtp_conn)) {
   614             $sock_status = stream_get_meta_data($this->smtp_conn);
   659             $sock_status = stream_get_meta_data($this->smtp_conn);
   615             if ($sock_status['eof']) {
   660             if ($sock_status['eof']) {
   616                 // The socket is valid but we are not connected
   661                 //The socket is valid but we are not connected
   617                 $this->edebug(
   662                 $this->edebug(
   618                     'SMTP NOTICE: EOF caught while checking if connected',
   663                     'SMTP NOTICE: EOF caught while checking if connected',
   619                     self::DEBUG_CLIENT
   664                     self::DEBUG_CLIENT
   620                 );
   665                 );
   621                 $this->close();
   666                 $this->close();
   622 
   667 
   623                 return false;
   668                 return false;
   624             }
   669             }
   625 
   670 
   626             return true; // everything looks good
   671             return true; //everything looks good
   627         }
   672         }
   628 
   673 
   629         return false;
   674         return false;
   630     }
   675     }
   631 
   676 
   639     {
   684     {
   640         $this->setError('');
   685         $this->setError('');
   641         $this->server_caps = null;
   686         $this->server_caps = null;
   642         $this->helo_rply = null;
   687         $this->helo_rply = null;
   643         if (is_resource($this->smtp_conn)) {
   688         if (is_resource($this->smtp_conn)) {
   644             // close the connection and cleanup
   689             //Close the connection and cleanup
   645             fclose($this->smtp_conn);
   690             fclose($this->smtp_conn);
   646             $this->smtp_conn = null; //Makes for cleaner serialization
   691             $this->smtp_conn = null; //Makes for cleaner serialization
   647             $this->edebug('Connection: closed', self::DEBUG_CONNECTION);
   692             $this->edebug('Connection: closed', self::DEBUG_CONNECTION);
   648         }
   693         }
   649     }
   694     }
   674          * smaller lines to fit within the limit.
   719          * smaller lines to fit within the limit.
   675          * We will also look for lines that start with a '.' and prepend an additional '.'.
   720          * We will also look for lines that start with a '.' and prepend an additional '.'.
   676          * NOTE: this does not count towards line-length limit.
   721          * NOTE: this does not count towards line-length limit.
   677          */
   722          */
   678 
   723 
   679         // Normalize line breaks before exploding
   724         //Normalize line breaks before exploding
   680         $lines = explode("\n", str_replace(["\r\n", "\r"], "\n", $msg_data));
   725         $lines = explode("\n", str_replace(["\r\n", "\r"], "\n", $msg_data));
   681 
   726 
   682         /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field
   727         /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field
   683          * of the first line (':' separated) does not contain a space then it _should_ be a header and we will
   728          * of the first line (':' separated) does not contain a space then it _should_ be a header and we will
   684          * process all lines before a blank line as headers.
   729          * process all lines before a blank line as headers.
   720             }
   765             }
   721             $lines_out[] = $line;
   766             $lines_out[] = $line;
   722 
   767 
   723             //Send the lines to the server
   768             //Send the lines to the server
   724             foreach ($lines_out as $line_out) {
   769             foreach ($lines_out as $line_out) {
   725                 //RFC2821 section 4.5.2
   770                 //Dot-stuffing as per RFC5321 section 4.5.2
       
   771                 //https://tools.ietf.org/html/rfc5321#section-4.5.2
   726                 if (!empty($line_out) && $line_out[0] === '.') {
   772                 if (!empty($line_out) && $line_out[0] === '.') {
   727                     $line_out = '.' . $line_out;
   773                     $line_out = '.' . $line_out;
   728                 }
   774                 }
   729                 $this->client_send($line_out . static::LE, 'DATA');
   775                 $this->client_send($line_out . static::LE, 'DATA');
   730             }
   776             }
   754      * @return bool
   800      * @return bool
   755      */
   801      */
   756     public function hello($host = '')
   802     public function hello($host = '')
   757     {
   803     {
   758         //Try extended hello first (RFC 2821)
   804         //Try extended hello first (RFC 2821)
   759         return $this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host);
   805         if ($this->sendHello('EHLO', $host)) {
       
   806             return true;
       
   807         }
       
   808 
       
   809         //Some servers shut down the SMTP service here (RFC 5321)
       
   810         if (substr($this->helo_rply, 0, 3) == '421') {
       
   811             return false;
       
   812         }
       
   813 
       
   814         return $this->sendHello('HELO', $host);
   760     }
   815     }
   761 
   816 
   762     /**
   817     /**
   763      * Send an SMTP HELO or EHLO command.
   818      * Send an SMTP HELO or EHLO command.
   764      * Low-level implementation used by hello().
   819      * Low-level implementation used by hello().
   944             return false;
   999             return false;
   945         }
  1000         }
   946         $this->client_send($commandstring . static::LE, $command);
  1001         $this->client_send($commandstring . static::LE, $command);
   947 
  1002 
   948         $this->last_reply = $this->get_lines();
  1003         $this->last_reply = $this->get_lines();
   949         // Fetch SMTP code and possible error code explanation
  1004         //Fetch SMTP code and possible error code explanation
   950         $matches = [];
  1005         $matches = [];
   951         if (preg_match('/^([\d]{3})[ -](?:([\d]\\.[\d]\\.[\d]{1,2}) )?/', $this->last_reply, $matches)) {
  1006         if (preg_match('/^([\d]{3})[ -](?:([\d]\\.[\d]\\.[\d]{1,2}) )?/', $this->last_reply, $matches)) {
   952             $code = (int) $matches[1];
  1007             $code = (int) $matches[1];
   953             $code_ex = (count($matches) > 2 ? $matches[2] : null);
  1008             $code_ex = (count($matches) > 2 ? $matches[2] : null);
   954             // Cut off error code from each response line
  1009             //Cut off error code from each response line
   955             $detail = preg_replace(
  1010             $detail = preg_replace(
   956                 "/{$code}[ -]" .
  1011                 "/{$code}[ -]" .
   957                 ($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . '/m',
  1012                 ($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . '/m',
   958                 '',
  1013                 '',
   959                 $this->last_reply
  1014                 $this->last_reply
   960             );
  1015             );
   961         } else {
  1016         } else {
   962             // Fall back to simple parsing if regex fails
  1017             //Fall back to simple parsing if regex fails
   963             $code = (int) substr($this->last_reply, 0, 3);
  1018             $code = (int) substr($this->last_reply, 0, 3);
   964             $code_ex = null;
  1019             $code_ex = null;
   965             $detail = substr($this->last_reply, 4);
  1020             $detail = substr($this->last_reply, 4);
   966         }
  1021         }
   967 
  1022 
  1056      */
  1111      */
  1057     public function client_send($data, $command = '')
  1112     public function client_send($data, $command = '')
  1058     {
  1113     {
  1059         //If SMTP transcripts are left enabled, or debug output is posted online
  1114         //If SMTP transcripts are left enabled, or debug output is posted online
  1060         //it can leak credentials, so hide credentials in all but lowest level
  1115         //it can leak credentials, so hide credentials in all but lowest level
  1061         if (self::DEBUG_LOWLEVEL > $this->do_debug &&
  1116         if (
  1062             in_array($command, ['User & Password', 'Username', 'Password'], true)) {
  1117             self::DEBUG_LOWLEVEL > $this->do_debug &&
       
  1118             in_array($command, ['User & Password', 'Username', 'Password'], true)
       
  1119         ) {
  1063             $this->edebug('CLIENT -> SERVER: [credentials hidden]', self::DEBUG_CLIENT);
  1120             $this->edebug('CLIENT -> SERVER: [credentials hidden]', self::DEBUG_CLIENT);
  1064         } else {
  1121         } else {
  1065             $this->edebug('CLIENT -> SERVER: ' . $data, self::DEBUG_CLIENT);
  1122             $this->edebug('CLIENT -> SERVER: ' . $data, self::DEBUG_CLIENT);
  1066         }
  1123         }
  1067         set_error_handler([$this, 'errorHandler']);
  1124         set_error_handler([$this, 'errorHandler']);
  1150      *
  1207      *
  1151      * @return string
  1208      * @return string
  1152      */
  1209      */
  1153     protected function get_lines()
  1210     protected function get_lines()
  1154     {
  1211     {
  1155         // If the connection is bad, give up straight away
  1212         //If the connection is bad, give up straight away
  1156         if (!is_resource($this->smtp_conn)) {
  1213         if (!is_resource($this->smtp_conn)) {
  1157             return '';
  1214             return '';
  1158         }
  1215         }
  1159         $data = '';
  1216         $data = '';
  1160         $endtime = 0;
  1217         $endtime = 0;
  1164         }
  1221         }
  1165         $selR = [$this->smtp_conn];
  1222         $selR = [$this->smtp_conn];
  1166         $selW = null;
  1223         $selW = null;
  1167         while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
  1224         while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
  1168             //Must pass vars in here as params are by reference
  1225             //Must pass vars in here as params are by reference
  1169             if (!stream_select($selR, $selW, $selW, $this->Timelimit)) {
  1226             //solution for signals inspired by https://github.com/symfony/symfony/pull/6540
       
  1227             set_error_handler([$this, 'errorHandler']);
       
  1228             $n = stream_select($selR, $selW, $selW, $this->Timelimit);
       
  1229             restore_error_handler();
       
  1230 
       
  1231             if ($n === false) {
       
  1232                 $message = $this->getError()['detail'];
       
  1233 
       
  1234                 $this->edebug(
       
  1235                     'SMTP -> get_lines(): select failed (' . $message . ')',
       
  1236                     self::DEBUG_LOWLEVEL
       
  1237                 );
       
  1238 
       
  1239                 //stream_select returns false when the `select` system call is interrupted
       
  1240                 //by an incoming signal, try the select again
       
  1241                 if (stripos($message, 'interrupted system call') !== false) {
       
  1242                     $this->edebug(
       
  1243                         'SMTP -> get_lines(): retrying stream_select',
       
  1244                         self::DEBUG_LOWLEVEL
       
  1245                     );
       
  1246                     $this->setError('');
       
  1247                     continue;
       
  1248                 }
       
  1249 
       
  1250                 break;
       
  1251             }
       
  1252 
       
  1253             if (!$n) {
  1170                 $this->edebug(
  1254                 $this->edebug(
  1171                     'SMTP -> get_lines(): select timed-out in (' . $this->Timelimit . ' sec)',
  1255                     'SMTP -> get_lines(): select timed-out in (' . $this->Timelimit . ' sec)',
  1172                     self::DEBUG_LOWLEVEL
  1256                     self::DEBUG_LOWLEVEL
  1173                 );
  1257                 );
  1174                 break;
  1258                 break;
  1175             }
  1259             }
       
  1260 
  1176             //Deliberate noise suppression - errors are handled afterwards
  1261             //Deliberate noise suppression - errors are handled afterwards
  1177             $str = @fgets($this->smtp_conn, self::MAX_REPLY_LENGTH);
  1262             $str = @fgets($this->smtp_conn, self::MAX_REPLY_LENGTH);
  1178             $this->edebug('SMTP INBOUND: "' . trim($str) . '"', self::DEBUG_LOWLEVEL);
  1263             $this->edebug('SMTP INBOUND: "' . trim($str) . '"', self::DEBUG_LOWLEVEL);
  1179             $data .= $str;
  1264             $data .= $str;
  1180             // If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled),
  1265             //If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled),
  1181             // or 4th character is a space or a line break char, we are done reading, break the loop.
  1266             //or 4th character is a space or a line break char, we are done reading, break the loop.
  1182             // String array access is a significant micro-optimisation over strlen
  1267             //String array access is a significant micro-optimisation over strlen
  1183             if (!isset($str[3]) || $str[3] === ' ' || $str[3] === "\r" || $str[3] === "\n") {
  1268             if (!isset($str[3]) || $str[3] === ' ' || $str[3] === "\r" || $str[3] === "\n") {
  1184                 break;
  1269                 break;
  1185             }
  1270             }
  1186             // Timed-out? Log and break
  1271             //Timed-out? Log and break
  1187             $info = stream_get_meta_data($this->smtp_conn);
  1272             $info = stream_get_meta_data($this->smtp_conn);
  1188             if ($info['timed_out']) {
  1273             if ($info['timed_out']) {
  1189                 $this->edebug(
  1274                 $this->edebug(
  1190                     'SMTP -> get_lines(): stream timed-out (' . $this->Timeout . ' sec)',
  1275                     'SMTP -> get_lines(): stream timed-out (' . $this->Timeout . ' sec)',
  1191                     self::DEBUG_LOWLEVEL
  1276                     self::DEBUG_LOWLEVEL
  1192                 );
  1277                 );
  1193                 break;
  1278                 break;
  1194             }
  1279             }
  1195             // Now check if reads took too long
  1280             //Now check if reads took too long
  1196             if ($endtime && time() > $endtime) {
  1281             if ($endtime && time() > $endtime) {
  1197                 $this->edebug(
  1282                 $this->edebug(
  1198                     'SMTP -> get_lines(): timelimit reached (' .
  1283                     'SMTP -> get_lines(): timelimit reached (' .
  1199                     $this->Timelimit . ' sec)',
  1284                     $this->Timelimit . ' sec)',
  1200                     self::DEBUG_LOWLEVEL
  1285                     self::DEBUG_LOWLEVEL