CHANGELOG | ●●●●● patch | view | raw | blame | history | |
plugins/krb_authentication/config.inc.php.dist | ●●●●● patch | view | raw | blame | history | |
plugins/krb_authentication/krb_authentication.php | ●●●●● patch | view | raw | blame | history | |
plugins/krb_authentication/tests/KrbAuthentication.php | ●●●●● patch | view | raw | blame | history | |
program/lib/Roundcube/rcube_imap_generic.php | ●●●●● patch | view | raw | blame | history | |
tests/phpunit.xml | ●●●●● patch | view | raw | blame | history |
CHANGELOG
@@ -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) plugins/krb_authentication/config.inc.php.dist
New file @@ -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'; plugins/krb_authentication/krb_authentication.php
New file @@ -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; } } plugins/krb_authentication/tests/KrbAuthentication.php
New file @@ -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); } } program/lib/Roundcube/rcube_imap_generic.php
@@ -552,14 +552,12 @@ $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, @@ -569,6 +567,66 @@ $this->putLine(''); } $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); @@ -738,7 +796,7 @@ 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 @@ } // 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 @@ case 'CRAM-MD5': case 'DIGEST-MD5': case 'PLAIN': case 'GSSAPI': $result = $this->authenticate($user, $password, $auth_method); break; case 'LOGIN': tests/phpunit.xml
@@ -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>