Mail POP3 Server

POP3 mailbox-access listener (RFC 1939 + RFC 2449 capabilities + RFC 2595 STLS + RFC 5034 SASL AUTH). Opt-in legacy fallback for MUAs that don't speak IMAP — the framework's blamepost roadmap makes JMAP primary and IMAP/POP3 opt-ins; this listener exists so operators with last-decade MUAs (older Outlook profiles, legacy mobile clients, simple device firmware) can still authenticate + pull messages.

## State machine (RFC 1939 §3)

AUTHORIZATION → TRANSACTION → UPDATE → (close)

- **AUTHORIZATION**: STLS / CAPA / USER / PASS / APOP / AUTH / QUIT. After successful USER+PASS / APOP / AUTH the connection enters TRANSACTION. - **TRANSACTION**: STAT / LIST / RETR / DELE / NOOP / RSET / TOP / UIDL / QUIT. DELE marks messages for deletion; actual deletion happens in UPDATE state on QUIT. - **UPDATE**: triggered by QUIT from TRANSACTION; the listener calls mailStore.commitPop3Drop(actor, dropId) to apply the pending deletes atomically, then closes.

## Wire-protocol defenses

- **Cleartext-auth refusal under strict** — RFC 1939 USER/PASS sends the password in plaintext. Strict + balanced profiles refuse USER/PASS pre-TLS; operators with legacy clients pass profile: "permissive".

- **STLS injection (CVE-2021-33515 class)** — STLS upgrade clears pre-handshake receive buffer; any pipelined command queued before TLS is dropped.

- **APOP refusal under strict** — RFC 1939 §7 APOP uses MD5 challenge-response. M³AAWG / NIST SP 800-131A r2 phase out MD5; the strict profile refuses APOP.

- **Per-IP rate limit + AUTH-failure budget** — composes b.mail.server.rateLimit (default-on). The submission listener's authFailuresPerIpPer15Min cap applies to USER+PASS / APOP / AUTH refusals.

- **Slow-loris on RETR / TOP** — per-connection idleTimeoutMs bounds dead connections; b.mail.server.rateLimit.minBytesPerSecond bounds trickle-receive class.

## Audit lifecycle

## What v1 does NOT ship

- **APOP** — refused under strict + balanced; permissive opts in. APOP uses MD5; modern deployments use TLS + USER/PASS or SASL instead. - **SASL mechanisms beyond PLAIN** — CRAM-MD5 / SCRAM-SHA-256 / OAUTHBEARER all wire through operator's auth.verify. v1 advertises PLAIN only; operators add via auth.mechanisms. - **Multi-step SASL exchange** — single-step PLAIN is sufficient for the v1 surface; SCRAM round-trip ships when an operator surfaces demand. - **Per-message lock** — POP3 has no native message-id beyond UIDL; concurrent connections from the same actor compete via mailStore.openPop3Drop({ exclusive: true }).

## Composition contract

- b.guardPop3Command — wire-protocol gate - b.mail.server.rateLimit — DoS defense - b.mailStore — operator-supplied backend (must expose openPop3Drop(actor, opts) / commitPop3Drop(actor, dropId) / getMessage(actor, dropId, msgNum, { headersOnly?, headerLines? }) / listMessages(actor, dropId) / markDelete(actor, dropId, msgNum)) - operator's auth.verify(mechanism, credentials) async predicate - b.network.tls.context — TLS posture

b.mail.server.pop3.create(opts) #

stable0.9.52
{
  tlsContext:        SecureContext,           // required (no plaintext)
  greeting:          string,                   // default "blamejs POP3"
  maxLineBytes:      number,                   // default 1024
  idleTimeoutMs:     number,                   // default 10 min
  commitTimeoutMs:   number,                   // default 30 s (UPDATE-state mailStore.commitPop3Drop cap)
  profile:           "strict" | "balanced" | "permissive",
  auth: {
    mechanisms:      ["PLAIN"],                 // SASL mechs to advertise
    verify:          async function (mech, credentials) → { ok, actor },
  },
  mailStore:         b.mailStore handle,
  rateLimit:         b.mail.server.rateLimit handle | opts | false,
  audit:             b.audit
}

Build a POP3 listener (RFC 1939). Returns a handle exposing listen({ port, address }) and close(). POP3 is opt-in legacy — deployments should prefer b.mail.server.imap + b.mail.server.jmap for new MUAs.

var pop3 = b.mail.server.pop3.create({
  tlsContext: b.mail.server.tls.context({ certFile, keyFile }).secureContext,
  auth: {
    mechanisms: ["PLAIN"],
    verify:     async function (mech, creds) {
      return { ok: true, actor: { username: creds.authzid, tenantId: "t1" } };
    },
  },
  mailStore: b.mailStore.create({ backend: b.db.handle() }),
});
await pop3.listen({ port: 110 });

Last updated 2026-08-08T16:39:49.652Z by seeder.