summaryrefslogtreecommitdiff
path: root/program
diff options
context:
space:
mode:
authoralecpl <alec@alec.pl>2011-11-24 10:54:59 +0000
committeralecpl <alec@alec.pl>2011-11-24 10:54:59 +0000
commit3875eb68139d878fba2f00bdcaae3c34ebb53da7 (patch)
treec4e8e20d5795b53c7bbb740025d0d9af85750405 /program
parentf4cfb1414ad85b70977fb83989ad906e061827bd (diff)
- Add possibility to add SASL mechanisms for SMTP in smtp_connect hook (#1487937)
Diffstat (limited to 'program')
-rw-r--r--program/include/rcube_smtp.php15
-rw-r--r--program/lib/Net/SMTP.php167
2 files changed, 134 insertions, 48 deletions
diff --git a/program/include/rcube_smtp.php b/program/include/rcube_smtp.php
index 5c2dd92d2..e1a6f3af0 100644
--- a/program/include/rcube_smtp.php
+++ b/program/include/rcube_smtp.php
@@ -50,13 +50,13 @@ class rcube_smtp
public function connect($host=null, $port=null, $user=null, $pass=null)
{
$RCMAIL = rcmail::get_instance();
-
+
// disconnect/destroy $this->conn
$this->disconnect();
-
+
// reset error/response var
$this->error = $this->response = null;
-
+
// let plugins alter smtp connection config
$CONFIG = $RCMAIL->plugins->exec_hook('smtp_connect', array(
'smtp_server' => $host ? $host : $RCMAIL->config->get('smtp_server'),
@@ -68,6 +68,7 @@ class rcube_smtp
'smtp_auth_type' => $RCMAIL->config->get('smtp_auth_type'),
'smtp_helo_host' => $RCMAIL->config->get('smtp_helo_host'),
'smtp_timeout' => $RCMAIL->config->get('smtp_timeout'),
+ 'smtp_auth_callbacks' => array(),
));
$smtp_host = rcube_parse_host($CONFIG['smtp_server']);
@@ -108,6 +109,14 @@ class rcube_smtp
if ($RCMAIL->config->get('smtp_debug'))
$this->conn->setDebug(true, array($this, 'debug_handler'));
+ // register authentication methods
+ if (!empty($CONFIG['smtp_auth_callbacks']) && method_exists($this->conn, 'setAuthMethod')) {
+ foreach ($CONFIG['smtp_auth_callbacks'] as $callback) {
+ $this->conn->setAuthMethod($callback['name'], $callback['function'],
+ isset($callback['prepend']) ? $callback['prepend'] : true);
+ }
+ }
+
// try to connect to server and exit on failure
$result = $this->conn->connect($smtp_timeout);
diff --git a/program/lib/Net/SMTP.php b/program/lib/Net/SMTP.php
index 0463758b3..4e04f9191 100644
--- a/program/lib/Net/SMTP.php
+++ b/program/lib/Net/SMTP.php
@@ -62,7 +62,7 @@ class Net_SMTP
* @var array
* @access public
*/
- var $auth_methods = array('DIGEST-MD5', 'CRAM-MD5', 'LOGIN', 'PLAIN');
+ var $auth_methods = array();
/**
* Use SMTP command pipelining (specified in RFC 2920) if the SMTP
@@ -187,15 +187,16 @@ class Net_SMTP
$this->_socket_options = $socket_options;
$this->_timeout = $timeout;
- /* Include the Auth_SASL package. If the package is not
- * available, we disable the authentication methods that
- * depend upon it. */
- if ((@include_once 'Auth/SASL.php') === false) {
- $pos = array_search('DIGEST-MD5', $this->auth_methods);
- unset($this->auth_methods[$pos]);
- $pos = array_search('CRAM-MD5', $this->auth_methods);
- unset($this->auth_methods[$pos]);
+ /* Include the Auth_SASL package. If the package is available, we
+ * enable the authentication methods that depend upon it. */
+ if ((@include_once 'Auth/SASL.php') === true) {
+ $this->setAuthMethod('CRAM-MD5', array($this, '_authCram_MD5'));
+ $this->setAuthMethod('DIGEST-MD5', array($this, '_authDigest_MD5'));
}
+
+ /* These standard authentication methods are always available. */
+ $this->setAuthMethod('LOGIN', array($this, '_authLogin'), false);
+ $this->setAuthMethod('PLAIN', array($this, '_authPlain'), false);
}
/**
@@ -250,7 +251,8 @@ class Net_SMTP
*
* @param string $data The string of data to send.
*
- * @return mixed True on success or a PEAR_Error object on failure.
+ * @return mixed The number of bytes that were actually written,
+ * or a PEAR_Error object on failure.
*
* @access private
* @since 1.1.0
@@ -259,13 +261,14 @@ class Net_SMTP
{
$this->_debug("Send: $data");
- $error = $this->_socket->write($data);
- if ($error === false || PEAR::isError($error)) {
- $msg = ($error) ? $error->getMessage() : "unknown error";
- return PEAR::raiseError("Failed to write to socket: $msg");
+ $result = $this->_socket->write($data);
+ if (!$result || PEAR::isError($result)) {
+ $msg = ($result) ? $result->getMessage() : "unknown error";
+ return PEAR::raiseError("Failed to write to socket: $msg",
+ null, PEAR_ERROR_RETURN);
}
- return true;
+ return $result;
}
/**
@@ -292,7 +295,8 @@ class Net_SMTP
}
if (strcspn($command, "\r\n") !== strlen($command)) {
- return PEAR::raiseError('Commands cannot contain newlines');
+ return PEAR::raiseError('Commands cannot contain newlines',
+ null, PEAR_ERROR_RETURN);
}
return $this->_send($command . "\r\n");
@@ -331,10 +335,11 @@ class Net_SMTP
while ($line = $this->_socket->readLine()) {
$this->_debug("Recv: $line");
- /* If we receive an empty line, the connection has been closed. */
+ /* If we receive an empty line, the connection was closed. */
if (empty($line)) {
$this->disconnect();
- return PEAR::raiseError('Connection was unexpectedly closed');
+ return PEAR::raiseError('Connection was closed',
+ null, PEAR_ERROR_RETURN);
}
/* Read the code and store the rest in the arguments array. */
@@ -366,7 +371,32 @@ class Net_SMTP
}
return PEAR::raiseError('Invalid response code received from server',
- $this->_code);
+ $this->_code, PEAR_ERROR_RETURN);
+ }
+
+ /**
+ * Issue an SMTP command and verify its response.
+ *
+ * @param string $command The SMTP command string or data.
+ * @param mixed $valid The set of valid response codes. These
+ * may be specified as an array of integer
+ * values or as a single integer value.
+ *
+ * @return mixed True on success or a PEAR_Error object on failure.
+ *
+ * @access public
+ * @since 1.6.0
+ */
+ function command($command, $valid)
+ {
+ if (PEAR::isError($error = $this->_put($command))) {
+ return $error;
+ }
+ if (PEAR::isError($error = $this->_parseResponse($valid))) {
+ return $error;
+ }
+
+ return true;
}
/**
@@ -499,7 +529,8 @@ class Net_SMTP
return $error;
}
if (PEAR::isError($this->_parseResponse(250))) {
- return PEAR::raiseError('HELO was not accepted: ', $this->_code);
+ return PEAR::raiseError('HELO was not accepted: ', $this->_code,
+ PEAR_ERROR_RETURN);
}
return true;
@@ -533,13 +564,14 @@ class Net_SMTP
{
$available_methods = explode(' ', $this->_esmtp['AUTH']);
- foreach ($this->auth_methods as $method) {
+ foreach ($this->auth_methods as $method => $callback) {
if (in_array($method, $available_methods)) {
return $method;
}
}
- return PEAR::raiseError('No supported authentication methods');
+ return PEAR::raiseError('No supported authentication methods',
+ null, PEAR_ERROR_RETURN);
}
/**
@@ -599,33 +631,27 @@ class Net_SMTP
}
} else {
$method = strtoupper($method);
- if (!in_array($method, $this->auth_methods)) {
+ if (!array_key_exists($method, $this->auth_methods)) {
return PEAR::raiseError("$method is not a supported authentication method");
}
}
- switch ($method) {
- case 'DIGEST-MD5':
- $result = $this->_authDigest_MD5($uid, $pwd, $authz);
- break;
-
- case 'CRAM-MD5':
- $result = $this->_authCRAM_MD5($uid, $pwd);
- break;
-
- case 'LOGIN':
- $result = $this->_authLogin($uid, $pwd);
- break;
-
- case 'PLAIN':
- $result = $this->_authPlain($uid, $pwd, $authz);
- break;
+ if (!isset($this->auth_methods[$method])) {
+ return PEAR::raiseError("$method is not a supported authentication method");
+ }
- default:
- $result = PEAR::raiseError("$method is not a supported authentication method");
- break;
+ if (!is_callable($this->auth_methods[$method], false)) {
+ return PEAR::raiseError("$method authentication method cannot be called");
}
+ if (is_array($this->auth_methods[$method])) {
+ list($object, $method) = $this->auth_methods[$method];
+ $result = $object->{$method}($uid, $pwd, $authz, $this);
+ } else {
+ $func = $this->auth_methods[$method];
+ $result = $func($uid, $pwd, $authz, $this);
+ }
+
/* If an error was encountered, return the PEAR_Error object. */
if (PEAR::isError($result)) {
return $result;
@@ -635,6 +661,46 @@ class Net_SMTP
}
/**
+ * Add a new authentication method.
+ *
+ * @param string The authentication method name (e.g. 'PLAIN')
+ * @param mixed The authentication callback (given as the name of a
+ * function or as an (object, method name) array).
+ * @param bool Should the new method be prepended to the list of
+ * available methods? This is the default behavior,
+ * giving the new method the highest priority.
+ *
+ * @return mixed True on success or a PEAR_Error object on failure.
+ *
+ * @access public
+ * @since 1.6.0
+ */
+ function setAuthMethod($name, $callback, $prepend = true)
+ {
+ if (!is_string($name)) {
+ return PEAR::raiseError('Method name is not a string');
+ }
+
+ if (!is_string($callback) && !is_array($callback)) {
+ return PEAR::raiseError('Method callback must be string or array');
+ }
+
+ if (is_array($callback)) {
+ if (!is_object($callback[0]) || !is_string($callback[1]))
+ return PEAR::raiseError('Bad mMethod callback array');
+ }
+
+ if ($prepend) {
+ $this->auth_methods = array_merge(array($name => $callback),
+ $this->auth_methods);
+ } else {
+ $this->auth_methods[$name] = $callback;
+ }
+
+ return true;
+ }
+
+ /**
* Authenticates the user using the DIGEST-MD5 method.
*
* @param string The userid to authenticate as.
@@ -691,13 +757,14 @@ class Net_SMTP
*
* @param string The userid to authenticate as.
* @param string The password to authenticate with.
+ * @param string The optional authorization proxy identifier.
*
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
* @access private
* @since 1.1.0
*/
- function _authCRAM_MD5($uid, $pwd)
+ function _authCRAM_MD5($uid, $pwd, $authz = '')
{
if (PEAR::isError($error = $this->_put('AUTH', 'CRAM-MD5'))) {
return $error;
@@ -730,13 +797,14 @@ class Net_SMTP
*
* @param string The userid to authenticate as.
* @param string The password to authenticate with.
+ * @param string The optional authorization proxy identifier.
*
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
* @access private
* @since 1.1.0
*/
- function _authLogin($uid, $pwd)
+ function _authLogin($uid, $pwd, $authz = '')
{
if (PEAR::isError($error = $this->_put('AUTH', 'LOGIN'))) {
return $error;
@@ -1012,7 +1080,16 @@ class Net_SMTP
/* Stream the contents of the file resource out over our socket
* connection, line by line. Each line must be run through the
* quoting routine. */
- while ($line = fgets($data, 1024)) {
+ while (strlen($line = fread($data, 8192)) > 0) {
+ /* If the last character is an newline, we need to grab the
+ * next character to check to see if it is a period. */
+ while (!feof($data)) {
+ $char = fread($data, 1);
+ $line .= $char;
+ if ($char != "\n") {
+ break;
+ }
+ }
$this->quotedata($line);
if (PEAR::isError($result = $this->_send($line))) {
return $result;