Added GSSAPI/Kerberos authentication plugin - krb_authentication

pull/290/head
Aleksander Machniak 9 years ago
parent 92649075ec
commit 1b8ca08e5b

@ -1,6 +1,7 @@
CHANGELOG Roundcube Webmail
===========================
- Added GSSAPI/Kerberos authentication plugin - krb_authentication
- Password: Allow temporarily disabling the plugin functionality with a notice
- Support more secure hashing algorithms for auth cookie - configurable by PHP's session.hash_function (#1490403)
- Require Mbstring and OpenSSL extensions (#1490415)

@ -0,0 +1,13 @@
<?php
// Kerberos/GSSAPI Authentication Plugin options
// ---------------------------------------------
// Default mail host to log-in using user/password from HTTP Authentication.
// This is useful if the users are free to choose arbitrary mail hosts (or
// from a list), but have one host they usually want to log into.
// Unlike $config['default_host'] this must be a string!
$config['krb_authentication_host'] = '';
// GSS API security context
$config['krb_authentication_context'] = 'imap/kolab.example.org@EXAMPLE.ORG';

@ -0,0 +1,110 @@
<?php
/**
* Kerberos Authentication
*
* Make use of an existing Kerberos authentication and perform login
* with the existing user credentials
*
* For other configuration options, see config.inc.php.dist!
*
* @version @package_version@
* @license GNU GPLv3+
* @author Jeroen van Meeuwen
*/
class krb_authentication extends rcube_plugin
{
private $redirect_query;
/**
* Plugin initialization
*/
function init()
{
$this->add_hook('startup', array($this, 'startup'));
$this->add_hook('authenticate', array($this, 'authenticate'));
$this->add_hook('login_after', array($this, 'login'));
$this->add_hook('storage_connect', array($this, 'storage_connect'));
}
/**
* Startup hook handler
*/
function startup($args)
{
if (!empty($_SERVER['REMOTE_USER']) && !empty($_SERVER['KRB5CCNAME'])) {
// handle login action
if (empty($_SESSION['user_id'])) {
$args['action'] = 'login';
$this->redirect_query = $_SERVER['QUERY_STRING'];
}
else {
$_SESSION['password'] = null;
}
}
return $args;
}
/**
* Authenticate hook handler
*/
function authenticate($args)
{
if (!empty($_SERVER['REMOTE_USER']) && !empty($_SERVER['KRB5CCNAME'])) {
// Load plugin's config file
$this->load_config();
$rcmail = rcmail::get_instance();
$host = $rcmail->config->get('krb_authentication_host');
if (is_string($host) && trim($host) !== '' && empty($args['host'])) {
$args['host'] = rcube_utils::idn_to_ascii(rcube_utils::parse_host($host));
}
if (!empty($_SERVER['REMOTE_USER'])) {
$args['user'] = $_SERVER['REMOTE_USER'];
$args['pass'] = null;
}
$args['cookiecheck'] = false;
$args['valid'] = true;
}
return $args;
}
/**
* Storage_connect hook handler
*/
function storage_connect($args)
{
if (!empty($_SERVER['REMOTE_USER']) && !empty($_SERVER['KRB5CCNAME'])) {
// Load plugin's config file
$this->load_config();
$rcmail = rcmail::get_instance();
$context = $rcmail->config->get('krb_authentication_context');
$args['gssapi_context'] = $context ?: 'imap/kolab.example.org@EXAMPLE.ORG';
$args['gssapi_cn'] = $_SERVER['KRB5CCNAME'];
$args['auth_type'] = 'GSSAPI';
}
return $args;
}
/**
* login_after hook handler
*/
function login($args)
{
// Redirect to the previous QUERY_STRING
if ($this->redirect_query) {
header('Location: ./?' . $this->redirect_query);
exit;
}
return $args;
}
}

@ -0,0 +1,23 @@
<?php
class KrbAuthentication_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()
{
include_once dirname(__FILE__) . '/../krb_authentication.php';
}
/**
* Plugin object construction test
*/
function test_constructor()
{
$rcube = rcube::get_instance();
$plugin = new krb_authentication($rcube->api);
$this->assertInstanceOf('krb_authentication', $plugin);
$this->assertInstanceOf('rcube_plugin', $plugin);
}
}

