diff --git a/lib/Api.php b/lib/Api.php index 3d8e4fba3..9b84f4afb 100644 --- a/lib/Api.php +++ b/lib/Api.php @@ -381,6 +381,27 @@ public function imapOb() return $GLOBALS['injector']->getInstance('IMP_Factory_Imap')->create()->client_ob; } + /** + * Ensure a live IMAP connection exists for the current user. + * + * Stateless entry points (ActiveSync, RPC) authenticate to Horde but + * still require an explicit IMAP login for hordeauth backends. Exposed + * as a 'mail' API method so Horde_Core can trigger the login without + * depending on IMP directly. + * + * @param array $credentials Optional fallback credentials (userId, + * password, server). See + * IMP_Auth::ensureImapConnection(). + * + * @throws Horde_Auth_Exception + * + * @author Torben Dannhauer + */ + public function ensureImapConnection(array $credentials = []) + { + IMP_Auth::ensureImapConnection($credentials); + } + /** * Return the list of user-settable IMAP flags. * diff --git a/lib/Auth.php b/lib/Auth.php index a45462f55..6897aca81 100644 --- a/lib/Auth.php +++ b/lib/Auth.php @@ -89,6 +89,98 @@ public static function authenticate($credentials = []) } } + /** + * Ensure a live IMAP connection exists. + * + * Unlike authenticate(), this always opens the mail server connection when + * needed. Stateless entry points (ActiveSync, RPC) authenticate to Horde + * first but still require an explicit IMAP login for hordeauth backends. + * + * @param array $credentials Optional fallback credentials: + * - userId: (string) Username. + * - password: (string) Password. + * - server: (string) Server key from backends.php. + * + * @throws Horde_Auth_Exception + * + * @author Torben Dannhauer + */ + public static function ensureImapConnection(array $credentials = []) + { + global $injector, $registry; + + $imp_imap = $injector->getInstance('IMP_Factory_Imap')->create(); + if ($imp_imap->init) { + try { + $imp_imap->login(); + } catch (IMP_Imap_Exception $e) { + self::_log(false, $imp_imap); + throw $e->authException(); + } + return; + } + + if (!isset($credentials['server'])) { + $credentials['server'] = self::getAutoLoginServer(); + } + if (empty($credentials['server'])) { + throw new Horde_Auth_Exception( + _('No IMAP backend is available for auto-login.'), + Horde_Auth::REASON_MESSAGE + ); + } + + $servers = IMP_Imap::loadServerConfig(); + if ($servers === false) { + throw new Horde_Auth_Exception( + _('Could not load IMAP backend configuration.'), + Horde_Auth::REASON_MESSAGE + ); + } + $serverConfig = $servers[$credentials['server']] ?? null; + if ((empty($credentials['userId']) || !isset($credentials['password'])) + && $registry->getAuth() + && $serverConfig + && !empty($serverConfig->hordeauth)) { + $hordeauth = $serverConfig->hordeauth; + $credentials['userId'] = $registry->getAuth( + strcasecmp($hordeauth, 'full') === 0 ? null : 'bare' + ); + $stored = $registry->getAuthCredential('password'); + if ($stored !== false) { + $credentials['password'] = $stored; + } + } + + if (empty($credentials['userId']) + || !isset($credentials['password']) + || (!is_string($credentials['password']) && !($credentials['password'] instanceof Horde_Imap_Client_Password_Xoauth2)) + || (is_string($credentials['password']) && $credentials['password'] === '')) { + throw new Horde_Auth_Exception('', Horde_Auth::REASON_BADLOGIN); + } + + try { + $credentials = $injector->getInstance('Horde_Core_Hooks')->callHook( + 'imap_preauthenticate', + 'imp', + [$credentials] + ); + } catch (Horde_Exception_HookNotSet $e) { + } + + try { + $imp_imap->createBaseImapObject( + $credentials['userId'], + $credentials['password'], + $credentials['server'] + ); + $imp_imap->login(); + } catch (IMP_Imap_Exception $e) { + self::_log(false, $imp_imap); + throw $e->authException(); + } + } + /** * Perform transparent authentication. *