@blamejs/core 0.15.12 → 0.15.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/index.js +2 -0
- package/lib/a2a-tasks.js +45 -29
- package/lib/acme.js +6 -5
- package/lib/agent-event-bus.js +18 -4
- package/lib/agent-idempotency.js +7 -6
- package/lib/agent-orchestrator.js +2 -5
- package/lib/agent-saga.js +3 -5
- package/lib/agent-snapshot.js +32 -2
- package/lib/agent-tenant.js +2 -5
- package/lib/ai-adverse-decision.js +2 -15
- package/lib/ai-aedt-bias-audit.js +2 -1
- package/lib/ai-capability.js +1 -6
- package/lib/ai-content-detect.js +1 -3
- package/lib/ai-dp.js +1 -5
- package/lib/ai-frontier-protocol.js +1 -1
- package/lib/ai-input.js +2 -2
- package/lib/ai-model-manifest.js +1 -1
- package/lib/ai-output.js +16 -7
- package/lib/ai-pref.js +3 -8
- package/lib/ai-quota.js +3 -14
- package/lib/api-key.js +37 -28
- package/lib/api-snapshot.js +4 -1
- package/lib/app-shutdown.js +7 -1
- package/lib/archive-adapters.js +2 -4
- package/lib/archive-entry-policy.js +32 -0
- package/lib/archive-gz.js +9 -0
- package/lib/archive-read.js +14 -24
- package/lib/archive-tar-read.js +56 -24
- package/lib/archive.js +6 -12
- package/lib/arg-parser.js +7 -6
- package/lib/asn1-der.js +70 -22
- package/lib/asyncapi-traits.js +2 -6
- package/lib/atomic-file.js +312 -33
- package/lib/audit-chain.js +183 -54
- package/lib/audit-daily-review.js +36 -16
- package/lib/audit-emit.js +82 -0
- package/lib/audit-sign.js +258 -0
- package/lib/audit-tools.js +108 -23
- package/lib/audit.js +91 -2
- package/lib/auth/access-lock.js +7 -29
- package/lib/auth/bot-challenge.js +1 -1
- package/lib/auth/ciba.js +43 -4
- package/lib/auth/dpop.js +29 -36
- package/lib/auth/fido-mds3.js +14 -16
- package/lib/auth/jwt-external.js +26 -8
- package/lib/auth/jwt.js +15 -17
- package/lib/auth/lockout.js +24 -27
- package/lib/auth/oauth.js +67 -45
- package/lib/auth/oid4vci.js +55 -32
- package/lib/auth/oid4vp.js +3 -2
- package/lib/auth/openid-federation.js +6 -6
- package/lib/auth/passkey.js +4 -4
- package/lib/auth/password.js +8 -2
- package/lib/auth/saml.js +70 -40
- package/lib/auth/sd-jwt-vc-holder.js +2 -14
- package/lib/auth/sd-jwt-vc-issuer.js +2 -14
- package/lib/auth/sd-jwt-vc.js +25 -5
- package/lib/auth/status-list.js +21 -9
- package/lib/auth/step-up.js +15 -13
- package/lib/auth-bot-challenge.js +27 -39
- package/lib/backup/bundle.js +18 -20
- package/lib/backup/crypto.js +23 -8
- package/lib/backup/index.js +55 -67
- package/lib/backup/manifest.js +7 -1
- package/lib/bounded-map.js +112 -1
- package/lib/breach-deadline.js +1 -11
- package/lib/break-glass.js +41 -22
- package/lib/cache.js +7 -18
- package/lib/cbor.js +36 -12
- package/lib/cdn-cache-control.js +15 -12
- package/lib/cert.js +8 -13
- package/lib/chain-writer.js +162 -47
- package/lib/cli.js +10 -2
- package/lib/client-hints.js +7 -9
- package/lib/cloud-events.js +43 -33
- package/lib/cluster-storage.js +9 -41
- package/lib/cms-codec.js +2 -1
- package/lib/codepoint-class.js +84 -1
- package/lib/compliance-ai-act-logging.js +1 -6
- package/lib/compliance-ai-act-transparency.js +2 -4
- package/lib/compliance-eaa.js +2 -12
- package/lib/compliance-sanctions-fetcher.js +21 -28
- package/lib/compliance-sanctions.js +11 -21
- package/lib/compliance.js +3 -10
- package/lib/config-drift.js +24 -20
- package/lib/content-credentials.js +11 -8
- package/lib/content-digest.js +21 -12
- package/lib/cookies.js +15 -13
- package/lib/cose.js +39 -11
- package/lib/cra-report.js +1 -11
- package/lib/crdt.js +2 -1
- package/lib/crypto-field.js +34 -33
- package/lib/crypto.js +138 -7
- package/lib/csp.js +239 -3
- package/lib/daemon.js +46 -42
- package/lib/data-act.js +46 -13
- package/lib/db-file-lifecycle.js +14 -6
- package/lib/db-query.js +75 -49
- package/lib/db.js +66 -27
- package/lib/dbsc.js +5 -6
- package/lib/ddl-change-control.js +18 -14
- package/lib/deprecate.js +4 -5
- package/lib/did.js +6 -2
- package/lib/dora.js +43 -13
- package/lib/dr-runbook.js +1 -1
- package/lib/dsa.js +2 -1
- package/lib/dsr.js +30 -20
- package/lib/early-hints.js +19 -0
- package/lib/eat.js +5 -1
- package/lib/external-db-migrate.js +21 -22
- package/lib/external-db.js +74 -30
- package/lib/fda-21cfr11.js +42 -13
- package/lib/fdx.js +22 -18
- package/lib/file-upload.js +80 -66
- package/lib/flag-evaluation-context.js +5 -8
- package/lib/flag-providers.js +6 -2
- package/lib/forms.js +1 -1
- package/lib/framework-error.js +12 -0
- package/lib/framework-schema.js +19 -0
- package/lib/fsm.js +7 -12
- package/lib/gate-contract.js +911 -39
- package/lib/gdpr-ropa.js +22 -22
- package/lib/graphql-federation.js +18 -5
- package/lib/guard-agent-registry.js +2 -1
- package/lib/guard-all.js +3 -2
- package/lib/guard-archive.js +9 -30
- package/lib/guard-auth.js +23 -80
- package/lib/guard-cidr.js +20 -96
- package/lib/guard-csv.js +135 -196
- package/lib/guard-domain.js +23 -106
- package/lib/guard-dsn.js +17 -14
- package/lib/guard-email.js +46 -53
- package/lib/guard-envelope.js +2 -2
- package/lib/guard-event-bus-topic.js +2 -1
- package/lib/guard-filename.js +12 -60
- package/lib/guard-graphql.js +28 -75
- package/lib/guard-html-wcag.js +15 -2
- package/lib/guard-html.js +74 -128
- package/lib/guard-idempotency-key.js +2 -1
- package/lib/guard-image.js +280 -77
- package/lib/guard-imap-command.js +9 -10
- package/lib/guard-jmap.js +1 -1
- package/lib/guard-json.js +101 -109
- package/lib/guard-jsonpath.js +20 -88
- package/lib/guard-jwt.js +32 -114
- package/lib/guard-list-id.js +2 -7
- package/lib/guard-list-unsubscribe.js +2 -7
- package/lib/guard-mail-compose.js +5 -6
- package/lib/guard-mail-move.js +3 -2
- package/lib/guard-mail-query.js +5 -3
- package/lib/guard-mail-sieve.js +6 -7
- package/lib/guard-managesieve-command.js +6 -5
- package/lib/guard-markdown.js +76 -110
- package/lib/guard-message-id.js +5 -6
- package/lib/guard-mime.js +20 -99
- package/lib/guard-oauth.js +19 -73
- package/lib/guard-pdf.js +65 -72
- package/lib/guard-pop3-command.js +13 -14
- package/lib/guard-posture-chain.js +2 -1
- package/lib/guard-regex.js +24 -99
- package/lib/guard-saga-config.js +2 -1
- package/lib/guard-shell.js +22 -87
- package/lib/guard-smtp-command.js +9 -12
- package/lib/guard-sql.js +15 -13
- package/lib/guard-stream-args.js +2 -1
- package/lib/guard-svg.js +103 -149
- package/lib/guard-template.js +23 -80
- package/lib/guard-tenant-id.js +2 -1
- package/lib/guard-text.js +592 -0
- package/lib/guard-time.js +27 -95
- package/lib/guard-uuid.js +21 -93
- package/lib/guard-xml.js +76 -106
- package/lib/guard-yaml.js +24 -60
- package/lib/html-balance.js +7 -3
- package/lib/http-client-cache.js +5 -12
- package/lib/http-client-cookie-jar.js +35 -16
- package/lib/http-client.js +233 -74
- package/lib/http-message-signature.js +3 -2
- package/lib/i18n-messageformat.js +5 -7
- package/lib/i18n.js +83 -26
- package/lib/iab-tcf.js +3 -2
- package/lib/importmap-integrity.js +41 -1
- package/lib/inbox.js +8 -8
- package/lib/incident-report.js +12 -27
- package/lib/ip-utils.js +49 -6
- package/lib/jobs.js +3 -2
- package/lib/json-patch.js +1 -1
- package/lib/json-path.js +24 -3
- package/lib/jtd.js +2 -2
- package/lib/keychain.js +6 -18
- package/lib/legal-hold.js +30 -23
- package/lib/log-stream-cloudwatch.js +44 -112
- package/lib/log-stream-otlp-grpc.js +17 -14
- package/lib/log-stream-otlp.js +16 -80
- package/lib/log-stream-webhook.js +20 -92
- package/lib/log.js +2 -2
- package/lib/mail-agent.js +2 -2
- package/lib/mail-arc-sign.js +8 -3
- package/lib/mail-arf.js +4 -3
- package/lib/mail-auth.js +43 -69
- package/lib/mail-bimi.js +35 -55
- package/lib/mail-bounce.js +3 -3
- package/lib/mail-crypto-pgp.js +3 -2
- package/lib/mail-crypto-smime.js +71 -6
- package/lib/mail-dav.js +8 -35
- package/lib/mail-deploy.js +15 -14
- package/lib/mail-dkim.js +19 -38
- package/lib/mail-greylist.js +17 -30
- package/lib/mail-helo.js +4 -7
- package/lib/mail-journal.js +13 -9
- package/lib/mail-mdn.js +12 -7
- package/lib/mail-rbl.js +7 -12
- package/lib/mail-scan.js +5 -7
- package/lib/mail-send-deliver.js +33 -20
- package/lib/mail-server-imap.js +32 -87
- package/lib/mail-server-jmap.js +39 -52
- package/lib/mail-server-managesieve.js +29 -83
- package/lib/mail-server-mx.js +33 -74
- package/lib/mail-server-net.js +177 -0
- package/lib/mail-server-pop3.js +30 -83
- package/lib/mail-server-rate-limit.js +30 -6
- package/lib/mail-server-registry.js +1 -1
- package/lib/mail-server-submission.js +34 -73
- package/lib/mail-server-tls.js +98 -2
- package/lib/mail-spam-score.js +9 -12
- package/lib/mail-store-fts.js +1 -1
- package/lib/mail-store.js +3 -2
- package/lib/mail.js +28 -13
- package/lib/markup-escape.js +31 -0
- package/lib/markup-tokenizer.js +78 -0
- package/lib/mcp-tool-registry.js +26 -23
- package/lib/mcp.js +7 -5
- package/lib/mdoc.js +36 -14
- package/lib/metrics.js +46 -33
- package/lib/middleware/age-gate.js +3 -23
- package/lib/middleware/api-encrypt.js +13 -24
- package/lib/middleware/assetlinks.js +2 -7
- package/lib/middleware/bearer-auth.js +2 -1
- package/lib/middleware/body-parser.js +41 -52
- package/lib/middleware/bot-disclose.js +7 -10
- package/lib/middleware/bot-guard.js +28 -28
- package/lib/middleware/clear-site-data.js +5 -1
- package/lib/middleware/compression.js +9 -0
- package/lib/middleware/cors.js +32 -23
- package/lib/middleware/csrf-protect.js +73 -23
- package/lib/middleware/daily-byte-quota.js +12 -30
- package/lib/middleware/deny-response.js +19 -1
- package/lib/middleware/fetch-metadata.js +35 -4
- package/lib/middleware/idempotency-key.js +4 -20
- package/lib/middleware/network-allowlist.js +61 -30
- package/lib/middleware/rate-limit.js +45 -44
- package/lib/middleware/scim-server.js +5 -3
- package/lib/middleware/security-headers.js +33 -7
- package/lib/middleware/security-txt.js +2 -6
- package/lib/middleware/speculation-rules.js +6 -3
- package/lib/middleware/tus-upload.js +14 -29
- package/lib/middleware/web-app-manifest.js +2 -7
- package/lib/migrations.js +4 -13
- package/lib/mime-parse.js +34 -17
- package/lib/module-loader.js +44 -0
- package/lib/money.js +105 -0
- package/lib/mtls-ca.js +125 -41
- package/lib/network-byte-quota.js +4 -18
- package/lib/network-dane.js +8 -6
- package/lib/network-dns-resolver.js +98 -7
- package/lib/network-dns.js +9 -2
- package/lib/network-dnssec.js +18 -17
- package/lib/network-heartbeat.js +3 -2
- package/lib/network-smtp-policy.js +52 -46
- package/lib/network-tls.js +67 -47
- package/lib/network-tsig.js +2 -2
- package/lib/nis2-report.js +2 -12
- package/lib/nist-crosswalk.js +1 -1
- package/lib/nonce-store.js +81 -3
- package/lib/ntp-check.js +28 -0
- package/lib/numeric-bounds.js +31 -8
- package/lib/object-store/azure-blob-bucket-ops.js +2 -6
- package/lib/object-store/azure-blob.js +6 -47
- package/lib/object-store/gcs-bucket-ops.js +4 -2
- package/lib/object-store/gcs.js +14 -75
- package/lib/object-store/http-put.js +2 -7
- package/lib/object-store/http-request.js +157 -0
- package/lib/object-store/local.js +37 -17
- package/lib/object-store/sigv4-bucket-ops.js +2 -1
- package/lib/object-store/sigv4.js +10 -79
- package/lib/observability-otlp-exporter.js +29 -29
- package/lib/observability.js +95 -0
- package/lib/openapi-paths-builder.js +7 -3
- package/lib/otel-export.js +7 -10
- package/lib/outbox.js +54 -41
- package/lib/pagination.js +11 -15
- package/lib/parsers/safe-env.js +5 -4
- package/lib/parsers/safe-ini.js +10 -4
- package/lib/parsers/safe-toml.js +11 -9
- package/lib/parsers/safe-xml.js +3 -3
- package/lib/parsers/safe-yaml.js +28 -9
- package/lib/pick.js +63 -5
- package/lib/pipl-cn.js +69 -59
- package/lib/privacy-pass.js +8 -6
- package/lib/problem-details.js +2 -5
- package/lib/pubsub.js +4 -8
- package/lib/queue-local.js +10 -3
- package/lib/redact.js +9 -4
- package/lib/render.js +4 -2
- package/lib/request-helpers.js +334 -29
- package/lib/resource-access-lock.js +3 -3
- package/lib/restore-bundle.js +46 -18
- package/lib/restore-rollback.js +10 -4
- package/lib/restore.js +24 -22
- package/lib/retention.js +23 -9
- package/lib/router.js +17 -4
- package/lib/safe-async.js +457 -0
- package/lib/safe-buffer.js +137 -6
- package/lib/safe-decompress.js +2 -1
- package/lib/safe-dns.js +2 -1
- package/lib/safe-ical.js +14 -28
- package/lib/safe-icap.js +1 -1
- package/lib/safe-json.js +51 -8
- package/lib/safe-jsonpath.js +6 -5
- package/lib/safe-mime.js +25 -29
- package/lib/safe-redirect.js +2 -5
- package/lib/safe-schema.js +7 -6
- package/lib/safe-sieve.js +1 -1
- package/lib/safe-sql.js +126 -0
- package/lib/safe-vcard.js +13 -27
- package/lib/sandbox-worker.js +15 -2
- package/lib/sandbox.js +4 -3
- package/lib/scheduler.js +22 -13
- package/lib/sec-cyber.js +82 -37
- package/lib/seeders.js +4 -11
- package/lib/self-update-standalone-verifier.js +16 -0
- package/lib/self-update.js +92 -69
- package/lib/session-device-binding.js +112 -66
- package/lib/session.js +219 -7
- package/lib/sql.js +228 -81
- package/lib/sse.js +6 -10
- package/lib/static.js +201 -111
- package/lib/storage.js +4 -2
- package/lib/structured-fields.js +275 -16
- package/lib/template.js +7 -5
- package/lib/tenant-quota.js +53 -38
- package/lib/tsa.js +16 -17
- package/lib/validate-opts.js +154 -8
- package/lib/vault/index.js +5 -0
- package/lib/vault/passphrase-ops.js +22 -26
- package/lib/vault/passphrase-source.js +8 -3
- package/lib/vault/rotate.js +13 -18
- package/lib/vault/seal-pem-file.js +34 -49
- package/lib/vault-aad.js +3 -2
- package/lib/vc.js +3 -8
- package/lib/vendor/MANIFEST.json +10 -10
- package/lib/vendor/public-suffix-list.dat +23 -10
- package/lib/vendor/public-suffix-list.data.js +5498 -5494
- package/lib/vex.js +35 -13
- package/lib/web-push-vapid.js +23 -20
- package/lib/webhook-dispatcher.js +706 -0
- package/lib/webhook.js +59 -19
- package/lib/websocket-channels.js +9 -7
- package/lib/websocket.js +8 -4
- package/lib/worm.js +3 -4
- package/lib/ws-client.js +65 -56
- package/lib/x509-chain.js +44 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/mail-server-mx.js
CHANGED
|
@@ -150,9 +150,10 @@ var guardSmtpCommand = require("./guard-smtp-command");
|
|
|
150
150
|
var guardDomain = require("./guard-domain");
|
|
151
151
|
var mailServerRateLimit = require("./mail-server-rate-limit");
|
|
152
152
|
var mailServerTls = require("./mail-server-tls");
|
|
153
|
+
var mailServerNet = require("./mail-server-net");
|
|
153
154
|
var { defineClass } = require("./framework-error");
|
|
154
155
|
|
|
155
|
-
var
|
|
156
|
+
var auditEmit = require("./audit-emit");
|
|
156
157
|
// Lazy like the sibling host primitives' guard loads — the inbound
|
|
157
158
|
// authentication pipeline (and the DKIM verifier whose range
|
|
158
159
|
// constants the boot validation mirrors) only loads when an operator
|
|
@@ -338,14 +339,7 @@ function create(opts) {
|
|
|
338
339
|
// handle from b.mail.server.rateLimit.create({...}) to share one
|
|
339
340
|
// budget across multiple listeners, or pass an opts object to
|
|
340
341
|
// override defaults.
|
|
341
|
-
var rateLimit;
|
|
342
|
-
if (opts.rateLimit === false) {
|
|
343
|
-
rateLimit = mailServerRateLimit.create({ disabled: true });
|
|
344
|
-
} else if (opts.rateLimit && typeof opts.rateLimit.admitConnection === "function") {
|
|
345
|
-
rateLimit = opts.rateLimit;
|
|
346
|
-
} else {
|
|
347
|
-
rateLimit = mailServerRateLimit.create(opts.rateLimit || {});
|
|
348
|
-
}
|
|
342
|
+
var rateLimit = mailServerRateLimit.resolve(opts.rateLimit);
|
|
349
343
|
|
|
350
344
|
// DATA-phase message-authentication gate. `guardEnvelope: true`
|
|
351
345
|
// gates with defaults; an object tunes it. Like the sibling gates
|
|
@@ -451,16 +445,12 @@ function create(opts) {
|
|
|
451
445
|
});
|
|
452
446
|
}
|
|
453
447
|
function _validateDomainHardened(d, label) {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
_emit
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
label: label,
|
|
461
|
-
}, "denied");
|
|
462
|
-
}
|
|
463
|
-
return verdict;
|
|
448
|
+
return mailServerNet.validateDomainHardened(d, label, {
|
|
449
|
+
guardDomainProfile: guardDomainProfile,
|
|
450
|
+
guardDomain: guardDomain,
|
|
451
|
+
emit: _emit,
|
|
452
|
+
refusedEvent: "mail.server.mx.domain_refused",
|
|
453
|
+
});
|
|
464
454
|
}
|
|
465
455
|
|
|
466
456
|
// Pre-validate operator-supplied localDomains at boot — the same
|
|
@@ -479,35 +469,19 @@ function create(opts) {
|
|
|
479
469
|
}
|
|
480
470
|
}
|
|
481
471
|
|
|
482
|
-
var tcpServer = null;
|
|
483
|
-
var listening = false;
|
|
484
472
|
var connections = new Set();
|
|
485
473
|
|
|
486
|
-
|
|
487
|
-
try {
|
|
488
|
-
audit().safeEmit({
|
|
489
|
-
action: action,
|
|
490
|
-
outcome: outcome || "success",
|
|
491
|
-
metadata: metadata || {},
|
|
492
|
-
});
|
|
493
|
-
} catch (_e) { /* drop-silent — audit best-effort */ }
|
|
494
|
-
}
|
|
474
|
+
var _emit = auditEmit.emit;
|
|
495
475
|
|
|
496
476
|
// ---- Per-connection state machine ---------------------------------------
|
|
497
477
|
function _handleConnection(socket) {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
try {
|
|
506
|
-
socket.write("421 4.7.0 Too many connections from your IP\r\n");
|
|
507
|
-
} catch (_e) { /* socket may already be torn down */ }
|
|
508
|
-
try { socket.destroy(); } catch (_e2) { /* idempotent */ }
|
|
509
|
-
return;
|
|
510
|
-
}
|
|
478
|
+
// 421 4.7.0 — transient refusal; sender retries elsewhere or later.
|
|
479
|
+
// RFC 5321 §3.8 + §4.5.4.2 (transient negative completion).
|
|
480
|
+
var remoteAddress = mailServerNet.admitConnection(socket, rateLimit, _emit, {
|
|
481
|
+
refusedEvent: "mail.server.mx.rate_limit_refused",
|
|
482
|
+
refusalLine: "421 4.7.0 Too many connections from your IP\r\n",
|
|
483
|
+
});
|
|
484
|
+
if (remoteAddress === null) return;
|
|
511
485
|
socket.once("close", function () { rateLimit.releaseConnection(remoteAddress); });
|
|
512
486
|
|
|
513
487
|
var connectionId = "mxconn-" + bCrypto.generateToken(8); // connection-id length
|
|
@@ -653,7 +627,7 @@ function create(opts) {
|
|
|
653
627
|
|
|
654
628
|
// Command phase — byte-buffered (8BITMIME-safe).
|
|
655
629
|
lineBuffer = lineBuffer.length === 0 ? chunk : Buffer.concat([lineBuffer, chunk]);
|
|
656
|
-
if (lineBuffer
|
|
630
|
+
if (safeBuffer.byteLengthOf(lineBuffer) > maxLineBytes * 4) {
|
|
657
631
|
_writeReply(socket, REPLY_500_SYNTAX,
|
|
658
632
|
"5.5.6 Line too long (>" + maxLineBytes + " bytes)");
|
|
659
633
|
_closeConnection(socket);
|
|
@@ -1229,38 +1203,23 @@ function create(opts) {
|
|
|
1229
1203
|
}
|
|
1230
1204
|
|
|
1231
1205
|
// ---- Lifecycle ----------------------------------------------------------
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
tcpServer = net.createServer(function (socket) {
|
|
1243
|
-
_handleConnection(socket);
|
|
1244
|
-
});
|
|
1245
|
-
return new Promise(function (resolve, reject) {
|
|
1246
|
-
tcpServer.once("error", reject);
|
|
1247
|
-
tcpServer.listen(port, address, function () {
|
|
1248
|
-
listening = true;
|
|
1249
|
-
tcpServer.removeListener("error", reject);
|
|
1250
|
-
_emit("mail.server.mx.listening", {
|
|
1251
|
-
port: port, address: address,
|
|
1252
|
-
});
|
|
1253
|
-
resolve({ port: tcpServer.address().port, address: address });
|
|
1254
|
-
});
|
|
1255
|
-
});
|
|
1256
|
-
}
|
|
1206
|
+
// Port 0 (ephemeral, test mode) must NOT fall back to 25 — the `|| 25`
|
|
1207
|
+
// short-circuit was a footgun on the test path; createTcpListener honors an
|
|
1208
|
+
// explicit 0 (only an OMITTED port falls back to the default).
|
|
1209
|
+
var _tcpListener = mailServerNet.createTcpListener(net, {
|
|
1210
|
+
defaultPort: 25, // SMTP MX port (IANA)
|
|
1211
|
+
handleConnection: _handleConnection,
|
|
1212
|
+
errorFactory: function (code, message) { return new MailServerMxError("mail-server-mx/" + code, message); },
|
|
1213
|
+
emit: _emit,
|
|
1214
|
+
listeningEvent: "mail.server.mx.listening",
|
|
1215
|
+
});
|
|
1257
1216
|
|
|
1258
1217
|
async function close(closeOpts) {
|
|
1259
1218
|
closeOpts = closeOpts || {};
|
|
1260
|
-
if (!
|
|
1219
|
+
if (!_tcpListener.isListening()) return;
|
|
1261
1220
|
var timeoutMs = closeOpts.timeoutMs || C.TIME.seconds(30);
|
|
1262
|
-
|
|
1263
|
-
|
|
1221
|
+
_tcpListener.markClosed();
|
|
1222
|
+
_tcpListener.getServer().close();
|
|
1264
1223
|
connections.forEach(function (sock) {
|
|
1265
1224
|
try { _writeReply(sock, REPLY_421_SERVICE_NOT_AVAIL, "4.3.0 Server shutting down"); }
|
|
1266
1225
|
catch (_e) { /* socket already gone */ }
|
|
@@ -1279,10 +1238,10 @@ function create(opts) {
|
|
|
1279
1238
|
function connectionCount() { return connections.size; }
|
|
1280
1239
|
|
|
1281
1240
|
return {
|
|
1282
|
-
listen: listen,
|
|
1241
|
+
listen: _tcpListener.listen,
|
|
1283
1242
|
close: close,
|
|
1284
1243
|
connectionCount: connectionCount,
|
|
1285
|
-
_portForTest: function () { return
|
|
1244
|
+
_portForTest: function () { var s = _tcpListener.getServer(); return s ? s.address().port : null; },
|
|
1286
1245
|
};
|
|
1287
1246
|
}
|
|
1288
1247
|
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// mail-server-net — the TCP-listener lifecycle shared by the mailbox / transfer
|
|
4
|
+
// servers (b.mail.server.imap / pop3 / mx / managesieve / submission). Each of
|
|
5
|
+
// those keeps its OWN connection set and close() drain because those diverge:
|
|
6
|
+
// the store servers (IMAP/POP3/ManageSieve) await a Promise-wrapped
|
|
7
|
+
// tcpServer.close(), while the transfer servers (MX/Submission) send an SMTP
|
|
8
|
+
// 421 to every live socket and drain with a timeout. What every server shares
|
|
9
|
+
// verbatim is the bind: refuse a double-listen, resolve the default port (never
|
|
10
|
+
// falling back off an explicit port 0 — the ephemeral test-bind path), create
|
|
11
|
+
// the listener, and arm a one-shot "error"→reject so a bind failure (EADDRINUSE,
|
|
12
|
+
// EACCES) rejects the listen promise instead of crashing the process. That, plus
|
|
13
|
+
// the listening/server state, is what createTcpListener owns.
|
|
14
|
+
|
|
15
|
+
// createTcpListener(net, cfg) — build a listener lifecycle.
|
|
16
|
+
// cfg.defaultPort port used when listenOpts.port is omitted (an explicit
|
|
17
|
+
// 0 is honored, for an ephemeral test bind).
|
|
18
|
+
// cfg.handleConnection (socket) => void — the server's per-connection handler.
|
|
19
|
+
// cfg.errorFactory (code, message) => Error — builds the typed
|
|
20
|
+
// "<prefix>/already-listening" double-listen error.
|
|
21
|
+
// cfg.emit (action, metadata) => void — the server's audit emitter.
|
|
22
|
+
// cfg.listeningEvent the "...listening" audit action.
|
|
23
|
+
// cfg.listeningExtra optional () => object merged onto the listening event
|
|
24
|
+
// payload (Submission reports implicitTls).
|
|
25
|
+
// Returns { listen, getServer, isListening, markClosed } — the server wires its
|
|
26
|
+
// own close() through getServer()/isListening()/markClosed().
|
|
27
|
+
function createTcpListener(net, cfg) {
|
|
28
|
+
var server = null;
|
|
29
|
+
var listening = false;
|
|
30
|
+
|
|
31
|
+
function listen(listenOpts) {
|
|
32
|
+
listenOpts = listenOpts || {};
|
|
33
|
+
if (listening) {
|
|
34
|
+
throw cfg.errorFactory("already-listening", "listen: already listening");
|
|
35
|
+
}
|
|
36
|
+
var port = listenOpts.port === undefined ? cfg.defaultPort : listenOpts.port;
|
|
37
|
+
var address = listenOpts.address || "0.0.0.0";
|
|
38
|
+
server = net.createServer(function (socket) { cfg.handleConnection(socket); });
|
|
39
|
+
return new Promise(function (resolve, reject) {
|
|
40
|
+
server.once("error", reject);
|
|
41
|
+
server.listen(port, address, function () {
|
|
42
|
+
listening = true;
|
|
43
|
+
server.removeListener("error", reject);
|
|
44
|
+
var payload = { port: port, address: address };
|
|
45
|
+
if (cfg.listeningExtra) {
|
|
46
|
+
var extra = cfg.listeningExtra();
|
|
47
|
+
for (var k in extra) {
|
|
48
|
+
if (Object.prototype.hasOwnProperty.call(extra, k)) payload[k] = extra[k];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
cfg.emit(cfg.listeningEvent, payload);
|
|
52
|
+
resolve({ port: server.address().port, address: address });
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// closeSimple(closeCfg) — the store-server shutdown (IMAP / POP3 / ManageSieve):
|
|
58
|
+
// mark closed, destroy every live socket immediately, then await the listener's
|
|
59
|
+
// own close before emitting the "...closed" audit. The transfer servers
|
|
60
|
+
// (MX / Submission) do NOT use this — they run a graceful SMTP-421 drain with a
|
|
61
|
+
// timeout, so they own their close() and drive it through markClosed()/getServer().
|
|
62
|
+
// closeCfg.connections the server's live-socket Set (destroyed + cleared).
|
|
63
|
+
// closeCfg.emit the server's audit emitter.
|
|
64
|
+
// closeCfg.closedEvent the "...closed" audit action.
|
|
65
|
+
function closeSimple(closeCfg) {
|
|
66
|
+
if (!listening) return Promise.resolve();
|
|
67
|
+
listening = false;
|
|
68
|
+
for (var s of closeCfg.connections) { try { s.destroy(); } catch (_e) { /* idempotent */ } }
|
|
69
|
+
closeCfg.connections.clear();
|
|
70
|
+
return new Promise(function (resolve) {
|
|
71
|
+
server.close(function () {
|
|
72
|
+
closeCfg.emit(closeCfg.closedEvent, {});
|
|
73
|
+
resolve();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
listen: listen,
|
|
80
|
+
closeSimple: closeSimple,
|
|
81
|
+
getServer: function () { return server; },
|
|
82
|
+
isListening: function () { return listening; },
|
|
83
|
+
markClosed: function () { listening = false; },
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// createStoreServer(net, cfg) — the COMPLETE lifecycle of a mailbox store server
|
|
88
|
+
// (b.mail.server.imap / pop3 / managesieve): compose createTcpListener with the
|
|
89
|
+
// destroy-then-await closeSimple shutdown and return the { listen, close } a
|
|
90
|
+
// store server exposes. The three store servers are byte-identical here, varying
|
|
91
|
+
// only in port, error class, error-code prefix, and audit-event base — so the
|
|
92
|
+
// wiring lives here once. The transfer servers (MX / Submission) do NOT use this:
|
|
93
|
+
// they run a graceful SMTP-421 drain close + a richer return ({ connectionCount,
|
|
94
|
+
// _portForTest, ... }), so they call createTcpListener directly.
|
|
95
|
+
// cfg.defaultPort port used when listen() omits one (explicit 0 honored).
|
|
96
|
+
// cfg.handleConnection (socket) => void — the per-connection handler.
|
|
97
|
+
// cfg.errorClass the server's typed error constructor (code, message).
|
|
98
|
+
// cfg.errorCodePrefix prepended to the double-listen error code
|
|
99
|
+
// (e.g. "mail-server-imap/").
|
|
100
|
+
// cfg.emit (action, metadata) => void — the server's audit emitter.
|
|
101
|
+
// cfg.connections the live-socket Set (destroyed + cleared on close).
|
|
102
|
+
// cfg.eventBase the audit-action base; listeningEvent = eventBase +
|
|
103
|
+
// ".listening", closedEvent = eventBase + ".closed".
|
|
104
|
+
// Returns { listen, close }.
|
|
105
|
+
function createStoreServer(net, cfg) {
|
|
106
|
+
var ErrorClass = cfg.errorClass;
|
|
107
|
+
var listener = createTcpListener(net, {
|
|
108
|
+
defaultPort: cfg.defaultPort,
|
|
109
|
+
handleConnection: cfg.handleConnection,
|
|
110
|
+
errorFactory: function (code, message) { return new ErrorClass(cfg.errorCodePrefix + code, message); },
|
|
111
|
+
emit: cfg.emit,
|
|
112
|
+
listeningEvent: cfg.eventBase + ".listening",
|
|
113
|
+
});
|
|
114
|
+
function close() {
|
|
115
|
+
return listener.closeSimple({
|
|
116
|
+
connections: cfg.connections,
|
|
117
|
+
emit: cfg.emit,
|
|
118
|
+
closedEvent: cfg.eventBase + ".closed",
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return { listen: listener.listen, close: close };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// admitConnection(socket, rateLimit, emit, cfg) — the per-connection rate-limit
|
|
125
|
+
// gate every mail listener's _handleConnection opens with: resolve the remote
|
|
126
|
+
// IP, admit it via the shared b.mail.server.rateLimit, or refuse it with a
|
|
127
|
+
// protocol-specific line + a "<...>.rate_limit_refused" audit (outcome "denied")
|
|
128
|
+
// and tear the socket down. Returns the remote address on admit, or null when
|
|
129
|
+
// refused — the caller does `if (addr === null) return;` then runs its own
|
|
130
|
+
// (protocol-specific) close handler, connection-id, tracking-set insert, and
|
|
131
|
+
// state machine, none of which this touches.
|
|
132
|
+
// cfg.refusedEvent the "<...>.rate_limit_refused" audit action.
|
|
133
|
+
// cfg.refusalLine the wire bytes written before destroy (IMAP "* BAD …",
|
|
134
|
+
// POP3 "-ERR …", SMTP "421 4.7.0 …", ManageSieve 'NO "…"').
|
|
135
|
+
function admitConnection(socket, rateLimit, emit, cfg) {
|
|
136
|
+
var remoteAddress = socket.remoteAddress || "0.0.0.0";
|
|
137
|
+
var admit = rateLimit.admitConnection(remoteAddress);
|
|
138
|
+
if (!admit.ok) {
|
|
139
|
+
emit(cfg.refusedEvent, { remoteAddress: remoteAddress, reason: admit.reason }, "denied");
|
|
140
|
+
try { socket.write(cfg.refusalLine); } catch (_e) { /* socket may be down */ }
|
|
141
|
+
try { socket.destroy(); } catch (_e2) { /* idempotent */ }
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
return remoteAddress;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// validateDomainHardened(d, label, cfg) — the hardened-domain check the MX and
|
|
148
|
+
// Submission transfer servers run on every HELO / MAIL FROM / RCPT TO domain.
|
|
149
|
+
// When a guardDomain profile is configured it validates the domain and, on
|
|
150
|
+
// refusal, emits a "<refusedEvent>" audit (the only per-server difference — MX
|
|
151
|
+
// vs Submission) before returning the verdict; with no profile it passes
|
|
152
|
+
// through { ok: true }. Sharing it keeps the two servers' domain-validation
|
|
153
|
+
// posture identical (a divergence would be a silent spoofing / IDN-homograph
|
|
154
|
+
// gap on one server).
|
|
155
|
+
// cfg.guardDomainProfile the b.guardDomain profile (falsy = validation off).
|
|
156
|
+
// cfg.guardDomain the b.guardDomain module (its validate(d, profile)).
|
|
157
|
+
// cfg.emit (action, metadata, outcome) => void audit emitter.
|
|
158
|
+
// cfg.refusedEvent the "<...>.domain_refused" audit action.
|
|
159
|
+
function validateDomainHardened(d, label, cfg) {
|
|
160
|
+
if (!cfg.guardDomainProfile) return { ok: true };
|
|
161
|
+
var verdict = cfg.guardDomain.validate(d, cfg.guardDomainProfile);
|
|
162
|
+
if (!verdict.ok) {
|
|
163
|
+
cfg.emit(cfg.refusedEvent, {
|
|
164
|
+
reason: verdict.issues && verdict.issues[0] && verdict.issues[0].kind,
|
|
165
|
+
domain: d,
|
|
166
|
+
label: label,
|
|
167
|
+
}, "denied");
|
|
168
|
+
}
|
|
169
|
+
return verdict;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
module.exports = {
|
|
173
|
+
createTcpListener: createTcpListener,
|
|
174
|
+
createStoreServer: createStoreServer,
|
|
175
|
+
admitConnection: admitConnection,
|
|
176
|
+
validateDomainHardened: validateDomainHardened,
|
|
177
|
+
};
|
package/lib/mail-server-pop3.js
CHANGED
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
*/
|
|
104
104
|
|
|
105
105
|
var net = require("node:net");
|
|
106
|
-
var
|
|
106
|
+
var safeBuffer = require("./safe-buffer");
|
|
107
107
|
var C = require("./constants");
|
|
108
108
|
var bCrypto = require("./crypto");
|
|
109
109
|
var numericBounds = require("./numeric-bounds");
|
|
@@ -111,11 +111,12 @@ var validateOpts = require("./validate-opts");
|
|
|
111
111
|
var guardPop3Command = require("./guard-pop3-command");
|
|
112
112
|
var mailServerRateLimit = require("./mail-server-rate-limit");
|
|
113
113
|
var mailServerTls = require("./mail-server-tls");
|
|
114
|
+
var mailServerNet = require("./mail-server-net");
|
|
114
115
|
var safeSmtp = require("./safe-smtp");
|
|
115
116
|
var safeAsync = require("./safe-async");
|
|
116
117
|
var { defineClass } = require("./framework-error");
|
|
117
118
|
|
|
118
|
-
var
|
|
119
|
+
var auditEmit = require("./audit-emit");
|
|
119
120
|
|
|
120
121
|
var MailServerPop3Error = defineClass("MailServerPop3Error", { alwaysPermanent: true });
|
|
121
122
|
|
|
@@ -229,40 +230,18 @@ function create(opts) {
|
|
|
229
230
|
}
|
|
230
231
|
}
|
|
231
232
|
|
|
232
|
-
var rateLimit;
|
|
233
|
-
if (opts.rateLimit === false) {
|
|
234
|
-
rateLimit = mailServerRateLimit.create({ disabled: true });
|
|
235
|
-
} else if (opts.rateLimit && typeof opts.rateLimit.admitConnection === "function") {
|
|
236
|
-
rateLimit = opts.rateLimit;
|
|
237
|
-
} else {
|
|
238
|
-
rateLimit = mailServerRateLimit.create(opts.rateLimit || {});
|
|
239
|
-
}
|
|
233
|
+
var rateLimit = mailServerRateLimit.resolve(opts.rateLimit);
|
|
240
234
|
|
|
241
|
-
var tcpServer = null;
|
|
242
|
-
var listening = false;
|
|
243
235
|
var connections = new Set();
|
|
244
236
|
|
|
245
|
-
|
|
246
|
-
try {
|
|
247
|
-
audit().safeEmit({
|
|
248
|
-
action: action,
|
|
249
|
-
outcome: outcome || "success",
|
|
250
|
-
metadata: metadata || {},
|
|
251
|
-
});
|
|
252
|
-
} catch (_e) { /* drop-silent */ }
|
|
253
|
-
}
|
|
237
|
+
var _emit = auditEmit.emit;
|
|
254
238
|
|
|
255
239
|
function _handleConnection(rawSocket) {
|
|
256
|
-
var remoteAddress = rawSocket
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
try { rawSocket.write("-ERR Too many connections from your IP\r\n"); }
|
|
262
|
-
catch (_e) { /* socket may be down */ }
|
|
263
|
-
try { rawSocket.destroy(); } catch (_e2) { /* idempotent */ }
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
240
|
+
var remoteAddress = mailServerNet.admitConnection(rawSocket, rateLimit, _emit, {
|
|
241
|
+
refusedEvent: "mail.server.pop3.rate_limit_refused",
|
|
242
|
+
refusalLine: "-ERR Too many connections from your IP\r\n",
|
|
243
|
+
});
|
|
244
|
+
if (remoteAddress === null) return;
|
|
266
245
|
var connectionId = "pop3conn-" + bCrypto.generateToken(8); // connection-id length
|
|
267
246
|
var socket = rawSocket;
|
|
268
247
|
connections.add(socket);
|
|
@@ -312,7 +291,7 @@ function create(opts) {
|
|
|
312
291
|
while (true) {
|
|
313
292
|
var crlf = state.lineBuffer.indexOf("\r\n");
|
|
314
293
|
if (crlf === -1) {
|
|
315
|
-
if (state.lineBuffer
|
|
294
|
+
if (safeBuffer.byteLengthOf(state.lineBuffer) > maxLineBytes) {
|
|
316
295
|
_writeErr(socket, "Line too long (cap " + maxLineBytes + ")");
|
|
317
296
|
_close(socket);
|
|
318
297
|
}
|
|
@@ -398,22 +377,18 @@ function create(opts) {
|
|
|
398
377
|
// Drain pre-handshake buffer (RFC 2595 §4 + CVE-2021-33515 class
|
|
399
378
|
// STLS-injection defense — any pipelined commands the client
|
|
400
379
|
// queued before the upgrade are discarded; post-TLS reads fresh).
|
|
401
|
-
//
|
|
402
|
-
//
|
|
403
|
-
|
|
404
|
-
//
|
|
405
|
-
//
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
plainSocket: socket,
|
|
380
|
+
// POP3 has no authPending shape (SASL state is local to
|
|
381
|
+
// _handleAuth), but tentativeUser is reset so a USER pipelined
|
|
382
|
+
// pre-handshake cannot bind a post-handshake PASS. The shared
|
|
383
|
+
// upgradeLineProtocol helper owns the lineBuffer drain + listener-
|
|
384
|
+
// strip + idle re-arm + read-pump.
|
|
385
|
+
mailServerTls.upgradeLineProtocol({
|
|
386
|
+
state: state,
|
|
387
|
+
socket: socket,
|
|
410
388
|
secureContext: opts.tlsContext,
|
|
411
389
|
idleTimeoutMs: idleTimeoutMs,
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
state.lineBuffer = Buffer.concat([state.lineBuffer, chunk]);
|
|
415
|
-
_drainBuffer(state, tlsSocket);
|
|
416
|
-
},
|
|
390
|
+
clearFields: ["tentativeUser"],
|
|
391
|
+
drain: _drainBuffer,
|
|
417
392
|
onError: function (err) {
|
|
418
393
|
_emit("mail.server.pop3.tls_handshake_failed",
|
|
419
394
|
{ connectionId: state.id, error: (err && err.message) || String(err) }, "failure");
|
|
@@ -870,43 +845,15 @@ function create(opts) {
|
|
|
870
845
|
}
|
|
871
846
|
|
|
872
847
|
// ---- Lifecycle ----------------------------------------------------------
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
return new Promise(function (resolve, reject) {
|
|
883
|
-
tcpServer.once("error", reject);
|
|
884
|
-
tcpServer.listen(port, address, function () {
|
|
885
|
-
listening = true;
|
|
886
|
-
tcpServer.removeListener("error", reject);
|
|
887
|
-
_emit("mail.server.pop3.listening", { port: port, address: address });
|
|
888
|
-
resolve({ port: tcpServer.address().port, address: address });
|
|
889
|
-
});
|
|
890
|
-
});
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
async function close() {
|
|
894
|
-
if (!listening) return;
|
|
895
|
-
listening = false;
|
|
896
|
-
for (var s of connections) { try { s.destroy(); } catch (_e) { /* idempotent */ } }
|
|
897
|
-
connections.clear();
|
|
898
|
-
return new Promise(function (resolve) {
|
|
899
|
-
tcpServer.close(function () {
|
|
900
|
-
_emit("mail.server.pop3.closed", {});
|
|
901
|
-
resolve();
|
|
902
|
-
});
|
|
903
|
-
});
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
return {
|
|
907
|
-
listen: listen,
|
|
908
|
-
close: close,
|
|
909
|
-
};
|
|
848
|
+
return mailServerNet.createStoreServer(net, {
|
|
849
|
+
defaultPort: 110, // RFC 1939 POP3 port (IANA)
|
|
850
|
+
handleConnection: _handleConnection,
|
|
851
|
+
errorClass: MailServerPop3Error,
|
|
852
|
+
errorCodePrefix: "mail-server-pop3/",
|
|
853
|
+
emit: _emit,
|
|
854
|
+
connections: connections,
|
|
855
|
+
eventBase: "mail.server.pop3",
|
|
856
|
+
});
|
|
910
857
|
}
|
|
911
858
|
|
|
912
859
|
module.exports = {
|
|
@@ -88,6 +88,7 @@ var C = require("./constants");
|
|
|
88
88
|
var lazyRequire = require("./lazy-require");
|
|
89
89
|
var numericBounds = require("./numeric-bounds");
|
|
90
90
|
var validateOpts = require("./validate-opts");
|
|
91
|
+
var boundedMap = require("./bounded-map");
|
|
91
92
|
var { defineClass } = require("./framework-error");
|
|
92
93
|
|
|
93
94
|
var audit = lazyRequire(function () { return require("./audit"); });
|
|
@@ -206,8 +207,7 @@ function create(opts) {
|
|
|
206
207
|
{ reason: "concurrent-per-ip", ip: ip, cap: cfg.maxConcurrentConnectionsPerIp });
|
|
207
208
|
return { ok: false, reason: "concurrent-per-ip" };
|
|
208
209
|
}
|
|
209
|
-
var times =
|
|
210
|
-
if (!times) { times = []; connectionTimes.set(ip, times); }
|
|
210
|
+
var times = boundedMap.getOrInsert(connectionTimes, ip, function () { return []; });
|
|
211
211
|
_pruneWindow(times, CONNECTION_RATE_WINDOW_MS);
|
|
212
212
|
if (times.length >= cfg.connectionsPerIpPerMinute) {
|
|
213
213
|
_audit("mail.server.rate_limit.refused", "denied",
|
|
@@ -258,8 +258,7 @@ function create(opts) {
|
|
|
258
258
|
|
|
259
259
|
function noteAuthFailure(ip) {
|
|
260
260
|
if (cfg.disabled) return;
|
|
261
|
-
var times =
|
|
262
|
-
if (!times) { times = []; authFailureTimes.set(ip, times); }
|
|
261
|
+
var times = boundedMap.getOrInsert(authFailureTimes, ip, function () { return []; });
|
|
263
262
|
times.push(Date.now());
|
|
264
263
|
}
|
|
265
264
|
|
|
@@ -288,8 +287,7 @@ function create(opts) {
|
|
|
288
287
|
|
|
289
288
|
function noteRcptFailure(ip) {
|
|
290
289
|
if (cfg.disabled) return;
|
|
291
|
-
var times =
|
|
292
|
-
if (!times) { times = []; rcptFailureTimes.set(ip, times); }
|
|
290
|
+
var times = boundedMap.getOrInsert(rcptFailureTimes, ip, function () { return []; });
|
|
293
291
|
times.push(Date.now());
|
|
294
292
|
}
|
|
295
293
|
|
|
@@ -308,8 +306,34 @@ function create(opts) {
|
|
|
308
306
|
};
|
|
309
307
|
}
|
|
310
308
|
|
|
309
|
+
/**
|
|
310
|
+
* @primitive b.mail.server.rateLimit.resolve
|
|
311
|
+
* @signature b.mail.server.rateLimit.resolve(spec)
|
|
312
|
+
* @since 0.15.13
|
|
313
|
+
* @status stable
|
|
314
|
+
* @related b.mail.server.rateLimit.create
|
|
315
|
+
*
|
|
316
|
+
* Resolve a rate-limit `spec` into a limiter. `false` disables limiting
|
|
317
|
+
* (a disabled limiter that always admits), an already-built limiter — one
|
|
318
|
+
* exposing `admitConnection` — passes through unchanged, and anything else
|
|
319
|
+
* is treated as `create()` options. Every mail server (IMAP / POP3 / SMTP
|
|
320
|
+
* MX / Submission / ManageSieve) composes this at the top of its `create()`
|
|
321
|
+
* so the spec contract is identical across protocols.
|
|
322
|
+
*
|
|
323
|
+
* @example
|
|
324
|
+
* var b = require("blamejs");
|
|
325
|
+
* var rl = b.mail.server.rateLimit.resolve(false); // disabled
|
|
326
|
+
* // → a limiter whose admitConnection always admits
|
|
327
|
+
*/
|
|
328
|
+
function resolve(spec) {
|
|
329
|
+
if (spec === false) return create({ disabled: true });
|
|
330
|
+
if (spec && typeof spec.admitConnection === "function") return spec;
|
|
331
|
+
return create(spec || {});
|
|
332
|
+
}
|
|
333
|
+
|
|
311
334
|
module.exports = {
|
|
312
335
|
create: create,
|
|
336
|
+
resolve: resolve,
|
|
313
337
|
MailServerRateLimitError: MailServerRateLimitError,
|
|
314
338
|
DEFAULTS: DEFAULTS,
|
|
315
339
|
};
|
|
@@ -142,7 +142,7 @@ function create(opts) {
|
|
|
142
142
|
validateOpts.requireNonEmptyString(opts.protocol,
|
|
143
143
|
"b.mail.serverRegistry.create: protocol", MailServerRegistryError,
|
|
144
144
|
"mail-server-registry/bad-protocol");
|
|
145
|
-
if (!CATALOGUE
|
|
145
|
+
if (!Object.prototype.hasOwnProperty.call(CATALOGUE, opts.protocol)) {
|
|
146
146
|
throw new MailServerRegistryError("mail-server-registry/unknown-protocol",
|
|
147
147
|
"create: protocol must be 'imap', 'jmap', or 'managesieve' (got '" + opts.protocol + "')");
|
|
148
148
|
}
|