From b3770c4f2d867abf1a469f19471dbdf85dfd0200 Mon Sep 17 00:00:00 2001 From: Michael Bartel Date: Fri, 26 Jun 2026 20:52:38 +0200 Subject: [PATCH 1/3] Append domain to login before trying IMAP connection Append email domain to login before trying connect to the IMAP server. Use this instead of strip_domain, which removes the domain before trying the IMAP connection, to avoid the domain in the folder names of the collection. --- DOCUMENTATION.md | 8 ++++++++ config | 4 ++++ radicale/auth/imap.py | 20 +++++++++++++++----- radicale/config.py | 4 ++++ 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index c76f70e2..98caf814 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -1389,6 +1389,14 @@ One of: Default: `tls` +##### imap_append_domain + +_(>= 3.7.6)_ + +Append domain to login before trying IMAP connection like @domain.tld + +Default: (unset) + ##### oauth2_token_endpoint _(>= 3.5.0)_ diff --git a/config b/config index 904986a6..fa15ef4e 100644 --- a/config +++ b/config @@ -161,6 +161,10 @@ # Value: tls | starttls | none #imap_security = tls +# Append domain to login before trying IMAP connection +# Value: none | @domain.tld +#imap_append_domain = + # OAuth2 token endpoint URL #oauth2_token_endpoint = diff --git a/radicale/auth/imap.py b/radicale/auth/imap.py index f0d52b47..d34d00c7 100644 --- a/radicale/auth/imap.py +++ b/radicale/auth/imap.py @@ -48,6 +48,11 @@ class Auth(auth.BaseAuth): logger.info("auth imap port (autoselected): %d", self._port) else: logger.info("auth imap port: %d", self._port) + self._append_domain = self.configuration.get("auth", "imap_append_domain") + if self._append_domain == "none": + logger.info("auth imap append domain not used") + else: + logger.info("auth imap append domain: %s", self._append_domain) def _login(self, login, password) -> str: try: @@ -63,21 +68,26 @@ class Auth(auth.BaseAuth): connection = imaplib.IMAP4(host=self._host, port=self._port) if self._security == "starttls": connection.starttls(ssl.create_default_context()) + if not self._append_domain: + imaplogin = login + else: + imaplogin = login + self._append_domain try: if "AUTH=PLAIN" in connection.capabilities: - logger.debug("IMAP authentication PLAIN selected for user %r via %s:%d (security: %s)", login, self._host, self._port, self._security) + logger.debug("IMAP authentication PLAIN selected for user %r via %s:%d (security: %s)", imaplogin, self._host, self._port, self._security) connection.authenticate( "PLAIN", - lambda _: "{0}\x00{0}\x00{1}".format(login, password).encode(), + lambda _: "{0}\x00{0}\x00{1}".format(imaplogin, password).encode(), ) elif "AUTH=LOGIN" in connection.capabilities: - logger.debug("IMAP authentication LOGIN selected for user %r via %s:%d (security: %s)", login, self._host, self._port, self._security) - connection.login(login, password) + logger.debug("IMAP authentication LOGIN selected for user %r via %s:%d (security: %s)", imaplogin, self._host, self._port, self._security) + print("using imaplogin: %r", imaplogin) + connection.login(imaplogin, password) else: logger.error("IMAP server is neither supporting AUTH=PLAIN or AUTH=LOGIN: %s:%d (security: %s)", self._host, self._port, self._security) return "" except imaplib.IMAP4.error as e: - logger.warning("IMAP authentication failed for user %r: %s", login, e, exc_info=False) + logger.warning("IMAP authentication failed for user %r: %s", imaplogin, e, exc_info=False) return "" connection.logout() return login diff --git a/radicale/config.py b/radicale/config.py index cf390a2d..6b2fd766 100644 --- a/radicale/config.py +++ b/radicale/config.py @@ -440,6 +440,10 @@ DEFAULT_CONFIG_SCHEMA: types.CONFIG_SCHEMA = OrderedDict([ "value": "tls", "help": "Secure the IMAP connection: *tls*|starttls|none", "type": imap_security}), + ("imap_append_domain", { + "value": "", + "help": "Append domain to login before trying IMAP connection like @domain.tld", + "type": str}), ("oauth2_token_endpoint", { "value": "", "help": "OAuth2 token endpoint URL", From 38a4715a7f64723ada3eea5a3e69a42edb96309e Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Sun, 5 Jul 2026 12:54:57 +0200 Subject: [PATCH 2/3] imap_append_domain: make "@" mandatory and add some failsafe checks --- DOCUMENTATION.md | 2 +- config | 4 ++-- radicale/auth/imap.py | 7 +++---- radicale/config.py | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 98caf814..7bfa9e72 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -1393,7 +1393,7 @@ Default: `tls` _(>= 3.7.6)_ -Append domain to login before trying IMAP connection like @domain.tld +Append `@` + domain to login before trying IMAP connection Default: (unset) diff --git a/config b/config index fa15ef4e..2bd39383 100644 --- a/config +++ b/config @@ -161,8 +161,8 @@ # Value: tls | starttls | none #imap_security = tls -# Append domain to login before trying IMAP connection -# Value: none | @domain.tld +# Append '@' + domain to login before trying IMAP connection (optional) +# Value: domain.tld #imap_append_domain = # OAuth2 token endpoint URL diff --git a/radicale/auth/imap.py b/radicale/auth/imap.py index d34d00c7..e9a1dfd4 100644 --- a/radicale/auth/imap.py +++ b/radicale/auth/imap.py @@ -49,7 +49,7 @@ class Auth(auth.BaseAuth): else: logger.info("auth imap port: %d", self._port) self._append_domain = self.configuration.get("auth", "imap_append_domain") - if self._append_domain == "none": + if self._append_domain is None or len(self._append_domain) == 0: logger.info("auth imap append domain not used") else: logger.info("auth imap append domain: %s", self._append_domain) @@ -68,10 +68,10 @@ class Auth(auth.BaseAuth): connection = imaplib.IMAP4(host=self._host, port=self._port) if self._security == "starttls": connection.starttls(ssl.create_default_context()) - if not self._append_domain: + if self._append_domain is None or len(self._append_domain) == 0: imaplogin = login else: - imaplogin = login + self._append_domain + imaplogin = login + "@" + self._append_domain try: if "AUTH=PLAIN" in connection.capabilities: logger.debug("IMAP authentication PLAIN selected for user %r via %s:%d (security: %s)", imaplogin, self._host, self._port, self._security) @@ -81,7 +81,6 @@ class Auth(auth.BaseAuth): ) elif "AUTH=LOGIN" in connection.capabilities: logger.debug("IMAP authentication LOGIN selected for user %r via %s:%d (security: %s)", imaplogin, self._host, self._port, self._security) - print("using imaplogin: %r", imaplogin) connection.login(imaplogin, password) else: logger.error("IMAP server is neither supporting AUTH=PLAIN or AUTH=LOGIN: %s:%d (security: %s)", self._host, self._port, self._security) diff --git a/radicale/config.py b/radicale/config.py index 6b2fd766..9554e4ff 100644 --- a/radicale/config.py +++ b/radicale/config.py @@ -442,7 +442,7 @@ DEFAULT_CONFIG_SCHEMA: types.CONFIG_SCHEMA = OrderedDict([ "type": imap_security}), ("imap_append_domain", { "value": "", - "help": "Append domain to login before trying IMAP connection like @domain.tld", + "help": "Append '@' + domain to login before trying IMAP connection", "type": str}), ("oauth2_token_endpoint", { "value": "", From 76d7dc3997833aaa6f1eb3b519ea5db8e88bc970 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Sun, 5 Jul 2026 12:58:39 +0200 Subject: [PATCH 3/3] imap_append_domain: changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 559a2d5d..49fc78fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Improvement: catch lack of support of PERIOD in vobject <= 0.9.9 * Fix: sharing: backmap of REPORT/PROPPATCH hrefs is now URL-encode-aware (edit of a shared collection failed when the principal contains '@') * Workaround: remove trailing spaces on TZID and TZNAME appended by buggy Microsoft clients +* Extension: [auth] imap_append_domain option (optional) ## 3.7.5 * Add: [sharing] conversion_bday_summary_template (customize summary)