1<?php 2 3/** 4 * Swift Mailer SMTP Connection component. 5 * Please read the LICENSE file 6 * @author Chris Corbyn <chris@w3style.co.uk> 7 * @package Swift_Connection 8 * @license GNU Lesser General Public License 9 */ 10 11require_once dirname(__FILE__) . "/../ClassLoader.php"; 12Swift_ClassLoader::load("Swift_ConnectionBase"); 13Swift_ClassLoader::load("Swift_Authenticator"); 14 15/** 16 * Swift SMTP Connection 17 * @package Swift_Connection 18 * @author Chris Corbyn <chris@w3style.co.uk> 19 */ 20class Swift_Connection_SMTP extends Swift_ConnectionBase 21{ 22 /** 23 * Constant for TLS connections 24 */ 25 const ENC_TLS = 2; 26 /** 27 * Constant for SSL connections 28 */ 29 const ENC_SSL = 4; 30 /** 31 * Constant for unencrypted connections 32 */ 33 const ENC_OFF = 8; 34 /** 35 * Constant for the default SMTP port 36 */ 37 const PORT_DEFAULT = 25; 38 /** 39 * Constant for the default secure SMTP port 40 */ 41 const PORT_SECURE = 465; 42 /** 43 * Constant for auto-detection of paramters 44 */ 45 const AUTO_DETECT = -2; 46 /** 47 * A connection handle 48 * @var resource 49 */ 50 protected $handle = null; 51 /** 52 * The remote port number 53 * @var int 54 */ 55 protected $port = null; 56 /** 57 * Encryption type to use 58 * @var int 59 */ 60 protected $encryption = null; 61 /** 62 * A connection timeout 63 * @var int 64 */ 65 protected $timeout = 15; 66 /** 67 * A username to authenticate with 68 * @var string 69 */ 70 protected $username = false; 71 /** 72 * A password to authenticate with 73 * @var string 74 */ 75 protected $password = false; 76 /** 77 * Loaded authentication mechanisms 78 * @var array 79 */ 80 protected $authenticators = array(); 81 /** 82 * Fsockopen() error codes. 83 * @var int 84 */ 85 protected $errno; 86 /** 87 * Fsockopen() error codes. 88 * @var string 89 */ 90 protected $errstr; 91 92 /** 93 * Constructor 94 * @param string The remote server to connect to 95 * @param int The remote port to connect to 96 * @param int The encryption level to use 97 */ 98 public function __construct($server="localhost", $port=null, $encryption=null) 99 { 100 $this->setServer($server); 101 $this->setEncryption($encryption); 102 $this->setPort($port); 103 } 104 /** 105 * Set the timeout to connect in seconds 106 * @param int Timeout to use 107 */ 108 public function setTimeout($time) 109 { 110 $this->timeout = (int) $time; 111 } 112 /** 113 * Get the timeout currently set for connecting 114 * @return int 115 */ 116 public function getTimeout() 117 { 118 return $this->timeout; 119 } 120 /** 121 * Set the remote server to connect to as a FQDN 122 * @param string Server name 123 */ 124 public function setServer($server) 125 { 126 if ($server == self::AUTO_DETECT) 127 { 128 $server = @ini_get("SMTP"); 129 if (!$server) $server = "localhost"; 130 } 131 $this->server = (string) $server; 132 } 133 /** 134 * Get the remote server name 135 * @return string 136 */ 137 public function getServer() 138 { 139 return $this->server; 140 } 141 /** 142 * Set the remote port number to connect to 143 * @param int Port number 144 */ 145 public function setPort($port) 146 { 147 if ($port == self::AUTO_DETECT) 148 { 149 $port = @ini_get("SMTP_PORT"); 150 } 151 if (!$port) $port = ($this->getEncryption() == self::ENC_OFF) ? self::PORT_DEFAULT : self::PORT_SECURE; 152 $this->port = (int) $port; 153 } 154 /** 155 * Get the remote port number currently used to connect 156 * @return int 157 */ 158 public function getPort() 159 { 160 return $this->port; 161 } 162 /** 163 * Provide a username for authentication 164 * @param string The username 165 */ 166 public function setUsername($user) 167 { 168 $this->setRequiresEHLO(true); 169 $this->username = $user; 170 } 171 /** 172 * Get the username for authentication 173 * @return string 174 */ 175 public function getUsername() 176 { 177 return $this->username; 178 } 179 /** 180 * Set the password for SMTP authentication 181 * @param string Password to use 182 */ 183 public function setPassword($pass) 184 { 185 $this->setRequiresEHLO(true); 186 $this->password = $pass; 187 } 188 /** 189 * Get the password for authentication 190 * @return string 191 */ 192 public function getPassword() 193 { 194 return $this->password; 195 } 196 /** 197 * Add an authentication mechanism to authenticate with 198 * @param Swift_Authenticator 199 */ 200 public function attachAuthenticator(Swift_Authenticator $auth) 201 { 202 $this->authenticators[$auth->getAuthExtensionName()] = $auth; 203 $log = Swift_LogContainer::getLog(); 204 if ($log->hasLevel(Swift_Log::LOG_EVERYTHING)) 205 { 206 $log->add("Authentication mechanism '" . $auth->getAuthExtensionName() . "' attached."); 207 } 208 } 209 /** 210 * Set the encryption level to use on the connection 211 * See the constants ENC_TLS, ENC_SSL and ENC_OFF 212 * NOTE: PHP needs to have been compiled with OpenSSL for SSL and TLS to work 213 * NOTE: Some PHP installations will not have the TLS stream wrapper 214 * @param int Level of encryption 215 */ 216 public function setEncryption($enc) 217 { 218 if (!$enc) $enc = self::ENC_OFF; 219 $this->encryption = (int) $enc; 220 } 221 /** 222 * Get the current encryption level used 223 * This method returns an integer corresponding to one of the constants ENC_TLS, ENC_SSL or ENC_OFF 224 * @return int 225 */ 226 public function getEncryption() 227 { 228 return $this->encryption; 229 } 230 /** 231 * Read a full response from the buffer 232 * inner !feof() patch provided by Christian Rodriguez: 233 * <a href="http://www.flyspray.org/">www.flyspray.org</a> 234 * @return string 235 * @throws Swift_ConnectionException Upon failure to read 236 */ 237 public function read() 238 { 239 if (!$this->handle) throw new Swift_ConnectionException( 240 "The SMTP connection is not alive and cannot be read from." . $this->smtpErrors()); 241 $ret = ""; 242 $line = 0; 243 while (!feof($this->handle)) 244 { 245 $line++; 246 stream_set_timeout($this->handle, $this->timeout); 247 $tmp = @fgets($this->handle); 248 if ($tmp === false && !feof($this->handle)) 249 { 250 throw new Swift_ConnectionException( 251 "There was a problem reading line " . $line . " of an SMTP response. The response so far was:<br />[" . $ret . 252 "]. It appears the connection has died without saying goodbye to us! Too many emails in one go perhaps?" . 253 $this->smtpErrors()); 254 } 255 $ret .= trim($tmp) . "\r\n"; 256 if ($tmp{3} == " ") break; 257 } 258 return $ret = substr($ret, 0, -2); 259 } 260 /** 261 * Write a command to the server (leave off trailing CRLF) 262 * @param string The command to send 263 * @throws Swift_ConnectionException Upon failure to write 264 */ 265 public function write($command, $end="\r\n") 266 { 267 if (!$this->handle) throw new Swift_ConnectionException( 268 "The SMTP connection is not alive and cannot be written to." . 269 $this->smtpErrors()); 270 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()); 271 } 272 /** 273 * Try to start the connection 274 * @throws Swift_ConnectionException Upon failure to start 275 */ 276 public function start() 277 { 278 if ($this->port === null) 279 { 280 switch ($this->encryption) 281 { 282 case self::ENC_TLS: case self::ENC_SSL: 283 $this->port = 465; 284 break; 285 case null: default: 286 $this->port = 25; 287 break; 288 } 289 } 290 291 $server = $this->server; 292 if ($this->encryption == self::ENC_TLS) $server = "tls://" . $server; 293 elseif ($this->encryption == self::ENC_SSL) $server = "ssl://" . $server; 294 295 $log = Swift_LogContainer::getLog(); 296 if ($log->hasLevel(Swift_log::LOG_EVERYTHING)) 297 { 298 $log->add("Trying to connect to SMTP server at '" . $server . ":" . $this->port); 299 } 300 301 if (!$this->handle = @fsockopen($server, $this->port, $errno, $errstr, $this->timeout)) 302 { 303 $error_msg = "The SMTP connection failed to start [" . $server . ":" . $this->port . "]: fsockopen returned Error Number " . $errno . " and Error String '" . $errstr . "'"; 304 if ($log->isEnabled()) 305 { 306 $log->add($error_msg, Swift_Log::ERROR); 307 } 308 $this->handle = null; 309 throw new Swift_ConnectionException($error_msg); 310 } 311 $this->errno =& $errno; 312 $this->errstr =& $errstr; 313 } 314 /** 315 * Get the smtp error string as recorded by fsockopen() 316 * @return string 317 */ 318 public function smtpErrors() 319 { 320 return " (fsockopen: " . $this->errstr . "#" . $this->errno . ") "; 321 } 322 /** 323 * Authenticate if required to do so 324 * @param Swift An instance of Swift 325 * @throws Swift_ConnectionException If authentication fails 326 */ 327 public function postConnect(Swift $instance) 328 { 329 if ($this->getUsername() && $this->getPassword()) 330 { 331 $this->runAuthenticators($this->getUsername(), $this->getPassword(), $instance); 332 } 333 } 334 /** 335 * Run each authenticator in turn an try for a successful login 336 * If none works, throw an exception 337 * @param string Username 338 * @param string Password 339 * @param Swift An instance of swift 340 * @throws Swift_ConnectionException Upon failure to authenticate 341 */ 342 public function runAuthenticators($user, $pass, Swift $swift) 343 { 344 $log = Swift_LogContainer::getLog(); 345 if ($log->hasLevel(Swift_Log::LOG_EVERYTHING)) 346 { 347 $log->add("Trying to authenticate with username '" . $user . "'."); 348 } 349 //Load in defaults 350 if (empty($this->authenticators)) 351 { 352 if ($log->hasLevel(Swift_Log::LOG_EVERYTHING)) 353 { 354 $log->add("No authenticators loaded; looking for defaults."); 355 } 356 $dir = dirname(__FILE__) . "/../Authenticator"; 357 $handle = opendir($dir); 358 while (false !== $file = readdir($handle)) 359 { 360 if (preg_match("/^[A-Za-z0-9-]+\\.php\$/", $file)) 361 { 362 $name = preg_replace('/[^a-zA-Z0-9]+/', '', substr($file, 0, -4)); 363 require_once $dir . "/" . $file; 364 $class = "Swift_Authenticator_" . $name; 365 $this->attachAuthenticator(new $class()); 366 } 367 } 368 closedir($handle); 369 } 370 371 $tried = 0; 372 $looks_supported = true; 373 374 //Allow everything we have if the server has the audacity not to help us out. 375 if (!$this->hasExtension("AUTH")) 376 { 377 if ($log->hasLevel(Swift_Log::LOG_EVERYTHING)) 378 { 379 $log->add("Server (perhaps wrongly) is not advertising AUTH... manually overriding."); 380 } 381 $looks_supported = false; 382 $this->setExtension("AUTH", array_keys($this->authenticators)); 383 } 384 385 foreach ($this->authenticators as $name => $obj) 386 { 387 //Server supports this authentication mechanism 388 if (in_array($name, $this->getAttributes("AUTH")) || $name{0} == "*") 389 { 390 $tried++; 391 if ($log->hasLevel(Swift_Log::LOG_EVERYTHING)) 392 { 393 $log->add("Trying '" . $name . "' authentication..."); 394 } 395 if ($this->authenticators[$name]->isAuthenticated($user, $pass, $swift)) 396 { 397 if ($log->hasLevel(Swift_Log::LOG_EVERYTHING)) 398 { 399 $log->add("Success! Authentication accepted."); 400 } 401 return true; 402 } 403 } 404 } 405 406 //Server doesn't support authentication 407 if (!$looks_supported && $tried == 0) 408 throw new Swift_ConnectionException("Authentication is not supported by the server but a username and password was given."); 409 410 if ($tried == 0) 411 throw new Swift_ConnectionException("No authentication mechanisms were tried since the server did not support any of the ones loaded. " . 412 "Loaded authenticators: [" . implode(", ", array_keys($this->authenticators)) . "]"); 413 else 414 throw new Swift_ConnectionException("Authentication failed using username '" . $user . "' and password '". str_repeat("*", strlen($pass)) . "'"); 415 } 416 /** 417 * Try to close the connection 418 * @throws Swift_ConnectionException Upon failure to close 419 */ 420 public function stop() 421 { 422 $log = Swift_LogContainer::getLog(); 423 if ($log->hasLevel(Swift_Log::LOG_EVERYTHING)) 424 { 425 $log->add("Closing down SMTP connection."); 426 } 427 if ($this->handle) 428 { 429 if (!fclose($this->handle)) 430 { 431 throw new Swift_ConnectionException("The SMTP connection could not be closed for an unknown reason." . $this->smtpErrors()); 432 } 433 $this->handle = null; 434 } 435 } 436 /** 437 * Check if the SMTP connection is alive 438 * @return boolean 439 */ 440 public function isAlive() 441 { 442 return ($this->handle !== null); 443 } 444 /** 445 * Destructor. 446 * Cleans up any open connections. 447 */ 448 public function __destruct() 449 { 450 $this->stop(); 451 } 452} 453