[ Index ]

PHP Cross Reference of MyBB 1.8.37

title

Body

[close]

/inc/mailhandlers/ -> smtp.php (source)

   1  <?php
   2  /**
   3   * MyBB 1.8
   4   * Copyright 2014 MyBB Group, All Rights Reserved
   5   *
   6   * Website: http://www.mybb.com
   7   * License: http://www.mybb.com/about/license
   8   *
   9   */
  10  
  11   // Disallow direct access to this file for security reasons
  12  if(!defined("IN_MYBB"))
  13  {
  14      die("Direct initialization of this file is not allowed.<br /><br />Please make sure IN_MYBB is defined.");
  15  }
  16  
  17  /**
  18   * SMTP mail handler class.
  19   */
  20  
  21  if(!defined('MYBB_SSL'))
  22  {
  23      define('MYBB_SSL', 1);
  24  }
  25  
  26  if(!defined('MYBB_TLS'))
  27  {
  28      define('MYBB_TLS', 2);
  29  }
  30  
  31  class SmtpMail extends MailHandler
  32  {
  33      /**
  34       * The SMTP connection.
  35       *
  36       * @var resource
  37       */
  38      public $connection;
  39  
  40      /**
  41       * SMTP username.
  42       *
  43       * @var string
  44       */
  45      public $username = '';
  46  
  47      /**
  48       * SMTP password.
  49       *
  50       * @var string
  51       */
  52      public $password = '';
  53  
  54      /**
  55       * Hello string sent to the smtp server with either HELO or EHLO.
  56       *
  57       * @var string
  58       */
  59      public $helo = 'localhost';
  60  
  61      /**
  62       * User authenticated or not.
  63       *
  64       * @var boolean
  65       */
  66      public $authenticated = false;
  67  
  68      /**
  69       * How long before timeout.
  70       *
  71       * @var integer
  72       */
  73      public $timeout = 5;
  74  
  75      /**
  76       * SMTP status.
  77       *
  78       * @var integer
  79       */
  80      public $status = 0;
  81  
  82      /**
  83       * SMTP default port.
  84       *
  85       * @var integer
  86       */
  87      public $port = 25;
  88  
  89      /**
  90       * SMTP default secure port.
  91       *
  92       * @var integer
  93       */
  94      public $secure_port = 465;
  95  
  96      /**
  97       * SMTP host.
  98       *
  99       * @var string
 100       */
 101      public $host = '';
 102  
 103      /**
 104       * The last received error message from the SMTP server.
 105       *
 106       * @var string
 107       */
 108      public $last_error = '';
 109  
 110      /**
 111       * Are we keeping the connection to the SMTP server alive?
 112       *
 113       * @var boolean
 114       */
 115      public $keep_alive = false;
 116  
 117      /**
 118       * Whether to use TLS encryption.
 119       *
 120       * @var boolean
 121       */
 122      public $use_tls = false;
 123  
 124  	function __construct()
 125      {
 126          global $mybb;
 127  
 128          $protocol = '';
 129          switch($mybb->settings['secure_smtp'])
 130          {
 131              case MYBB_SSL:
 132                  $protocol = 'ssl://';
 133                  break;
 134              case MYBB_TLS:
 135                  $this->use_tls = true;
 136                  break;
 137          }
 138  
 139          if(empty($mybb->settings['smtp_host']))
 140          {
 141              $this->host = @ini_get('SMTP');
 142          }
 143          else
 144          {
 145              $this->host = $mybb->settings['smtp_host'];
 146          }
 147  
 148          $local = array('127.0.0.1', '::1', 'localhost');
 149          if(!in_array($this->host, $local))
 150          {
 151              if(function_exists('gethostname') && gethostname() !== false)
 152              {
 153                  $this->helo = gethostname();
 154              }
 155              elseif(function_exists('php_uname'))
 156              {
 157                  $helo = php_uname('n');
 158                  if(!empty($helo))
 159                  {
 160                      $this->helo = $helo;
 161                  }
 162              }
 163              elseif(!empty($_SERVER['SERVER_NAME']))
 164              {
 165                  $this->helo = $_SERVER['SERVER_NAME'];
 166              }
 167          }
 168  
 169          $this->host = $protocol . $this->host;
 170  
 171          if(empty($mybb->settings['smtp_port']) && !empty($protocol) && !@ini_get('smtp_port'))
 172          {
 173              $this->port = $this->secure_port;
 174          }
 175          else if(empty($mybb->settings['smtp_port']) && @ini_get('smtp_port'))
 176          {
 177              $this->port = @ini_get('smtp_port');
 178          }
 179          else if(!empty($mybb->settings['smtp_port']))
 180          {
 181              $this->port = $mybb->settings['smtp_port'];
 182          }
 183  
 184          $this->password = $mybb->settings['smtp_pass'];
 185          $this->username = $mybb->settings['smtp_user'];
 186      }
 187  
 188      /**
 189       * Sends the email.
 190       *
 191       * @return bool whether or not the email got sent or not.
 192       */
 193  	function send()
 194      {
 195          global $lang, $mybb;
 196  
 197          if(!$this->connected())
 198          {
 199              if(!$this->connect())
 200              {
 201                  $this->close();
 202              }
 203          }
 204  
 205          if($this->connected())
 206          {
 207              if(!$this->send_data('MAIL FROM:<'.$this->from.'>', 250))
 208              {
 209                  $this->fatal_error("The mail server does not understand the MAIL FROM command. Reason: ".$this->get_error());
 210                  return false;
 211              }
 212  
 213              // Loop through recipients
 214              $emails = explode(',', $this->to);
 215              foreach($emails as $to)
 216              {
 217                  $to = trim($to);
 218                  if(!$this->send_data('RCPT TO:<'.$to.'>', 250))
 219                  {
 220                      $this->fatal_error("The mail server does not understand the RCPT TO command. Reason: ".$this->get_error());
 221                      return false;
 222                  }
 223              }
 224  
 225              if($this->send_data('DATA', 354))
 226              {
 227                  $this->send_data('Date: ' . gmdate('r'));
 228                  $this->send_data('To: ' . $this->to);
 229  
 230                  $this->send_data('Subject: ' . $this->subject);
 231  
 232                  // Only send additional headers if we've got any
 233                  if(trim($this->headers))
 234                  {
 235                      $this->send_data(trim($this->headers));
 236                  }
 237  
 238                  $this->send_data("");
 239  
 240                  // Queue the actual message
 241                  $this->message = str_replace("\n.", "\n..", $this->message);
 242                  $this->send_data($this->message);
 243              }
 244              else
 245              {
 246                  $this->fatal_error("The mail server did not understand the DATA command");
 247                  return false;
 248              }
 249  
 250              if(!$this->send_data('.', 250))
 251              {
 252                  $this->fatal_error("Mail may not be delivered. Reason: ".$this->get_error());
 253              }
 254  
 255              if(!$this->keep_alive)
 256              {
 257                  $this->close();
 258              }
 259              return true;
 260          }
 261          else
 262          {
 263              return false;
 264          }
 265      }
 266  
 267      /**
 268       * Connect to the SMTP server.
 269       *
 270       * @return boolean True if connection was successful
 271       */
 272  	function connect()
 273      {
 274          global $lang, $mybb;
 275  
 276          $this->connection = @fsockopen($this->host, $this->port, $error_number, $error_string, $this->timeout);
 277  
 278          // DIRECTORY_SEPARATOR checks if running windows
 279          if(is_resource($this->connection) && function_exists('stream_set_timeout') && DIRECTORY_SEPARATOR != '\\')
 280          {
 281              @stream_set_timeout($this->connection, $this->timeout, 0);
 282          }
 283  
 284          if(is_resource($this->connection))
 285          {
 286              $this->status = 1;
 287              $this->get_data();
 288              if(!$this->check_status('220'))
 289              {
 290                  $this->fatal_error("The mail server is not ready, it did not respond with a 220 status message.");
 291                  return false;
 292              }
 293  
 294              if($this->use_tls || (!empty($this->username) && !empty($this->password)))
 295              {
 296                  $helo = 'EHLO';
 297              }
 298              else
 299              {
 300                  $helo = 'HELO';
 301              }
 302  
 303              $data = $this->send_data("{$helo} {$this->helo}", 250);
 304              if(!$data)
 305              {
 306                  $this->fatal_error("The server did not understand the {$helo} command");
 307                  return false;
 308              }
 309  
 310              if($this->use_tls && preg_match("#250( |-)STARTTLS#mi", $data))
 311              {
 312                  if(!$this->send_data('STARTTLS', 220))
 313                  {
 314                      $this->fatal_error("The server did not understand the STARTTLS command. Reason: ".$this->get_error());
 315                      return false;
 316                  }
 317  
 318                  $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
 319                  // Fix for PHP >=5.6.7 and <7.2 not including TLS 1.1 and 1.2
 320                  if(defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT'))
 321                  {
 322                      $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
 323                      $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
 324                  }
 325  
 326                  if(!@stream_socket_enable_crypto($this->connection, true, $crypto_method))
 327                  {
 328                      $this->fatal_error("Failed to start TLS encryption");
 329                      return false;
 330                  }
 331                  // Resend EHLO to get updated service list
 332                  $data = $this->send_data("{$helo} {$this->helo}", 250);
 333                  if(!$data)
 334                  {
 335                      $this->fatal_error("The server did not understand the EHLO command");
 336                      return false;
 337                  }
 338              }
 339  
 340              if(!empty($this->username) && !empty($this->password))
 341              {
 342                  if(!preg_match("#250( |-)AUTH( |=)(.+)$#mi", $data, $matches))
 343                  {
 344                      $this->fatal_error("The server did not understand the AUTH command");
 345                      return false;
 346                  }
 347                  if(!$this->auth($matches[3]))
 348                  {
 349                      return false;
 350                  }
 351              }
 352              return true;
 353          }
 354          else
 355          {
 356              $this->fatal_error("Unable to connect to the mail server with the given details. Reason: {$error_number}: {$error_string}");
 357              return false;
 358          }
 359      }
 360  
 361      /**
 362       * Authenticate against the SMTP server.
 363       *
 364       * @param string $auth_methods A list of authentication methods supported by the server
 365       * @return boolean True on success
 366       */
 367  	function auth($auth_methods)
 368      {
 369          global $lang, $mybb;
 370  
 371          $auth_methods = explode(" ", trim($auth_methods));
 372  
 373          if(in_array("LOGIN", $auth_methods))
 374          {
 375              if(!$this->send_data("AUTH LOGIN", 334))
 376              {
 377                  if($this->code == 503)
 378                  {
 379                      return true;
 380                  }
 381                  $this->fatal_error("The SMTP server did not respond correctly to the AUTH LOGIN command");
 382                  return false;
 383              }
 384  
 385              if(!$this->send_data(base64_encode($this->username), 334))
 386              {
 387                  $this->fatal_error("The SMTP server rejected the supplied SMTP username. Reason: ".$this->get_error());
 388                  return false;
 389              }
 390  
 391              if(!$this->send_data(base64_encode($this->password), 235))
 392              {
 393                  $this->fatal_error("The SMTP server rejected the supplied SMTP password. Reason: ".$this->get_error());
 394                  return false;
 395              }
 396          }
 397          else if(in_array("PLAIN", $auth_methods))
 398          {
 399              if(!$this->send_data("AUTH PLAIN", 334))
 400              {
 401                  if($this->code == 503)
 402                  {
 403                      return true;
 404                  }
 405                  $this->fatal_error("The SMTP server did not respond correctly to the AUTH PLAIN command");
 406                  return false;
 407              }
 408              $auth = base64_encode(chr(0).$this->username.chr(0).$this->password);
 409              if(!$this->send_data($auth, 235))
 410              {
 411                  $this->fatal_error("The SMTP server rejected the supplied login username and password. Reason: ".$this->get_error());
 412                  return false;
 413              }
 414          }
 415          else if(in_array("CRAM-MD5", $auth_methods))
 416          {
 417              $data = $this->send_data("AUTH CRAM-MD5", 334);
 418              if(!$data)
 419              {
 420                  if($this->code == 503)
 421                  {
 422                      return true;
 423                  }
 424                  $this->fatal_error("The SMTP server did not respond correctly to the AUTH CRAM-MD5 command");
 425                  return false;
 426              }
 427  
 428              $challenge = base64_decode(substr($data, 4));
 429              $auth = base64_encode($this->username.' '.$this->cram_md5_response($this->password, $challenge));
 430  
 431              if(!$this->send_data($auth, 235))
 432              {
 433                  $this->fatal_error("The SMTP server rejected the supplied login username and password. Reason: ".$this->get_error());
 434                  return false;
 435              }
 436          }
 437          else
 438          {
 439              $this->fatal_error("The SMTP server does not support any of the AUTH methods that MyBB supports");
 440              return false;
 441          }
 442  
 443          // Still here, we're authenticated
 444          return true;
 445      }
 446  
 447      /**
 448       * Fetch data from the SMTP server.
 449       *
 450       * @return string The data from the SMTP server
 451       */
 452  	function get_data()
 453      {
 454          $string = '';
 455  
 456          while((($line = fgets($this->connection, 515)) !== false))
 457          {
 458              $string .= $line;
 459              if(substr($line, 3, 1) == ' ')
 460              {
 461                  break;
 462              }
 463          }
 464          $string = trim($string);
 465          $this->data = $string;
 466          $this->code = substr($this->data, 0, 3);
 467          return $string;
 468      }
 469  
 470      /**
 471       * Check if we're currently connected to an SMTP server
 472       *
 473       * @return boolean true if connected
 474       */
 475  	function connected()
 476      {
 477          if($this->status == 1)
 478          {
 479              return true;
 480          }
 481          return false;
 482      }
 483  
 484      /**
 485       * Send data through to the SMTP server.
 486       *
 487       * @param string $data The data to be sent
 488       * @param int|bool $status_num The response code expected back from the server (if we have one)
 489       * @return boolean True on success
 490       */
 491  	function send_data($data, $status_num = false)
 492      {
 493          if($this->connected())
 494          {
 495              if(fwrite($this->connection, $data."\r\n"))
 496              {
 497                  if($status_num != false)
 498                  {
 499                      $rec = $this->get_data();
 500                      if($this->check_status($status_num))
 501                      {
 502                          return $rec;
 503                      }
 504                      else
 505                      {
 506                          $this->set_error($rec);
 507                          return false;
 508                      }
 509                  }
 510                  return true;
 511              }
 512              else
 513              {
 514                  $this->fatal_error("Unable to send the data to the SMTP server");
 515                  return false;
 516              }
 517          }
 518          return false;
 519      }
 520  
 521      /**
 522       * Checks if the received status code matches the one we expect.
 523       *
 524       * @param int $status_num The status code we expected back from the server
 525       * @return string|bool
 526       */
 527  	function check_status($status_num)
 528      {
 529          if($this->code == $status_num)
 530          {
 531              return $this->data;
 532          }
 533          else
 534          {
 535              return false;
 536          }
 537      }
 538  
 539      /**
 540       * Close the connection to the SMTP server.
 541       */
 542  	function close()
 543      {
 544          if($this->status == 1)
 545          {
 546              $this->send_data('QUIT');
 547              fclose($this->connection);
 548              $this->status = 0;
 549          }
 550      }
 551  
 552      /**
 553       * Get the last error message response from the SMTP server
 554       *
 555       * @return string The error message response from the SMTP server
 556       */
 557  	function get_error()
 558      {
 559          if(!$this->last_error)
 560          {
 561              $this->last_error = "N/A";
 562          }
 563  
 564          return $this->last_error;
 565      }
 566  
 567      /**
 568       * Set the last error message response from the SMTP server
 569       *
 570       * @param string $error The error message response
 571       */
 572  	function set_error($error)
 573      {
 574          $this->last_error = $error;
 575      }
 576  
 577      /**
 578       * Generate a CRAM-MD5 response from a server challenge.
 579       *
 580       * @param string $password Password.
 581       * @param string $challenge Challenge sent from SMTP server.
 582       *
 583       * @return string CRAM-MD5 response.
 584       */
 585  	function cram_md5_response($password, $challenge)
 586      {
 587          if(strlen($password) > 64)
 588          {
 589              $password = pack('H32', md5($password));
 590          }
 591  
 592          if(strlen($password) < 64)
 593          {
 594              $password = str_pad($password, 64, chr(0));
 595          }
 596  
 597          $k_ipad = substr($password, 0, 64) ^ str_repeat(chr(0x36), 64);
 598          $k_opad = substr($password, 0, 64) ^ str_repeat(chr(0x5C), 64);
 599  
 600          $inner = pack('H32', md5($k_ipad.$challenge));
 601  
 602          return md5($k_opad.$inner);
 603      }
 604  }


2005 - 2021 © MyBB.de | Alle Rechte vorbehalten! | Sponsor: netcup Cross-referenced by PHPXref