From 2a6b458cb9761957fdb8a2fcea2560d4e2bdb10c Mon Sep 17 00:00:00 2001 From: Torben Dannhauer Date: Wed, 8 Jul 2026 07:51:50 +0200 Subject: [PATCH 1/4] feat(imp): add ensureImapConnection for stateless IMAP login Stateless entry points (ActiveSync, RPC) authenticate to Horde but, with hordeauth backends, still need an explicit IMAP login that authenticate() does not guarantee outside the interactive login flow. Add IMP_Auth::ensureImapConnection(), which opens the mail server connection on demand, resolving hordeauth credentials from the session when none are supplied. Expose it through the 'mail' API (IMP_Api::ensureImapConnection()) so Horde_Core can trigger the login via the registry without depending on IMP directly. --- lib/Api.php | 21 ++++++++++++++ lib/Auth.php | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) 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..a1b012e35 100644 --- a/lib/Auth.php +++ b/lib/Auth.php @@ -89,6 +89,83 @@ 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) { + return; + } + + if (!isset($credentials['server'])) { + $credentials['server'] = self::getAutoLoginServer(); + } + if (empty($credentials['server'])) { + throw new Horde_Auth_Exception('', Horde_Auth::REASON_MESSAGE); + } + + $servers = IMP_Imap::loadServerConfig(); + $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']) + || !strlen((string) $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. * From 0ce0b1269e4d393960c7344f8394fe7539416ba0 Mon Sep 17 00:00:00 2001 From: Torben Dannhauer Date: Wed, 8 Jul 2026 08:10:53 +0200 Subject: [PATCH 2/4] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- lib/Auth.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/Auth.php b/lib/Auth.php index a1b012e35..1d8d21ea9 100644 --- a/lib/Auth.php +++ b/lib/Auth.php @@ -118,12 +118,20 @@ public static function ensureImapConnection(array $credentials = []) $credentials['server'] = self::getAutoLoginServer(); } if (empty($credentials['server'])) { - throw new Horde_Auth_Exception('', Horde_Auth::REASON_MESSAGE); + 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 From 2fcdfac70c520c610c3cc084fed59a33265bebed Mon Sep 17 00:00:00 2001 From: Torben Dannhauer Date: Wed, 8 Jul 2026 08:12:02 +0200 Subject: [PATCH 3/4] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- lib/Auth.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/Auth.php b/lib/Auth.php index 1d8d21ea9..842087df7 100644 --- a/lib/Auth.php +++ b/lib/Auth.php @@ -111,6 +111,12 @@ public static function ensureImapConnection(array $credentials = []) $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; } From a0435a0e20ab85e742114a57ce2f87eb72b7dd97 Mon Sep 17 00:00:00 2001 From: Torben Dannhauer Date: Wed, 8 Jul 2026 08:13:38 +0200 Subject: [PATCH 4/4] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- lib/Auth.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Auth.php b/lib/Auth.php index 842087df7..6897aca81 100644 --- a/lib/Auth.php +++ b/lib/Auth.php @@ -154,7 +154,8 @@ public static function ensureImapConnection(array $credentials = []) if (empty($credentials['userId']) || !isset($credentials['password']) - || !strlen((string) $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); }