Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide imap auth interface for plugins #9418

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion program/actions/mail/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public function run($args = [])
// set main env variables, labels and page title
if (empty($rcmail->action) || $rcmail->action == 'list') {
// connect to storage server and trigger error on failure
$rcmail->storage_connect();
$rcmail->connect_storage_from_session();

$mbox_name = $rcmail->storage->get_folder();

Expand Down
78 changes: 16 additions & 62 deletions program/include/rcmail.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,6 @@ class rcmail extends rcube
private $address_books = [];
private $action_args = [];

public const ERROR_STORAGE = -2;
public const ERROR_INVALID_REQUEST = 1;
public const ERROR_INVALID_HOST = 2;
public const ERROR_COOKIES_DISABLED = 3;
public const ERROR_RATE_LIMIT = 4;

/**
* This implements the 'singleton' design pattern
*
Expand Down Expand Up @@ -648,14 +642,15 @@ public function session_init()
* Perform login to the mail server and to the webmail service.
* This will also create a new user entry if auto_create_user is configured.
*
* @param string $username Mail storage (IMAP) user name
* @param string $password Mail storage (IMAP) password
* @param string $host Mail storage (IMAP) host
* @param bool $cookiecheck Enables cookie check
* @param string $username Mail storage (IMAP) user name
* @param string $password Mail storage (IMAP) password
* @param string $host Mail storage (IMAP) host
* @param bool $cookiecheck Enables cookie check
* @param bool $just_connect Breaks after successful connect
*
* @return bool True on success, False on failure
*/
public function login($username, $password, $host = null, $cookiecheck = false)
public function login($username, $password, $host = null, $cookiecheck = false, $just_connect = false)
{
$this->login_error = null;

Expand Down Expand Up @@ -750,68 +745,27 @@ public function login($username, $password, $host = null, $cookiecheck = false)
$username = rcube_utils::idn_to_ascii($username);
}

// user already registered -> overwrite username
if ($user = rcube_user::query($username, $host)) {
$username = $user->data['username'];

// Brute-force prevention
if ($user->is_locked()) {
$this->login_error = self::ERROR_RATE_LIMIT;
return false;
}
}

$storage = $this->get_storage();

// try to log in
if (!$storage->connect($host, $username, $password, $port, $ssl)) {
if ($user) {
$user->failed_login();
}
$user = $this->storage_connect($storage, $host, $username, $password, $port, $ssl, $just_connect);

// Wait a second to slow down brute-force attacks (#1490549)
sleep(1);
if (!$user) {
return false;
}

// user already registered -> update user's record
if (is_object($user)) {
// update last login timestamp
$user->touch();
if (is_int($user) && $user == self::ERROR_RATE_LIMIT) {
$this->login_error = self::ERROR_RATE_LIMIT;
return false;
}
// create new system user
elseif ($this->config->get('auto_create_user')) {
// Temporarily set user email and password, so plugins can use it
// this way until we set it in session later. This is required e.g.
// by the user-specific LDAP operations from new_user_identity plugin.
$domain = $this->config->mail_domain($host);
$this->user_email = strpos($username, '@') ? $username : sprintf('%s@%s', $username, $domain);
$this->password = $password;

$user = rcube_user::create($username, $host);

$this->user_email = null;
$this->password = null;

if (!$user) {
self::raise_error([
'code' => 620,
'message' => 'Failed to create a user record. Maybe aborted by a plugin?',
], true, false);
}
} else {
self::raise_error([
'code' => 621,
'message' => "Access denied for new user {$username}. 'auto_create_user' is disabled",
], true, false);
// This is enough if just connecting
if ($just_connect) {
return true;
}

// login succeeded
if (is_object($user) && $user->ID) {
// Configure environment
$this->set_user($user);
$this->set_storage_prop();

if ($user->ID) {
// set session vars
$_SESSION['user_id'] = $user->ID;
$_SESSION['username'] = $user->data['username'];
Expand Down Expand Up @@ -1992,7 +1946,7 @@ public function html2text($html, $options = [])
*
* @return bool True on success, False on error
*/
public function storage_connect()
public function connect_storage_from_session()
{
$storage = $this->get_storage();

Expand Down
94 changes: 94 additions & 0 deletions program/lib/Roundcube/rcube.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ class rcube

public const DEBUG_LINE_LENGTH = 4096;

// Error codes
public const ERROR_STORAGE = -2;
public const ERROR_INVALID_REQUEST = 1;
public const ERROR_INVALID_HOST = 2;
public const ERROR_COOKIES_DISABLED = 3;
public const ERROR_RATE_LIMIT = 4;

/** @var rcube_config Stores instance of rcube_config */
public $config;

Expand Down Expand Up @@ -1862,6 +1869,93 @@ public function deliver_message($message, $from, $mailto, &$error,

return $sent;
}

/**
* Helper method to establish connection to an IMAP backend.
*
* @param rcube_storage $imap IMAP storage handler
* @param string $host IMAP host
* @param string $username IMAP username
* @param string $password IMAP password
* @param int $port IMAP port to connect to
* @param string $ssl SSL schema or false if plain connection
* @param array $imap_options Additional IMAP options
* @param bool $just_connect Breaks after successful connect
*
* @return rcube_user|int|null Return user object on success, null or error code on failure
*/
public function storage_connect($imap, $host, $username, $password, $port, $ssl, $imap_options = [], $just_connect = false)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to not have the $imap argument. We can call get_storage() in here. Then probably $imap_options should be gone too. BTW, you miss $imap_options in the storage_connect() call.

{
// user already registered -> overwrite username
if ($user = rcube_user::query($username, $host)) {
$username = $user->data['username'];

// Brute-force prevention
if ($user->is_locked()) {
return self::ERROR_RATE_LIMIT;
}
}

// enable proxy authentication
if (!empty($imap_options)) {
$imap->set_options($imap_options);
}

// try to log in
if (!$imap->connect($host, $username, $password, $port, $ssl)) {
if ($user) {
$user->failed_login();
}

// Wait a second to slow down brute-force attacks (#1490549)
sleep(1);
return null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to never return null from this method. I.e. return error code or object, here it could be ERROR_STORAGE. That would mean we probably need other codes to cover the cases below.

I'm not sure what to do with $just_connect=true and empty $user (but successful connection).

}

// Only set user if just wanting to connect.
// Note that for other scenarios user will also be set after successful login.
if (!$just_connect) {
// user already registered -> update user's record
if (is_object($user)) {
// update last login timestamp
$user->touch();
}
// create new system user
elseif ($this->config->get('auto_create_user')) {
// Temporarily set user email and password, so plugins can use it
// this way until we set it in session later. This is required e.g.
// by the user-specific LDAP operations from new_user_identity plugin.
$domain = $this->config->mail_domain($host);
$this->user_email = strpos($username, '@') ? $username : sprintf('%s@%s', $username, $domain);
$this->password = $password;

$user = rcube_user::create($username, $host);

$this->user_email = null;
$this->password = null;

if (!$user) {
self::raise_error([
'code' => 620,
'message' => 'Failed to create a user record. Maybe aborted by a plugin?',
], true, false);
}
} else {
self::raise_error([
'code' => 621,
'message' => "Access denied for new user {$username}. 'auto_create_user' is disabled",
], true, false);
}
}

if (is_object($user) && $user->ID) {
// Configure environment
$this->set_user($user);
$this->set_storage_prop();
}

return $user;
}
}

/**
Expand Down