Skip to content

Commit

Permalink
[mms] Added an LMTP (RFC 2033) driver.
Browse files Browse the repository at this point in the history
[mms] Horde_Smtp#send() now returns recipient status information if at least one recipient was successfully accepted.
  • Loading branch information
slusarz committed May 14, 2014
1 parent 43a5ea9 commit 4414d7d
Show file tree
Hide file tree
Showing 5 changed files with 228 additions and 40 deletions.
19 changes: 19 additions & 0 deletions framework/Smtp/doc/Horde/Smtp/UPGRADING
Expand Up @@ -11,6 +11,25 @@
This lists the API changes between releases of the package.


Upgrading to 1.5.0
==================

- Horde_Smtp

- send()

Now returns an array of information on successful send to at least one
receipient.

- Horde_Smtp_Exception

Added the LOGIN_MISSINGEXTENSION error code.

- Horde_Smtp_Lmtp

Added the LMTP driver (RFC 2033).


Upgrading to 1.4.0
==================

Expand Down
139 changes: 101 additions & 38 deletions framework/Smtp/lib/Horde/Smtp.php
Expand Up @@ -66,7 +66,14 @@ class Horde_Smtp implements Serializable
protected $_debug;

/**
* The list of extensions.
* The hello command to use for extended SMTP support.
*
* @var string
*/
protected $_ehlo = 'EHLO';

/**
* The list of ESMTP extensions.
* If this value is null, we have not connected to server yet.
*
* @var array
Expand All @@ -80,6 +87,13 @@ class Horde_Smtp implements Serializable
*/
protected $_params = array();

/**
* List of required ESMTP extensions.
*
* @var array
*/
protected $_requiredExts = array();

/**
* Constructor.
*
Expand Down Expand Up @@ -364,48 +378,27 @@ public function login()
$this->_getResponse(220, 'logout');
}

$ehlo = $host = gethostname();
if ($host === false) {
$ehlo = $_SERVER['SERVER_ADDR'];
$host = 'localhost';
}

$this->_connection->write('EHLO ' . $ehlo);
try {
$resp = $this->_getResponse(250);

foreach ($resp as $val) {
$tmp = explode(' ', $val, 2);
$this->_extensions[$tmp[0]] = empty($tmp[1])
? true
: $tmp[1];
}
} catch (Horde_Smtp_Exception $e) {
switch ($e->getSmtpCode()) {
case 502:
// Old server - doesn't support EHLO
$this->_connection->write('HELO ' . $host);
try {
$this->_getResponse(250);
} catch (Horde_Smtp_Exception $e2) {
$this->logout();
throw $e;
}
$this->_extensions = array();
break;

default:
$this->logout();
throw $e;
}
}
$this->_hello();

if ($this->_startTls()) {
$this->_extensions = null;
$this->login();
return;
}

/* Check for required ESMTP extensions. */
foreach ($this->_requiredExts as $val) {
if (!$this->queryExtension($val)) {
throw new Horde_Smtp_Exception(
sprintf(
Horde_Smtp_Translation::r("Server does not support a necessary server extension: %s."),
$val
),
Horde_Smtp_Exception::LOGIN_MISSINGEXTENSION
);
}
}

/* If we reached this point and don't have a secure connection, then
* a secure connections is not available. */
if (!$this->isSecureConnection() &&
Expand Down Expand Up @@ -477,6 +470,14 @@ public function logout()
* DEFAULT: false
* </pre>
*
* @return array If no receipients were successful, a
* Horde_Smtp_Exception will be thrown. If at least one
* recipient was successful, an array with the following
* format is returned: (@since 1.5.0)
* - KEYS: Recipient addresses ($to addresses).
* - VALUES: Boolean true (if message was accepted for this recpieint)
* or a Horde_Smtp_Exception (if messages was not accepted).
*
* @throws Horde_Smtp_Exception
*/
public function send($from, $to, $data, array $opts = array())
Expand Down Expand Up @@ -512,7 +513,8 @@ public function send($from, $to, $data, array $opts = array())
$to = new Horde_Mail_Rfc822_List($to);
}

foreach ($to->bare_addresses_idn as $val) {
$recipients = $to->bare_addresses_idn;
foreach ($recipients as $val) {
$cmds[] = 'RCPT TO:<' . $val . '>';
}

Expand Down Expand Up @@ -579,7 +581,8 @@ public function send($from, $to, $data, array $opts = array())
stream_filter_remove($res);

$this->_connection->write('.');
$this->_getResponse(250, 'reset');

return $this->_processData($recipients);
}

