6.0.0-git
2020-04-04

Diff for ImapSelect between 13 and 14

+ Dynamically selecting an IMAP server for authentication

++ Introduction**##red|This information is valid for Horde 5 or later only. See ImapSelectH4 for Horde 4 or ImapSelectH3 for Horde 3.##**

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,For 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, but5 or later there is a better way. 
The first example shows a way using a !MySQL backend to selectalready exists an IMAP serverexample hook for authentication, given a username.
The second example instead uses the realmed username to select thedynamic IMAP server \
(i.e.selection in IMP (imp/config/hooks.php.dist). For the username used by Horde internally to prevent username clashes).
----sake of completeness, here is a stripped down copy of the example:

++ 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:

<code>
---------------------------------------------------------
|   username        |     cyrus                         |
---------------------------------------------------------
</code>

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:

<code type="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;
    }
}

</code>

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}}
<code>/* 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' => '',
);
</code>

++++ 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}}:
<code type="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;
    }
}
</code>

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
<code>...
$conf['hooks']['preauthenticate'] = true;
...
</code> 

++ The same IMAP server selector hook function in Hode 4 {{imp/config/hooks.php}}
<code type="php">
class IMP_Hooks
{

public    /**
     * AUTHENTICATION HOOK: pre-authentication actions.
     *
     * See horde/config/hooks.php.dist for more information.
     *
     * IMP uses the following credentials:
     *   - password: (string) The password for mail server authentication.
     *   - server: (string) [optional] Use this server key (see
     *             config/backends.php).
     *   - transparent: (boolean) If $credentials['authMethod'] is
     *                  'transparent', and you want IMP to use the
     *                  userId/credentials generated in the preauthenticate
     *                  hook, this must be true. If false, IMP will instead
     *                  try to authenticate using hordeauth.
     *
     * The following credentials will exist in $credentials, but changing
     * these values has no effect on authentication:
     *   - imp_server_key: (string; 'authenticate' only) The backend server
     *                     key selected on the login page.
     */
     public function preauthenticate($userId, $credentials)
{
    //Horde::logMessage('authM: '.$credentials['authMethod'].' id='.$userId, 'ERROR');
    //return true;

    /* when no userId given */
    if (empty($userId)) return true;

    /* list of ALL remote users */
    $remote_users
     {
         if ($credentials['authMethod'] == 'authenticate') {
             // Example: Load-balance - pick IMAP server based on first
             // letter in username. Server entries 'server_[a-z]' must
             // be defined in config/backends.local.php.
             $credentials['server'] = array('username@remoteserver.hu' => 'remote-imap-servers-key');

    /* local user */
    if (!array_key_exists($userId, $remote_users)) return'server_' . substr($userId, 0, 1);
             return array(
                 'credentials' => $credentials,
                 'userId' => $userId
             );
         }
         return true;

    /* remote user */
    return array('credentials' => array('server' => $remote_users[$userId],
                                        'transparent' => true,
                                        'password' => $GLOBALS['registry']->getAuthCredential('password')
                                  )
    );
}

    }
}
</code>

Set server list to 'hidden' in Imp prefs, and all backend's hordeauth to full.