+ 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 Hordeonly 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 thereis a better way. The first example shows a way using a !MySQL backend to selectalready exists anIMAP serverexample hook forauthentication, 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 theusername 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.