1<?php 2 3/* 4 * This file is part of SwiftMailer. 5 * (c) 2004-2009 Chris Corbyn 6 * 7 * For the full copyright and license information, please view the LICENSE 8 * file that was distributed with this source code. 9 */ 10 11/** 12 * Sends Messages over SMTP with ESMTP support. 13 * 14 * @author Chris Corbyn 15 */ 16class Swift_Transport_EsmtpTransport extends Swift_Transport_AbstractSmtpTransport implements Swift_Transport_SmtpAgent 17{ 18 /** 19 * ESMTP extension handlers. 20 * 21 * @var Swift_Transport_EsmtpHandler[] 22 */ 23 private $_handlers = array(); 24 25 /** 26 * ESMTP capabilities. 27 * 28 * @var string[] 29 */ 30 private $_capabilities = array(); 31 32 /** 33 * Connection buffer parameters. 34 * 35 * @var array 36 */ 37 private $_params = array( 38 'protocol' => 'tcp', 39 'host' => 'localhost', 40 'port' => 25, 41 'timeout' => 30, 42 'blocking' => 1, 43 'tls' => false, 44 'type' => Swift_Transport_IoBuffer::TYPE_SOCKET, 45 'stream_context_options' => array(), 46 ); 47 48 /** 49 * Creates a new EsmtpTransport using the given I/O buffer. 50 * 51 * @param Swift_Transport_IoBuffer $buf 52 * @param Swift_Transport_EsmtpHandler[] $extensionHandlers 53 * @param Swift_Events_EventDispatcher $dispatcher 54 */ 55 public function __construct(Swift_Transport_IoBuffer $buf, array $extensionHandlers, Swift_Events_EventDispatcher $dispatcher) 56 { 57 parent::__construct($buf, $dispatcher); 58 $this->setExtensionHandlers($extensionHandlers); 59 } 60 61 /** 62 * Set the host to connect to. 63 * 64 * @param string $host 65 * 66 * @return $this 67 */ 68 public function setHost($host) 69 { 70 $this->_params['host'] = $host; 71 72 return $this; 73 } 74 75 /** 76 * Get the host to connect to. 77 * 78 * @return string 79 */ 80 public function getHost() 81 { 82 return $this->_params['host']; 83 } 84 85 /** 86 * Set the port to connect to. 87 * 88 * @param int $port 89 * 90 * @return $this 91 */ 92 public function setPort($port) 93 { 94 $this->_params['port'] = (int) $port; 95 96 return $this; 97 } 98 99 /** 100 * Get the port to connect to. 101 * 102 * @return int 103 */ 104 public function getPort() 105 { 106 return $this->_params['port']; 107 } 108 109 /** 110 * Set the connection timeout. 111 * 112 * @param int $timeout seconds 113 * 114 * @return $this 115 */ 116 public function setTimeout($timeout) 117 { 118 $this->_params['timeout'] = (int) $timeout; 119 $this->_buffer->setParam('timeout', (int) $timeout); 120 121 return $this; 122 } 123 124 /** 125 * Get the connection timeout. 126 * 127 * @return int 128 */ 129 public function getTimeout() 130 { 131 return $this->_params['timeout']; 132 } 133 134 /** 135 * Set the encryption type (tls or ssl). 136 * 137 * @param string $encryption 138 * 139 * @return $this 140 */ 141 public function setEncryption($encryption) 142 { 143 $encryption = strtolower($encryption); 144 if ('tls' == $encryption) { 145 $this->_params['protocol'] = 'tcp'; 146 $this->_params['tls'] = true; 147 } else { 148 $this->_params['protocol'] = $encryption; 149 $this->_params['tls'] = false; 150 } 151 152 return $this; 153 } 154 155 /** 156 * Get the encryption type. 157 * 158 * @return string 159 */ 160 public function getEncryption() 161 { 162 return $this->_params['tls'] ? 'tls' : $this->_params['protocol']; 163 } 164 165 /** 166 * Sets the stream context options. 167 * 168 * @param array $options 169 * 170 * @return $this 171 */ 172 public function setStreamOptions($options) 173 { 174 $this->_params['stream_context_options'] = $options; 175 176 return $this; 177 } 178 179 /** 180 * Returns the stream context options. 181 * 182 * @return array 183 */ 184 public function getStreamOptions() 185 { 186 return $this->_params['stream_context_options']; 187 } 188 189 /** 190 * Sets the source IP. 191 * 192 * @param string $source 193 * 194 * @return $this 195 */ 196 public function setSourceIp($source) 197 { 198 $this->_params['sourceIp'] = $source; 199 200 return $this; 201 } 202 203 /** 204 * Returns the IP used to connect to the destination. 205 * 206 * @return string 207 */ 208 public function getSourceIp() 209 { 210 return isset($this->_params['sourceIp']) ? $this->_params['sourceIp'] : null; 211 } 212 213 /** 214 * Set ESMTP extension handlers. 215 * 216 * @param Swift_Transport_EsmtpHandler[] $handlers 217 * 218 * @return $this 219 */ 220 public function setExtensionHandlers(array $handlers) 221 { 222 $assoc = array(); 223 foreach ($handlers as $handler) { 224 $assoc[$handler->getHandledKeyword()] = $handler; 225 } 226 227 @uasort($assoc, array($this, '_sortHandlers')); 228 $this->_handlers = $assoc; 229 $this->_setHandlerParams(); 230 231 return $this; 232 } 233 234 /** 235 * Get ESMTP extension handlers. 236 * 237 * @return Swift_Transport_EsmtpHandler[] 238 */ 239 public function getExtensionHandlers() 240 { 241 return array_values($this->_handlers); 242 } 243 244 /** 245 * Run a command against the buffer, expecting the given response codes. 246 * 247 * If no response codes are given, the response will not be validated. 248 * If codes are given, an exception will be thrown on an invalid response. 249 * 250 * @param string $command 251 * @param int[] $codes 252 * @param string[] $failures An array of failures by-reference 253 * 254 * @return string 255 */ 256 public function executeCommand($command, $codes = array(), &$failures = null) 257 { 258 $failures = (array) $failures; 259 $stopSignal = false; 260 $response = null; 261 foreach ($this->_getActiveHandlers() as $handler) { 262 $response = $handler->onCommand( 263 $this, $command, $codes, $failures, $stopSignal 264 ); 265 if ($stopSignal) { 266 return $response; 267 } 268 } 269 270 return parent::executeCommand($command, $codes, $failures); 271 } 272 273 /** Mixin handling method for ESMTP handlers */ 274 public function __call($method, $args) 275 { 276 foreach ($this->_handlers as $handler) { 277 if (in_array(strtolower($method), 278 array_map('strtolower', (array) $handler->exposeMixinMethods()) 279 )) { 280 $return = call_user_func_array(array($handler, $method), $args); 281 // Allow fluid method calls 282 if (null === $return && substr($method, 0, 3) == 'set') { 283 return $this; 284 } else { 285 return $return; 286 } 287 } 288 } 289 trigger_error('Call to undefined method '.$method, E_USER_ERROR); 290 } 291 292 /** Get the params to initialize the buffer */ 293 protected function _getBufferParams() 294 { 295 return $this->_params; 296 } 297 298 /** Overridden to perform EHLO instead */ 299 protected function _doHeloCommand() 300 { 301 try { 302 $response = $this->executeCommand( 303 sprintf("EHLO %s\r\n", $this->_domain), array(250) 304 ); 305 } catch (Swift_TransportException $e) { 306 return parent::_doHeloCommand(); 307 } 308 309 if ($this->_params['tls']) { 310 try { 311 $this->executeCommand("STARTTLS\r\n", array(220)); 312 313 if (!$this->_buffer->startTLS()) { 314 throw new Swift_TransportException('Unable to connect with TLS encryption'); 315 } 316 317 try { 318 $response = $this->executeCommand( 319 sprintf("EHLO %s\r\n", $this->_domain), array(250) 320 ); 321 } catch (Swift_TransportException $e) { 322 return parent::_doHeloCommand(); 323 } 324 } catch (Swift_TransportException $e) { 325 $this->_throwException($e); 326 } 327 } 328 329 $this->_capabilities = $this->_getCapabilities($response); 330 $this->_setHandlerParams(); 331 foreach ($this->_getActiveHandlers() as $handler) { 332 $handler->afterEhlo($this); 333 } 334 } 335 336 /** Overridden to add Extension support */ 337 protected function _doMailFromCommand($address) 338 { 339 $handlers = $this->_getActiveHandlers(); 340 $params = array(); 341 foreach ($handlers as $handler) { 342 $params = array_merge($params, (array) $handler->getMailParams()); 343 } 344 $paramStr = !empty($params) ? ' '.implode(' ', $params) : ''; 345 $this->executeCommand( 346 sprintf("MAIL FROM:<%s>%s\r\n", $address, $paramStr), array(250) 347 ); 348 } 349 350 /** Overridden to add Extension support */ 351 protected function _doRcptToCommand($address) 352 { 353 $handlers = $this->_getActiveHandlers(); 354 $params = array(); 355 foreach ($handlers as $handler) { 356 $params = array_merge($params, (array) $handler->getRcptParams()); 357 } 358 $paramStr = !empty($params) ? ' '.implode(' ', $params) : ''; 359 $this->executeCommand( 360 sprintf("RCPT TO:<%s>%s\r\n", $address, $paramStr), array(250, 251, 252) 361 ); 362 } 363 364 /** Determine ESMTP capabilities by function group */ 365 private function _getCapabilities($ehloResponse) 366 { 367 $capabilities = array(); 368 $ehloResponse = trim($ehloResponse); 369 $lines = explode("\r\n", $ehloResponse); 370 array_shift($lines); 371 foreach ($lines as $line) { 372 if (preg_match('/^[0-9]{3}[ -]([A-Z0-9-]+)((?:[ =].*)?)$/Di', $line, $matches)) { 373 $keyword = strtoupper($matches[1]); 374 $paramStr = strtoupper(ltrim($matches[2], ' =')); 375 $params = !empty($paramStr) ? explode(' ', $paramStr) : array(); 376 $capabilities[$keyword] = $params; 377 } 378 } 379 380 return $capabilities; 381 } 382 383 /** Set parameters which are used by each extension handler */ 384 private function _setHandlerParams() 385 { 386 foreach ($this->_handlers as $keyword => $handler) { 387 if (array_key_exists($keyword, $this->_capabilities)) { 388 $handler->setKeywordParams($this->_capabilities[$keyword]); 389 } 390 } 391 } 392 393 /** Get ESMTP handlers which are currently ok to use */ 394 private function _getActiveHandlers() 395 { 396 $handlers = array(); 397 foreach ($this->_handlers as $keyword => $handler) { 398 if (array_key_exists($keyword, $this->_capabilities)) { 399 $handlers[] = $handler; 400 } 401 } 402 403 return $handlers; 404 } 405 406 /** Custom sort for extension handler ordering */ 407 private function _sortHandlers($a, $b) 408 { 409 return $a->getPriorityOver($b->getHandledKeyword()); 410 } 411} 412