- Add possibility to add SASL mechanisms for SMTP in smtp_connect hook (#1487937)

pull/1/head
alecpl 14 years ago
parent f4cfb1414a
commit 3875eb6813

@ -1,6 +1,7 @@
CHANGELOG Roundcube Webmail
===========================
- Add possibility to add SASL mechanisms for SMTP in smtp_connect hook (#1487937)
- Mark (with different color) folders with recent messages (#1486234)
- Fix possible infinite redirect on attachment preview (#1488199)
- Improved clickjacking protection for browsers which don't support X-Frame-Options headers

@ -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);

@ -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;
@ -634,6 +660,46 @@ class Net_SMTP
return true;
}
/**
* 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.
*
@ -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;

Loading…
Cancel
Save