@ -552,14 +552,12 @@ class rcube_imap_generic
$this->putLine($reply, true, true);
$line = trim($this->readReply());
if ($line[0] == '+') {
$challenge = substr($line, 2);
}
else {
if ($line[0] != '+') {
return $this->parseResult($line);
}
// check response
$challenge = substr($line, 2);
$challenge = base64_decode($challenge);
if (strpos($challenge, 'rspauth=') === false) {
$this->setError(self::ERROR_BAD,
@ -573,6 +571,66 @@ class rcube_imap_generic
$line = $this->readReply();
$result = $this->parseResult($line);
}
elseif ($type == 'GSSAPI') {
if (!extension_loaded('krb5')) {
$this->setError(self::ERROR_BYE,
"The krb5 extension is required for GSSAPI authentication");
return self::ERROR_BAD;
}
if (empty($this->prefs['gssapi_cn'])) {
$this->setError(self::ERROR_BYE,
"The gssapi_cn parameter is required for GSSAPI authentication");
return self::ERROR_BAD;
}
if (empty($this->prefs['gssapi_context'])) {
$this->setError(self::ERROR_BYE,
"The gssapi_context parameter is required for GSSAPI authentication");
return self::ERROR_BAD;
}
putenv('KRB5CCNAME=' . $this->prefs['gssapi_cn']);
try {
$ccache = new KRB5CCache();
$ccache->open($this->prefs['gssapi_cn']);
$gssapicontext = new GSSAPIContext();
$gssapicontext->acquireCredentials($ccache);
$token = '';
$success = $gssapicontext->initSecContext($this->prefs['gssapi_context'], null, null, null, $token);
$token = base64_encode($token);
}
catch (Exception $e) {
trigger_error($e->getMessage(), E_USER_WARNING);
$this->setError(self::ERROR_BYE, "GSSAPI authentication failed");
return self::ERROR_BAD;
}
$this->putLine($this->nextTag() . " AUTHENTICATE GSSAPI " . $token);
$line = trim($this->readReply());
if ($line[0] != '+') {
return $this->parseResult($line);
}
try {
$challenge = base64_decode(substr($line, 2));
$gssapicontext->unwrap($challenge, $challenge);
$gssapicontext->wrap($challenge, $challenge, true);
}
catch (Exception $e) {
trigger_error($e->getMessage(), E_USER_WARNING);
$this->setError(self::ERROR_BYE, "GSSAPI authentication failed");
return self::ERROR_BAD;
}
$this->putLine(base64_encode($challenge));
$line = $this->readReply();
$result = $this->parseResult($line);
}
else { // PLAIN
// proxy authorization
if (!empty($this->prefs['auth_cid'])) {
@ -738,7 +796,7 @@ class rcube_imap_generic
return false;
}
if (empty($password)) {
if (empty($password) && empty($options['gssapi_cn'])) {
$this->setError(self::ERROR_NO, "Empty password");
return false;
}
@ -774,7 +832,8 @@ class rcube_imap_generic
}
// Use best (for security) supported authentication method
foreach (array('DIGEST-MD5', 'CRAM-MD5', 'CRAM_MD5', 'PLAIN', 'LOGIN') as $auth_method) {
$all_methods = array('GSSAPI', 'DIGEST-MD5', 'CRAM-MD5', 'CRAM_MD5', 'PLAIN', 'LOGIN');
foreach ($all_methods as $auth_method) {
if (in_array($auth_method, $auth_methods)) {
break;
}
@ -803,6 +862,7 @@ class rcube_imap_generic
case 'CRAM-MD5':
case 'DIGEST-MD5':
case 'PLAIN':
case 'GSSAPI':
$result = $this->authenticate($user, $password, $auth_method);
break;
case 'LOGIN':

@ -64,6 +64,7 @@
<file>./../plugins/http_authentication/tests/HttpAuthentication.php</file>
<file>./../plugins/identity_select/tests/IdentitySelect.php</file>
<file>./../plugins/jqueryui/tests/Jqueryui.php</file>
<file>./../plugins/krb_authentication/tests/KrbAuthentication.php</file>
<file>./../plugins/legacy_browser/tests/LegacyBrowser.php</file>
<file>./../plugins/managesieve/tests/Managesieve.php</file>
<file>./../plugins/managesieve/tests/Parser.php</file>

Loading…
Cancel
Save