/**
Expand Down Expand Up @@ -618,6 +621,50 @@ public function noop()

/* Internal methods. */

/**
* Send "Hello" command to the server.
*
* @throws Horde_Smtp_Exception
*/
protected function _hello()
{
$ehlo = $host = gethostname();
if ($host === false) {
$ehlo = $_SERVER['SERVER_ADDR'];
$host = 'localhost';
}

$this->_connection->write($this->_ehlo . ' ' . $ehlo);
try {
$resp = $this->_getResponse(250);

foreach ($resp as $val) {
$tmp = explode(' ', $val, 2);
$this->_extensions[$tmp[0]] = empty($tmp[1])
? true
: $tmp[1];
}
} catch (Horde_Smtp_Exception $e) {
switch ($e->getSmtpCode()) {
case 502:
// Old server - doesn't support EHLO
$this->_connection->write('HELO ' . $host);
try {
$this->_getResponse(250);
} catch (Horde_Smtp_Exception $e2) {
$this->logout();
throw $e;
}
$this->_extensions = array();
break;

default:
$this->logout();
throw $e;
}
}
}

/**
* Starts the TLS connection to the server, if necessary. See RFC 3207.
*
Expand Down Expand Up @@ -834,4 +881,20 @@ protected function _getResponse($code, $error = null)
throw $e;
}

/**
* Process the return from the DATA command.
*
* @see _send()
*
* @param array $recipients The list of message recipients.
*
* @return array See _send().
* @throws Horde_Smtp_Exception
*/
protected function _processData($recipients)
{
$this->_getResponse(250, 'reset');
return array_fill_keys($recipients, true);
}

}
4 changes: 4 additions & 0 deletions framework/Smtp/lib/Horde/Smtp/Exception.php
Expand Up @@ -63,6 +63,10 @@ class Horde_Smtp_Exception extends Horde_Exception
// Requires authentication.
const LOGIN_REQUIREAUTHENTICATION = 102;

// Server does not support necessary extension(s).
// @since 1.5.0
const LOGIN_MISSINGEXTENSION = 103;


/**
* Raw error message (in English).
Expand Down
88 changes: 88 additions & 0 deletions framework/Smtp/lib/Horde/Smtp/Lmtp.php
@@ -0,0 +1,88 @@
<?php
/**
* Copyright 2014 Horde LLC (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
*
* @category Horde
* @copyright 2014 Horde LLC
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
* @package Smtp
*/

