Page MenuHomePhorge

No OneTemporary

diff --git a/plugins/kolab_2fa/kolab_2fa.php b/plugins/kolab_2fa/kolab_2fa.php
index fb1afdc8..9d415ce5 100644
--- a/plugins/kolab_2fa/kolab_2fa.php
+++ b/plugins/kolab_2fa/kolab_2fa.php
@@ -1,787 +1,790 @@
<?php
/**
* Kolab 2-Factor-Authentication plugin
*
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2015, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class kolab_2fa extends rcube_plugin
{
public $task = '(login|settings)';
protected $login_verified = null;
protected $login_factors = array();
protected $drivers = array();
protected $storage;
/**
* Plugin init
*/
public function init()
{
$this->load_config();
$this->add_hook('startup', array($this, 'startup'));
}
/**
* Startup hook
*/
public function startup($args)
{
$rcmail = rcmail::get_instance();
// register library namespace to autoloader
$loader = include(INSTALL_PATH . 'vendor/autoload.php');
$loader->set('Kolab2FA', array($this->home . '/lib'));
if ($args['task'] === 'login' && $this->api->output) {
$this->add_texts('localization/', false);
$this->add_hook('authenticate', array($this, 'authenticate'));
// process 2nd factor auth step after regular login
if ($args['action'] === 'plugin.kolab-2fa-login' /* || !empty($_SESSION['kolab_2fa_factors']) */) {
return $this->login_verify($args);
}
}
else if ($args['task'] === 'settings') {
$this->add_texts('localization/', !$this->api->output->ajax_call);
$this->add_hook('settings_actions', array($this, 'settings_actions'));
$this->register_action('plugin.kolab-2fa', array($this, 'settings_view'));
$this->register_action('plugin.kolab-2fa-data', array($this, 'settings_data'));
$this->register_action('plugin.kolab-2fa-save', array($this, 'settings_save'));
$this->register_action('plugin.kolab-2fa-verify', array($this, 'settings_verify'));
}
return $args;
}
/**
* Handler for 'authenticate' plugin hook.
*
* ATTENTION: needs to be called *after* kolab_auth::authenticate()
*/
public function authenticate($args)
{
// nothing to be done for me
if ($args['abort'] || $this->login_verified !== null) {
return $args;
}
$rcmail = rcmail::get_instance();
// parse $host URL
$a_host = parse_url($args['host']);
$hostname = $_SESSION['hostname'] = $a_host['host'] ?: $args['host'];
+ $username = !empty($_SESSION['kolab_auth_admin']) ? $_SESSION['kolab_auth_admin'] : $args['user'];
// Convert username to lowercase. Copied from rcmail::login()
$login_lc = $rcmail->config->get('login_lc', 2);
if ($login_lc) {
if ($login_lc == 2 || $login_lc === true) {
- $args['user'] = mb_strtolower($args['user']);
+ $username = mb_strtolower($username);
}
- else if (strpos($args['user'], '@')) {
+ else if (strpos($username, '@')) {
// lowercase domain name
- list($local, $domain) = explode('@', $args['user']);
- $args['user'] = $local . '@' . mb_strtolower($domain);
+ list($local, $domain) = explode('@', $username);
+ $username = $local . '@' . mb_strtolower($domain);
}
}
- // 1. find user record (and its prefs) before IMAP login
- if ($user = rcube_user::query($args['user'], $hostname)) {
- $rcmail->config->set_user_prefs($user->get_prefs());
- }
-
// 2a. let plugins provide the list of active authentication factors
$lookup = $rcmail->plugins->exec_hook('kolab_2fa_lookup', array(
- 'user' => $args['user'],
+ 'user' => $username,
'host' => $hostname,
- 'factors' => $rcmail->config->get('kolab_2fa_factors'),
+ 'factors' => null,
'check' => $rcmail->config->get('kolab_2fa_check', true),
));
+
if (isset($lookup['factors'])) {
$factors = (array)$lookup['factors'];
}
// 2b. check storage if this user has 2FA enabled
- else if ($lookup['check'] !== false && ($storage = $this->get_storage($args['user']))) {
+ else if ($lookup['check'] !== false && ($storage = $this->get_storage($username))) {
$factors = (array)$storage->enumerate();
}
if (count($factors) > 0) {
$args['abort'] = true;
$factors = array_unique($factors);
// 3. flag session for 2nd factor verification
$_SESSION['kolab_2fa_time'] = time();
$_SESSION['kolab_2fa_nonce'] = bin2hex(openssl_random_pseudo_bytes(32));
$_SESSION['kolab_2fa_factors'] = $factors;
$_SESSION['username'] = $args['user'];
$_SESSION['host'] = $args['host'];
$_SESSION['password'] = $rcmail->encrypt($args['pass']);
// 4. render to 2nd auth step
$this->login_step($factors);
}
return $args;
}
/**
* Handler for the additional login step requesting the 2FA verification code
*/
public function login_step($factors)
{
// replace handler for login form
$this->login_factors = array_values($factors);
$this->api->output->add_handler('loginform', array($this, 'auth_form'));
// focus the code input field on load
$this->api->output->add_script('$("input.kolab2facode").first().select();', 'docready');
$this->api->output->send('login');
}
/**
* Process the 2nd factor code verification form submission
*/
public function login_verify($args)
{
- $rcmail = rcmail::get_instance();
+ $this->login_verified = false;
- $time = $_SESSION['kolab_2fa_time'];
- $nonce = $_SESSION['kolab_2fa_nonce'];
- $factors = (array)$_SESSION['kolab_2fa_factors'];
+ $rcmail = rcmail::get_instance();
- $this->login_verified = false;
- $expired = $time < time() - $rcmail->config->get('kolab_2fa_timeout', 120);
+ $time = $_SESSION['kolab_2fa_time'];
+ $nonce = $_SESSION['kolab_2fa_nonce'];
+ $factors = (array)$_SESSION['kolab_2fa_factors'];
+ $expired = $time < time() - $rcmail->config->get('kolab_2fa_timeout', 120);
+ $username = !empty($_SESSION['kolab_auth_admin']) ? $_SESSION['kolab_auth_admin'] : $_SESSION['username'];
if (!empty($factors) && !empty($nonce) && !$expired) {
// TODO: check signature
// try to verify each configured factor
foreach ($factors as $factor) {
list($method) = explode(':', $factor, 2);
// verify the submitted code
$code = rcube_utils::get_input_value("_${nonce}_${method}", rcube_utils::INPUT_POST);
- $this->login_verified = $this->verify_factor_auth($factor, $code);
+ $this->login_verified = $this->verify_factor_auth($factor, $code, $username);
// accept first successful method
if ($this->login_verified) {
break;
}
}
}
if ($this->login_verified) {
// restore POST data from session
$_POST['_user'] = $_SESSION['username'];
$_POST['_host'] = $_SESSION['host'];
$_POST['_pass'] = $rcmail->decrypt($_SESSION['password']);
+
+ if ($_SESSION['kolab_auth_admin']) {
+ $_POST['_user'] = $_SESSION['kolab_auth_admin'];
+ $_POST['_loginas'] = $_SESSION['username'];
+ }
}
// proceed with regular login ...
$args['action'] = 'login';
// session data will be reset in index.php thus additional
// auth attempts with intercepted data will be rejected
// $rcmail->kill_session();
// we can't display any custom error messages on failed login
// but that's actually desired to expose as little information as possible
return $args;
}
-
+
/**
* Helper method to verify the given method/code tuple
*/
- protected function verify_factor_auth($method, $code)
+ protected function verify_factor_auth($method, $code, $username)
{
if (strlen($code) && ($driver = $this->get_driver($method))) {
// set properties from login
- $driver->username = $_SESSION['username'];
+ $driver->username = $username;
try {
// verify the submitted code
return $driver->verify($code, $_SESSION['kolab_2fa_time']);
}
catch (Exception $e) {
rcube::raise_error($e, true, false);
}
}
return false;
}
/**
* Render 2nd factor authentication form in place of the regular login form
*/
public function auth_form($attrib = array())
{
$form_name = !empty($attrib['form']) ? $attrib['form'] : 'form';
$nonce = $_SESSION['kolab_2fa_nonce'];
$methods = array_unique(array_map(function($factor) {
list($method, $id) = explode(':', $factor);
return $method;
},
$this->login_factors
));
// forward these values as the regular login screen would submit them
$input_task = new html_hiddenfield(array('name' => '_task', 'value' => 'login'));
$input_action = new html_hiddenfield(array('name' => '_action', 'value' => 'plugin.kolab-2fa-login'));
$input_tzone = new html_hiddenfield(array('name' => '_timezone', 'id' => 'rcmlogintz', 'value' => rcube_utils::get_input_value('_timezone', rcube_utils::INPUT_POST)));
$input_url = new html_hiddenfield(array('name' => '_url', 'id' => 'rcmloginurl', 'value' => rcube_utils::get_input_value('_url', rcube_utils::INPUT_POST)));
// create HTML table with two cols
$table = new html_table(array('cols' => 2));
$required = count($methods) > 1 ? null : 'required';
// render input for each configured auth method
foreach ($methods as $i => $method) {
if ($row++ > 0) {
$table->add(array('colspan' => 2, 'class' => 'title hint', 'style' => 'text-align:center'),
$this->gettext('or'));
}
$field_id = "rcmlogin2fa$method";
$input_code = new html_inputfield(array(
'name' => "_${nonce}_${method}",
'class' => 'kolab2facode',
'id' => $field_id,
'required' => $required,
'autocomplete' => 'off',
'data-icon' => 'key' // for Elastic
) + $attrib);
$table->add('title', html::label($field_id, html::quote($this->gettext($method))));
$table->add('input', $input_code->show(''));
}
$out = $input_task->show();
$out .= $input_action->show();
$out .= $input_tzone->show();
$out .= $input_url->show();
$out .= $table->show();
// add submit button
if (rcube_utils::get_boolean($attrib['submit'])) {
$out .= html::p('formbuttons', html::tag('button', array(
'type' => 'submit',
'id' => 'rcmloginsubmit',
'class' => 'button mainaction save',
), $this->gettext('continue'))
);
}
// surround html output with a form tag
if (empty($attrib['form'])) {
$out = $this->api->output->form_tag(array('name' => $form_name, 'method' => 'post'), $out);
}
return $out;
}
/**
* Load driver class for the given authentication factor
*
* @param string $factor Factor identifier (<method>:<id>)
* @return Kolab2FA\Driver\Base
*/
public function get_driver($factor)
{
list($method) = explode(':', $factor, 2);
$rcmail = rcmail::get_instance();
if ($this->drivers[$factor]) {
return $this->drivers[$factor];
}
$config = $rcmail->config->get('kolab_2fa_' . $method, array());
// use product name as "issuer""
if (empty($config['issuer'])) {
$config['issuer'] = $rcmail->config->get('product_name');
}
try {
// TODO: use external auth service if configured
$driver = \Kolab2FA\Driver\Base::factory($factor, $config);
// attach storage
$driver->storage = $this->get_storage();
if ($rcmail->user->ID) {
$driver->username = $rcmail->get_user_name();
}
$this->drivers[$factor] = $driver;
return $driver;
}
catch (Exception $e) {
$error = strval($e);
}
rcube::raise_error(array(
'code' => 600,
'type' => 'php',
'file' => __FILE__,
'line' => __LINE__,
'message' => $error),
true, false);
return false;
}
/**
* Getter for a storage instance singleton
*/
public function get_storage($for = null)
{
if (!isset($this->storage) || (!empty($for) && $this->storage->username !== $for)) {
$rcmail = rcmail::get_instance();
try {
$this->storage = \Kolab2FA\Storage\Base::factory(
$rcmail->config->get('kolab_2fa_storage', 'roundcube'),
$rcmail->config->get('kolab_2fa_storage_config', array())
);
$this->storage->set_username($for);
$this->storage->set_logger(new \Kolab2FA\Log\RcubeLogger());
// set user properties from active session
if (!empty($_SESSION['kolab_dn'])) {
$this->storage->userdn = $_SESSION['kolab_dn'];
}
}
catch (Exception $e) {
$this->storage = false;
rcube::raise_error(array(
'code' => 600,
'type' => 'php',
'file' => __FILE__,
'line' => __LINE__,
'message' => $error),
true, false);
}
}
return $this->storage;
}
/**
* Handler for 'settings_actions' hook
*/
public function settings_actions($args)
{
// register as settings action
$args['actions'][] = array(
'action' => 'plugin.kolab-2fa',
'class' => 'twofactorauth',
'label' => 'settingslist',
'title' => 'settingstitle',
'domain' => 'kolab_2fa',
);
return $args;
}
/**
* Handler for settings/plugin.kolab-2fa requests
*/
public function settings_view()
{
$this->register_handler('plugin.settingsform', array($this, 'settings_form'));
$this->register_handler('plugin.settingslist', array($this, 'settings_list'));
$this->register_handler('plugin.factoradder', array($this, 'settings_factoradder'));
$this->register_handler('plugin.highsecuritydialog', array($this, 'settings_highsecuritydialog'));
$this->include_script('kolab2fa.js');
$this->include_stylesheet($this->local_skin_path() . '/kolab2fa.css');
if ($this->check_secure_mode()) {
$this->api->output->set_env('session_secured', $_SESSION['kolab_2fa_secure_mode']);
}
$this->api->output->add_label('save','cancel');
$this->api->output->set_pagetitle($this->gettext('settingstitle'));
$this->api->output->send('kolab_2fa.config');
}
/**
* Render the menu to add another authentication factor
*/
public function settings_factoradder($attrib)
{
$rcmail = rcmail::get_instance();
$attrib['id'] = 'kolab2fa-add';
$select = new html_select($attrib);
$select->add($this->gettext('addfactor') . '...', '');
foreach ((array)$rcmail->config->get('kolab_2fa_drivers', array()) as $method) {
$select->add($this->gettext($method), $method);
}
return $select->show();
}
/**
* Render a list of active factor this user has configured
*/
public function settings_list($attrib = array())
{
$attrib['id'] = 'kolab2fa-factors';
$table = new html_table(array('cols' => 3));
$table->add_header('name', $this->gettext('factor'));
$table->add_header('created', $this->gettext('created'));
$table->add_header('actions', '');
return $table->show($attrib);
}
/**
* Render the settings form template object
*/
public function settings_form($attrib = array())
{
$rcmail = rcmail::get_instance();
$storage = $this->get_storage($rcmail->get_user_name());
$factors = $storage ? (array)$storage->enumerate() : array();
$drivers = (array)$rcmail->config->get('kolab_2fa_drivers', array());
$env_methods = array();
foreach ($drivers as $j => $method) {
$out .= $this->settings_factor($method, $attrib);
$env_methods[$method] = array(
'name' => $this->gettext($method),
'active' => 0,
);
}
$me = $this;
$factors = array_combine(
$factors,
array_map(function($id) use ($me, &$env_methods) {
$props = array('id' => $id);
if ($driver = $me->get_driver($id)) {
$props += $this->format_props($driver->props());
$props['method'] = $driver->method;
$props['name'] = $me->gettext($driver->method);
$env_methods[$driver->method]['active']++;
}
return $props;
}, $factors)
);
$this->api->output->set_env('kolab_2fa_methods', $env_methods);
$this->api->output->set_env('kolab_2fa_factors', !empty($factors) ? $factors : null);
return html::div(array('id' => 'kolab2fapropform'), $out);
}
/**
* Render the settings UI for the given method/driver
*/
protected function settings_factor($method, $attrib)
{
$out = '';
$rcmail = rcmail::get_instance();
$attrib += array('class' => 'propform');
if ($driver = $this->get_driver($method)) {
$table = new html_table(array('cols' => 2, 'class' => $attrib['class']));
foreach ($driver->props() as $field => $prop) {
if (!$prop['editable']) {
continue;
}
switch ($prop['type']) {
case 'boolean':
case 'checkbox':
$input = new html_checkbox(array('value' => '1'));
break;
case 'enum':
case 'select':
$input = new html_select(array('disabled' => $prop['readonly']));
$input->add(array_map(array($this, 'gettext'), $prop['options']), $prop['options']);
break;
default:
$input = new html_inputfield(array('size' => $prop['size'] ?: 30, 'disabled' => !$prop['editable']));
}
$explain_label = $field . 'explain' . $method;
$explain_html = $rcmail->text_exists($explain_label, 'kolab_2fa') ? html::div('explain form-text', $this->gettext($explain_label)) : '';
$field_id = 'rcmk2fa' . $method . $field;
$table->add('title', html::label($field_id, $this->gettext($field)));
$table->add(null, $input->show('', array('id' => $field_id, 'name' => "_prop[$field]")) . $explain_html);
}
// add row for displaying the QR code
if (method_exists($driver, 'get_provisioning_uri')) {
$table->add('title', $this->gettext('qrcode'));
$table->add(null,
html::div('explain form-text', $this->gettext("qrcodeexplain$method"))
. html::tag('img', array('src' => 'data:image/gif;base64,R0lGODlhDwAPAIAAAMDAwAAAACH5BAEAAAAALAAAAAAPAA8AQAINhI+py+0Po5y02otnAQA7', 'class' => 'qrcode', 'rel' => $method))
);
// add row for testing the factor
$field_id = 'rcmk2faverify' . $method;
$table->add('title', html::label($field_id, $this->gettext('verifycode')));
$table->add(null,
html::tag('input', array('type' => 'text', 'name' => '_verify_code', 'id' => $field_id, 'class' => 'k2fa-verify', 'size' => 20, 'required' => true)) .
html::div('explain form-text', $this->gettext("verifycodeexplain$method"))
);
}
$input_id = new html_hiddenfield(array('name' => '_prop[id]', 'value' => ''));
$out .= html::tag('form', array(
'method' => 'post',
'action' => '#',
'id' => 'kolab2fa-prop-' . $method,
'style' => 'display:none',
'class' => 'propform',
),
html::tag('fieldset', array(),
html::tag('legend', array(), $this->gettext($method)) .
html::div('factorprop', $table->show()) .
$input_id->show()
)
);
}
return $out;
}
/**
* Render the high-security-dialog content
*/
public function settings_highsecuritydialog($attrib = array())
{
$attrib += array('id' => 'kolab2fa-highsecuritydialog');
$field_id = 'rcmk2facode';
$input = new html_inputfield(array('name' => '_code', 'id' => $field_id, 'class' => 'verifycode', 'size' => 20));
$label = html::label(array('for' => $field_id, 'class' => 'col-form-label col-sm-4'), '$name');
return html::div($attrib,
html::div('explain form-text', $this->gettext('highsecuritydialog'))
. html::div('propform row form-group', $label . html::div('col-sm-8', $input->show('')))
);
}
/**
* Handler for settings/plugin.kolab-2fa-save requests
*/
public function settings_save()
{
$method = rcube_utils::get_input_value('_method', rcube_utils::INPUT_POST);
$data = @json_decode(rcube_utils::get_input_value('_data', rcube_utils::INPUT_POST), true);
$rcmail = rcmail::get_instance();
$storage = $this->get_storage($rcmail->get_user_name());
$success = false;
$errors = 0;
$save_data = array();
if ($driver = $this->get_driver($method)) {
if ($data === false) {
if ($this->check_secure_mode()) {
// remove method from active factors and clear stored settings
$success = $driver->clear();
}
else {
$errors++;
}
}
else {
// verify the submitted code before saving
$verify_code = rcube_utils::get_input_value('_verify_code', rcube_utils::INPUT_POST);
$timestamp = intval(rcube_utils::get_input_value('_timestamp', rcube_utils::INPUT_POST));
if (!empty($verify_code)) {
if (!$driver->verify($verify_code, $timestamp)) {
$this->api->output->command('plugin.verify_response', array(
'id' => $driver->id,
'method' => $driver->method,
'success' => false,
'message' => str_replace('$method', $this->gettext($driver->method), $this->gettext('codeverificationfailed'))
));
$this->api->output->send();
}
}
foreach ($data as $prop => $value) {
if (!$driver->set($prop, $value)) {
$errors++;
}
}
$driver->set('active', true);
}
// commit changes to the user properties
if (!$errors) {
if ($success = $driver->commit()) {
$save_data = $data !== false ? $this->format_props($driver->props()) : array();
}
else {
$errors++;
}
}
}
if ($success) {
$this->api->output->show_message($data === false ? $this->gettext('factorremovesuccess') : $this->gettext('factorsavesuccess'), 'confirmation');
$this->api->output->command('plugin.save_success', array(
'method' => $method,
'active' => $data !== false,
'id' => $driver->id) + $save_data);
}
else if ($errors) {
$this->api->output->show_message($this->gettext('factorsaveerror'), 'error');
$this->api->output->command('plugin.reset_form', $method);
}
$this->api->output->send();
}
/**
* Handler for settings/plugin.kolab-2fa-data requests
*/
public function settings_data()
{
$method = rcube_utils::get_input_value('_method', rcube_utils::INPUT_POST);
if ($driver = $this->get_driver($method)) {
$data = array('method' => $method, 'id' => $driver->id);
foreach ($driver->props(true) as $field => $prop) {
$data[$field] = $prop['text'] ?: $prop['value'];
}
// generate QR code for provisioning URI
if (method_exists($driver, 'get_provisioning_uri')) {
try {
$uri = $driver->get_provisioning_uri();
$qr = new Endroid\QrCode\QrCode();
$qr->setText($uri)
->setSize(240)
->setPadding(10)
->setErrorCorrection('high')
->setForegroundColor(array('r' => 0, 'g' => 0, 'b' => 0, 'a' => 0))
->setBackgroundColor(array('r' => 255, 'g' => 255, 'b' => 255, 'a' => 0));
$data['qrcode'] = base64_encode($qr->get());
}
catch (Exception $e) {
rcube::raise_error($e, true, false);
}
}
$this->api->output->command('plugin.render_data', $data);
}
$this->api->output->send();
}
/**
* Handler for settings/plugin.kolab-2fa-verify requests
*/
public function settings_verify()
{
$method = rcube_utils::get_input_value('_method', rcube_utils::INPUT_POST);
$timestamp = intval(rcube_utils::get_input_value('_timestamp', rcube_utils::INPUT_POST));
$success = false;
if ($driver = $this->get_driver($method)) {
$data = @json_decode(rcube_utils::get_input_value('_data', rcube_utils::INPUT_POST), true);
if (is_array($data)) {
foreach ($data as $key => $value) {
if ($value !== '******') {
$driver->$key = $value;
}
}
}
$success = $driver->verify(rcube_utils::get_input_value('_code', rcube_utils::INPUT_POST), $timestamp);
$method = $driver->method;
}
// put session into high-security mode
if ($success && !empty($_POST['_session'])) {
$_SESSION['kolab_2fa_secure_mode'] = time();
}
$this->api->output->command('plugin.verify_response', array(
'method' => $method,
'success' => $success,
'message' => str_replace('$method', $this->gettext($method),
$this->gettext($success ? 'codeverificationpassed' : 'codeverificationfailed'))
));
$this->api->output->send();
}
/**
*
*/
protected function format_props($props)
{
$rcmail = rcmail::get_instance();
$values = array();
foreach ($props as $key => $prop) {
switch ($prop['type']) {
case 'datetime':
$value = $rcmail->format_date($prop['value']);
break;
default:
$value = $prop['value'];
}
$values[$key] = $value;
}
return $values;
}
/**
*
*/
protected function check_secure_mode()
{
$valid = ($_SESSION['kolab_2fa_secure_mode'] && $_SESSION['kolab_2fa_secure_mode'] > time() - 180);
return $valid;
}
}
\ No newline at end of file
diff --git a/plugins/kolab_auth/kolab_auth.php b/plugins/kolab_auth/kolab_auth.php
index d10c792b..85f97da1 100644
--- a/plugins/kolab_auth/kolab_auth.php
+++ b/plugins/kolab_auth/kolab_auth.php
@@ -1,888 +1,890 @@
<?php
/**
* Kolab Authentication (based on ldap_authentication plugin)
*
* Authenticates on LDAP server, finds canonized authentication ID for IMAP
* and for new users creates identity based on LDAP information.
*
* Supports impersonate feature (login as another user). To use this feature
* imap_auth_type/smtp_auth_type must be set to DIGEST-MD5 or PLAIN.
*
* @version @package_version@
* @author Aleksander Machniak <machniak@kolabsys.com>
*
* Copyright (C) 2011-2013, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class kolab_auth extends rcube_plugin
{
static $ldap;
private $username;
private $data = array();
public function init()
{
$rcmail = rcube::get_instance();
$this->load_config();
$this->require_plugin('libkolab');
$this->add_hook('authenticate', array($this, 'authenticate'));
$this->add_hook('startup', array($this, 'startup'));
$this->add_hook('ready', array($this, 'ready'));
$this->add_hook('user_create', array($this, 'user_create'));
// Hook for password change
$this->add_hook('password_ldap_bind', array($this, 'password_ldap_bind'));
// Hooks related to "Login As" feature
$this->add_hook('template_object_loginform', array($this, 'login_form'));
$this->add_hook('storage_connect', array($this, 'imap_connect'));
$this->add_hook('managesieve_connect', array($this, 'imap_connect'));
$this->add_hook('smtp_connect', array($this, 'smtp_connect'));
$this->add_hook('identity_form', array($this, 'identity_form'));
// Hook to modify some configuration, e.g. ldap
$this->add_hook('config_get', array($this, 'config_get'));
// Hook to modify logging directory
$this->add_hook('write_log', array($this, 'write_log'));
$this->username = $_SESSION['username'];
// Enable debug logs (per-user), when logged as another user
if (!empty($_SESSION['kolab_auth_admin']) && $rcmail->config->get('kolab_auth_auditlog')) {
$rcmail->config->set('debug_level', 1);
$rcmail->config->set('smtp_log', true);
$rcmail->config->set('log_logins', true);
$rcmail->config->set('log_session', true);
$rcmail->config->set('memcache_debug', true);
$rcmail->config->set('imap_debug', true);
$rcmail->config->set('ldap_debug', true);
$rcmail->config->set('smtp_debug', true);
$rcmail->config->set('sql_debug', true);
// SQL debug need to be set directly on DB object
// setting config variable will not work here because
// the object is already initialized/configured
if ($db = $rcmail->get_dbh()) {
$db->set_debug(true);
}
}
}
/**
* Ready hook handler
*/
public function ready($args)
{
$rcmail = rcube::get_instance();
// Store user unique identifier for freebusy_session_auth feature
if (!($uniqueid = $rcmail->config->get('kolab_uniqueid'))) {
$uniqueid = $_SESSION['kolab_auth_uniqueid'];
if (!$uniqueid) {
// Find user record in LDAP
if (($ldap = self::ldap()) && $ldap->ready) {
if ($record = $ldap->get_user_record($rcmail->get_user_name(), $_SESSION['kolab_host'])) {
$uniqueid = $record['uniqueid'];
}
}
}
if ($uniqueid) {
$uniqueid = md5($uniqueid);
$rcmail->user->save_prefs(array('kolab_uniqueid' => $uniqueid));
}
}
// Set/update freebusy_session_auth entry
if ($uniqueid && empty($_SESSION['kolab_auth_admin'])
&& ($ttl = $rcmail->config->get('freebusy_session_auth'))
) {
if ($ttl === true) {
$ttl = $rcmail->config->get('session_lifetime', 0) * 60;
if (!$ttl) {
$ttl = 10 * 60;
}
}
$rcmail->config->set('freebusy_auth_cache', 'db');
$rcmail->config->set('freebusy_auth_cache_ttl', $ttl);
if ($cache = $rcmail->get_cache_shared('freebusy_auth', false)) {
$key = md5($uniqueid . ':' . rcube_utils::remote_addr() . ':' . $rcmail->get_user_name());
$value = $cache->get($key);
$deadline = new DateTime('now', new DateTimeZone('UTC'));
// We don't want to do the cache update on every request
// do it once in a 1/10 of the ttl
if ($value) {
$value = new DateTime($value);
$value->sub(new DateInterval('PT' . intval($ttl * 9/10) . 'S'));
if ($value > $deadline) {
return;
}
}
$deadline->add(new DateInterval('PT' . $ttl . 'S'));
$cache->set($key, $deadline->format(DateTime::ISO8601));
}
}
}
/**
* Startup hook handler
*/
public function startup($args)
{
// Check access rights when logged in as another user
if (!empty($_SESSION['kolab_auth_admin']) && $args['task'] != 'login' && $args['task'] != 'logout') {
// access to specified task is forbidden,
// redirect to the first task on the list
if (!empty($_SESSION['kolab_auth_allowed_tasks'])) {
$tasks = (array)$_SESSION['kolab_auth_allowed_tasks'];
if (!in_array($args['task'], $tasks) && !in_array('*', $tasks)) {
header('Location: ?_task=' . array_shift($tasks));
die;
}
// add script that will remove disabled taskbar buttons
if (!in_array('*', $tasks)) {
$this->add_hook('render_page', array($this, 'render_page'));
}
}
}
// load per-user settings
$this->load_user_role_plugins_and_settings();
return $args;
}
/**
* Modify some configuration according to LDAP user record
*/
public function config_get($args)
{
// Replaces ldap_vars (%dc, etc) in public kolab ldap addressbooks
// config based on the users base_dn. (for multi domain support)
if ($args['name'] == 'ldap_public' && !empty($args['result'])) {
$rcmail = rcube::get_instance();
$kolab_books = (array) $rcmail->config->get('kolab_auth_ldap_addressbooks');
foreach ($args['result'] as $name => $config) {
if (in_array($name, $kolab_books) || in_array('*', $kolab_books)) {
$args['result'][$name] = $this->patch_ldap_config($config);
}
}
}
else if ($args['name'] == 'kolab_users_directory' && !empty($args['result'])) {
$args['result'] = $this->patch_ldap_config($args['result']);
}
return $args;
}
/**
* Helper method to patch the given LDAP directory config with user-specific values
*/
protected function patch_ldap_config($config)
{
if (is_array($config)) {
$config['base_dn'] = self::parse_ldap_vars($config['base_dn']);
$config['search_base_dn'] = self::parse_ldap_vars($config['search_base_dn']);
$config['bind_dn'] = str_replace('%dn', $_SESSION['kolab_dn'], $config['bind_dn']);
if (!empty($config['groups'])) {
$config['groups']['base_dn'] = self::parse_ldap_vars($config['groups']['base_dn']);
}
}
return $config;
}
/**
* Modifies list of plugins and settings according to
* specified LDAP roles
*/
public function load_user_role_plugins_and_settings($startup = false)
{
if (empty($_SESSION['user_roledns'])) {
return;
}
$rcmail = rcube::get_instance();
// Example 'kolab_auth_role_plugins' =
//
// Array(
// '<role_dn>' => Array('plugin1', 'plugin2'),
// );
//
// NOTE that <role_dn> may in fact be something like: 'cn=role,%dc'
$role_plugins = $rcmail->config->get('kolab_auth_role_plugins');
// Example $rcmail_config['kolab_auth_role_settings'] =
//
// Array(
// '<role_dn>' => Array(
// '$setting' => Array(
// 'mode' => '(override|merge)', (default: override)
// 'value' => <>,
// 'allow_override' => (true|false) (default: false)
// ),
// ),
// );
//
// NOTE that <role_dn> may in fact be something like: 'cn=role,%dc'
$role_settings = $rcmail->config->get('kolab_auth_role_settings');
if (!empty($role_plugins)) {
foreach ($role_plugins as $role_dn => $plugins) {
$role_dn = self::parse_ldap_vars($role_dn);
if (!empty($role_plugins[$role_dn])) {
$role_plugins[$role_dn] = array_unique(array_merge((array)$role_plugins[$role_dn], $plugins));
} else {
$role_plugins[$role_dn] = $plugins;
}
}
}
if (!empty($role_settings)) {
foreach ($role_settings as $role_dn => $settings) {
$role_dn = self::parse_ldap_vars($role_dn);
if (!empty($role_settings[$role_dn])) {
$role_settings[$role_dn] = array_merge((array)$role_settings[$role_dn], $settings);
} else {
$role_settings[$role_dn] = $settings;
}
}
}
foreach ($_SESSION['user_roledns'] as $role_dn) {
if (!empty($role_settings[$role_dn]) && is_array($role_settings[$role_dn])) {
foreach ($role_settings[$role_dn] as $setting_name => $setting) {
if (!isset($setting['mode'])) {
$setting['mode'] = 'override';
}
if ($setting['mode'] == "override") {
$rcmail->config->set($setting_name, $setting['value']);
} elseif ($setting['mode'] == "merge") {
$orig_setting = $rcmail->config->get($setting_name);
if (!empty($orig_setting)) {
if (is_array($orig_setting)) {
$rcmail->config->set($setting_name, array_merge($orig_setting, $setting['value']));
}
} else {
$rcmail->config->set($setting_name, $setting['value']);
}
}
$dont_override = (array) $rcmail->config->get('dont_override');
if (empty($setting['allow_override'])) {
$rcmail->config->set('dont_override', array_merge($dont_override, array($setting_name)));
}
else {
if (in_array($setting_name, $dont_override)) {
$_dont_override = array();
foreach ($dont_override as $_setting) {
if ($_setting != $setting_name) {
$_dont_override[] = $_setting;
}
}
$rcmail->config->set('dont_override', $_dont_override);
}
}
if ($setting_name == 'skin') {
if ($rcmail->output->type == 'html') {
$rcmail->output->set_skin($setting['value']);
$rcmail->output->set_env('skin', $setting['value']);
}
}
}
}
if (!empty($role_plugins[$role_dn])) {
foreach ((array)$role_plugins[$role_dn] as $plugin) {
$loaded = $this->api->load_plugin($plugin);
// Some plugins e.g. kolab_2fa use 'startup' hook to
// register other hooks, but when called on 'authenticate' hook
// we're already after 'startup', so we'll call it directly
if ($loaded && $startup && $plugin == 'kolab_2fa'
&& ($plugin = $this->api->get_plugin($plugin))
) {
$plugin->startup(array('task' => $rcmail->task, 'action' => $rcmail->action));
}
}
}
}
}
/**
* Logging method replacement to print debug/errors into
* a separate (sub)folder for each user
*/
public function write_log($args)
{
$rcmail = rcube::get_instance();
if ($rcmail->config->get('log_driver') == 'syslog') {
return $args;
}
// log_driver == 'file' is assumed here
$log_dir = $rcmail->config->get('log_dir', RCUBE_INSTALL_PATH . 'logs');
// Append original username + target username for audit-logging
if ($rcmail->config->get('kolab_auth_auditlog') && !empty($_SESSION['kolab_auth_admin'])) {
$args['dir'] = $log_dir . '/' . strtolower($_SESSION['kolab_auth_admin']) . '/' . strtolower($this->username);
// Attempt to create the directory
if (!is_dir($args['dir'])) {
@mkdir($args['dir'], 0750, true);
}
}
// Define the user log directory if a username is provided
else if ($rcmail->config->get('per_user_logging') && !empty($this->username)
&& !stripos($log_dir, '/' . $this->username) // maybe already set by syncroton, skip
) {
$user_log_dir = $log_dir . '/' . strtolower($this->username);
if (is_writable($user_log_dir)) {
$args['dir'] = $user_log_dir;
}
else if (!in_array($args['name'], array('errors', 'userlogins', 'sendmail'))) {
$args['abort'] = true; // don't log if unauthenticed or no per-user log dir
}
}
return $args;
}
/**
* Sets defaults for new user.
*/
public function user_create($args)
{
if (!empty($this->data['user_email'])) {
// addresses list is supported
if (array_key_exists('email_list', $args)) {
$email_list = array_unique($this->data['user_email']);
// add organization to the list
if (!empty($this->data['user_organization'])) {
foreach ($email_list as $idx => $email) {
$email_list[$idx] = array(
'organization' => $this->data['user_organization'],
'email' => $email,
);
}
}
$args['email_list'] = $email_list;
}
else {
$args['user_email'] = $this->data['user_email'][0];
}
}
if (!empty($this->data['user_name'])) {
$args['user_name'] = $this->data['user_name'];
}
return $args;
}
/**
* Modifies login form adding additional "Login As" field
*/
public function login_form($args)
{
$this->add_texts('localization/');
$rcmail = rcube::get_instance();
$admin_login = $rcmail->config->get('kolab_auth_admin_login');
$group = $rcmail->config->get('kolab_auth_group');
$role_attr = $rcmail->config->get('kolab_auth_role');
// Show "Login As" input
if (empty($admin_login) || (empty($group) && empty($role_attr))) {
return $args;
}
+ // Don't add the extra field on 2FA form
+ if (strpos($args['content'], 'plugin.kolab-2fa-login')) {
+ return $args;
+ }
+
$input = new html_inputfield(array('name' => '_loginas', 'id' => 'rcmloginas',
'type' => 'text', 'autocomplete' => 'off'));
-
$row = html::tag('tr', null,
html::tag('td', 'title', html::label('rcmloginas', rcube::Q($this->gettext('loginas'))))
. html::tag('td', 'input', $input->show(trim(rcube_utils::get_input_value('_loginas', rcube_utils::INPUT_POST))))
);
-
// add icon style for Elastic
$style = html::tag('style', [], '#login-form .input-group .icon.loginas::before { content: "\f508"; } ');
-
$args['content'] = preg_replace('/<\/tbody>/i', $row . '</tbody>' . $style, $args['content']);
return $args;
}
/**
* Find user credentials In LDAP.
*/
public function authenticate($args)
{
// get username and host
$host = $args['host'];
$user = $args['user'];
$pass = $args['pass'];
$loginas = trim(rcube_utils::get_input_value('_loginas', rcube_utils::INPUT_POST));
if (empty($user) || (empty($pass) && empty($_SERVER['REMOTE_USER']))) {
$args['abort'] = true;
return $args;
}
// temporarily set the current username to the one submitted
$this->username = $user;
$ldap = self::ldap();
if (!$ldap || !$ldap->ready) {
self::log_login_error($user, "LDAP not ready");
$args['abort'] = true;
$args['kolab_ldap_error'] = true;
return $args;
}
// Find user record in LDAP
$record = $ldap->get_user_record($user, $host);
if (empty($record)) {
self::log_login_error($user, "No user record found");
$args['abort'] = true;
return $args;
}
$rcmail = rcube::get_instance();
$admin_login = $rcmail->config->get('kolab_auth_admin_login');
$admin_pass = $rcmail->config->get('kolab_auth_admin_password');
$login_attr = $rcmail->config->get('kolab_auth_login');
$name_attr = $rcmail->config->get('kolab_auth_name');
$email_attr = $rcmail->config->get('kolab_auth_email');
$org_attr = $rcmail->config->get('kolab_auth_organization');
$role_attr = $rcmail->config->get('kolab_auth_role');
$imap_attr = $rcmail->config->get('kolab_auth_mailhost');
if (!empty($role_attr) && !empty($record[$role_attr])) {
$_SESSION['user_roledns'] = (array)($record[$role_attr]);
}
if (!empty($imap_attr) && !empty($record[$imap_attr])) {
$default_host = $rcmail->config->get('default_host');
if (!empty($default_host)) {
rcube::write_log("errors", "Both default host and kolab_auth_mailhost set. Incompatible.");
} else {
$args['host'] = "tls://" . $record[$imap_attr];
}
}
// Login As...
if (!empty($loginas) && $admin_login) {
// Authenticate to LDAP
$result = $ldap->bind($record['dn'], $pass);
if (!$result) {
self::log_login_error($user, "Unable to bind with '" . $record['dn'] . "'");
$args['abort'] = true;
return $args;
}
$isadmin = false;
$admin_rights = $rcmail->config->get('kolab_auth_admin_rights', array());
// @deprecated: fall-back to the old check if the original user has/belongs to administrative role/group
if (empty($admin_rights)) {
$group = $rcmail->config->get('kolab_auth_group');
$role_dn = $rcmail->config->get('kolab_auth_role_value');
// check role attribute
if (!empty($role_attr) && !empty($role_dn) && !empty($record[$role_attr])) {
$role_dn = $ldap->parse_vars($role_dn, $user, $host);
if (in_array($role_dn, (array)$record[$role_attr])) {
$isadmin = true;
}
}
// check group
if (!$isadmin && !empty($group)) {
$groups = $ldap->get_user_groups($record['dn'], $user, $host);
if (in_array($group, $groups)) {
$isadmin = true;
}
}
if ($isadmin) {
// user has admin privileges privilage, get "login as" user credentials
$target_entry = $ldap->get_user_record($loginas, $host);
$allowed_tasks = $rcmail->config->get('kolab_auth_allowed_tasks');
}
}
else {
// get "login as" user credentials
$target_entry = $ldap->get_user_record($loginas, $host);
if (!empty($target_entry)) {
// get effective rights to determine login-as permissions
$effective_rights = (array)$ldap->effective_rights($target_entry['dn']);
if (!empty($effective_rights)) {
// compat with out of date Net_LDAP3
$effective_rights = array_change_key_case($effective_rights, CASE_LOWER);
$effective_rights['attrib'] = $effective_rights['attributelevelrights'];
$effective_rights['entry'] = $effective_rights['entrylevelrights'];
// compare the rights with the permissions mapping
$allowed_tasks = array();
foreach ($admin_rights as $task => $perms) {
$perms_ = explode(':', $perms);
$type = array_shift($perms_);
$req = array_pop($perms_);
$attrib = array_pop($perms_);
if (array_key_exists($type, $effective_rights)) {
if ($type == 'entry' && in_array($req, $effective_rights[$type])) {
$allowed_tasks[] = $task;
}
else if ($type == 'attrib' && array_key_exists($attrib, $effective_rights[$type]) &&
in_array($req, $effective_rights[$type][$attrib])) {
$allowed_tasks[] = $task;
}
}
}
$isadmin = !empty($allowed_tasks);
}
}
}
// Save original user login for log (see below)
if ($login_attr) {
$origname = is_array($record[$login_attr]) ? $record[$login_attr][0] : $record[$login_attr];
}
else {
$origname = $user;
}
if (!$isadmin || empty($target_entry)) {
$this->add_texts('localization/');
$args['abort'] = true;
$args['error'] = $this->gettext(array(
'name' => 'loginasnotallowed',
'vars' => array('user' => rcube::Q($loginas)),
));
self::log_login_error($user, "No privileges to login as '" . $loginas . "'", $loginas);
return $args;
}
// replace $record with target entry
$record = $target_entry;
$args['user'] = $this->username = $loginas;
// Mark session to use SASL proxy for IMAP authentication
$_SESSION['kolab_auth_admin'] = strtolower($origname);
$_SESSION['kolab_auth_login'] = $rcmail->encrypt($admin_login);
$_SESSION['kolab_auth_password'] = $rcmail->encrypt($admin_pass);
$_SESSION['kolab_auth_allowed_tasks'] = $allowed_tasks;
}
// Store UID and DN of logged user in session for use by other plugins
$_SESSION['kolab_uid'] = is_array($record['uid']) ? $record['uid'][0] : $record['uid'];
$_SESSION['kolab_dn'] = $record['dn'];
// Store LDAP replacement variables used for current user
// This improves performance of load_user_role_plugins_and_settings()
// which is executed on every request (via startup hook) and where
// we don't like to use LDAP (connection + bind + search)
$_SESSION['kolab_auth_vars'] = $ldap->get_parse_vars();
// Store user unique identifier for freebusy_session_auth feature
$_SESSION['kolab_auth_uniqueid'] = is_array($record['uniqueid']) ? $record['uniqueid'][0] : $record['uniqueid'];
// Store also host as we need it for get_user_reacod() in 'ready' hook handler
$_SESSION['kolab_host'] = $host;
// Set user login
if ($login_attr) {
$this->data['user_login'] = is_array($record[$login_attr]) ? $record[$login_attr][0] : $record[$login_attr];
}
if ($this->data['user_login']) {
$args['user'] = $this->username = $this->data['user_login'];
}
// User name for identity (first log in)
foreach ((array)$name_attr as $field) {
$name = is_array($record[$field]) ? $record[$field][0] : $record[$field];
if (!empty($name)) {
$this->data['user_name'] = $name;
break;
}
}
// User email(s) for identity (first log in)
foreach ((array)$email_attr as $field) {
$email = is_array($record[$field]) ? array_filter($record[$field]) : $record[$field];
if (!empty($email)) {
$this->data['user_email'] = array_merge((array)$this->data['user_email'], (array)$email);
}
}
// Organization name for identity (first log in)
foreach ((array)$org_attr as $field) {
$organization = is_array($record[$field]) ? $record[$field][0] : $record[$field];
if (!empty($organization)) {
$this->data['user_organization'] = $organization;
break;
}
}
// Log "Login As" usage
if (!empty($origname)) {
rcube::write_log('userlogins', sprintf('Admin login for %s by %s from %s',
$args['user'], $origname, rcube_utils::remote_ip()));
}
// load per-user settings/plugins
$this->load_user_role_plugins_and_settings(true);
return $args;
}
/**
* Set user DN for password change (password plugin with ldap_simple driver)
*/
public function password_ldap_bind($args)
{
$args['user_dn'] = $_SESSION['kolab_dn'];
$rcmail = rcube::get_instance();
$rcmail->config->set('password_ldap_method', 'user');
return $args;
}
/**
* Sets SASL Proxy login/password for IMAP and Managesieve auth
*/
public function imap_connect($args)
{
if (!empty($_SESSION['kolab_auth_admin'])) {
$rcmail = rcube::get_instance();
$admin_login = $rcmail->decrypt($_SESSION['kolab_auth_login']);
$admin_pass = $rcmail->decrypt($_SESSION['kolab_auth_password']);
$args['auth_cid'] = $admin_login;
$args['auth_pw'] = $admin_pass;
}
return $args;
}
/**
* Sets SASL Proxy login/password for SMTP auth
*/
public function smtp_connect($args)
{
if (!empty($_SESSION['kolab_auth_admin'])) {
$rcmail = rcube::get_instance();
$admin_login = $rcmail->decrypt($_SESSION['kolab_auth_login']);
$admin_pass = $rcmail->decrypt($_SESSION['kolab_auth_password']);
$args['smtp_auth_cid'] = $admin_login;
$args['smtp_auth_pw'] = $admin_pass;
}
return $args;
}
/**
* Hook to replace the plain text input field for email address by a drop-down list
* with all email addresses (including aliases) from this user's LDAP record.
*/
public function identity_form($args)
{
$rcmail = rcube::get_instance();
$ident_level = intval($rcmail->config->get('identities_level', 0));
// do nothing if email address modification is disabled
if ($ident_level == 1 || $ident_level == 3) {
return $args;
}
$ldap = self::ldap();
if (!$ldap || !$ldap->ready || empty($_SESSION['kolab_dn'])) {
return $args;
}
$emails = array();
$user_record = $ldap->get_record($_SESSION['kolab_dn']);
foreach ((array)$rcmail->config->get('kolab_auth_email', array()) as $col) {
$values = rcube_addressbook::get_col_values($col, $user_record, true);
if (!empty($values))
$emails = array_merge($emails, array_filter($values));
}
// kolab_delegation might want to modify this addresses list
$plugin = $rcmail->plugins->exec_hook('kolab_auth_emails', array('emails' => $emails));
$emails = $plugin['emails'];
if (!empty($emails)) {
$args['form']['addressing']['content']['email'] = array(
'type' => 'select',
'options' => array_combine($emails, $emails),
);
}
return $args;
}
/**
* Action executed before the page is rendered to add an onload script
* that will remove all taskbar buttons for disabled tasks
*/
public function render_page($args)
{
$rcmail = rcube::get_instance();
$tasks = (array)$_SESSION['kolab_auth_allowed_tasks'];
$tasks[] = 'logout';
// disable buttons in taskbar
$script = "
\$('a').filter(function() {
var ev = \$(this).attr('onclick');
return ev && ev.match(/'switch-task','([a-z]+)'/)
&& \$.inArray(RegExp.\$1, " . json_encode($tasks) . ") < 0;
}).remove();
";
$rcmail->output->add_script($script, 'docready');
}
/**
* Initializes LDAP object and connects to LDAP server
*/
public static function ldap()
{
self::$ldap = kolab_storage::ldap('kolab_auth_addressbook');
if (self::$ldap) {
self::$ldap->extend_fieldmap(array('uniqueid' => 'nsuniqueid'));
}
return self::$ldap;
}
/**
* Close LDAP connection
*/
public static function ldap_close()
{
if (self::$ldap) {
self::$ldap->close();
self::$ldap = null;
}
}
/**
* Parses LDAP DN string with replacing supported variables.
* See kolab_ldap::parse_vars()
*
* @param string $str LDAP DN string
*
* @return string Parsed DN string
*/
public static function parse_ldap_vars($str)
{
if (!empty($_SESSION['kolab_auth_vars'])) {
$str = strtr($str, $_SESSION['kolab_auth_vars']);
}
return $str;
}
/**
* Log failed logins
*
* @param string $username Username/Login
* @param string $message Error message (failure reason)
* @param string $login_as Username/Login of "login as" user
*/
public static function log_login_error($username, $message = null, $login_as = null)
{
$config = rcube::get_instance()->config;
if ($config->get('log_logins')) {
// don't fill the log with complete input, which could
// have been prepared by a hacker
if (strlen($username) > 256) {
$username = substr($username, 0, 256) . '...';
}
if (strlen($login_as) > 256) {
$login_as = substr($login_as, 0, 256) . '...';
}
if ($login_as) {
$username = sprintf('%s (as user %s)', $username, $login_as);
}
// Don't log full session id for better security
$session_id = session_id();
$session_id = $session_id ? substr($session_id, 0, 16) : 'no-session';
$message = sprintf(
"Failed login for %s from %s in session %s %s",
$username,
rcube_utils::remote_ip(),
$session_id,
$message ? "($message)" : ''
);
rcube::write_log('userlogins', $message);
// disable log_logins to prevent from duplicate log entries
$config->set('log_logins', false);
}
}
}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Aug 25, 2:52 PM (18 h, 51 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
257670
Default Alt Text
(63 KB)

Event Timeline