6.0.0-git
2024-03-19
Last Modified 2013-09-04 by Jan Schneider

Dynamically selecting an IMAP server for authentication

This information is valid for Horde 3 only. See ImapSelectH4 for Horde 4 or ImapSelect for Horde 5 and later.

Introduction

During a migration from one IMAP server to another, the need arose to run both the old and the new IMAP servers in parallel. By default, Horde only allows a single primary server to be enabled in servers.php. Of course, we could have allowed our users to simply select their server by enabling IMAP server selection, but there is a better way.
The first example shows a way using a MySQL backend to select an IMAP server for authentication, given a username.
The second example instead uses the realmed username to select the IMAP server (i.e. the username used by Horde internally to prevent username clashes).


The SQL table

There are many ways to do this of course. In this example, we'll just be using a table with two rows:

---------------------------------------------------------
|   username        |     cyrus                         |
---------------------------------------------------------

Your table can be constructed however you like. It can even be a part of your existing Horde DB. The important point is that you need the table to be constructed in such a way as to be able to query a username and have the lookup return a server name from imp/config/servers.php.

Writing a hook

Here's some sample code for a hook placed inside horde/config/hooks.php:

if (!function_exists('_horde_hook_preauthenticate')) {
    function _horde_hook_preauthenticate($userID, $credential, $realm)
    {
        require dirname(__FILE__) . '/../imp/config/servers.php';

        // Strip domain part from user.
        $userID = substr($userID, 0, strpos($userID, '@'));
        // Connect to database server.
        $db = mysql_connect('hostname', 'dbuser', 'dbpasswd') or die('Can not connect to database.');
        // Select database.
        mysql_select_db('dbname') or die('Can not select database.');
        // Execute the query
        $sth = mysql_query('SELECT server_name FROM users WHERE user=\'' . mysql_real_escape_string($userID) . '\'') or die ('Can not query database.');
        // Fetch the server name.
        $server = mysql_fetch_row($sth);
        if ($server === false) {
            die('Can not read user from database.');
        }

        // Set IMAP server values.
        foreach (array('server', 'folders', 'namespace') as $key) {
            $_SESSION['imp'][$key] = $servers[$server[0]][$key];
        }

        return true;
    }
}

All that's left to do, is to activate the preauthenticate hook in Horde's configuration.


IMAP server selection by realmed username

Requirements

  • IMP is used to do the authentication
  • The usernames to authenticate against the mailserver may not have a domain part (separated with '@' from the username).
  • There is more than one active server entry in imp/config/servers.php.
    The realm values are different and the default server has an empty string as realm.
    Note: The default server is either the first server entry in the list or the one with the preferred value
    set.
  • Realm values are only lowercase strings (e.g. 'server1.example.com').

Examplary snippet of imp/config/servers.php

/* Example configuration: */

$servers['imap'] = array(
    'name' => 'IMAP Server',
    'server' => 'imap.example.com',
    'hordeauth' => true,
    'protocol' => 'imap/notls',
    'port' => 143,
    'maildomain' => 'example.com',
    'smtphost' => 'smtp.example.com',
    'smtpport' => 25,
    'realm' => '',
    'preferred' => '',
);

$servers['imap1'] = array(
    'name' => 'IMAP Server 1',
    'server' => 'imap1.example.com',
    'hordeauth' => true,
    'protocol' => 'imap/ssl/novalidate-cert',
    'port' => 993,
    'maildomain' => 'example.com',
    'smtphost' => 'smtp.example.com',
    'smtpport' => 25,
    'realm' => 'imap1.example.com',
    'preferred' => '',
);

Functionality of the hook

Entering the username smith in the login screen selects the necessary values given by $servers['imap']:

  • Authentication against imap.example.com
  • using protocol imap/notls
  • ...

Entering the username smith@imap1.example.com instead selects the values of $servers['imap1']:

  • Authentication against imap1.example.com
  • using protocol imap/ssl/novalidate-cert
  • ...

Note:

  • Horde treats them as different users with their own preferences.
  • The selection of the server is done by the realm value NOT by the server value.

The Preauthenticate-Hook

The following hook has to be inserted in config/hooks.php:

if (!function_exists('_horde_hook_preauthenticate')) {
    function _horde_hook_preauthenticate($userID, $credential, $realm)
    {
        require dirname(__FILE__) . '/../imp/config/servers.php';

        // Convert all to lower chars (even the possible domain part)
        $userID=strtolower($userID);
        // Strip domain part from user, if it exists.
        if (($domainpart = strpos($userID, '@'))) {
          $server=substr($userID, $domainpart+1);
          $userID=substr($userID, 0, $domainpart);
          // Change the values only if a domain part was found ...
          if (!empty($server)) {
            foreach ($servers as $serverkey => $curServer) {
              if (!empty($curServer['realm']) && $server == $curServer['realm']) {
                // We found an entry, now set IMAP server values.
                foreach (array('server', 'folders', 'namespace',
                  'protocol', 'port', 'smtphost', 'smtpport', 'maildomain') as $key) {
                  if (isset($servers[$serverkey][$key])) {
                    $_SESSION['imp'][$key] = $servers[$serverkey][$key];
                  }
                }
                // Now use only the stripped version of the userID to logon to the server
                $_SESSION['imp']['user'] = $userID;
                // Setup the correct base_protocol
                $_SESSION['imp']['base_protocol'] = $_SESSION['imp']['protocol'];
                if (($pos = strpos($_SESSION['imp']['protocol'], '/'))) {
                  $_SESSION['imp']['base_protocol'] = substr($_SESSION['imp']['protocol'], 0, $pos);
                }
              }
            }
          }
        }
        return true;
    }
}

Like in the first example, the hook has to be activated inside config/conf.php.

  • Either use the administration user interface of Horde (recommended)
  • or set the following entry in config/conf.php with your favorite editor

...
$conf['hooks']['preauthenticate'] = true;
...