feat: ignore client-supplied POP3 credentials
Build and publish container / build (pull_request) Successful in 9m23s

POP3 clients may be required to send USER/PASS, but the proxy never uses
them. Accept any client credentials blindly and always authenticate to the
IMAP backend with the configured BACKEND_IMAP_USER / BACKEND_IMAP_PASS.

Remove the previous fallback that connected to the backend using
client-supplied credentials when backend credentials were unset; the proxy
now raises a clear configuration error in that case.

Add tests covering that client credentials are ignored and that missing
backend credentials are reported.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Emma Thorpe
2026-06-17 18:07:25 +01:00
parent e51740b8db
commit 78a3c21ac7
2 changed files with 60 additions and 16 deletions
+17 -16
View File
@@ -253,30 +253,31 @@ class POP3Session:
return await self.send_line("-ERR Unsupported command")
async def handle_user(self, args):
if len(args) != 1:
return await self.send_line("-ERR USER requires username")
self.username = args[0]
# Accept any username. Client credentials are intentionally ignored;
# some legacy clients insist on supplying them, so they are accepted
# blindly. The backend is always reached with the proxy's own creds.
self.username = args[0] if args else None
return await self.send_line("+OK")
async def handle_pass(self, args):
if len(args) != 1:
return await self.send_line("-ERR PASS requires password")
self.password = args[0]
# Accept any password. See handle_user: client credentials are
# accepted but never used or validated.
self.password = args[0] if args else None
await asyncio.to_thread(self.authenticate)
return await self.send_line("+OK User authenticated")
def authenticate(self):
"""Authenticate to the IMAP backend using configured credentials."""
if Settings.BACKEND_IMAP_USER and Settings.BACKEND_IMAP_PASS:
username = Settings.BACKEND_IMAP_USER
password = Settings.BACKEND_IMAP_PASS
elif self.username and self.password:
username = self.username
password = self.password
else:
raise RuntimeError("No IMAP credentials available")
"""Authenticate to the IMAP backend using the configured proxy credentials.
backend = IMAPBackend(username, password)
Client-supplied POP3 credentials are deliberately ignored: the proxy
always connects to the backend with ``BACKEND_IMAP_USER`` /
``BACKEND_IMAP_PASS``. This is by design for legacy clients that require
credentials to be entered even though the proxy does not use them.
"""
if not (Settings.BACKEND_IMAP_USER and Settings.BACKEND_IMAP_PASS):
raise RuntimeError("Backend IMAP credentials are not configured")
backend = IMAPBackend(Settings.BACKEND_IMAP_USER, Settings.BACKEND_IMAP_PASS)
backend.login()
self._imap = backend
# Snapshot the maildrop once; it stays static for the session lifetime