* @package Swift_Connection * @license GNU Lesser General Public License */ require_once dirname(__FILE__) . "/../ClassLoader.php"; Swift_ClassLoader::load("Swift_ConnectionBase"); Swift_ClassLoader::load("Swift_Authenticator"); /** * Swift SMTP Connection * @package Swift_Connection * @author Chris Corbyn */ class Swift_Connection_SMTP extends Swift_ConnectionBase { /** * Constant for TLS connections */ const ENC_TLS = 2; /** * Constant for SSL connections */ const ENC_SSL = 4; /** * Constant for unencrypted connections */ const ENC_OFF = 8; /** * Constant for the default SMTP port */ const PORT_DEFAULT = 25; /** * Constant for the default secure SMTP port */ const PORT_SECURE = 465; /** * Constant for auto-detection of paramters */ const AUTO_DETECT = -2; /** * A connection handle * @var resource */ protected $handle = null; /** * The remote port number * @var int */ protected $port = null; /** * Encryption type to use * @var int */ protected $encryption = null; /** * A connection timeout * @var int */ protected $timeout = 15; /** * A username to authenticate with * @var string */ protected $username = false; /** * A password to authenticate with * @var string */ protected $password = false; /** * Loaded authentication mechanisms * @var array */ protected $authenticators = array(); /** * Fsockopen() error codes. * @var int */ protected $errno; /** * Fsockopen() error codes. * @var string */ protected $errstr; /** * Constructor * @param string The remote server to connect to * @param int The remote port to connect to * @param int The encryption level to use */ public function __construct($server="localhost", $port=null, $encryption=null) { $this->setServer($server); $this->setEncryption($encryption); $this->setPort($port); } /** * Set the timeout to connect in seconds * @param int Timeout to use */ public function setTimeout($time) { $this->timeout = (int) $time; } /** * Get the timeout currently set for connecting * @return int */ public function getTimeout() { return $this->timeout; } /** * Set the remote server to connect to as a FQDN * @param string Server name */ public function setServer($server) { if ($server == self::AUTO_DETECT) { $server = @ini_get("SMTP"); if (!$server) $server = "localhost"; } $this->server = (string) $server; } /** * Get the remote server name * @return string */ public function getServer() { return $this->server; } /** * Set the remote port number to connect to * @param int Port number */ public function setPort($port) { if ($port == self::AUTO_DETECT) { $port = @ini_get("SMTP_PORT"); } if (!$port) $port = ($this->getEncryption() == self::ENC_OFF) ? self::PORT_DEFAULT : self::PORT_SECURE; $this->port = (int) $port; } /** * Get the remote port number currently used to connect * @return int */ public function getPort() { return $this->port; } /** * Provide a username for authentication * @param string The username */ public function setUsername($user) { $this->setRequiresEHLO(true); $this->username = $user; } /** * Get the username for authentication * @return string */ public function getUsername() { return $this->username; } /** * Set the password for SMTP authentication * @param string Password to use */ public function setPassword($pass) { $this->setRequiresEHLO(true); $this->password = $pass; } /** * Get the password for authentication * @return string */ public function getPassword() { return $this->password; } /** * Add an authentication mechanism to authenticate with * @param Swift_Authenticator */ public function attachAuthenticator(Swift_Authenticator $auth) { $this->authenticators[$auth->getAuthExtensionName()] = $auth; $log = Swift_LogContainer::getLog(); if ($log->hasLevel(Swift_Log::LOG_EVERYTHING)) { $log->add("Authentication mechanism '" . $auth->getAuthExtensionName() . "' attached."); } } /** * Set the encryption level to use on the connection * See the constants ENC_TLS, ENC_SSL and ENC_OFF * NOTE: PHP needs to have been compiled with OpenSSL for SSL and TLS to work * NOTE: Some PHP installations will not have the TLS stream wrapper * @param int Level of encryption */ public function setEncryption($enc) { if (!$enc) $enc = self::ENC_OFF; $this->encryption = (int) $enc; } /** * Get the current encryption level used * This method returns an integer corresponding to one of the constants ENC_TLS, ENC_SSL or ENC_OFF * @return int */ public function getEncryption() { return $this->encryption; } /** * Read a full response from the buffer * inner !feof() patch provided by Christian Rodriguez: * www.flyspray.org * @return string * @throws Swift_ConnectionException Upon failure to read */ public function read() { if (!$this->handle) throw new Swift_ConnectionException( "The SMTP connection is not alive and cannot be read from." . $this->smtpErrors()); $ret = ""; $line = 0; while (!feof($this->handle)) { $line++; stream_set_timeout($this->handle, $this->timeout); $tmp = @fgets($this->handle); if ($tmp === false && !feof($this->handle)) { throw new Swift_ConnectionException( "There was a problem reading line " . $line . " of an SMTP response. The response so far was:
[" . $ret . "]. It appears the connection has died without saying goodbye to us! Too many emails in one go perhaps?" . $this->smtpErrors()); } $ret .= trim($tmp) . "\r\n"; if ($tmp{3} == " ") break; } return $ret = substr($ret, 0, -2); } /** * Write a command to the server (leave off trailing CRLF) * @param string The command to send * @throws Swift_ConnectionException Upon failure to write */ public function write($command, $end="\r\n") { if (!$this->handle) throw new Swift_ConnectionException( "The SMTP connection is not alive and cannot be written to." . $this->smtpErrors()); if (!@fwrite($this->handle, $command . $end) && !empty($command)) throw new Swift_ConnectionException("The SMTP connection did not allow the command '" . $command . "' to be sent." . $this->smtpErrors()); } /** * Try to start the connection * @throws Swift_ConnectionException Upon failure to start */ public function start() { if ($this->port === null) { switch ($this->encryption) { case self::ENC_TLS: case self::ENC_SSL: $this->port = 465; break; case null: default: $this->port = 25; break; } } $server = $this->server; if ($this->encryption == self::ENC_TLS) $server = "tls://" . $server; elseif ($this->encryption == self::ENC_SSL) $server = "ssl://" . $server; $log = Swift_LogContainer::getLog(); if ($log->hasLevel(Swift_log::LOG_EVERYTHING)) { $log->add("Trying to connect to SMTP server at '" . $server . ":" . $this->port); } if (!$this->handle = @fsockopen($server, $this->port, $errno, $errstr, $this->timeout)) { $error_msg = "The SMTP connection failed to start [" . $server . ":" . $this->port . "]: fsockopen returned Error Number " . $errno . " and Error String '" . $errstr . "'"; if ($log->isEnabled()) { $log->add($error_msg, Swift_Log::ERROR); } $this->handle = null; throw new Swift_ConnectionException($error_msg); } $this->errno =& $errno; $this->errstr =& $errstr; } /** * Get the smtp error string as recorded by fsockopen() * @return string */ public function smtpErrors() { return " (fsockopen: " . $this->errstr . "#" . $this->errno . ") "; } /** * Authenticate if required to do so * @param Swift An instance of Swift * @throws Swift_ConnectionException If authentication fails */ public function postConnect(Swift $instance) { if ($this->getUsername() && $this->getPassword()) { $this->runAuthenticators($this->getUsername(), $this->getPassword(), $instance); } } /** * Run each authenticator in turn an try for a successful login * If none works, throw an exception * @param string Username * @param string Password * @param Swift An instance of swift * @throws Swift_ConnectionException Upon failure to authenticate */ public function runAuthenticators($user, $pass, Swift $swift) { $log = Swift_LogContainer::getLog(); if ($log->hasLevel(Swift_Log::LOG_EVERYTHING)) { $log->add("Trying to authenticate with username '" . $user . "'."); } //Load in defaults if (empty($this->authenticators)) { if ($log->hasLevel(Swift_Log::LOG_EVERYTHING)) { $log->add("No authenticators loaded; looking for defaults."); } $dir = dirname(__FILE__) . "/../Authenticator"; $handle = opendir($dir); while (false !== $file = readdir($handle)) { if (preg_match("/^[A-Za-z0-9-]+\\.php\$/", $file)) { $name = preg_replace('/[^a-zA-Z0-9]+/', '', substr($file, 0, -4)); require_once $dir . "/" . $file; $class = "Swift_Authenticator_" . $name; $this->attachAuthenticator(new $class()); } } closedir($handle); } $tried = 0; $looks_supported = true; //Allow everything we have if the server has the audacity not to help us out. if (!$this->hasExtension("AUTH")) { if ($log->hasLevel(Swift_Log::LOG_EVERYTHING)) { $log->add("Server (perhaps wrongly) is not advertising AUTH... manually overriding."); } $looks_supported = false; $this->setExtension("AUTH", array_keys($this->authenticators)); } foreach ($this->authenticators as $name => $obj) { //Server supports this authentication mechanism if (in_array($name, $this->getAttributes("AUTH")) || $name{0} == "*") { $tried++; if ($log->hasLevel(Swift_Log::LOG_EVERYTHING)) { $log->add("Trying '" . $name . "' authentication..."); } if ($this->authenticators[$name]->isAuthenticated($user, $pass, $swift)) { if ($log->hasLevel(Swift_Log::LOG_EVERYTHING)) { $log->add("Success! Authentication accepted."); } return true; } } } //Server doesn't support authentication if (!$looks_supported && $tried == 0) throw new Swift_ConnectionException("Authentication is not supported by the server but a username and password was given."); if ($tried == 0) throw new Swift_ConnectionException("No authentication mechanisms were tried since the server did not support any of the ones loaded. " . "Loaded authenticators: [" . implode(", ", array_keys($this->authenticators)) . "]"); else throw new Swift_ConnectionException("Authentication failed using username '" . $user . "' and password '". str_repeat("*", strlen($pass)) . "'"); } /** * Try to close the connection * @throws Swift_ConnectionException Upon failure to close */ public function stop() { $log = Swift_LogContainer::getLog(); if ($log->hasLevel(Swift_Log::LOG_EVERYTHING)) { $log->add("Closing down SMTP connection."); } if ($this->handle) { if (!fclose($this->handle)) { throw new Swift_ConnectionException("The SMTP connection could not be closed for an unknown reason." . $this->smtpErrors()); } $this->handle = null; } } /** * Check if the SMTP connection is alive * @return boolean */ public function isAlive() { return ($this->handle !== null); } /** * Destructor. * Cleans up any open connections. */ public function __destruct() { $this->stop(); } }