3 * Zend Framework (http://framework.zend.com/)
5 * @link http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license http://framework.zend.com/license/new-bsd New BSD License
10 namespace Zend\Mail\Protocol
;
13 * SMTP implementation of Zend\Mail\Protocol\AbstractProtocol
15 * Minimum implementation according to RFC2821: EHLO, MAIL FROM, RCPT TO, DATA, RSET, NOOP, QUIT
17 class Smtp
extends AbstractProtocol
20 * The transport method for the socket
24 protected $transport = 'tcp';
28 * Indicates that a session is requested to be secure
36 * Indicates an smtp session has been started by the HELO command
40 protected $sess = false;
44 * Indicates an smtp AUTH has been issued and authenticated
48 protected $auth = false;
52 * Indicates a MAIL command has been issued
56 protected $mail = false;
60 * Indicates one or more RCTP commands have been issued
64 protected $rcpt = false;
68 * Indicates that DATA has been issued and sent
72 protected $data = null;
78 * The first argument may be an array of all options. If so, it must include
79 * the 'host' and 'port' keys in order to ensure that all required values
82 * @param string|array $host
83 * @param null|int $port
84 * @param null|array $config
85 * @throws Exception\InvalidArgumentException
87 public function __construct($host = '127.0.0.1', $port = null, array $config = null)
89 // Did we receive a configuration array?
90 if (is_array($host)) {
91 // Merge config array with principal array, if provided
92 if (is_array($config)) {
93 $config = array_replace_recursive($host, $config);
98 // Look for a host key; if none found, use default value
99 if (isset($config['host'])) {
100 $host = $config['host'];
105 // Look for a port key; if none found, use default value
106 if (isset($config['port'])) {
107 $port = $config['port'];
113 // If we don't have a config array, initialize it
114 if (null === $config) {
118 if (isset($config['ssl'])) {
119 switch (strtolower($config['ssl'])) {
121 $this->secure
= 'tls';
125 $this->transport
= 'ssl';
126 $this->secure
= 'ssl';
133 throw new Exception\
InvalidArgumentException($config['ssl'] . ' is unsupported SSL type');
138 // If no port has been specified then check the master PHP ini file. Defaults to 25 if the ini setting is null.
140 if (($port = ini_get('smtp_port')) == '') {
145 parent
::__construct($host, $port);
150 * Connect to the server with the parameters given in the constructor.
154 public function connect()
156 return $this->_connect($this->transport
. '://' . $this->host
. ':' . $this->port
);
161 * Initiate HELO/EHLO sequence and set flag to indicate valid smtp session
163 * @param string $host The client hostname or IP address (default: 127.0.0.1)
164 * @throws Exception\RuntimeException
166 public function helo($host = '127.0.0.1')
168 // Respect RFC 2821 and disallow HELO attempts if session is already initiated.
169 if ($this->sess
=== true) {
170 throw new Exception\
RuntimeException('Cannot issue HELO to existing session');
173 // Validate client hostname
174 if (!$this->validHost
->isValid($host)) {
175 throw new Exception\
RuntimeException(implode(', ', $this->validHost
->getMessages()));
178 // Initiate helo sequence
179 $this->_expect(220, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
182 // If a TLS session is required, commence negotiation
183 if ($this->secure
== 'tls') {
184 $this->_send('STARTTLS');
185 $this->_expect(220, 180);
186 if (!stream_socket_enable_crypto($this->socket
, true, STREAM_CRYPTO_METHOD_TLS_CLIENT
)) {
187 throw new Exception\
RuntimeException('Unable to connect via TLS');
192 $this->_startSession();
197 * Returns the perceived session status
201 public function hasSession()
207 * Send EHLO or HELO depending on capabilities of smtp host
209 * @param string $host The client hostname or IP address (default: 127.0.0.1)
210 * @throws \Exception|Exception\ExceptionInterface
212 protected function _ehlo($host)
214 // Support for older, less-compliant remote servers. Tries multiple attempts of EHLO or HELO.
216 $this->_send('EHLO ' . $host);
217 $this->_expect(250, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
218 } catch (Exception\ExceptionInterface
$e) {
219 $this->_send('HELO ' . $host);
220 $this->_expect(250, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
221 } catch (\Exception
$e) {
228 * Issues MAIL command
230 * @param string $from Sender mailbox
231 * @throws Exception\RuntimeException
233 public function mail($from)
235 if ($this->sess
!== true) {
236 throw new Exception\
RuntimeException('A valid session has not been started');
239 $this->_send('MAIL FROM:<' . $from . '>');
240 $this->_expect(250, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
242 // Set mail to true, clear recipients and any existing data flags as per 4.1.1.2 of RFC 2821
250 * Issues RCPT command
252 * @param string $to Receiver(s) mailbox
253 * @throws Exception\RuntimeException
255 public function rcpt($to)
258 if ($this->mail
!== true) {
259 throw new Exception\
RuntimeException('No sender reverse path has been supplied');
262 // Set rcpt to true, as per 4.1.1.3 of RFC 2821
263 $this->_send('RCPT TO:<' . $to . '>');
264 $this->_expect(array(250, 251), 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
270 * Issues DATA command
272 * @param string $data
273 * @throws Exception\RuntimeException
275 public function data($data)
277 // Ensure recipients have been set
278 if ($this->rcpt
!== true) { // Per RFC 2821 3.3 (page 18)
279 throw new Exception\
RuntimeException('No recipient forward path has been supplied');
282 $this->_send('DATA');
283 $this->_expect(354, 120); // Timeout set for 2 minutes as per RFC 2821 4.5.3.2
285 foreach (explode(self
::EOL
, $data) as $line) {
286 if (strpos($line, '.') === 0) {
287 // Escape lines prefixed with a '.'
294 $this->_expect(250, 600); // Timeout set for 10 minutes as per RFC 2821 4.5.3.2
300 * Issues the RSET command end validates answer
302 * Can be used to restore a clean smtp communication state when a transaction has been cancelled or commencing a new transaction.
305 public function rset()
307 $this->_send('RSET');
308 // MS ESMTP doesn't follow RFC, see [ZF-1377]
309 $this->_expect(array(250, 220));
318 * Issues the NOOP command end validates answer
320 * Not used by Zend\Mail, could be used to keep a connection alive or check if it is still open.
323 public function noop()
325 $this->_send('NOOP');
326 $this->_expect(250, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
331 * Issues the VRFY command end validates answer
333 * Not used by Zend\Mail.
335 * @param string $user User Name or eMail to verify
337 public function vrfy($user)
339 $this->_send('VRFY ' . $user);
340 $this->_expect(array(250, 251, 252), 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
345 * Issues the QUIT command and clears the current session
348 public function quit()
352 $this->_send('QUIT');
353 $this->_expect(221, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
354 $this->_stopSession();
360 * Default authentication method
362 * This default method is implemented by AUTH adapters to properly authenticate to a remote host.
364 * @throws Exception\RuntimeException
366 public function auth()
368 if ($this->auth
=== true) {
369 throw new Exception\
RuntimeException('Already authenticated for this session');
378 public function disconnect()
380 $this->_disconnect();
384 * Disconnect from remote host and free resource
386 protected function _disconnect()
388 // Make sure the session gets closed
390 parent
::_disconnect();
397 protected function _startSession()
407 protected function _stopSession()