@serve.zone/dcrouter 17.0.0 → 17.1.1
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/.smartconfig.json +7 -4
- package/deno.json +1 -1
- package/dist_serve/bundle.js +1316 -1151
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.dcrouter.d.ts +3 -1
- package/dist_ts/classes.dcrouter.js +113 -52
- package/dist_ts/db/classes.cache.cleaner.d.ts +3 -0
- package/dist_ts/db/classes.cache.cleaner.js +7 -3
- package/dist_ts/db/classes.dcrouter-db.js +1 -2
- package/dist_ts/db/documents/classes.cached.email.d.ts +41 -4
- package/dist_ts/db/documents/classes.cached.email.js +36 -5
- package/dist_ts/db/documents/classes.dns-record.doc.js +3 -1
- package/dist_ts/dns/classes.dns-server-runtime.d.ts +0 -1
- package/dist_ts/dns/classes.dns-server-runtime.js +4 -36
- package/dist_ts/dns/index.d.ts +1 -0
- package/dist_ts/dns/index.js +2 -1
- package/dist_ts/dns/manager.dns.d.ts +1 -0
- package/dist_ts/dns/manager.dns.js +49 -14
- package/dist_ts/dns/providers/cloudflare.provider.js +19 -4
- package/dist_ts/dns/txt-record-presentation.d.ts +20 -0
- package/dist_ts/dns/txt-record-presentation.js +131 -0
- package/dist_ts/email/classes.accepted-email-spool.d.ts +32 -3
- package/dist_ts/email/classes.accepted-email-spool.js +174 -13
- package/dist_ts/email/classes.email-domain.manager.d.ts +14 -1
- package/dist_ts/email/classes.email-domain.manager.js +151 -8
- package/dist_ts/email/classes.email-settings.manager.js +2 -2
- package/dist_ts/email/classes.mail-dns-sync.d.ts +31 -5
- package/dist_ts/email/classes.mail-dns-sync.js +508 -117
- package/dist_ts/email/classes.smartmta-blob-storage-manager.d.ts +2 -2
- package/dist_ts/email/classes.smartmta-blob-storage-manager.js +4 -3
- package/dist_ts/email/classes.workapp-mail-manager.d.ts +1 -0
- package/dist_ts/email/classes.workapp-mail-manager.js +26 -2
- package/dist_ts/email/index.d.ts +2 -0
- package/dist_ts/email/index.js +3 -1
- package/dist_ts/email/mail-dns-txt-policy.d.ts +18 -0
- package/dist_ts/email/mail-dns-txt-policy.js +466 -0
- package/dist_ts/email/reconcile-mail-dns-singleton.d.ts +47 -0
- package/dist_ts/email/reconcile-mail-dns-singleton.js +159 -0
- package/dist_ts/opsserver/classes.opsserver.d.ts +8 -0
- package/dist_ts/opsserver/classes.opsserver.js +34 -1
- package/dist_ts/opsserver/handlers/certificate.handler.js +25 -4
- package/dist_ts/opsserver/handlers/config.handler.js +9 -5
- package/dist_ts/opsserver/handlers/dns-provider.handler.js +19 -2
- package/dist_ts/opsserver/handlers/dns-record.handler.js +25 -4
- package/dist_ts/opsserver/handlers/domain.handler.js +37 -3
- package/dist_ts/opsserver/handlers/email-domain.handler.js +5 -7
- package/dist_ts/opsserver/handlers/email-ops.handler.d.ts +1 -0
- package/dist_ts/opsserver/handlers/email-ops.handler.js +26 -7
- package/dist_ts/opsserver/handlers/index.d.ts +1 -0
- package/dist_ts/opsserver/handlers/index.js +2 -1
- package/dist_ts/opsserver/handlers/logs.handler.d.ts +1 -1
- package/dist_ts/opsserver/handlers/logs.handler.js +3 -3
- package/dist_ts/opsserver/handlers/realtime.handler.d.ts +37 -0
- package/dist_ts/opsserver/handlers/realtime.handler.js +154 -0
- package/dist_ts/opsserver/handlers/remoteingress.handler.js +22 -1
- package/dist_ts/opsserver/handlers/route-management.handler.js +47 -5
- package/dist_ts/opsserver/handlers/stats.handler.js +5 -1
- package/dist_ts/paths.d.ts +0 -11
- package/dist_ts/paths.js +1 -22
- package/dist_ts/plugins.d.ts +0 -34
- package/dist_ts/plugins.js +1 -62
- package/dist_ts_interfaces/data/email-domain.d.ts +14 -0
- package/dist_ts_interfaces/requests/config.d.ts +1 -1
- package/dist_ts_interfaces/requests/email-ops.d.ts +34 -1
- package/dist_ts_interfaces/requests/index.d.ts +1 -0
- package/dist_ts_interfaces/requests/index.js +2 -1
- package/dist_ts_interfaces/requests/realtime.d.ts +38 -0
- package/dist_ts_interfaces/requests/realtime.js +11 -0
- package/dist_ts_migrations/index.d.ts +6 -0
- package/dist_ts_migrations/index.js +208 -9
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate/certificates.js +9 -2
- package/dist_ts_web/appstate/domains.js +16 -5
- package/dist_ts_web/appstate/email-domains.d.ts +3 -0
- package/dist_ts_web/appstate/email-domains.js +47 -14
- package/dist_ts_web/appstate/email-ops.d.ts +1 -0
- package/dist_ts_web/appstate/email-ops.js +20 -2
- package/dist_ts_web/appstate/realtime.d.ts +27 -0
- package/dist_ts_web/appstate/realtime.js +71 -0
- package/dist_ts_web/appstate/remoteingress.js +9 -3
- package/dist_ts_web/appstate/routes.js +16 -5
- package/dist_ts_web/appstate/runtime.js +135 -7
- package/dist_ts_web/appstate.d.ts +1 -0
- package/dist_ts_web/appstate.js +2 -1
- package/dist_ts_web/elements/email/email-domain-creation-progress.d.ts +33 -0
- package/dist_ts_web/elements/email/email-domain-creation-progress.js +257 -0
- package/dist_ts_web/elements/email/ops-view-email-domains.js +233 -68
- package/dist_ts_web/elements/email/ops-view-emails.d.ts +4 -2
- package/dist_ts_web/elements/email/ops-view-emails.js +25 -13
- package/dist_ts_web/elements/overview/ops-view-config.js +5 -5
- package/package.json +22 -19
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.dcrouter.ts +124 -57
- package/ts/db/classes.cache.cleaner.ts +18 -3
- package/ts/db/classes.dcrouter-db.ts +0 -2
- package/ts/db/documents/classes.cached.email.ts +78 -4
- package/ts/db/documents/classes.dns-record.doc.ts +1 -0
- package/ts/dns/classes.dns-server-runtime.ts +9 -45
- package/ts/dns/index.ts +1 -0
- package/ts/dns/manager.dns.ts +50 -16
- package/ts/dns/providers/cloudflare.provider.ts +21 -3
- package/ts/dns/txt-record-presentation.ts +134 -0
- package/ts/email/classes.accepted-email-spool.ts +220 -14
- package/ts/email/classes.email-domain.manager.ts +201 -7
- package/ts/email/classes.email-settings.manager.ts +1 -1
- package/ts/email/classes.mail-dns-sync.ts +588 -122
- package/ts/email/classes.smartmta-blob-storage-manager.ts +3 -2
- package/ts/email/classes.workapp-mail-manager.ts +34 -1
- package/ts/email/index.ts +2 -0
- package/ts/email/mail-dns-txt-policy.ts +505 -0
- package/ts/email/reconcile-mail-dns-singleton.ts +224 -0
- package/ts/opsserver/classes.opsserver.ts +42 -0
- package/ts/opsserver/handlers/certificate.handler.ts +24 -3
- package/ts/opsserver/handlers/config.handler.ts +9 -5
- package/ts/opsserver/handlers/dns-provider.handler.ts +18 -1
- package/ts/opsserver/handlers/dns-record.handler.ts +24 -3
- package/ts/opsserver/handlers/domain.handler.ts +36 -2
- package/ts/opsserver/handlers/email-domain.handler.ts +4 -5
- package/ts/opsserver/handlers/email-ops.handler.ts +29 -6
- package/ts/opsserver/handlers/index.ts +1 -0
- package/ts/opsserver/handlers/logs.handler.ts +2 -2
- package/ts/opsserver/handlers/realtime.handler.ts +222 -0
- package/ts/opsserver/handlers/remoteingress.handler.ts +21 -0
- package/ts/opsserver/handlers/route-management.handler.ts +46 -4
- package/ts/opsserver/handlers/stats.handler.ts +4 -0
- package/ts/paths.ts +0 -25
- package/ts/plugins.ts +0 -68
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate/certificates.ts +6 -1
- package/ts_web/appstate/domains.ts +11 -4
- package/ts_web/appstate/email-domains.ts +68 -14
- package/ts_web/appstate/email-ops.ts +20 -1
- package/ts_web/appstate/realtime.ts +109 -0
- package/ts_web/appstate/remoteingress.ts +6 -2
- package/ts_web/appstate/routes.ts +11 -4
- package/ts_web/appstate/runtime.ts +176 -7
- package/ts_web/appstate.ts +1 -0
- package/ts_web/elements/email/email-domain-creation-progress.ts +340 -0
- package/ts_web/elements/email/ops-view-email-domains.ts +273 -74
- package/ts_web/elements/email/ops-view-emails.ts +23 -16
- package/ts_web/elements/overview/ops-view-config.ts +4 -4
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
import { decodeTxtRecordContent } from '../dns/txt-record-presentation.js';
|
|
3
|
+
function intentKind(intent) {
|
|
4
|
+
const desired = intent.value.trim().toLowerCase();
|
|
5
|
+
if (desired.startsWith('v=spf1'))
|
|
6
|
+
return 'spf';
|
|
7
|
+
if (desired.startsWith('v=dmarc1'))
|
|
8
|
+
return 'dmarc';
|
|
9
|
+
if (desired.startsWith('v=dkim1'))
|
|
10
|
+
return 'dkim';
|
|
11
|
+
return 'exact';
|
|
12
|
+
}
|
|
13
|
+
function markerForKind(kind) {
|
|
14
|
+
if (kind === 'spf')
|
|
15
|
+
return 'v=spf1';
|
|
16
|
+
if (kind === 'dmarc')
|
|
17
|
+
return 'v=dmarc1';
|
|
18
|
+
if (kind === 'dkim')
|
|
19
|
+
return 'v=dkim1';
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
export function classifyMailTxtRecord(intent, rawValue) {
|
|
23
|
+
const kind = intentKind(intent);
|
|
24
|
+
const decoded = decodeTxtRecordContent(rawValue);
|
|
25
|
+
if (!decoded.ok) {
|
|
26
|
+
const marker = markerForKind(kind);
|
|
27
|
+
const rawNormalized = rawValue.trim().toLowerCase();
|
|
28
|
+
const plausiblyRelevant = kind === 'dkim'
|
|
29
|
+
? rawNormalized.length > 0
|
|
30
|
+
: kind === 'dmarc'
|
|
31
|
+
? /(?:^|;)\s*v=dmarc1(?:;|$)/.test(rawNormalized.replace(/^"/, ''))
|
|
32
|
+
: marker
|
|
33
|
+
? rawNormalized.slice(0, marker.length + 4).includes(marker)
|
|
34
|
+
: rawValue.trim().startsWith('"');
|
|
35
|
+
return plausiblyRelevant
|
|
36
|
+
? { relevant: true, malformed: decoded.error }
|
|
37
|
+
: { relevant: false };
|
|
38
|
+
}
|
|
39
|
+
const canonicalValue = decoded.value.trim();
|
|
40
|
+
const marker = markerForKind(kind);
|
|
41
|
+
const normalizedValue = canonicalValue.toLowerCase();
|
|
42
|
+
return {
|
|
43
|
+
relevant: kind === 'dkim'
|
|
44
|
+
? canonicalValue.length > 0
|
|
45
|
+
: kind === 'dmarc'
|
|
46
|
+
? /(?:^|;)\s*v=dmarc1(?:;|$)/.test(normalizedValue)
|
|
47
|
+
: marker
|
|
48
|
+
? normalizedValue.startsWith(marker)
|
|
49
|
+
: canonicalValue === intent.value.trim(),
|
|
50
|
+
canonicalValue,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function ipv4Bytes(address) {
|
|
54
|
+
const parts = address.split('.');
|
|
55
|
+
if (parts.length !== 4)
|
|
56
|
+
return undefined;
|
|
57
|
+
const bytes = parts.map((part) => Number(part));
|
|
58
|
+
return bytes.every((part) => Number.isInteger(part) && part >= 0 && part <= 255)
|
|
59
|
+
? bytes
|
|
60
|
+
: undefined;
|
|
61
|
+
}
|
|
62
|
+
function ipv6Bytes(address) {
|
|
63
|
+
let normalized = address.toLowerCase();
|
|
64
|
+
if (normalized.includes('.')) {
|
|
65
|
+
const lastColon = normalized.lastIndexOf(':');
|
|
66
|
+
const ipv4 = ipv4Bytes(normalized.slice(lastColon + 1));
|
|
67
|
+
if (lastColon < 0 || !ipv4)
|
|
68
|
+
return undefined;
|
|
69
|
+
normalized = `${normalized.slice(0, lastColon)}:${((ipv4[0] << 8) | ipv4[1]).toString(16)}:${((ipv4[2] << 8) | ipv4[3]).toString(16)}`;
|
|
70
|
+
}
|
|
71
|
+
const halves = normalized.split('::');
|
|
72
|
+
if (halves.length > 2)
|
|
73
|
+
return undefined;
|
|
74
|
+
const left = halves[0] ? halves[0].split(':') : [];
|
|
75
|
+
const right = halves[1] ? halves[1].split(':') : [];
|
|
76
|
+
const missing = 8 - left.length - right.length;
|
|
77
|
+
if ((halves.length === 1 && missing !== 0) || missing < 0)
|
|
78
|
+
return undefined;
|
|
79
|
+
const groups = [...left, ...Array.from({ length: missing }, () => '0'), ...right];
|
|
80
|
+
if (groups.length !== 8 || groups.some((group) => !/^[0-9a-f]{1,4}$/.test(group))) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
return groups.flatMap((group) => {
|
|
84
|
+
const value = Number.parseInt(group, 16);
|
|
85
|
+
return [value >> 8, value & 0xff];
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function parseIpNetwork(value, family) {
|
|
89
|
+
const slashIndex = value.lastIndexOf('/');
|
|
90
|
+
const address = slashIndex >= 0 ? value.slice(0, slashIndex) : value;
|
|
91
|
+
const maximumPrefix = family === 4 ? 32 : 128;
|
|
92
|
+
const rawPrefix = slashIndex >= 0 ? value.slice(slashIndex + 1) : String(maximumPrefix);
|
|
93
|
+
if (!/^\d{1,3}$/.test(rawPrefix))
|
|
94
|
+
return undefined;
|
|
95
|
+
const prefixLength = Number.parseInt(rawPrefix, 10);
|
|
96
|
+
if (prefixLength < 0 || prefixLength > maximumPrefix)
|
|
97
|
+
return undefined;
|
|
98
|
+
const bytes = family === 4 ? ipv4Bytes(address) : ipv6Bytes(address);
|
|
99
|
+
return bytes ? { family, bytes, prefixLength } : undefined;
|
|
100
|
+
}
|
|
101
|
+
function networkContains(network, address) {
|
|
102
|
+
if (network.family !== address.family)
|
|
103
|
+
return false;
|
|
104
|
+
const wholeBytes = Math.floor(network.prefixLength / 8);
|
|
105
|
+
const remainingBits = network.prefixLength % 8;
|
|
106
|
+
for (let index = 0; index < wholeBytes; index++) {
|
|
107
|
+
if (network.bytes[index] !== address.bytes[index])
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
if (remainingBits === 0)
|
|
111
|
+
return true;
|
|
112
|
+
const mask = (0xff << (8 - remainingBits)) & 0xff;
|
|
113
|
+
return (network.bytes[wholeBytes] & mask) === (address.bytes[wholeBytes] & mask);
|
|
114
|
+
}
|
|
115
|
+
function parseAMechanism(body) {
|
|
116
|
+
let remaining = body.slice(1);
|
|
117
|
+
let ipv6PrefixLength = 128;
|
|
118
|
+
const doubleSlashIndex = remaining.lastIndexOf('//');
|
|
119
|
+
if (doubleSlashIndex >= 0) {
|
|
120
|
+
const value = remaining.slice(doubleSlashIndex + 2);
|
|
121
|
+
if (!/^\d{1,3}$/.test(value))
|
|
122
|
+
return undefined;
|
|
123
|
+
ipv6PrefixLength = Number.parseInt(value, 10);
|
|
124
|
+
remaining = remaining.slice(0, doubleSlashIndex);
|
|
125
|
+
}
|
|
126
|
+
let ipv4PrefixLength = 32;
|
|
127
|
+
const slashIndex = remaining.lastIndexOf('/');
|
|
128
|
+
if (slashIndex >= 0) {
|
|
129
|
+
const value = remaining.slice(slashIndex + 1);
|
|
130
|
+
if (!/^\d{1,2}$/.test(value))
|
|
131
|
+
return undefined;
|
|
132
|
+
ipv4PrefixLength = Number.parseInt(value, 10);
|
|
133
|
+
remaining = remaining.slice(0, slashIndex);
|
|
134
|
+
}
|
|
135
|
+
if (ipv4PrefixLength > 32 || ipv6PrefixLength > 128)
|
|
136
|
+
return undefined;
|
|
137
|
+
if (remaining && !remaining.startsWith(':'))
|
|
138
|
+
return undefined;
|
|
139
|
+
const domain = remaining.slice(1).trim().toLowerCase().replace(/\.$/, '');
|
|
140
|
+
if (domain.includes('%') || (domain && !/^[a-z0-9_.-]+$/.test(domain)))
|
|
141
|
+
return undefined;
|
|
142
|
+
return {
|
|
143
|
+
...(domain ? { domain } : {}),
|
|
144
|
+
ipv4PrefixLength,
|
|
145
|
+
ipv6PrefixLength,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function parseSpfRecord(value) {
|
|
149
|
+
const terms = value.trim().split(/\s+/);
|
|
150
|
+
if (terms.shift()?.toLowerCase() !== 'v=spf1') {
|
|
151
|
+
return { error: 'SPF record must start with v=spf1' };
|
|
152
|
+
}
|
|
153
|
+
const parsed = { mechanisms: [], hasRedirect: false };
|
|
154
|
+
const modifiers = new Set();
|
|
155
|
+
for (const rawTerm of terms) {
|
|
156
|
+
if (!rawTerm)
|
|
157
|
+
continue;
|
|
158
|
+
let term = rawTerm;
|
|
159
|
+
let qualifier = '+';
|
|
160
|
+
let hadQualifier = false;
|
|
161
|
+
if (/^[+\-~?]/.test(term)) {
|
|
162
|
+
qualifier = term[0];
|
|
163
|
+
term = term.slice(1);
|
|
164
|
+
hadQualifier = true;
|
|
165
|
+
}
|
|
166
|
+
if (!term)
|
|
167
|
+
return { error: 'SPF contains an empty mechanism' };
|
|
168
|
+
const normalized = term.toLowerCase();
|
|
169
|
+
if (normalized.includes('=')) {
|
|
170
|
+
const match = term.match(/^([a-z][a-z0-9_.-]*)=(\S+)$/i);
|
|
171
|
+
if (hadQualifier || !match)
|
|
172
|
+
return { error: `SPF contains an invalid modifier: ${rawTerm}` };
|
|
173
|
+
const name = match[1].toLowerCase();
|
|
174
|
+
if (modifiers.has(name))
|
|
175
|
+
return { error: `SPF contains duplicate ${name} modifiers` };
|
|
176
|
+
modifiers.add(name);
|
|
177
|
+
if (name === 'redirect')
|
|
178
|
+
parsed.hasRedirect = true;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (normalized.startsWith('ip4:') || normalized.startsWith('ip6:')) {
|
|
182
|
+
const family = normalized.startsWith('ip4:') ? 4 : 6;
|
|
183
|
+
const network = parseIpNetwork(term.slice(4), family);
|
|
184
|
+
if (!network)
|
|
185
|
+
return { error: `SPF contains an invalid ${family === 4 ? 'ip4' : 'ip6'} mechanism: ${rawTerm}` };
|
|
186
|
+
parsed.mechanisms.push({ kind: 'ip', qualifier, network });
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (normalized === 'a' || normalized.startsWith('a:') || normalized.startsWith('a/')) {
|
|
190
|
+
const mechanism = parseAMechanism(term);
|
|
191
|
+
if (!mechanism)
|
|
192
|
+
return { error: `SPF contains an invalid a mechanism: ${rawTerm}` };
|
|
193
|
+
parsed.mechanisms.push({ kind: 'a', qualifier, mechanism });
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (normalized === 'all') {
|
|
197
|
+
parsed.mechanisms.push({ kind: 'all', qualifier });
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (normalized === 'mx'
|
|
201
|
+
|| normalized.startsWith('mx:')
|
|
202
|
+
|| normalized.startsWith('mx/')
|
|
203
|
+
|| normalized.startsWith('include:')
|
|
204
|
+
|| normalized.startsWith('exists:')
|
|
205
|
+
|| normalized === 'ptr'
|
|
206
|
+
|| normalized.startsWith('ptr:')) {
|
|
207
|
+
parsed.mechanisms.push({ kind: 'unsupported', name: rawTerm });
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
return { error: `SPF contains an unknown mechanism: ${rawTerm}` };
|
|
211
|
+
}
|
|
212
|
+
return { parsed };
|
|
213
|
+
}
|
|
214
|
+
function desiredSpfAddresses(value) {
|
|
215
|
+
const parsed = parseSpfRecord(value);
|
|
216
|
+
if (!parsed.parsed)
|
|
217
|
+
return { error: parsed.error };
|
|
218
|
+
const addresses = [];
|
|
219
|
+
for (const mechanism of parsed.parsed.mechanisms) {
|
|
220
|
+
if (mechanism.kind === 'all' && mechanism.qualifier === '-')
|
|
221
|
+
continue;
|
|
222
|
+
if (mechanism.kind !== 'ip'
|
|
223
|
+
|| mechanism.qualifier !== '+'
|
|
224
|
+
|| mechanism.network.prefixLength !== (mechanism.network.family === 4 ? 32 : 128)) {
|
|
225
|
+
return { error: 'Desired SPF intent does not contain only concrete sender addresses' };
|
|
226
|
+
}
|
|
227
|
+
addresses.push(mechanism.network);
|
|
228
|
+
}
|
|
229
|
+
return addresses.length > 0
|
|
230
|
+
? { addresses }
|
|
231
|
+
: { error: 'Desired SPF intent does not contain concrete sender addresses' };
|
|
232
|
+
}
|
|
233
|
+
async function withTimeout(promise, timeoutMs) {
|
|
234
|
+
let timer;
|
|
235
|
+
try {
|
|
236
|
+
return await Promise.race([
|
|
237
|
+
promise,
|
|
238
|
+
new Promise((_resolve, reject) => {
|
|
239
|
+
timer = setTimeout(() => reject(new Error(`DNS lookup timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
240
|
+
}),
|
|
241
|
+
]);
|
|
242
|
+
}
|
|
243
|
+
finally {
|
|
244
|
+
if (timer)
|
|
245
|
+
clearTimeout(timer);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
async function evaluateSpf(intent, actualValue, options) {
|
|
249
|
+
const desired = desiredSpfAddresses(intent.value);
|
|
250
|
+
if (!desired.addresses)
|
|
251
|
+
return { outcome: 'invalid', reason: desired.error };
|
|
252
|
+
const actual = parseSpfRecord(actualValue);
|
|
253
|
+
if (!actual.parsed)
|
|
254
|
+
return { outcome: 'invalid', reason: actual.error };
|
|
255
|
+
const timeoutMs = Math.max(1, options.lookupTimeoutMs ?? 2_000);
|
|
256
|
+
const maxDnsLookups = Math.max(1, options.maxDnsLookups ?? 10);
|
|
257
|
+
let lookupCount = 0;
|
|
258
|
+
const cache = new Map();
|
|
259
|
+
const resolveBounded = async (name, type) => {
|
|
260
|
+
const key = `${type}:${name}`;
|
|
261
|
+
const cached = cache.get(key);
|
|
262
|
+
if (cached)
|
|
263
|
+
return await cached;
|
|
264
|
+
if (!options.resolveDns)
|
|
265
|
+
throw new Error('SPF a mechanism requires a DNS resolver');
|
|
266
|
+
if (lookupCount >= maxDnsLookups) {
|
|
267
|
+
throw new Error(`SPF DNS lookup budget of ${maxDnsLookups} was exceeded`);
|
|
268
|
+
}
|
|
269
|
+
lookupCount++;
|
|
270
|
+
const lookup = withTimeout(options.resolveDns(name, type), timeoutMs);
|
|
271
|
+
cache.set(key, lookup);
|
|
272
|
+
return await lookup;
|
|
273
|
+
};
|
|
274
|
+
try {
|
|
275
|
+
for (const address of desired.addresses) {
|
|
276
|
+
let authorized = false;
|
|
277
|
+
for (const mechanism of actual.parsed.mechanisms) {
|
|
278
|
+
if (mechanism.kind === 'unsupported') {
|
|
279
|
+
return {
|
|
280
|
+
outcome: 'conflict',
|
|
281
|
+
reason: `SPF authorization is indeterminate before a proven match: ${mechanism.name}`,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
let matches = false;
|
|
285
|
+
if (mechanism.kind === 'ip') {
|
|
286
|
+
matches = networkContains(mechanism.network, address);
|
|
287
|
+
}
|
|
288
|
+
else if (mechanism.kind === 'a') {
|
|
289
|
+
const domain = mechanism.mechanism.domain || intent.name.toLowerCase().replace(/\.$/, '');
|
|
290
|
+
const type = address.family === 4 ? 'A' : 'AAAA';
|
|
291
|
+
const prefixLength = address.family === 4
|
|
292
|
+
? mechanism.mechanism.ipv4PrefixLength
|
|
293
|
+
: mechanism.mechanism.ipv6PrefixLength;
|
|
294
|
+
for (const answer of await resolveBounded(domain, type)) {
|
|
295
|
+
const network = parseIpNetwork(`${answer}/${prefixLength}`, address.family);
|
|
296
|
+
if (network && networkContains(network, address)) {
|
|
297
|
+
matches = true;
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
matches = true;
|
|
304
|
+
}
|
|
305
|
+
if (!matches)
|
|
306
|
+
continue;
|
|
307
|
+
if (mechanism.qualifier !== '+') {
|
|
308
|
+
return {
|
|
309
|
+
outcome: 'conflict',
|
|
310
|
+
reason: `SPF first match for a desired edge has qualifier ${mechanism.qualifier}`,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
authorized = true;
|
|
314
|
+
break;
|
|
315
|
+
}
|
|
316
|
+
if (!authorized) {
|
|
317
|
+
return {
|
|
318
|
+
outcome: 'conflict',
|
|
319
|
+
reason: actual.parsed.hasRedirect
|
|
320
|
+
? 'SPF redirect is unsupported and authorization cannot be proven'
|
|
321
|
+
: 'SPF record does not authorize every desired mail edge address',
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
catch (error) {
|
|
327
|
+
return { outcome: 'lookup-error', reason: `SPF DNS lookup failed: ${error.message}` };
|
|
328
|
+
}
|
|
329
|
+
return { outcome: 'valid', effectiveValue: actualValue };
|
|
330
|
+
}
|
|
331
|
+
function parseDmarc(value) {
|
|
332
|
+
const orderedTags = [];
|
|
333
|
+
const seen = new Set();
|
|
334
|
+
for (const segment of value.split(';')) {
|
|
335
|
+
const trimmed = segment.trim();
|
|
336
|
+
if (!trimmed)
|
|
337
|
+
continue;
|
|
338
|
+
const equalsIndex = trimmed.indexOf('=');
|
|
339
|
+
if (equalsIndex <= 0)
|
|
340
|
+
return { error: `Malformed DMARC tag: ${trimmed}` };
|
|
341
|
+
const key = trimmed.slice(0, equalsIndex).trim().toLowerCase();
|
|
342
|
+
const tagValue = trimmed.slice(equalsIndex + 1).trim();
|
|
343
|
+
if (!key || !tagValue || seen.has(key))
|
|
344
|
+
return { error: `Invalid or duplicate DMARC tag: ${key}` };
|
|
345
|
+
seen.add(key);
|
|
346
|
+
orderedTags.push({ key, value: tagValue });
|
|
347
|
+
}
|
|
348
|
+
if (orderedTags[0]?.key !== 'v' || orderedTags[0].value !== 'DMARC1') {
|
|
349
|
+
return { error: 'DMARC v=DMARC1 must be the first tag' };
|
|
350
|
+
}
|
|
351
|
+
const tags = new Map(orderedTags.map((tag) => [tag.key, tag.value]));
|
|
352
|
+
const rawPolicy = tags.get('p')?.toLowerCase();
|
|
353
|
+
// Malformed optional tag values are ignored and locally defaulted. A missing
|
|
354
|
+
// or malformed p therefore has effective policy "none". pct and unknown
|
|
355
|
+
// extension tags do not change the policy-strength comparison.
|
|
356
|
+
const policy = rawPolicy === 'quarantine' || rawPolicy === 'reject' || rawPolicy === 'none'
|
|
357
|
+
? rawPolicy
|
|
358
|
+
: 'none';
|
|
359
|
+
return { policy: { policy } };
|
|
360
|
+
}
|
|
361
|
+
function evaluateDmarc(intent, actualValue) {
|
|
362
|
+
const desired = parseDmarc(intent.value);
|
|
363
|
+
const actual = parseDmarc(actualValue);
|
|
364
|
+
if (!desired.policy)
|
|
365
|
+
return { outcome: 'invalid', reason: desired.error };
|
|
366
|
+
if (!actual.policy)
|
|
367
|
+
return { outcome: 'invalid', reason: actual.error };
|
|
368
|
+
const strength = { none: 0, quarantine: 1, reject: 2 };
|
|
369
|
+
if (strength[actual.policy.policy] < strength[desired.policy.policy]) {
|
|
370
|
+
return { outcome: 'conflict', reason: 'Operator DMARC policy is weaker than the desired policy' };
|
|
371
|
+
}
|
|
372
|
+
return { outcome: 'valid', effectiveValue: actualValue };
|
|
373
|
+
}
|
|
374
|
+
function decodeDkimPublicKey(value) {
|
|
375
|
+
const normalized = value.replace(/\s+/g, '');
|
|
376
|
+
if (!normalized || !/^[A-Za-z0-9+/]+={0,2}$/.test(normalized) || normalized.length % 4 !== 0) {
|
|
377
|
+
return undefined;
|
|
378
|
+
}
|
|
379
|
+
const decoded = plugins.buffer.Buffer.from(normalized, 'base64');
|
|
380
|
+
return decoded.toString('base64').replace(/=+$/, '') === normalized.replace(/=+$/, '')
|
|
381
|
+
? decoded
|
|
382
|
+
: undefined;
|
|
383
|
+
}
|
|
384
|
+
function parseDkim(value) {
|
|
385
|
+
const tags = new Map();
|
|
386
|
+
for (const segment of value.split(';')) {
|
|
387
|
+
const trimmed = segment.trim();
|
|
388
|
+
if (!trimmed)
|
|
389
|
+
continue;
|
|
390
|
+
const equalsIndex = trimmed.indexOf('=');
|
|
391
|
+
if (equalsIndex <= 0)
|
|
392
|
+
return { error: `Malformed DKIM tag: ${trimmed}` };
|
|
393
|
+
const key = trimmed.slice(0, equalsIndex).trim().toLowerCase();
|
|
394
|
+
const tagValue = trimmed.slice(equalsIndex + 1).trim();
|
|
395
|
+
if (!/^[a-z][a-z0-9]*$/.test(key) || tags.has(key)) {
|
|
396
|
+
return { error: `Invalid or duplicate DKIM tag: ${key}` };
|
|
397
|
+
}
|
|
398
|
+
tags.set(key, tagValue);
|
|
399
|
+
}
|
|
400
|
+
if (tags.has('v') && tags.get('v') !== 'DKIM1') {
|
|
401
|
+
return { error: 'DKIM v must be DKIM1 when present' };
|
|
402
|
+
}
|
|
403
|
+
const publicKey = decodeDkimPublicKey(tags.get('p') || '');
|
|
404
|
+
if (!publicKey)
|
|
405
|
+
return { error: 'DKIM p must contain a valid non-empty base64 public key' };
|
|
406
|
+
const keyType = (tags.get('k') || 'rsa').toLowerCase();
|
|
407
|
+
const hashes = (tags.get('h') || 'sha256')
|
|
408
|
+
.split(':').map((entry) => entry.trim().toLowerCase()).filter(Boolean);
|
|
409
|
+
const services = (tags.get('s') || '*')
|
|
410
|
+
.split(':').map((entry) => entry.trim().toLowerCase()).filter(Boolean);
|
|
411
|
+
return { policy: { publicKey, keyType, hashes, services } };
|
|
412
|
+
}
|
|
413
|
+
function equalBytes(left, right) {
|
|
414
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
415
|
+
}
|
|
416
|
+
function evaluateDkim(intent, actualValue) {
|
|
417
|
+
const desired = parseDkim(intent.value);
|
|
418
|
+
const actual = parseDkim(actualValue);
|
|
419
|
+
if (!desired.policy)
|
|
420
|
+
return { outcome: 'invalid', reason: desired.error };
|
|
421
|
+
if (!actual.policy)
|
|
422
|
+
return { outcome: 'invalid', reason: actual.error };
|
|
423
|
+
if (actual.policy.keyType !== 'rsa') {
|
|
424
|
+
return { outcome: 'conflict', reason: 'DKIM k does not permit the required RSA key' };
|
|
425
|
+
}
|
|
426
|
+
if (!actual.policy.hashes.includes('sha256')) {
|
|
427
|
+
return { outcome: 'conflict', reason: 'DKIM h does not permit sha256' };
|
|
428
|
+
}
|
|
429
|
+
if (!actual.policy.services.some((service) => service === '*' || service === 'email')) {
|
|
430
|
+
return { outcome: 'conflict', reason: 'DKIM s does not permit email service' };
|
|
431
|
+
}
|
|
432
|
+
if (!equalBytes(actual.policy.publicKey, desired.policy.publicKey)) {
|
|
433
|
+
return { outcome: 'conflict', reason: `DKIM record at ${intent.name} contains a different public key` };
|
|
434
|
+
}
|
|
435
|
+
return { outcome: 'valid', effectiveValue: actualValue };
|
|
436
|
+
}
|
|
437
|
+
export async function evaluateMailTxtIntent(intent, rawValues, options = {}) {
|
|
438
|
+
const classified = rawValues
|
|
439
|
+
.map((value) => classifyMailTxtRecord(intent, value))
|
|
440
|
+
.filter((entry) => entry.relevant);
|
|
441
|
+
if (classified.length === 0) {
|
|
442
|
+
return { outcome: 'missing', reason: `${intent.type} ${intent.name} is missing` };
|
|
443
|
+
}
|
|
444
|
+
if (classified.length > 1) {
|
|
445
|
+
return { outcome: 'duplicate', reason: `${intent.name} has multiple relevant TXT records` };
|
|
446
|
+
}
|
|
447
|
+
const record = classified[0];
|
|
448
|
+
if (record.malformed || record.canonicalValue === undefined) {
|
|
449
|
+
return { outcome: 'invalid', reason: record.malformed || 'TXT record is malformed' };
|
|
450
|
+
}
|
|
451
|
+
const kind = intentKind(intent);
|
|
452
|
+
if (kind === 'spf')
|
|
453
|
+
return await evaluateSpf(intent, record.canonicalValue, options);
|
|
454
|
+
if (kind === 'dmarc')
|
|
455
|
+
return evaluateDmarc(intent, record.canonicalValue);
|
|
456
|
+
if (kind === 'dkim')
|
|
457
|
+
return evaluateDkim(intent, record.canonicalValue);
|
|
458
|
+
if (record.canonicalValue === intent.value.trim()) {
|
|
459
|
+
return { outcome: 'valid', effectiveValue: record.canonicalValue };
|
|
460
|
+
}
|
|
461
|
+
return {
|
|
462
|
+
outcome: 'conflict',
|
|
463
|
+
reason: `TXT record at ${intent.name} does not exactly match the desired value`,
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFpbC1kbnMtdHh0LXBvbGljeS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3RzL2VtYWlsL21haWwtZG5zLXR4dC1wb2xpY3kudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxLQUFLLE9BQU8sTUFBTSxlQUFlLENBQUM7QUFDekMsT0FBTyxFQUFFLHNCQUFzQixFQUFFLE1BQU0sbUNBQW1DLENBQUM7QUE4QzNFLFNBQVMsVUFBVSxDQUFDLE1BQTZCO0lBQy9DLE1BQU0sT0FBTyxHQUFHLE1BQU0sQ0FBQyxLQUFLLENBQUMsSUFBSSxFQUFFLENBQUMsV0FBVyxFQUFFLENBQUM7SUFDbEQsSUFBSSxPQUFPLENBQUMsVUFBVSxDQUFDLFFBQVEsQ0FBQztRQUFFLE9BQU8sS0FBSyxDQUFDO0lBQy9DLElBQUksT0FBTyxDQUFDLFVBQVUsQ0FBQyxVQUFVLENBQUM7UUFBRSxPQUFPLE9BQU8sQ0FBQztJQUNuRCxJQUFJLE9BQU8sQ0FBQyxVQUFVLENBQUMsU0FBUyxDQUFDO1FBQUUsT0FBTyxNQUFNLENBQUM7SUFDakQsT0FBTyxPQUFPLENBQUM7QUFDakIsQ0FBQztBQUVELFNBQVMsYUFBYSxDQUFDLElBQWtCO0lBQ3ZDLElBQUksSUFBSSxLQUFLLEtBQUs7UUFBRSxPQUFPLFFBQVEsQ0FBQztJQUNwQyxJQUFJLElBQUksS0FBSyxPQUFPO1FBQUUsT0FBTyxVQUFVLENBQUM7SUFDeEMsSUFBSSxJQUFJLEtBQUssTUFBTTtRQUFFLE9BQU8sU0FBUyxDQUFDO0lBQ3RDLE9BQU8sU0FBUyxDQUFDO0FBQ25CLENBQUM7QUFFRCxNQUFNLFVBQVUscUJBQXFCLENBQ25DLE1BQTZCLEVBQzdCLFFBQWdCO0lBRWhCLE1BQU0sSUFBSSxHQUFHLFVBQVUsQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUNoQyxNQUFNLE9BQU8sR0FBRyxzQkFBc0IsQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUNqRCxJQUFJLENBQUMsT0FBTyxDQUFDLEVBQUUs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QUksU0FBUyxHQUFHLENBQUMsSUFBSSxDQUFDLElBQUk7WUFBRSxPQUFPLFNBQVMsQ0FBQztRQUM3QyxVQUFVLEdBQUcsR0FBRyxVQUFVLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxTQUFTLENBQUMsSUFBSSxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxHQUFHLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxHQUFHLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDO0lBQ3pJLENBQUM7SUFDRCxNQUFNLE1BQU0sR0FBRyxVQUFVLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ3RDLElBQUksTUFBTSxDQUFDLE1BQU0sR0FBRyxDQUFDO1FBQUUsT0FBTyxTQUFTLENBQUM7SUFDeEMsTUFBTSxJQUFJLEdBQUcsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7SUFDbkQsTUFBTSxLQUFLLEdBQUcsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7SUFDcEQsTUFBTSxPQUFPLEdBQUcsQ0FBQyxHQUFHLElBQUksQ0FBQyxNQUFNLEdBQUcsS0FBSyxDQUFDLE1BQU0sQ0FBQztJQUMvQyxJQUFJLENBQUMsTUFBTSxDQUFDLE1BQU0sS0FBSyxDQUFDLElBQUksT0FBTyxLQUFLLENBQUMsQ0FBQyxJQUFJLE9BQU8sR0FBRyxDQUFDO1FBQUUsT0FBTyxTQUFTLENBQUM7SUFDNUUsTUFBTSxNQUFN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
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { DnsRecordDoc } from '../db/documents/classes.dns-record.doc.js';
|
|
2
|
+
import type { IEmailDnsRecordIntent } from '../../dist_ts_interfaces/data/email-domain.js';
|
|
3
|
+
import { type IMailDnsTxtPolicyResult } from './mail-dns-txt-policy.js';
|
|
4
|
+
export interface IMailDnsMutationClient {
|
|
5
|
+
createRecord(args: {
|
|
6
|
+
domainId: string;
|
|
7
|
+
name: string;
|
|
8
|
+
type: 'MX' | 'TXT' | 'A' | 'AAAA';
|
|
9
|
+
value: string;
|
|
10
|
+
ttl: number;
|
|
11
|
+
createdBy: string;
|
|
12
|
+
managedBy: string;
|
|
13
|
+
managedOwnerId: string;
|
|
14
|
+
managedRecordKey: string;
|
|
15
|
+
}): Promise<{
|
|
16
|
+
success: boolean;
|
|
17
|
+
id?: string;
|
|
18
|
+
message?: string;
|
|
19
|
+
}>;
|
|
20
|
+
updateRecord(args: {
|
|
21
|
+
id: string;
|
|
22
|
+
name?: string;
|
|
23
|
+
value?: string;
|
|
24
|
+
ttl?: number;
|
|
25
|
+
}): Promise<{
|
|
26
|
+
success: boolean;
|
|
27
|
+
message?: string;
|
|
28
|
+
}>;
|
|
29
|
+
deleteRecord(id: string): Promise<{
|
|
30
|
+
success: boolean;
|
|
31
|
+
message?: string;
|
|
32
|
+
}>;
|
|
33
|
+
}
|
|
34
|
+
export interface IReconcileMailDnsSingletonOptions {
|
|
35
|
+
intent: IEmailDnsRecordIntent;
|
|
36
|
+
rrset: DnsRecordDoc[];
|
|
37
|
+
dnsManager: IMailDnsMutationClient;
|
|
38
|
+
managedBy: string;
|
|
39
|
+
createdBy: string;
|
|
40
|
+
evaluate: (intent: IEmailDnsRecordIntent, values: string[]) => Promise<IMailDnsTxtPolicyResult>;
|
|
41
|
+
}
|
|
42
|
+
export declare function applyMailDnsVerificationResult(intent: IEmailDnsRecordIntent, result: IMailDnsTxtPolicyResult, phase: 'reconcile' | 'verify'): void;
|
|
43
|
+
/**
|
|
44
|
+
* Reconcile SPF, DMARC, and DKIM singleton TXT records without adopting or
|
|
45
|
+
* mutating operator policy. Returns false for non-singleton intents.
|
|
46
|
+
*/
|
|
47
|
+
export declare function reconcileMailDnsSingleton(options: IReconcileMailDnsSingletonOptions): Promise<boolean>;
|