Mail ManageSieve Server
ManageSieve listener (RFC 5804 — "A Protocol for Remotely Managing Sieve Scripts"). Lets MUAs upload, replace, list, activate, fetch, delete, and rename Sieve filter scripts on the server. Composes b.safeSieve.validate for pre-storage validation per RFC 5804 §2.3: "An implementation MUST verify the script's validity ... and MUST reject scripts which fail validity tests."
## State machine (RFC 5804 §1)
NOT-AUTHENTICATED → STARTTLS → AUTHENTICATED → LOGOUT
- **NOT-AUTHENTICATED**: CAPABILITY / NOOP / STARTTLS / AUTHENTICATE / LOGOUT. The listener sends an unsolicited capability banner on connect (RFC 5804 §1.7). - **STARTTLS** (transient): triggered by STARTTLS. Pre-handshake receive buffer is drained before the TLS upgrade to defend the STARTTLS-injection class (CVE-2021-38371 / CVE-2021-33515 / CVE-2011-0411). Capabilities are re-emitted post-TLS so the client sees the post-TLS mechanism list (RFC 5804 §2.2). - **AUTHENTICATED**: HAVESPACE / PUTSCRIPT / LISTSCRIPTS / SETACTIVE / GETSCRIPT / DELETESCRIPT / RENAMESCRIPT / NOOP / CAPABILITY / LOGOUT.
## Wire-protocol defenses
- **No-implicit-plaintext** — opts.tlsContext is required at create(). Operators that genuinely need plaintext (intra-rack testing) explicitly pass allowPlaintext: true, which emits a mail.server.managesieve.plaintext_warning audit on every boot.
- **AUTHENTICATE-mechanism advertisement parity** — CAPABILITY output advertises ONLY the mechanisms listed in opts.auth.mechanisms. The framework hardcodes no defaults; an operator who omits mechanisms gets a listener that refuses every AUTHENTICATE attempt with "mechanism not advertised" (otherwise advertising AUTH=PLAIN when authConfig is null sets clients up to attempt PLAIN against a listener that hasn't wired the verifier).
- **Cleartext-AUTH refusal under strict** — RFC 5804 §1.1 + RFC 4954 §4. AUTHENTICATE PLAIN / LOGIN / SCRAM* pre-TLS under strict refused at both the validator and the dispatch boundary. AUTHENTICATE EXTERNAL exempt (TLS client-cert credential, not a password).
- **STARTTLS injection (CVE-2021-33515 class)** — STARTTLS upgrade clears the per-connection receive buffer; any pipelined command queued before the upgrade is discarded. Capabilities are re-emitted on the post-TLS socket per RFC 5804 §2.2.
- **PUTSCRIPT pre-validation (RFC 5804 §2.3)** — every PUTSCRIPT payload is parsed via b.safeSieve.validate before mailStore.sieveScripts.put. Invalid scripts are refused with NO (QUOTA/MAXSCRIPTS) "..." per §2.3 + audited with the safe-sieve/... issue code so operators can correlate refusals.
- **Per-IP rate limit + AUTH-failure budget** — composes b.mail.server.rateLimit (default-on). Brute-force protection applies to AUTHENTICATE failures identically to POP3/IMAP.
## Audit lifecycle
mail.server.managesieve.connect— IP, TLS statemail.server.managesieve.auth_attempt— mechmail.server.managesieve.auth_success— mech, tenantIdmail.server.managesieve.auth_failed— mech, reasonmail.server.managesieve.auth_refused_cleartext— mechmail.server.managesieve.starttls_upgradedmail.server.managesieve.starttls_handshake_failedmail.server.managesieve.putscript— name, bytesmail.server.managesieve.putscript_refused— name, reason (safeSieve issue code)mail.server.managesieve.getscript— namemail.server.managesieve.listscripts— countmail.server.managesieve.setactive— name (empty == deactivate-all)mail.server.managesieve.delete— namemail.server.managesieve.rename— old, newmail.server.managesieve.havespace— name, size, okmail.server.managesieve.logoutmail.server.managesieve.listening— port, addressmail.server.managesieve.closedmail.server.managesieve.socket_errormail.server.managesieve.handler_threw— verb, error
## What v1 does NOT ship
- **CHECKSCRIPT** (RFC 5804 §2.12) — parse-only verb. Operators who want it compose b.safeSieve.validate directly via JMAP SieveScript/validate (RFC 9661). The MTA-side ManageSieve surface is PUTSCRIPT + HAVESPACE; CHECKSCRIPT adds a third entry point with no operator demand yet. - **UNAUTHENTICATE** (RFC 5804 §2.14) — exotic. Operators close the TCP connection or send LOGOUT + reconnect.
## Composition contract
- b.guardManageSieveCommand — wire-protocol gate - b.safeSieve.validate — PUTSCRIPT pre-validation - b.mail.server.rateLimit — DoS defense - b.mailStore — operator-supplied backend (must expose sieveScripts.put(actor, name, body) / sieveScripts.list(actor) / sieveScripts.get(actor, name) / sieveScripts.setActive(actor, name) / sieveScripts.delete(actor, name) / sieveScripts.rename(actor, oldName, newName) / sieveScripts.haveSpace(actor, name, size)) - operator's auth.verify(mechanism, credentials) async predicate - b.network.tls.context — TLS posture
b.mail.server.managesieve.create(opts) #
{
tlsContext: SecureContext, // required (no implicit plaintext)
allowPlaintext: boolean, // explicit opt-in; emits warning audit
greeting: string, // default "blamejs ManageSieve"
maxLineBytes: number, // default 8192
idleTimeoutMs: number, // default 5 min
profile: "strict" | "balanced" | "permissive", // default "strict"
auth: {
mechanisms: ["SCRAM-SHA-256", "OAUTHBEARER", ...], // SASL mechs to advertise
verify: async function (mech, credentials) → { ok, actor },
},
mailStore: b.mailStore handle, // must expose sieveScripts.*
rateLimit: b.mail.server.rateLimit handle | opts | false,
audit: b.audit
}
Build a ManageSieve listener (RFC 5804). Returns a handle exposing listen({ port, address }) and close(). Composes b.safeSieve for PUTSCRIPT pre-validation per RFC 5804 §2.3.
var msv = b.mail.server.managesieve.create({
tlsContext: b.mail.server.tls.context({ certFile, keyFile }).secureContext,
auth: {
mechanisms: ["SCRAM-SHA-256", "OAUTHBEARER", "EXTERNAL"],
verify: async function (mech, creds) {
return { ok: true, actor: { username: creds.authzid, tenantId: "t1" } };
},
},
mailStore: b.mailStore.create({ backend: b.db.handle() }),
});
await msv.listen({ port: 4190 });
Last updated 2026-08-08T16:39:49.652Z by seeder.