/**
* An interface to an LMTP server (RFC 2033).
*
* @todo If/when CHUNKING is supported in base driver, RFC 2033 [4.3] (BDAT
* command) needs to be implemented here.
*
* @author Michael Slusarz <slusarz@horde.org>
* @category Horde
* @copyright 2014 Horde LLC
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
* @package Smtp
* @since 1.5.0
*/
class Horde_Smtp_Lmtp extends Horde_Smtp
{
/**
* LMTP only supports LHLO hello command (RFC 2033 [4.1]), not EHLO.
*
* HELO isn't supported, but HELO code in _hello() should not be reached
* since failure of LHLO command will result in 500 error code (instead
* of 502 necessary to fallback to HELO).
*/
protected $_ehlo = 'LHLO';

/**
* These extensions are required for LMTP (RFC 2033 [5]).
*/
protected $_requiredExts = array(
'ENHANCEDSTATUSCODES',
'PIPELINING'
);

/**
*/
public function __construct(array $params = array())
{
// LMTP MUST NOT be on port 25 (RFC 2033 [5]).
if (isset($params['port']) && ($params['port'] == 25)) {
throw new InvalidArgumentException(
'Cannot use port 25 for a LMTP server.'
);
}

parent::__construct($params);
}

/**
*/
protected function _processData($recipients)
{
/* RFC 2033 [4.2]: there is one response for each successful
* recipient, so need to iterate through the array. If no successful
* recipients found, throw an exception. */
$out = array();
$success = false;

foreach ($recipients as $val) {
try {
$this->_getResponse(250);
$out[$val] = $success = true;
} catch (Horde_Smtp_Exception $e) {
$out[$val] = $e;
}
}

if (!$success) {
$e = new Horde_Smtp_Exception('Sending to all recipients failed.');
$e->setSmtpCode(550);
throw $e;
}

return $out;
}

}
18 changes: 16 additions & 2 deletions framework/Smtp/package.xml
Expand Up @@ -10,7 +10,7 @@
<email>slusarz@horde.org</email>
<active>yes</active>
</lead>
<date>2014-04-03</date>
<date>2014-05-14</date>
<version>
<release>1.4.2</release>
<api>1.4.0</api>
Expand All @@ -21,6 +21,8 @@
</stability>
<license uri="http://www.horde.org/licenses/lgpl21">LGPL-2.1</license>
<notes>
* [mms] Horde_Smtp#send() now returns recipient status information if at least one recipient was successfully accepted.
* [mms] Added an LMTP (RFC 2033) driver.
* [jan] Add Hungarian translation (Andras Galos &lt;galosa@netinform.hu&gt;).
</notes>
<contents>
Expand All @@ -45,6 +47,7 @@
<file name="Connection.php" role="php" />
<file name="Debug.php" role="php" />
<file name="Exception.php" role="php" />
<file name="Lmtp.php" role="php" />
<file name="Password.php" role="php" />
<file name="Translation.php" role="php">
<tasks:replace from="@data_dir@" to="data_dir" type="pear-config" />
Expand All @@ -60,6 +63,12 @@
<file name="Horde_Smtp.po" role="data" />
</dir> <!-- /locale/da/LC_MESSAGES -->
</dir> <!-- /locale/da -->
<dir name="hu">
<dir name="LC_MESSAGES">
<file name="Horde_Smtp.mo" role="data" />
<file name="Horde_Smtp.po" role="data" />
</dir> <!-- /locale/hu/LC_MESSAGES -->
</dir> <!-- /locale/hu -->
<file name="Horde_Smtp.pot" role="data" />
</dir> <!-- /locale -->
<dir name="test">
Expand Down Expand Up @@ -155,13 +164,16 @@
<install as="Horde/Smtp/Connection.php" name="lib/Horde/Smtp/Connection.php" />
<install as="Horde/Smtp/Debug.php" name="lib/Horde/Smtp/Debug.php" />
<install as="Horde/Smtp/Exception.php" name="lib/Horde/Smtp/Exception.php" />
<install as="Horde/Smtp/Lmtp.php" name="lib/Horde/Smtp/Lmtp.php" />
<install as="Horde/Smtp/Password.php" name="lib/Horde/Smtp/Password.php" />
<install as="Horde/Smtp/Translation.php" name="lib/Horde/Smtp/Translation.php" />
<install as="Horde/Smtp/Filter/Data.php" name="lib/Horde/Smtp/Filter/Data.php" />
<install as="Horde/Smtp/Password/Xoauth2.php" name="lib/Horde/Smtp/Password/Xoauth2.php" />
<install as="locale/Horde_Smtp.pot" name="locale/Horde_Smtp.pot" />
<install as="locale/da/LC_MESSAGES/Horde_Smtp.mo" name="locale/da/LC_MESSAGES/Horde_Smtp.mo" />
<install as="locale/da/LC_MESSAGES/Horde_Smtp.po" name="locale/da/LC_MESSAGES/Horde_Smtp.po" />
<install as="locale/hu/LC_MESSAGES/Horde_Smtp.mo" name="locale/hu/LC_MESSAGES/Horde_Smtp.mo" />
<install as="locale/hu/LC_MESSAGES/Horde_Smtp.po" name="locale/hu/LC_MESSAGES/Horde_Smtp.po" />
<install as="Horde/Smtp/AllTests.php" name="test/Horde/Smtp/AllTests.php" />
<install as="Horde/Smtp/bootstrap.php" name="test/Horde/Smtp/bootstrap.php" />
<install as="Horde/Smtp/conf.php.dist" name="test/Horde/Smtp/conf.php.dist" />
Expand Down Expand Up @@ -360,9 +372,11 @@
<stability>
<release>stable</release>
<api>stable</api></stability>
<date>2014-04-03</date>
<date>2014-05-14</date>
<license uri="http://www.horde.org/licenses/lgpl21">LGPL-2.1</license>
<notes>
* [mms] Horde_Smtp#send() now returns recipient status information if at least one recipient was successfully accepted.
* [mms] Added an LMTP (RFC 2033) driver.
* [jan] Add Hungarian translation (Andras Galos &lt;galosa@netinform.hu&gt;).
</notes>
</release>
Expand Down

0 comments on commit 4414d7d

Please sign in to comment.