<?php

  class wrap_smtp {
    private $_socket;
    private $_host;
    private $_username;
    private $_password;
    private $_log_handle;
    private $_last_response;

    function __construct($host, $port=25, $username='', $password='') {

      if ($port == 465) {
        $this->_host = "ssl://$host:$port";
      } else {
        $this->_host = "tcp://$host:$port";
      }

      $this->_username = $username;
      $this->_password = $password;

      $this->_log_handle = fopen(FS_DIR_STORAGE . 'logs/last_smtp.log', 'w');
    }

    function __destruct() {
      if (is_resource($this->_socket)) $this->disconnect();
    }

    public function connect() {

      $stream_context = $context = stream_context_create([
        'ssl' => [
          // set some SSL/TLS specific options
          'verify_peer' => false,
          'verify_peer_name' => false,
          'allow_self_signed' => true
        ],
      ]);

      fwrite($this->_log_handle, "Connecting to $this->_host ...\r\n");
      $this->_socket = stream_socket_client($this->_host, $errno, $errstr, 3, STREAM_CLIENT_CONNECT, $stream_context);

      if ($errno) throw new Exception('Could not connect to socket '. $this->_host .': '. $errstr);
      if (empty($this->_socket)) throw new Exception('Failed opening socket connection to '. $this->_host);

      stream_set_blocking($this->_socket, true);
      stream_set_timeout($this->_socket, 6);

      $this->read(220)
           ->write("EHLO {$_SERVER['SERVER_NAME']}\r\n", 250);

      if (!empty($this->_username)) {

        $auths = [];
        if (preg_match('#250.AUTH (.*?)\R#', $this->_last_response, $matches)) {
          $auths = explode(' ', $matches[1]);
        }

        switch (true) {

          case (in_array('CRAM-MD5', $auths));
            $this->write("AUTH CRAM-MD5\r\n", 334);
            $challenge = base64_decode(substr($this->_last_response, 4));
            $this->write(base64_encode($this->_username .' '. hash_hmac('md5', $challenge, $this->_password)) . "\r\n", 235);
            break;

          case (in_array('LOGIN', $auths)):
            $this->write("AUTH LOGIN\r\n", 334)
                 ->write(base64_encode($this->_username) . "\r\n", 334)
                 ->write(base64_encode($this->_password) . "\r\n", 235);
            break;

          case (in_array('PLAIN', $auths)):
            $this->write("AUTH PLAIN\r\n", 334)
                 ->write(base64_encode("\0" . $this->_username . "\0" . $this->_password) . "\r\n", 235);
            break;

          default:
            throw new Exception('No supported authentication methods ('. implode(', ', $auths).')');
        }
      }

      return $this;
    }

    public function disconnect() {

      if (!is_resource($this->_socket)) return;

      $this->write("QUIT\r\n");
      fwrite($this->_log_handle, "\r\n");

      fclose($this->_socket);
      fclose($this->_log_handle);

      return $this;
    }

    public function read($expected_response=null) {

      $response = $buffer = '';
      while (substr($buffer, 3, 1) != ' ') {
        if (!$buffer = fgets($this->_socket, 256)) throw new Exception('No response from socket');
        fwrite($this->_log_handle, "< $buffer");
        $response .= $buffer;
      }

      $this->_last_response = $response;

      if (substr($response, 0, 3) != $expected_response) {
        copy(FS_DIR_STORAGE . 'logs/last_smtp.log', FS_DIR_STORAGE . 'logs/last_smtp_error.log');
        throw new Exception('Unexpected socket response; '. $response);
      }

      return $this;
    }

    public function write($data, $expected_response=null) {

      fwrite($this->_log_handle, "> $data");
      $result = fwrite($this->_socket, $data);

      if ($expected_response !== null) {
        $this->read($expected_response);
      }

      return $this;
    }

    ###################################################################

    public function send($sender, $recipients, $data='') {

      if (!is_array($recipients)) $recipients = [$recipients];

      if (!is_resource($this->_socket)) $this->connect();

      $this->write("MAIL FROM: <$sender>\r\n", 250);

      foreach ($recipients as $recipient) {
        $this->write("RCPT TO: <$recipient>\r\n", 250);
      }

      $this->write("DATA\r\n", 354);

      // Split into segments of data to avoid exceeding the maximum line length
      $data_lines = preg_split('#\r\n?|\n#', $data);

      foreach ($data_lines as $line) {
        $line = rtrim($line, "\r\n");
        while (strlen($line) > 256) {
          $this->write(substr($line, 0, 256) . "\r\n");
          $line = substr($line, 256);
        }
        $this->write($line . "\r\n");
      }

      $this->write(".\r\n", 250);

      return true;
    }
  }
