IMAP (RFC 3501): LOGIN through UID, previously all stubs except CAPABILITY/LOGOUT/a partial APPEND. Multi-folder support with per-folder UID/flag indexing (new MailFolder class, backed by .uidvalidity/.uidnext/ .imapindex sidecar files), ENVELOPE support (new MessageHeaders class: header parsing, address-list parsing, IMAP quoting), and shared cursor-based argument parsing (new IMAPArgs class: sequence sets, flag lists, dates, folder-name normalization, and path-traversal protection via isValidFolderName - a bare ".." folder name would otherwise resolve into a sibling user's mailbox directory). POP3 (RFC 1939): USER through QUIT, previously only CAPA. Added UIDL. POP3 handlers use cursor-based parsing throughout rather than indexed access - ZString's copy constructor doesn't copy its `list` field, and POP3's dispatch passes the request by value, so indexed access into a copy was silently reading nothing. SMTP: DMARC evaluation added to the DATA-terminator path (DKIM verification + alignment/policy, Authentication-Results header, conditional rejection matching RFC 7489 6.3's p=reject + sampled pct=). AUTH LOGIN now handles RFC 4954's optional inline initial response (e.g. Python smtplib's "AUTH LOGIN <base64>" in one line) instead of only the exact three-line form. Outbound relay now forces IPv4 (AF_INET) instead of AF_UNSPEC - this server's IPv4 has a correct PTR record and SPF coverage, its IPv6 doesn't, and Gmail was rejecting outbound mail with 550 5.7.25 as a result. Delivery pipeline: local delivery now runs the message through SpamAssassin (new SpamAssassinFilter class, speaking spamd's PROCESS protocol directly over a raw socket - fails open/delivers unscanned if spamd is unreachable) instead of a plain hardlink, since scanning can tag/modify content. BARANTMailConfig: extracted config loading into its own class. compile: (see individual file changes - build script adjustments to match the above). Deliberately excluded from this commit: barantmail.log/ barantmail-service.log (operational logs, not source - barantmail.log in particular was the runaway 55GB/12.9GB file from a busy-loop bug fixed separately in ServerCore) and barantmail.cfg (this server's own live deployment config, matching how APIServer's equivalent config also stays untracked).
69 lines
2.3 KiB
Python
69 lines
2.3 KiB
Python
import imaplib
|
|
import ssl
|
|
import sys
|
|
|
|
HOST = "127.0.0.1"
|
|
USER = "barant@barant.com"
|
|
PASS = "imaptest123"
|
|
|
|
ctx = ssl.create_default_context()
|
|
ctx.check_hostname = False
|
|
ctx.verify_mode = ssl.CERT_NONE
|
|
|
|
def step(name, fn):
|
|
try:
|
|
result = fn()
|
|
print(f"OK {name}: {result}")
|
|
return result
|
|
except Exception as e:
|
|
print(f"FAIL {name}: {e}")
|
|
raise
|
|
|
|
m = imaplib.IMAP4_SSL(HOST, 993, ssl_context=ctx)
|
|
print("connected:", m.welcome)
|
|
|
|
step("LOGIN", lambda: m.login(USER, PASS))
|
|
step("CAPABILITY", lambda: m.capability())
|
|
step("LIST", lambda: m.list())
|
|
typ, data = step("SELECT INBOX", lambda: m.select("INBOX"))
|
|
print(" EXISTS:", data)
|
|
|
|
typ, data = step("SEARCH ALL", lambda: m.search(None, "ALL"))
|
|
ids = data[0].split()
|
|
print(f" {len(ids)} messages")
|
|
|
|
if ids:
|
|
first = ids[0]
|
|
typ, data = step("FETCH ENVELOPE", lambda: m.fetch(first, "(ENVELOPE)"))
|
|
print(" ", data)
|
|
typ, data = step("FETCH FLAGS UID", lambda: m.fetch(first, "(FLAGS UID)"))
|
|
print(" ", data)
|
|
typ, data = step("FETCH BODY[]", lambda: m.fetch(first, "(BODY[])"))
|
|
print(" body length:", len(data[0][1]) if data and data[0] else 0)
|
|
step("STORE +FLAGS \\Flagged", lambda: m.store(first, "+FLAGS", "\\Flagged"))
|
|
typ, data = step("SEARCH FLAGGED", lambda: m.search(None, "FLAGGED"))
|
|
print(" flagged ids:", data)
|
|
step("STORE -FLAGS \\Flagged", lambda: m.store(first, "-FLAGS", "\\Flagged"))
|
|
|
|
step("STATUS INBOX", lambda: m.status("INBOX", "(MESSAGES UIDNEXT UIDVALIDITY UNSEEN)"))
|
|
|
|
# APPEND a literal message, then verify it shows up, then clean it up via COPY+EXPUNGE
|
|
msg = b"From: test@barant.com\r\nTo: barant@barant.com\r\nSubject: IMAP APPEND test\r\n\r\nHello from APPEND.\r\n"
|
|
typ, data = step("APPEND", lambda: m.append("INBOX", "", None, msg))
|
|
print(" ", data)
|
|
|
|
typ, data = step("SEARCH SUBJECT", lambda: m.search(None, 'SUBJECT "APPEND test"'))
|
|
appended_ids = data[0].split()
|
|
print(" found:", appended_ids)
|
|
|
|
if appended_ids:
|
|
step("COPY to Trash", lambda: m.copy(appended_ids[-1], "Trash"))
|
|
step("STORE +FLAGS \\Deleted", lambda: m.store(appended_ids[-1], "+FLAGS", "\\Deleted"))
|
|
step("EXPUNGE", lambda: m.expunge())
|
|
|
|
typ, data = step("SELECT Trash", lambda: m.select("Trash"))
|
|
print(" Trash EXISTS:", data)
|
|
|
|
step("LOGOUT", lambda: m.logout())
|
|
print("ALL DONE")
|