feat: ignore client-supplied POP3 credentials (#15)
Build and publish container / build (push) Successful in 12m15s

Accept any POP3 `USER`/`PASS` from the client and discard them. The proxy always authenticates to the IMAP backend with the configured `BACKEND_IMAP_USER` / `BACKEND_IMAP_PASS`.

## Changes

- `handle_user` / `handle_pass`: accept client credentials unconditionally, no validation.
- `authenticate`: always use backend credentials; remove the fallback that connected with client-supplied credentials when backend credentials were unset. Raise a clear configuration error when backend credentials are missing.
- Tests: client credentials are ignored; missing backend credentials are reported.

Closes #14

---------

Co-authored-by: Emma Thorpe <emma.thorpe@citrix.com>
Reviewed-on: #15
This commit was merged in pull request #15.
This commit is contained in:
2026-06-17 18:16:37 +01:00
parent e51740b8db
commit 9a4bab33e2
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