<?php namespace HashOver;// Copyright (C) 2018-2021 Jacob Barkdull// This file is part of HashOver.//// HashOver is free software: you can redistribute it and/or modify// it under the terms of the GNU Affero General Public License as// published by the Free Software Foundation, either version 3 of the// License, or (at your option) any later version.//// HashOver is distributed in the hope that it will be useful,// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the// GNU Affero General Public License for more details.//// You should have received a copy of the GNU Affero General Public License// along with HashOver.  If not, see <http://www.gnu.org/licenses/>.class SMTP{	// Unix domain socket connection file pointer	protected $fp;	// Time until connection gives up	protected $timeout = 30;	// Local host to greet connection with	protected $localhost = 'localhost';	// Server authentication credentials	protected $host;	protected $port;	protected $crypto;	protected $auth;	protected $user;	protected $password;	// Email data to send	protected $to = array ();	protected $from = array ();	protected $reply = array ();	protected $subject;	protected $text;	protected $html;	// Type of content being sent	protected $type = 'text';	// Sets SMTP host server	public function setHost ($host)	{		$this->host = $host;	}	// Sets SMTP server port	public function setPort ($port)	{		$this->port = $port;	}	// Sets SMTP cryptography	public function setCrypto ($crypto)	{		$this->crypto = $crypto;	}	// Sets SMTP authentication	public function setAuth ($auth)	{		$this->auth = $auth;	}	// Sets SMTP server user	public function setUser ($user)	{		$this->user = $user;	}	// Sets SMTP server password	public function setPassword ($password)	{		$this->password = $password;	}	// Sets who we're sending email to	public function to ($email, $name = null)	{		$this->to['email'] = $email;		$this->to['name'] = $name;	}	// Sets where recipient can reply to	public function replyTo ($email, $name = null)	{		$this->reply['email'] = $email;		$this->reply['name'] = $name;	}	// Sets who email is coming from	public function from ($email, $name = null)	{		// Set "from" email address and name		$this->from['email'] = $email;		$this->from['name'] = $name;		// Set "reply-to" the same way		if (empty ($this->reply['email'])) {			$this->replyTo ($email, $name);		}	}	// Sets subject line	public function subject ($text)	{		$this->subject = strip_tags ($text);	}	// Makes content comply to RFC-821	protected function rfc ($content)	{		// Line ending styles to convert		$styles = array ("\r\n", "\r");		// Convert line endings to UNIX-style		$content = str_replace ($styles, "\n", $content);		// Wordwrap content to 998 characters		$content = wordwrap ($content, 998, "\n");		// Split content by lines		$lines = explode ("\n", $content);		// Initial output		$output = '';		// Run through lines		foreach ($lines as $line) {			// RFC 821 section 4.5.2			if (!empty ($line) and $line[0] === '.') {				$line = '.' . $line;			}			// Add line to output			$output .= $line . "\r\n";		}		// And return final output		return $output;	}	// Converts message to plain text	protected function plainText ($text)	{		// Strip HTML tags		$text = strip_tags ($text);		// Convert HTML entities to normal characters		$text = html_entity_decode ($text, ENT_COMPAT, 'UTF-8');		// Make text comply to RFC-821		$text = $this->rfc ($text);		// Encode text in quoted-printable format		$text = quoted_printable_encode ($text);		// And return text		return $text;	}	// Sets message body to plain text	public function text ($text)	{		// Set text property		$this->text = $this->plainText ($text);		// And set type to text		$this->type = 'text';	}	// Sets message body to HTML	public function html ($html)	{		// Conform HTML to comply with RFC-821		$this->html = $this->rfc ($html);		// Encode HTML in quoted-printable format		$this->html = quoted_printable_encode ($this->html);		// Set automatic text version of message		if (empty ($this->text)) {			$this->text = $this->plainText ($html);		}		// And set type to HTML		$this->type = 'html';	}	// Sets message body	public function body ($text, $html = false)	{		// Set body as HTML if told to		if ($html === true) {			return $this->html ($text);		}		// Otherwise, set body as plain text		return $this->text ($text);	}	// Gets connection response	protected function getResponse ()	{		// Initial response		$response = '';		// Get response in 4KB chunks		while ($data = @fgets ($this->fp, 4096)) {			// Add current lines to response			$response .= $data;			// End loop if 4th character is a space			if (isset ($data[3]) and $data[3] == ' ') {				break;			}		}		// And return response		return $response;	}	// Gets code from connection response	protected function getCode ()	{		// Get response code		$response = $this->getResponse ();		// Filter code from response		$code = substr ($response, 0, 3);		// And return code as integer		return (int) ($code);	}	// Sends request data to SMTP server	protected function request ($data)	{		fwrite ($this->fp, $data . "\r\n");	}	// Connects to server	protected function smtpConnect ()	{		// Prepend proper URL scheme if we're using SSL		if ($this->crypto === 'ssl') {			$this->host = 'ssl://' . $this->host;		}		// Check if stream sockets are available		if (function_exists ('stream_socket_client')) {			// Create a stream context			$socket_context = stream_context_create ();			// Open Unix domain stream connection			$this->fp = @stream_socket_client (				$this->host . ':' . $this->port,				$errno,				$errstr,				$this->timeout,				STREAM_CLIENT_CONNECT,				$socket_context			);		} else {			// Open Unix domain socket connection			$this->fp = @fsockopen (				$this->host,				$this->port,				$errno,				$errstr,				$this->timeout			);		}		// Return false if connection is not a resource		if (!is_resource ($this->fp)) {			return false;		}		// Return false if connection failed		if ($this->getCode () !== 220) {			return false;		}		// Decide greeting		$greeting = $this->auth ? 'EHLO' : 'HELO';		// Send greeting to server		$this->request ($greeting . ' ' . $this->localhost);		// Return false if greeting failed		if ($this->getCode () !== 250) {			return false;		}		// Check if we are using TLS		if ($this->crypto === 'tls') {			// If so, send TLS handshake to server			$this->request ('STARTTLS');			// Return false if TLS handshake failed			if ($this->getCode () !== 220) {				return false;			}			// Type of encryption of stream			$crypto_type = STREAM_CRYPTO_METHOD_TLS_CLIENT;			// PHP 5.6 backwards compatibility			if (defined ('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {				$crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;				$crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;			}			// Turn on encryption			stream_socket_enable_crypto ($this->fp, true, $crypto_type);			// Send greeting again			$this->request ($greeting . ' ' . $this->localhost);			// Return false if greeting failed			if ($this->getCode () !== 250) {				return false;			}		}		// Check if authorization is required		if ($this->auth === true) {			// If so, send request for login			$this->request ('AUTH LOGIN');			// Return false if login authentication failed			if ($this->getCode () !== 334) {				return false;			}			// Send user name for login			$this->request (base64_encode ($this->user));			// Return false if user name login failed			if ($this->getCode () !== 334) {				return false;			}			// Send password for login			$this->request (base64_encode ($this->password));			// Return false if password login failed			if ($this->getCode () !== 235) {				return false;			}		}		// Otherwise, return true		return true;	}	// Encodes given text as MIME "encoded word"	protected function encode ($text)	{		return mb_encode_mimeheader ($text);	}	// Converts to/from/reply-to to a formatted string	public function format (array $recipient)	{		// Check if a name was given		if (!empty ($recipient['name'])) {			// If so, get encoded name			$name = $this->encode ($recipient['name']);			// And construct email address in "name <email>" format			$address = $name . ' <' . $recipient['email'] . '>';		} else {			// If not, use email address as-is			$address = $recipient['email'];		}		// And return email address		return $address;	}	// Creates SMTP transport	protected function smtpTransport ()	{		// Initial transport headers		$headers = array ();		// Add recipient headers		$headers[] = 'MIME-Version: 1.0';		$headers[] = 'Content-Transfer-Encoding: 7bit';		$headers[] = 'To: ' . $this->format ($this->to);		$headers[] = 'From: ' . $this->format ($this->from);		$headers[] = 'Reply-To: ' . $this->format ($this->reply);		$headers[] = 'Subject: ' . $this->encode ($this->subject);		$headers[] = 'Date: ' . date ('r');		// Check if message type is text		if ($this->type === 'text') {			// If so, only add headers for text version			$headers[] = 'Content-Type: text/plain; charset="UTF-8"';			$headers[] = 'Content-Transfer-Encoding: quoted-printable';			$headers[] = '';			$headers[] = $this->text;		} else {			// If not, create unique boundary			$boundary = md5 (uniqid (time ()));			// Add multipart headers			$headers[] = 'Content-Type: multipart/alternative; boundary="' . $boundary . '"';			$headers[] = '';			$headers[] = 'This is a multi-part message in MIME format.';			// Start multipart boundary			$headers[] = '--' . $boundary;			// Add headers for text version			$headers[] = 'Content-Type: text/plain; charset="UTF-8"';			$headers[] = 'Content-Transfer-Encoding: quoted-printable';			$headers[] = '';			$headers[] = $this->text;			// Add another multipart boundary			$headers[] = '--' . $boundary;			// Add headers for HTML version			$headers[] = 'Content-Type: text/html; charset="UTF-8"';			$headers[] = 'Content-Transfer-Encoding: quoted-printable';			$headers[] = '';			$headers[] = $this->html;			// And end multipart boundary			$headers[] = '--' . $boundary . '--';		}		// Add final period to end message data		$headers[] = '.';		// Convert headers to string		$transport = implode ("\r\n", $headers);		// And return final headers for transport		return $transport;	}	// Sends full SMTP request	protected function smtpDeliver ()	{		// Send who email is coming from		$this->request ('MAIL FROM: <' . $this->from['email'] . '>');		// Return false if sender address failed		if ($this->getCode () !== 250) {			return false;		}		// Send recipient email address		$this->request ('RCPT TO: <' . $this->to['email'] . '>');		// Return false if recipient address failed		if ($this->getCode () !== 250) {			return false;		}		// Send intent to begin data		$this->request ('DATA');		// Return false if data intent failed		if ($this->getCode () !== 354) {			return false;		}		// Send message data		$this->request ($this->smtpTransport ());		// Return false if message data failed		if ($this->getCode () === 250) {			return false;		}		// Otherwise, return ture		return true;	}	// Disconnects from server	protected function smtpDisconnect ()	{		// Do nothing if connection is not a resource		if (!is_resource ($this->fp)) {			return;		}		// Send intent to close connection		$this->request ('QUIT');		// Ignore response		$this->getResponse ();		// And close connection		fclose ($this->fp);	}	// Sends an email	public function send ()	{		// Check if we can connect to server		if ($this->smtpConnect () === true) {			// If so, send email			$result = $this->smtpDeliver ();		} else {			// If not, assume failure			$result = false;		}		// Disconnect from server		$this->smtpDisconnect ();		// And return SMTP delivery result		return $result;	}}