Mail IMAP Server

IMAP4rev2 mailbox-access listener (RFC 9051; obsoletes RFC 3501). Modern MUAs (Thunderbird, Apple Mail, mutt, K-9, FairEmail, etc.) connect here to read + manage messages without operators running dovecot / cyrus alongside. Composes the framework's existing substrates:

- b.guardImapCommand for wire-protocol shape + smuggling defense (literal-injection, bare-CR/LF refusal, per-verb shape, RFC 9051 §2.2.2 literal framing) - b.mail.server.rateLimit for per-IP DoS defense (concurrent + rate + AUTH-failure budget + slow-loris) - b.mailStore (operator-supplied backend) for the actual mail storage + UIDVALIDITY + modseq tracking - operator-supplied authenticator for SASL credential verify - b.mail.server.tls recommended for cert + key loading + rotation

## State machine (RFC 9051 §3)

NOT-AUTHENTICATED → [STARTTLS → NOT-AUTH-TLS] → AUTH/LOGIN →
AUTHENTICATED ↔ SELECTED → LOGOUT
                             ↑ EXAMINE  ↓ CLOSE / UNSELECT

Commands gated by state:

- NOT-AUTHENTICATED: STARTTLS / AUTHENTICATE / LOGIN / NOOP / CAPABILITY / LOGOUT / ID - AUTHENTICATED: SELECT / EXAMINE / CREATE / DELETE / RENAME / SUBSCRIBE / UNSUBSCRIBE / LIST / STATUS / APPEND / NAMESPACE / IDLE / ENABLE / NOOP / CAPABILITY / LOGOUT / ID - SELECTED: CHECK / CLOSE / UNSELECT / EXPUNGE / SEARCH / FETCH / STORE / COPY / MOVE / UID … / IDLE / NOOP / CAPABILITY / LOGOUT + every AUTHENTICATED command

Tagged response model: every client command carries a tag (A001 LOGIN …); server replies with one or more untagged responses (* …) then A001 OK … / A001 NO … / A001 BAD ….

## Wire-protocol defenses

- **STARTTLS stripping (CVE-2021-33515 Dovecot class)** — STARTTLS upgrade clears pre-handshake receive buffer; any pipelined command queued before TLS is refused with BAD Pipelined post-STARTTLS not permitted.

- **Literal-injection / command-continuation smuggling** — {n} literal continuation MUST come on a line of its own (per b.guardImapCommand.detectLiteralSmuggling); oversize literals refused (default 64 MiB); LITERAL+ (RFC 7888) non- synchronizing literals only honored post-AUTH.

- **Mailbox-name traversal** — mailbox path components validated through _validateMailboxName: refuses .., NUL, control chars, oversize. UTF-8 mailbox names (RFC 9051 §5.1) accepted; modified-UTF7 (RFC 3501 §5.1.3 legacy) refused unless allowLegacyMUtf7: true.

- **APPEND-flood** — per-tenant byte/sec cap surfaces via the b.mail.server.rateLimit's minBytesPerSecond floor on the APPEND-literal-body phase (same shape the MX listener uses for DATA-body).

- **Resource exhaustion** — per-line cap (default 8 KiB sans literal payload), per-literal cap (64 MiB), per-connection idle cap (default 30 min when not in IDLE; IDLE itself capped at 29 min per RFC 2177 §3 to force re-issue).

- **Connection-rate + AUTH-failure budget** — composes b.mail.server.rateLimit. Each AUTH failure increments the budget; trip the cap and new AUTH attempts get * BAD Too many AUTH failures + connection close.

## Audit lifecycle

## What v1 does NOT ship

- **SEARCH** — operator wires opts.search(actor, mailbox, query) when ready; the listener emits BAD search-not-configured until then. SEARCH expressions are operator-domain logic against the mailStore index. - **NOTIFY (RFC 5465)**, **METADATA (RFC 5464)**, **CATENATE (RFC 4469)**, **URLAUTH (RFC 4467)**, **IMAPSIEVE (RFC 6785)**, **COMPRESS=DEFLATE (RFC 4978)** — opt-in / refused. - **CONDSTORE / QRESYNC (RFC 7162)** — modseq is exposed via STATUS but per-FETCH CHANGEDSINCE delta is operator-side follow-up.

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

stable0.9.49
{
  tlsContext:        SecureContext,   // required (no plaintext mode)
  greeting:          string,           // default "blamejs IMAP4rev2"
  maxLineBytes:      number,           // default 8192
  maxLiteralBytes:   number,           // default 64 MiB
  idleTimeoutMs:     number,           // default 30 min
  profile:           "strict" | "balanced" | "permissive",
  auth: {
    mechanisms:      ["PLAIN", "LOGIN", "SCRAM-SHA-256", "EXTERNAL", "XOAUTH2"],
    verify:          async function (mechanism, credentials) → { ok, actor },
  },
  mailStore:         b.mailStore handle,    // required
  rateLimit:         b.mail.server.rateLimit handle | opts | false,
  audit:             b.audit                // optional
}

Build an IMAP4rev2 listener (RFC 9051). The handle exposes listen({ port, address }) → ephemeral-bind promise resolving to { port, address }, plus close() for graceful shutdown.

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

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