* * @package Joomla.Framework * @subpackage Client * @since 2.0 */ class JSmtp { /** * Carriage Return + Line Feed. * * @var string * @since 2.0 */ const CRLF = "\r\n"; /** * Host name to use for connecting to the server. * * @var string * @since 2.0 */ public $host = 'localhost'; /** * Port number to use for connecting to the server. * * @var integer * @since 2.0 */ public $port = 25; /** * Username to use for server authentication. * * @var string * @since 2.0 */ public $username; /** * Password to use for server authentication. * * @var string * @since 2.0 */ public $password; /** * Server connection resource. * * @var resource * @since 2.0 */ protected $_connection; /** * Timeout limit in seconds for the server connection. * * @var integer * @since 2.0 */ protected $_timeout = 30; /** * Server greeting response. * * @var string * @since 2.0 */ protected $_greeting; /** * Server response code. * * @var string * @since 2.0 */ protected $_responseCode; /** * Server response message. * * @var string * @since 2.0 */ protected $_responseMessage; /** * Client object constructor. * * @param array Array of configuration options for the client. * @return void * @since 2.0 */ public function __construct($options = array()) { // If a connection timeout is set, use it. if (!isset($options['timeout'])) { $this->_timeout = $options['timeout']; } } /** * Client object destructor. * * @return void * @since 2.0 */ public function __destruct() { if (is_resource($this->_connection)) { $this->_sendCmd('QUIT', 221); fclose($this->_connection); } } /** * Method to connect to a server. * * @param string The host name of the server for which to connect. * @param integer The port number for which to make a connection. * @return boolean True on success. * @since 2.0 */ public function connect($host = null, $port = null) { // If we are already connected return true. if ($this->isConnected()) { return true; } // Get the host name and port values from the class if not set explicitly. $host = (empty($host)) ? $this->host : $host; $port = (empty($port)) ? $this->port : $port; // Attempt to connect to the server. try { // Initialize variables. $errno = null; $err = null; $this->_connection = fsockopen($host, $port, $errno, $err, $this->_timeout); } catch (JException $e) { die((string)$e); } // Set the timeout for this connection. stream_set_timeout($this->_connection, $this->_timeout); // Check for welcome response. if (!$this->_checkResponse(array(220), array(421))) { return false; } return true; } /** * Method to determine if the object is connected to a server and not timed out. * * @return boolean True if connected. * @since 2.0 */ public function isConnected() { // Make sure the connection is a valid resource. if (is_resource($this->_connection)) { // Make sure the connection has not timed out. $meta = stream_get_meta_data($this->_connection); if (!$meta['timed_out']) { return true; } } return false; } /** * Method to send the hello (HELO) or extended hello (EHLO) command to the server. This not * only ensures that we are in the same state as the server but also gives us a greeting * with valuable information about the capabilities of the server. * * @param string Host name to use for client. * @return boolean True on success. * @since 2.0 * * @throws JException */ public function hello($host = null) { // Make sure we have a connection to the server and it is not timed out. if (!$this->isConnected()) { throw new JException('Not connected to server.', 0, E_WARNING); } // Determine the hostname to use for the command. if(empty($host)) { // Attempt to detect the hostname from the PHP interpreter. $host = (function_exists('gethostname')) ? gethostname() : php_uname('n'); // If we still don't have a defined host use 'localhost' as the default. if (empty($host)) { $host = 'localhost'; } } // First attempt the extended hello from RFC 2821. if (!$this->_sendCmd('EHLO '.$host, 250)) { // If the extended hello failed attempt the standard hello. if($this->_sendCmd('HELO '.$host, 250)) { return false; } } // Save the greeting in case we need it. $this->_greeting = $this->_responseMessage; return true; } /** * Initiate a TLS encrypted session with the server. * * Based on RFC 2487 * * @return boolean True on success. * @since 2.0 * * @throws JException */ public function starttls() { // Make sure we have a connection to the server and it is not timed out. if (!$this->isConnected()) { throw new JException('Not connected to server.', 0, E_WARNING); } // Send the Start TLS command. if (!$this->_sendCmd('STARTTLS', 220, array(454))) { return false; } // Begin encrypted connection. if(!stream_socket_enable_crypto($this->_connection, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { return false; } return true; } /** * Method to authenticate the user using standard username/password authentication. * * @param string The username to use for authentication. * @param string The password to use for authentication. * @return boolean True on success. * @since 2.0 * * @throws JException */ public function login($username = null, $password = null) { // Make sure we have a connection to the server and it is not timed out. if (!$this->isConnected()) { throw new JException('Not connected to server.', 0, E_WARNING); } // Get the username and password values from the class if not set explicitly. $username = (empty($username)) ? $this->username : $username; $password = (empty($username)) ? $this->password : $password; // Start the authentication process. if (!$this->_sendCmd('AUTH LOGIN', 334)) { return false; } // Send the username as a base64 encoded string. if (!$this->_sendCmd(base64_encode($username), 334)) { return false; } // Send the password as a base64 encoded string. if (!$this->_sendCmd(base64_encode($password), 235)) { return false; } return true; } /** * Method to quit the session and close the connection with the server. * * @return boolean True on success. * @since 2.0 */ public function quit() { if ($this->_sendCmd('QUIT', 221)) { fclose($this->_connection); return true; } return false; } /** * Send a command to the server and validate an expected response. * * @param string Command to send to the server. * @param mixed Valid response code or array of response codes. * @return boolean True on success. * @since 2.0 * * @throws JException */ protected function _sendCmd($cmd, $success = array(), $failure = array()) { // Make sure we have a connection to the server and it is not timed out. if (!$this->isConnected()) { throw new JException('Not connected to server.', 0, E_WARNING); } // Get the command terminated by CRLF and the command length. $command = $cmd.JSmtp::CRLF; $length = strlen($command); // Send the command to the server. if (!fwrite($this->_connection, $command, $length)) { return false; } return $this->_checkResponse($success, $failure); } /** * Verify the response code from the server. * * @param mixed Valid response code or array of response codes indicating success. * @param mixed Valid response code or array of response codes indicating failure. * @return boolean True on successful response. * @since 2.0 * * @throws JException */ protected function _checkResponse($success = array(), $failure = array()) { // Make sure we have a connection to the server and it is not timed out. if (!$this->isConnected()) { throw new JException('Not connected to server.', 0, E_WARNING); } // Make sure the valid responses are in array form. if (!is_array($success)) { $success = array($success); } if (!is_array($failure)) { $failure = array($failure); } // Get the response value from the server. $response = fgets($this->_connection, 4096); // Get the code and message from the response. $code = trim(substr($response, 0, 3)); $message = trim(substr($response, 4)); // If the response code is not numeric throw an exception. if (!is_numeric($code)) { throw new JException('Invalid server response.', 0, E_WARNING, $response); } // Check the response status indicator. if (in_array($code, $success)) { $this->_responseCode = (int) $code; $this->_responseMessage = $message; return true; } elseif (in_array($code, $failure)) { $this->_responseCode = (int) $code; $this->_responseMessage = $message; return false; } // The response was not valid. else { throw new JException('Invalid server response.', 0, E_WARNING, $response); } } }