@serve.zone/dcrouter 32.6.3 → 32.7.0
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/deno.json +1 -1
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.dcrouter.d.ts +14 -20
- package/dist_ts/classes.dcrouter.js +48 -85
- package/dist_ts/db/documents/classes.email-server-settings.doc.d.ts +3 -1
- package/dist_ts/db/documents/classes.email-server-settings.doc.js +9 -2
- package/dist_ts/email/classes.email-route-builder.d.ts +8 -0
- package/dist_ts/email/classes.email-route-builder.js +20 -13
- package/dist_ts/email/classes.email-settings.manager.d.ts +5 -1
- package/dist_ts/email/classes.email-settings.manager.js +34 -1
- package/dist_ts/email/classes.email-tls-manager.d.ts +99 -0
- package/dist_ts/email/classes.email-tls-manager.js +321 -0
- package/dist_ts/email/index.d.ts +1 -0
- package/dist_ts/email/index.js +2 -1
- package/dist_ts/opsserver/handlers/email-settings.handler.js +5 -1
- package/dist_ts_interfaces/data/email-settings.d.ts +43 -0
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/package.json +8 -8
- package/readme.md +7 -4
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.dcrouter.ts +50 -93
- package/ts/db/documents/classes.email-server-settings.doc.ts +5 -1
- package/ts/email/classes.email-route-builder.ts +22 -13
- package/ts/email/classes.email-settings.manager.ts +42 -0
- package/ts/email/classes.email-tls-manager.ts +398 -0
- package/ts/email/index.ts +1 -0
- package/ts/opsserver/handlers/email-settings.handler.ts +4 -0
- package/ts_web/00_commitinfo_data.ts +1 -1
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
import { logger } from '../logger.js';
|
|
3
|
+
import { ProxyCertDoc } from '../db/index.js';
|
|
4
|
+
/** Whether `emailConfig.tls` carries operator-managed certificate material. */
|
|
5
|
+
export const hasConfiguredEmailTlsMaterial = (tlsArg) => Boolean(tlsArg?.certPem || tlsArg?.keyPem || tlsArg?.certPath || tlsArg?.keyPath);
|
|
6
|
+
/**
|
|
7
|
+
* Source for settings persisted without one: the material they served before
|
|
8
|
+
* the source existed — configured material when present, otherwise the
|
|
9
|
+
* managed certificate.
|
|
10
|
+
*/
|
|
11
|
+
export const deriveEmailTlsSource = (tlsArg) => hasConfiguredEmailTlsMaterial(tlsArg) ? 'configuredMaterial' : 'managedCertificate';
|
|
12
|
+
/** The `tls` block without certificate material; undefined when nothing else remains. */
|
|
13
|
+
export const withoutConfiguredEmailTlsMaterial = (tlsArg) => {
|
|
14
|
+
if (!tlsArg)
|
|
15
|
+
return undefined;
|
|
16
|
+
const { certPem, keyPem, certPath, keyPath, ...remaining } = tlsArg;
|
|
17
|
+
return Object.keys(remaining).length > 0 ? remaining : undefined;
|
|
18
|
+
};
|
|
19
|
+
/** The `tls` block handed to SmartMTA: configured options plus the resolved PEM material. */
|
|
20
|
+
export const composeEmailServerTls = (tlsArg, materialArg) => {
|
|
21
|
+
const options = withoutConfiguredEmailTlsMaterial(tlsArg);
|
|
22
|
+
if (!materialArg)
|
|
23
|
+
return options;
|
|
24
|
+
return { ...options, certPem: materialArg.certPem, keyPem: materialArg.keyPem };
|
|
25
|
+
};
|
|
26
|
+
const describeError = (errorArg) => errorArg instanceof Error ? errorArg.message : String(errorArg);
|
|
27
|
+
const normalizeHostname = (hostnameArg) => hostnameArg.trim().toLowerCase().replace(/\.$/, '');
|
|
28
|
+
/** DNS subject alternative names of a certificate, lowercased. */
|
|
29
|
+
export const getCertificateDnsNames = (certificateArg) => (certificateArg.subjectAltName || '')
|
|
30
|
+
.split(',')
|
|
31
|
+
.map((entry) => entry.trim())
|
|
32
|
+
.filter((entry) => entry.startsWith('DNS:'))
|
|
33
|
+
.map((entry) => normalizeHostname(entry.slice('DNS:'.length).replace(/^"|"$/g, '')));
|
|
34
|
+
/**
|
|
35
|
+
* RFC 6125 name matching: exact, or a `*.` wildcard standing for exactly one
|
|
36
|
+
* left-most label. Implemented here because X509Certificate.checkHost does not
|
|
37
|
+
* match wildcards on every runtime dcrouter ships on.
|
|
38
|
+
*/
|
|
39
|
+
export const certificateNameCovers = (nameArg, hostnameArg) => {
|
|
40
|
+
const hostname = normalizeHostname(hostnameArg);
|
|
41
|
+
if (nameArg === hostname)
|
|
42
|
+
return true;
|
|
43
|
+
if (!nameArg.startsWith('*.'))
|
|
44
|
+
return false;
|
|
45
|
+
const firstDot = hostname.indexOf('.');
|
|
46
|
+
return firstDot > 0 && hostname.slice(firstDot + 1) === nameArg.slice(2);
|
|
47
|
+
};
|
|
48
|
+
const findUncoveredHostnames = (namesArg, hostnamesArg) => hostnamesArg.filter((hostname) => !namesArg.some((name) => certificateNameCovers(name, hostname)));
|
|
49
|
+
const parseCertificate = (certPemArg) => new plugins.crypto.X509Certificate(certPemArg);
|
|
50
|
+
const isCurrentlyValid = (certificateArg, nowArg) => certificateArg.validFromDate.getTime() <= nowArg && nowArg < certificateArg.validToDate.getTime();
|
|
51
|
+
/**
|
|
52
|
+
* Owns the SMTP listener's certificate: resolves it from the selected source
|
|
53
|
+
* on every listener start and relevant renewal, and reports what is served.
|
|
54
|
+
*
|
|
55
|
+
* Resolved material is held here and never written back into the email
|
|
56
|
+
* configuration, so every resolution reads the source of truth again.
|
|
57
|
+
*/
|
|
58
|
+
export class EmailTlsManager {
|
|
59
|
+
served;
|
|
60
|
+
/** Resolve the listener material from the selected source and record it as served. */
|
|
61
|
+
async resolve(requestArg) {
|
|
62
|
+
const material = requestArg.source === 'configuredMaterial'
|
|
63
|
+
? await this.resolveConfiguredMaterial(requestArg.tls)
|
|
64
|
+
: await this.resolveManagedCertificate(requestArg.requiredHostnames);
|
|
65
|
+
const status = this.getStatus(requestArg.requiredHostnames);
|
|
66
|
+
if (status && status.problems.length > 0) {
|
|
67
|
+
logger.log('error', `SMTP TLS certificate problem (source=${status.source}, origin=${status.origin ?? 'none'}): ${status.problems.join('; ')}`);
|
|
68
|
+
}
|
|
69
|
+
else if (status) {
|
|
70
|
+
logger.log('info', `SMTP TLS certificate: source=${status.source}, origin=${status.origin}, notAfter=${status.notAfter === null ? 'unknown' : new Date(status.notAfter).toISOString()}, names=[${status.names.join(', ')}]`);
|
|
71
|
+
}
|
|
72
|
+
return material;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Push an issued or renewed certificate to the running listener when it can
|
|
76
|
+
* change what the listener should serve. Returns the applied material.
|
|
77
|
+
*/
|
|
78
|
+
async applyIssuance(domainArg, listenerArg, requestArg) {
|
|
79
|
+
if (!(await this.isRelevantIssuance(domainArg, requestArg.requiredHostnames)))
|
|
80
|
+
return undefined;
|
|
81
|
+
const material = await this.resolve(requestArg);
|
|
82
|
+
if (!material)
|
|
83
|
+
return undefined;
|
|
84
|
+
listenerArg.updateOptions({ tls: composeEmailServerTls(requestArg.tls, material) });
|
|
85
|
+
return material;
|
|
86
|
+
}
|
|
87
|
+
/** Forget the served certificate (the listener is stopped). */
|
|
88
|
+
clear() {
|
|
89
|
+
this.served = undefined;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Whether an issued or renewed certificate can change what the listener
|
|
93
|
+
* should serve. Only the managed source follows issuance; the stored
|
|
94
|
+
* certificate is keyed by the requested route domain, so a wildcard or
|
|
95
|
+
* sibling issuance is matched by the names it covers, not by its key.
|
|
96
|
+
*/
|
|
97
|
+
async isRelevantIssuance(domainArg, requiredHostnamesArg) {
|
|
98
|
+
const served = this.served;
|
|
99
|
+
if (!served || served.source !== 'managedCertificate')
|
|
100
|
+
return false;
|
|
101
|
+
const domain = normalizeHostname(domainArg);
|
|
102
|
+
if (served.storedDomain !== null && normalizeHostname(served.storedDomain) === domain)
|
|
103
|
+
return true;
|
|
104
|
+
if (requiredHostnamesArg.some((hostname) => normalizeHostname(hostname) === domain))
|
|
105
|
+
return true;
|
|
106
|
+
const stored = await ProxyCertDoc.findByDomain(domainArg);
|
|
107
|
+
if (!stored?.publicKey)
|
|
108
|
+
return false;
|
|
109
|
+
try {
|
|
110
|
+
const names = getCertificateDnsNames(parseCertificate(stored.publicKey));
|
|
111
|
+
return findUncoveredHostnames(names, requiredHostnamesArg).length < requiredHostnamesArg.length;
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* The best stored certificate for the required hostnames: currently valid
|
|
119
|
+
* and covering every name first, then valid, then the most names covered,
|
|
120
|
+
* then the latest expiry. Undefined when none covers any required hostname.
|
|
121
|
+
*/
|
|
122
|
+
async selectStoredCertificate(requiredHostnamesArg, nowArg = Date.now()) {
|
|
123
|
+
const candidates = [];
|
|
124
|
+
for (const doc of await ProxyCertDoc.findAll()) {
|
|
125
|
+
if (!doc.publicKey || !doc.privateKey)
|
|
126
|
+
continue;
|
|
127
|
+
let certificate;
|
|
128
|
+
try {
|
|
129
|
+
certificate = parseCertificate(doc.publicKey);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const names = getCertificateDnsNames(certificate);
|
|
135
|
+
const uncoveredHostnames = findUncoveredHostnames(names, requiredHostnamesArg);
|
|
136
|
+
if (uncoveredHostnames.length === requiredHostnamesArg.length)
|
|
137
|
+
continue;
|
|
138
|
+
candidates.push({
|
|
139
|
+
domain: doc.domain,
|
|
140
|
+
certPem: doc.publicKey,
|
|
141
|
+
keyPem: doc.privateKey,
|
|
142
|
+
certificate,
|
|
143
|
+
names,
|
|
144
|
+
uncoveredHostnames,
|
|
145
|
+
currentlyValid: isCurrentlyValid(certificate, nowArg),
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
const rank = (candidateArg) => [
|
|
149
|
+
candidateArg.currentlyValid && candidateArg.uncoveredHostnames.length === 0 ? 1 : 0,
|
|
150
|
+
candidateArg.currentlyValid ? 1 : 0,
|
|
151
|
+
-candidateArg.uncoveredHostnames.length,
|
|
152
|
+
candidateArg.certificate.validToDate.getTime(),
|
|
153
|
+
];
|
|
154
|
+
candidates.sort((left, right) => {
|
|
155
|
+
const leftRank = rank(left);
|
|
156
|
+
const rightRank = rank(right);
|
|
157
|
+
for (let index = 0; index < leftRank.length; index++) {
|
|
158
|
+
if (leftRank[index] !== rightRank[index])
|
|
159
|
+
return rightRank[index] - leftRank[index];
|
|
160
|
+
}
|
|
161
|
+
return left.domain.localeCompare(right.domain);
|
|
162
|
+
});
|
|
163
|
+
return candidates[0];
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Gate for switching to the managed source: refuses unless a currently
|
|
167
|
+
* valid stored certificate covers every required hostname.
|
|
168
|
+
*/
|
|
169
|
+
async assertManagedCertificateAvailable(requiredHostnamesArg) {
|
|
170
|
+
const stored = await this.selectStoredCertificate(requiredHostnamesArg);
|
|
171
|
+
if (stored?.currentlyValid && stored.uncoveredHostnames.length === 0)
|
|
172
|
+
return stored;
|
|
173
|
+
const closest = !stored
|
|
174
|
+
? ''
|
|
175
|
+
: stored.currentlyValid
|
|
176
|
+
? ` The closest stored certificate '${stored.domain}' does not cover ${stored.uncoveredHostnames.join(', ')}.`
|
|
177
|
+
: ` The closest stored certificate '${stored.domain}' is outside its validity period (notAfter ${stored.certificate.validToDate.toISOString()}).`;
|
|
178
|
+
throw new Error(`Refusing the managedCertificate email TLS source: no currently valid stored certificate covers ${requiredHostnamesArg.join(', ')}.${closest}`);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* The served certificate checked against the required hostnames now, so
|
|
182
|
+
* expiry while running shows up without a new resolution.
|
|
183
|
+
*/
|
|
184
|
+
getStatus(requiredHostnamesArg, nowArg = Date.now()) {
|
|
185
|
+
const served = this.served;
|
|
186
|
+
if (!served)
|
|
187
|
+
return undefined;
|
|
188
|
+
const problems = [...served.materialProblems];
|
|
189
|
+
const certificate = served.certificate;
|
|
190
|
+
if (certificate) {
|
|
191
|
+
const notBefore = certificate.validFromDate.getTime();
|
|
192
|
+
const notAfter = certificate.validToDate.getTime();
|
|
193
|
+
if (nowArg >= notAfter) {
|
|
194
|
+
problems.push(`certificate expired at ${new Date(notAfter).toISOString()}`);
|
|
195
|
+
}
|
|
196
|
+
else if (nowArg < notBefore) {
|
|
197
|
+
problems.push(`certificate is not valid before ${new Date(notBefore).toISOString()}`);
|
|
198
|
+
}
|
|
199
|
+
const uncovered = findUncoveredHostnames(served.names, requiredHostnamesArg);
|
|
200
|
+
if (uncovered.length > 0) {
|
|
201
|
+
problems.push(`certificate does not cover ${uncovered.join(', ')}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
source: served.source,
|
|
206
|
+
origin: served.origin,
|
|
207
|
+
names: [...served.names],
|
|
208
|
+
notBefore: certificate ? certificate.validFromDate.getTime() : null,
|
|
209
|
+
notAfter: certificate ? certificate.validToDate.getTime() : null,
|
|
210
|
+
requiredHostnames: [...requiredHostnamesArg],
|
|
211
|
+
problems,
|
|
212
|
+
checkedAt: nowArg,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Operator-managed material. Paths that cannot be read FAIL listener
|
|
217
|
+
* startup: continuing without TLS is what once produced a cleartext
|
|
218
|
+
* submission port, so a broken explicit configuration must be impossible
|
|
219
|
+
* to miss.
|
|
220
|
+
*/
|
|
221
|
+
async resolveConfiguredMaterial(tlsArg) {
|
|
222
|
+
let material;
|
|
223
|
+
if (tlsArg?.certPem && tlsArg.keyPem) {
|
|
224
|
+
material = { certPem: tlsArg.certPem, keyPem: tlsArg.keyPem, origin: 'inline PEM' };
|
|
225
|
+
}
|
|
226
|
+
else if (tlsArg?.certPath || tlsArg?.keyPath) {
|
|
227
|
+
if (!tlsArg.certPath || !tlsArg.keyPath) {
|
|
228
|
+
throw new Error('emailConfig.tls requires both certPath and keyPath when either is configured');
|
|
229
|
+
}
|
|
230
|
+
let certPem;
|
|
231
|
+
let keyPem;
|
|
232
|
+
try {
|
|
233
|
+
certPem = await plugins.fs.promises.readFile(tlsArg.certPath, 'utf8');
|
|
234
|
+
keyPem = await plugins.fs.promises.readFile(tlsArg.keyPath, 'utf8');
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
throw new Error(`Unable to read the configured SMTP TLS material (certPath=${tlsArg.certPath}, keyPath=${tlsArg.keyPath}): ${describeError(error)}`);
|
|
238
|
+
}
|
|
239
|
+
if (!certPem.trim() || !keyPem.trim()) {
|
|
240
|
+
throw new Error(`The configured SMTP TLS material is empty (certPath=${tlsArg.certPath}, keyPath=${tlsArg.keyPath})`);
|
|
241
|
+
}
|
|
242
|
+
material = { certPem, keyPem, origin: `configured paths (${tlsArg.certPath})` };
|
|
243
|
+
}
|
|
244
|
+
else if (tlsArg?.certPem || tlsArg?.keyPem) {
|
|
245
|
+
throw new Error('emailConfig.tls requires both certPem and keyPem when either is configured');
|
|
246
|
+
}
|
|
247
|
+
if (!material) {
|
|
248
|
+
this.served = {
|
|
249
|
+
source: 'configuredMaterial',
|
|
250
|
+
origin: null,
|
|
251
|
+
storedDomain: null,
|
|
252
|
+
certificate: null,
|
|
253
|
+
names: [],
|
|
254
|
+
materialProblems: ['the configuredMaterial source is selected but emailConfig.tls carries no certificate material'],
|
|
255
|
+
};
|
|
256
|
+
return undefined;
|
|
257
|
+
}
|
|
258
|
+
const materialProblems = [];
|
|
259
|
+
let certificate = null;
|
|
260
|
+
try {
|
|
261
|
+
certificate = parseCertificate(material.certPem);
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
materialProblems.push(`certificate cannot be parsed: ${describeError(error)}`);
|
|
265
|
+
}
|
|
266
|
+
if (certificate) {
|
|
267
|
+
materialProblems.push(...this.checkKeyPair(certificate, material.keyPem));
|
|
268
|
+
}
|
|
269
|
+
this.served = {
|
|
270
|
+
source: 'configuredMaterial',
|
|
271
|
+
origin: material.origin,
|
|
272
|
+
storedDomain: null,
|
|
273
|
+
certificate,
|
|
274
|
+
names: certificate ? getCertificateDnsNames(certificate) : [],
|
|
275
|
+
materialProblems,
|
|
276
|
+
};
|
|
277
|
+
return material;
|
|
278
|
+
}
|
|
279
|
+
async resolveManagedCertificate(requiredHostnamesArg) {
|
|
280
|
+
let stored;
|
|
281
|
+
let lookupProblem;
|
|
282
|
+
try {
|
|
283
|
+
stored = await this.selectStoredCertificate(requiredHostnamesArg);
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
lookupProblem = `stored certificates cannot be read: ${describeError(error)}`;
|
|
287
|
+
}
|
|
288
|
+
if (!stored) {
|
|
289
|
+
this.served = {
|
|
290
|
+
source: 'managedCertificate',
|
|
291
|
+
origin: null,
|
|
292
|
+
storedDomain: null,
|
|
293
|
+
certificate: null,
|
|
294
|
+
names: [],
|
|
295
|
+
materialProblems: [lookupProblem ?? `no stored certificate covers ${requiredHostnamesArg.join(', ')}`],
|
|
296
|
+
};
|
|
297
|
+
return undefined;
|
|
298
|
+
}
|
|
299
|
+
const origin = `stored certificate '${stored.domain}'`;
|
|
300
|
+
this.served = {
|
|
301
|
+
source: 'managedCertificate',
|
|
302
|
+
origin,
|
|
303
|
+
storedDomain: stored.domain,
|
|
304
|
+
certificate: stored.certificate,
|
|
305
|
+
names: stored.names,
|
|
306
|
+
materialProblems: this.checkKeyPair(stored.certificate, stored.keyPem),
|
|
307
|
+
};
|
|
308
|
+
return { certPem: stored.certPem, keyPem: stored.keyPem, origin };
|
|
309
|
+
}
|
|
310
|
+
checkKeyPair(certificateArg, keyPemArg) {
|
|
311
|
+
try {
|
|
312
|
+
return certificateArg.checkPrivateKey(plugins.crypto.createPrivateKey(keyPemArg))
|
|
313
|
+
? []
|
|
314
|
+
: ['private key does not match the certificate'];
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
return [`private key cannot be parsed: ${describeError(error)}`];
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5lbWFpbC10bHMtbWFuYWdlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3RzL2VtYWlsL2NsYXNzZXMuZW1haWwtdGxzLW1hbmFnZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxLQUFLLE9BQU8sTUFBTSxlQUFlLENBQUM7QUFDekMsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUN0QyxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUE4QzlDLCtFQUErRTtBQUMvRSxNQUFNLENBQUMsTUFBTSw2QkFBNkIsR0FBRyxDQUFDLE1BQTJDLEVBQVcsRUFBRSxDQUNwRyxPQUFPLENBQUMsTUFBTSxFQUFFLE9BQU8sSUFBSSxNQUFNLEVBQUUsTUFBTSxJQUFJLE1BQU0sRUFBRSxRQUFRLElBQUksTUFBTSxFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBRXBGOzs7O0dBSUc7QUFDSCxNQUFNLENBQUMsTUFBTSxvQkFBb0IsR0FBRyxDQUFDLE1BQTJDLEVBQW1CLEVBQUUsQ0FDbkcsNkJBQTZCLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLG9CQUFvQixDQUFDLENBQUMsQ0FBQyxvQkFBb0IsQ0FBQztBQUV0Rix5RkFBeUY7QUFDekYsTUFBTSxDQUFDLE1BQU0saUNBQWlDLEdBQUcsQ0FDL0MsTUFBMkMsRUFDTixFQUFFO0lBQ3ZDLElBQUksQ0FBQyxNQUFNO1FBQUUsT0FBTyxTQUFTLENBQUM7SUFDOUIsTUFBTSxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsUUFBUSxFQUFFLE9BQU8sRUFBRSxHQUFHLFNBQVMsRUFBRSxHQUFHLE1BQU0sQ0FBQztJQUNwRSxPQUFPLE1BQU0sQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUM7QUFDbkUsQ0FBQyxDQUFDO0FBRUYsNkZBQTZGO0FBQzdGLE1BQU0sQ0FBQyxNQUFNLHFCQUFxQixHQUFHLENBQ25DLE1BQTJDLEVBQzNDLFdBQTBDLEVBQ0wsRUFBRTtJQUN2QyxNQUFNLE9BQU8sR0FBRyxpQ0FBaUMsQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUMxRCxJQUFJLENBQUMsV0FBVztRQUFFLE9BQU8sT0FBTyxDQUFDO0lBQ2pDLE9BQU8sRUFBRSxHQUFHLE9BQU8sRUFBRSxPQUFPLEVBQUUsV0FBVyxDQUFDLE9BQU8sRUFBRSxNQUFNLEVBQUUsV0FBVyxDQUFDLE1BQU0sRUFBRSxDQUFDO0FBQ2xGLENBQUMsQ0FBQztBQUVGLE1BQU0sYUFBYSxHQUFHLENBQUMsUUFBaUIsRUFBVSxFQUFFLENBQ2xELFFBQVEsWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsQ0FBQztBQUVsRSxNQUFNLGlCQUFpQixHQUFHLENBQUMsV0FBbUIsRUFBVSxFQUFFLENBQ3hELFdBQVcsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxXQUFXLEVBQUUsQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxDQUFDO0FBRXRELGtFQUFrRTtBQUNsRSxNQUFNLENBQUMsTUFBTSxzQkFBc0IsR0FBRyxDQUFDLGNBQThDLEVBQVksRUFBRSxDQUNqRyxDQUFDLGNBQWMsQ0FBQyxjQUFjLElBQUksRUFBRSxDQUFDO0tBQ2xDLEtBQUssQ0FBQyxHQUFHLENBQUM7S0FDVixHQUFHLENBQUMsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLEtBQUssQ0FBQyxJQUFJLEVBQUUsQ0FBQztLQUM1QixNQUFNLENBQUMsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLEtBQUssQ0FBQyxVQUFVLENBQUMsTUFBTSxDQUFDLENBQUM7S0FDM0MsR0FBRyxDQUFDLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxpQkFBaUIsQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQyxPQUFPLENBQUMsUUFBUSxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUV6Rjs7OztHQUlHO0FBQ0gsTUFBTSxDQUFDLE1BQU0scUJBQXFCLEdBQUcsQ0FBQyxPQUFlLEVBQUUsV0FBbUIsRUFBVyxFQUFFO0lBQ3JGLE1BQU0sUUFBUSxHQUFHLGlCQUFpQixDQUFDLFdBQVcsQ0FBQyxDQUFDO0lBQ2hELElBQUksT0FBTyxLQUFLLFFBQVE7UUFBRSxPQUFPLElBQUksQ0FBQztJQUN0QyxJQUFJLENBQUMsT0FBTyxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUM7UUFBRSxPQUFPLEtBQUssQ0FBQztJQUM1QyxNQUFNLFFBQVEsR0FBRyxRQUFRLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBQ3ZDLE9BQU8sUUFBUSxHQUFHLENBQUMsSUFBSSxRQUFRLENBQUMsS0FBSyxDQUFDLFFBQVEsR0FBRyxDQUFDLENBQUMsS0FBSyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQzNFLENBQUMsQ0FBQztBQUVGLE1BQU0sc0JBQXNCLEdBQUcsQ0FBQyxRQUFrQixFQUFFLFlBQXNCLEVBQVksRUFBRSxDQUN0RixZQUFZLENBQUMsTUFBTSxDQUFDLENBQUMsUUFBUSxFQUFFLEVBQUUsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLHFCQUFxQixDQUFDLElBQUksRUFBRSxRQUFRLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFFckcsTUFBTSxnQkFBZ0IsR0FBRyxDQUFDLFVBQWtCLEVBQWtDLEVBQUUsQ0FBQyxJQUFJLE9BQU8sQ0FBQyxNQUFNLENBQUMsZUFBZSxDQUFDLFVBQVUsQ0FBQyxDQUFDO0FBRWhJLE1BQU0sZ0JBQWdCLEdBQUcsQ0FBQyxjQUE4QyxFQUFFLE1BQWMsRUFBVyxFQUFFLENBQ25HLGNBQWMsQ0FBQyxhQUFhLENBQUMsT0FBTyxFQUFFLElBQUksTUFBTSxJQUFJLE1BQU0sR0FBRyxjQUFjLENBQUMsV0FBVyxDQUFDLE9BQU8sRUFBRSxDQUFDO0FBRXBHOzs7Ozs7R0FNRztBQUNILE1BQU0sT0FBTyxlQUFlO0lBQ2xCLE1BQU0sQ0FBOEI7SUFFNUMsc0ZBQXNGO0lBQy9FLEtBQUssQ0FBQyxPQUFPLENBQUMsVUFBNEI7UUFDL0MsTUFBTSxRQUFRLEdBQUcsVUFBVSxDQUFDLE1BQU0sS0FBSyxvQkFBb0I7WUFDekQsQ0FBQyxDQUFDLE1BQU0sSUFBSSxDQUFDLHlCQUF5QixDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUM7WUFDdEQsQ0FBQyxDQUFDLE1BQU0sSUFBSSxDQUFDLHlCQUF5QixDQUFDLFVBQVUsQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDO1FBRXZFLE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUMsVUFBVSxDQUFDLGlCQUFpQixDQUFDLENBQUM7UUFDNUQsSUFBSSxNQUFNLElBQUksTUFBTSxDQUFDLFFBQVEsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUM7WUFDekMsTUFBTSxDQUFDLEdBQUcsQ0FDUixPQUFPLEVBQ1Asd0NBQXdDLE1BQU0sQ0FBQyxNQUFNLFlBQVksTUFBTSxDQUFDLE1BQU0sSUFBSSxNQUFNLE1BQU0sTUFBTSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FDM0gsQ0FBQztRQUNKLENBQUM7YUFBTSxJQUFJLE1BQU0sRUFBRSxDQUFDO1lBQ2xCLE1BQU0sQ0FBQyxHQUFHLENBQ1IsTUFBTSxFQUNOLGdDQUFnQyxNQUFNLENBQUMsTUFBTSxZQUFZLE1BQU0sQ0FBQyxNQUFNLGNBQWMsTUFBTSxDQUFDLFFBQVEsS0FBSyxJQUFJLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsSUFBSSxJQUFJLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxDQUFDLFdBQVcsRUFBRSxZQUFZLE1BQU0sQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQ3pNLENBQUM7UUFDSixDQUFDO1FBQ0QsT0FBTyxRQUFRLENBQUM7SUFDbEIsQ0FBQztJQUVEOzs7T0FHRztJQUNJLEtBQUssQ0FBQyxhQUFhLENBQ3hCLFNBQWlCLEVBQ2pCLFdBQThCLEVBQzlCLFVBQTRCO1FBRTVCLElBQUksQ0FBQyxDQUFDLE1BQU0sSUFBSSxDQUFDLGtCQUFrQixDQUFDLFNBQVMsRUFBRSxVQUFVLENBQUMsaUJBQWlCLENBQUMsQ0FBQztZQUFFLE9BQU8sU0FBUyxDQUFDO1FBQ2hHLE1BQU0sUUFBUSxHQUFHLE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUNoRCxJQUFJLENBQUMsUUFBUTtZQUFFLE9BQU8sU0FBUyxDQUFDO1FBQ2hDLFdBQVcsQ0FBQyxhQUFhLENBQUMsRUFBRSxHQUFHLEVBQUUscUJBQXFCLENBQUMsVUFBVSxDQUFDLEdBQUcsRUFBRSxRQUFRLENBQUMsRUFBRSxDQUFDLENBQUM7UUFDcEYsT0FBTyxRQUFRLENBQUM7SUFDbEIsQ0FBQztJQUVELCtEQUErRDtJQUN4RCxLQUFLO1FBQ1YsSUFBSSxDQUFDLE1BQU0sR0FBRyxTQUFTLENBQUM7SUFDMUIsQ0FBQztJQUVEOzs7OztPQUtHO0lBQ0ksS0FBSyxDQUFDLGtCQUFrQixDQUFDLFNBQWlCLEVBQUUsb0JBQThCO1FBQy9FLE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUM7UUFDM0IsSUFBSSxDQUFDLE1BQU0sSUFBSSxNQUFNLENBQUMsTUFBTSxLQUFLLG9CQUFvQjtZQUFFLE9BQU8sS0FBSyxDQUFDO1FBQ3BFLE1BQU0sTUFBTSxHQUFHLGlCQUFpQixDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQzVDLElBQUksTUFBTSxDQUFDLFlBQVksS0FBSyxJQUFJLElBQUksaUJBQWlCLENBQUMsTUFBTSxDQUFDLFlBQVksQ0FBQyxLQUFLLE1BQU07WUFBRSxPQUFPLElBQUksQ0FBQztRQUNuRyxJQUFJLG9CQUFvQixDQUFDLElBQUksQ0FBQyxDQUFDLFFBQVEsRUFBRSxFQUFFLENBQUMsaUJBQWlCLENBQUMsUUFBUSxDQUFDLEtBQUssTUFBTSxDQUFDO1lBQUUsT0FBTyxJQUFJLENBQUM7UUFDakcsTUFBTSxNQUFNLEdBQUcsTUFBTSxZQUFZLENBQUMsWUFBWSxDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQzFELElBQUksQ0FBQyxNQUFNLEVBQUUsU0FBUztZQUFFLE9BQU8sS0FBSyxDQUFDO1FBQ3JDLElBQUksQ0FBQztZQUNILE1BQU0sS0FBSyxHQUFHLHNCQUFzQixDQUFDLGdCQUFnQixDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDO1lBQ3pFLE9BQU8sc0JBQXNCLENBQUMsS0FBSyxFQUFFLG9CQUFvQixDQUFDLENBQUMsTUFBTSxHQUFHLG9CQUFvQixDQUFDLE1BQU0sQ0FBQztRQUNsRyxDQUFDO1FBQUMsTUFBTSxDQUFDO1lBQ1AsT0FBTyxLQUFLLENBQUM7UUFDZixDQUFDO0lBQ0gsQ0FBQztJQUVEOzs7O09BSUc7SUFDSSxLQUFLLENBQUMsdUJBQXVCLENBQ2xDLG9CQUE4QixFQUM5QixNQUFNLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRTtRQUVuQixNQUFNLFVBQVUsR0FBOEIsRUFBRSxDQUFDO1FBQ2pELEtBQUssTUFBTSxHQUFHLElBQUksTUFBTSxZQUFZLENBQUMsT0FBTyxFQUFFLEVBQUUsQ0FBQztZQUMvQyxJQUFJLENBQUMsR0FBRyxDQUFDLFNBQVMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxVQUFVO2dCQUFFLFNBQVM7WUFDaEQsSUFBSSxXQUEyQyxDQUFDO1lBQ2hELElBQUksQ0FBQztnQkFDSCxXQUFXLEdBQUcsZ0JBQWdCLENBQUMsR0FBRyxDQUFDLFNBQVMsQ0FBQyxDQUFDO1lBQ2hELENBQUM7WUFBQyxNQUFNLENBQUM7Z0JBQ1AsU0FBUztZQUNYLENBQUM7WUFDRCxNQUFNLEtBQUssR0FBRyxzQkFBc0IsQ0FBQyxXQUFXLENBQUMsQ0FBQztZQUNsRCxNQUFNLGtCQUFrQixHQUFHLHNCQUFzQixDQUFDLEtBQUssRUFBRSxvQkFBb0IsQ0FBQyxDQUFDO1lBQy9FLElBQUksa0JBQWtCLENBQUMsTUFBTSxLQUFLLG9CQUFvQixDQUFDLE1BQU07Z0JBQUUsU0FBUztZQUN4RSxVQUFVLENBQUMsSUFBSSxDQUFDO2dCQUNkLE1BQU0sRUFBRSxHQUFHLENBQUMsTUFBTTtnQkFDbEIsT0FBTyxFQUFFLEdBQUcsQ0FBQyxTQUFTO2dCQUN0QixNQUFNLEVBQUUsR0FBRyxDQUFDLFVBQVU7Z0JBQ3RCLFdBQVc7Z0JBQ1gsS0FBSztnQkFDTCxrQkFBa0I7Z0JBQ2xCLGNBQWMsRUFBRSxnQkFBZ0IsQ0FBQyxXQUFXLEVBQUUsTUFBTSxDQUFDO2FBQ3RELENBQUMsQ0FBQztRQUNMLENBQUM7UUFDRCxNQUFNLElBQUksR0FBRyxDQUFDLFlBQXFDLEVBQVksRUFBRSxDQUFDO1lBQ2hFLFlBQVksQ0FBQyxjQUFjLElBQUksWUFBWSxDQUFDLGtCQUFrQixDQUFDLE1BQU0sS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUNuRixZQUFZLENBQUMsY0FBYyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDbkMsQ0FBQyxZQUFZLENBQUMsa0JBQWtCLENBQUMsTUFBTTtZQUN2QyxZQUFZLENBQUMsV0FBVyxDQUFDLFdBQVcsQ0FBQyxPQUFPLEVBQUU7U0FDL0MsQ0FBQztRQUNGLFVBQVUsQ0FBQyxJQUFJLENBQUMsQ0FBQyxJQUFJLEVBQUUsS0FBSyxFQUFFLEVBQUU7WUFDOUIsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQzVCLE1BQU0sU0FBUyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQztZQUM5QixLQUFLLElBQUksS0FBSyxHQUFHLENBQUMsRUFBRSxLQUFLLEdBQUcsUUFBUSxDQUFDLE1BQU0sRUFBRSxLQUFLLEVBQUUsRUFBRSxDQUFDO2dCQUNyRCxJQUFJLFFBQVEsQ0FBQyxLQUFLLENBQUMsS0FBSyxTQUFTLENBQUMsS0FBSyxDQUFDO29CQUFFLE9BQU8sU0FBUyxDQUFDLEtBQUssQ0FBQyxHQUFHLFFBQVEsQ0FBQyxLQUFLLENBQUMsQ0FBQztZQUN0RixDQUFDO1lBQ0QsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDLGFBQWEsQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDakQsQ0FBQyxDQUFDLENBQUM7UUFDSCxPQUFPLFVBQVUsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUN2QixDQUFDO0lBRUQ7OztPQUdHO0lBQ0ksS0FBSyxDQUFDLGlDQUFpQyxDQUFDLG9CQUE4QjtRQUMzRSxNQUFNLE1BQU0sR0FBRyxNQUFNLElBQUksQ0FBQyx1QkFBdUIsQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDO1FBQ3hFLElBQUksTUFBTSxFQUFFLGNBQWMsSUFBSSxNQUFNLENBQUMsa0JBQWtCLENBQUMsTUFBTSxLQUFLLENBQUM7WUFBRSxPQUFPLE1BQU0sQ0FBQztRQUNwRixNQUFNLE9BQU8sR0FBRyxDQUFDLE1BQU07WUFDckIsQ0FBQyxDQUFDLEVBQUU7WUFDSixDQUFDLENBQUMsTUFBTSxDQUFDLGNBQWM7Z0JBQ3JCLENBQUMsQ0FBQyxvQ0FBb0MsTUFBTSxDQUFDLE1BQU0sb0JBQW9CLE1BQU0sQ0FBQyxrQkFBa0IsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUc7Z0JBQzlHLENBQUMsQ0FBQyxvQ0FBb0MsTUFBTSxDQUFDLE1BQU0sOENBQThDLE1BQU0sQ0FBQyxXQUFXLENBQUMsV0FBVyxDQUFDLFdBQVcsRUFBRSxJQUFJLENBQUM7UUFDdEosTUFBTSxJQUFJLEtBQUssQ0FDYixrR0FBa0csb0JBQW9CLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLE9BQU8sRUFBRSxDQUMvSSxDQUFDO0lBQ0osQ0FBQztJQUVEOzs7T0FHRztJQUNJLFNBQVMsQ0FBQyxvQkFBOEIsRUFBRSxNQUFNLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRTtRQUNsRSxNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDO1FBQzNCLElBQUksQ0FBQyxNQUFNO1lBQUUsT0FBTyxTQUFTLENBQUM7UUFDOUIsTUFBTSxRQUFRLEdBQUcsQ0FBQyxHQUFHLE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO1FBQzlDLE1BQU0sV0FBVyxHQUFHLE1BQU0sQ0FBQyxXQUFXLENBQUM7UUFDdkMsSUFBSSxXQUFXLEVBQUUsQ0FBQztZQUNoQixNQUFNLFNBQVMsR0FBRyxXQUFXLENBQUMsYUFBYSxDQUFDLE9BQU8sRUFBRSxDQUFDO1lBQ3RELE1BQU0sUUFBUSxHQUFHLFdBQVcsQ0FBQyxXQUFXLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDbkQsSUFBSSxNQUFNLElBQUksUUFBUSxFQUFFLENBQUM7Z0JBQ3ZCLFFBQVEsQ0FBQyxJQUFJLENBQUMsMEJBQTBCLElBQUksSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDLFdBQVcsRUFBRSxFQUFFLENBQUMsQ0FBQztZQUM5RSxDQUFDO2lCQUFNLElBQUksTUFBTSxHQUFHLFNBQVMsRUFBRSxDQUFDO2dCQUM5QixRQUFRLENBQUMsSUFBSSxDQUFDLG1DQUFtQyxJQUFJLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQyxXQUFXLEVBQUUsRUFBRSxDQUFDLENBQUM7WUFDeEYsQ0FBQztZQUNELE1BQU0sU0FBUyxHQUFHLHNCQUFzQixDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUUsb0JBQW9CLENBQUMsQ0FBQztZQUM3RSxJQUFJLFNBQVMsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUM7Z0JBQ3pCLFFBQVEsQ0FBQyxJQUFJLENBQUMsOEJBQThCLFNBQVMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDO1lBQ3RFLENBQUM7UUFDSCxDQUFDO1FBQ0QsT0FBTztZQUNMLE1BQU0sRUFBRSxNQUFNLENBQUMsTUFBTTtZQUNyQixNQUFNLEVBQUUsTUFBTSxDQUFDLE1BQU07WUFDckIsS0FBSyxFQUFFLENBQUMsR0FBRyxNQUFNLENBQUMsS0FBSyxDQUFDO1lBQ3hCLFNBQVMsRUFBRSxXQUFXLENBQUMsQ0FBQyxDQUFDLFdBQVcsQ0FBQyxhQUFhLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQyxDQUFDLElBQUk7WUFDbkUsUUFBUSxFQUFFLFdBQVcsQ0FBQyxDQUFDLENBQUMsV0FBVyxDQUFDLFdBQVcsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSTtZQUNoRSxpQkFBaUIsRUFBRSxDQUFDLEdBQUcsb0JBQW9CLENBQUM7WUFDNUMsUUFBUTtZQUNSLFNBQVMsRUFBRSxNQUFNO1NBQ2xCLENBQUM7SUFDSixDQUFDO0lBRUQ7Ozs7O09BS0c7SUFDSyxLQUFLLENBQUMseUJBQXlCLENBQ3JDLE1BQTJDO1FBRTNDLElBQUksUUFBdUMsQ0FBQztRQUM1QyxJQUFJLE1BQU0sRUFBRSxPQUFPLElBQUksTUFBTSxDQUFDLE1BQU0sRUFBRSxDQUFDO1lBQ3JDLFFBQVEsR0FBRyxFQUFFLE9BQU8sRUFBRSxNQUFNLENBQUMsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLENBQUMsTUFBTSxFQUFFLE1BQU0sRUFBRSxZQUFZLEVBQUUsQ0FBQztRQUN0RixDQUFDO2FBQU0sSUFBSSxNQUFNLEVBQUUsUUFBUSxJQUFJLE1BQU0sRUFBRSxPQUFPLEVBQUUsQ0FBQztZQUMvQyxJQUFJLENBQUMsTUFBTSxDQUFDLFFBQVEsSUFBSSxDQUFDLE1BQU0sQ0FBQyxPQUFPLEVBQUUsQ0FBQztnQkFDeEMsTUFBTSxJQUFJLEtBQUssQ0FBQyw4RUFBOEUsQ0FBQyxDQUFDO1lBQ2xHLENBQUM7WUFDRCxJQUFJLE9BQWUsQ0FBQztZQUNwQixJQUFJLE1BQWMsQ0FBQztZQUNuQixJQUFJLENBQUM7Z0JBQ0gsT0FBTyxHQUFHLE1BQU0sT0FBTyxDQUFDLEVBQUUsQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxRQUFRLEVBQUUsTUFBTSxDQUFDLENBQUM7Z0JBQ3RFLE1BQU0sR0FBRyxNQUFNLE9BQU8sQ0FBQyxFQUFFLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQUMsT0FBTyxFQUFFLE1BQU0sQ0FBQyxDQUFDO1lBQ3RFLENBQUM7WUFBQyxPQUFPLEtBQWMsRUFBRSxDQUFDO2dCQUN4QixNQUFNLElBQUksS0FBSyxDQUNiLDZEQUE2RCxNQUFNLENBQUMsUUFBUSxhQUFhLE1BQU0sQ0FBQyxPQUFPLE1BQU0sYUFBYSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQ3BJLENBQUM7WUFDSixDQUFDO1lBQ0QsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDO2dCQUN0QyxNQUFNLElBQUksS0FBSyxDQUNiLHVEQUF1RCxNQUFNLENBQUMsUUFBUSxhQUFhLE1BQU0sQ0FBQyxPQUFPLEdBQUcsQ0FDckcsQ0FBQztZQUNKLENBQUM7WUFDRCxRQUFRLEdBQUcsRUFBRSxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxxQkFBcUIsTUFBTSxDQUFDLFFBQVEsR0FBRyxFQUFFLENBQUM7UUFDbEYsQ0FBQzthQUFNLElBQUksTUFBTSxFQUFFLE9BQU8sSUFBSSxNQUFNLEVBQUUsTUFBTSxFQUFFLENBQUM7WUFDN0MsTUFBTSxJQUFJLEtBQUssQ0FBQyw0RUFBNEUsQ0FBQyxDQUFDO1FBQ2hHLENBQUM7UUFFRCxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUM7WUFDZCxJQUFJLENBQUMsTUFBTSxHQUFHO2dCQUNaLE1BQU0sRUFBRSxvQkFBb0I7Z0JBQzVCLE1BQU0sRUFBRSxJQUFJO2dCQUNaLFlBQVksRUFBRSxJQUFJO2dCQUNsQixXQUFXLEVBQUUsSUFBSTtnQkFDakIsS0FBSyxFQUFFLEVBQUU7Z0JBQ1QsZ0JBQWdCLEVBQUUsQ0FBQywrRkFBK0YsQ0FBQzthQUNwSCxDQUFDO1lBQ0YsT0FBTyxTQUFTLENBQUM7UUFDbkIsQ0FBQztRQUVELE1BQU0sZ0JBQWdCLEdBQWEsRUFBRSxDQUFDO1FBQ3RDLElBQUksV0FBVyxHQUEwQyxJQUFJLENBQUM7UUFDOUQsSUFBSSxDQUFDO1lBQ0gsV0FBVyxHQUFHLGdCQUFnQixDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUNuRCxDQUFDO1FBQUMsT0FBTyxLQUFjLEVBQUUsQ0FBQztZQUN4QixnQkFBZ0IsQ0FBQyxJQUFJLENBQUMsaUNBQWlDLGFBQWEsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLENBQUM7UUFDakYsQ0FBQztRQUNELElBQUksV0FBVyxFQUFFLENBQUM7WUFDaEIsZ0JBQWdCLENBQUMsSUFBSSxDQUFDLEdBQUcsSUFBSSxDQUFDLFlBQVksQ0FBQyxXQUFXLEVBQUUsUUFBUSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUM7UUFDNUUsQ0FBQztRQUNELElBQUksQ0FBQyxNQUFNLEdBQUc7WUFDWixNQUFNLEVBQUUsb0JBQW9CO1lBQzVCLE1BQU0sRUFBRSxRQUFRLENBQUMsTUFBTTtZQUN2QixZQUFZLEVBQUUsSUFBSTtZQUNsQixXQUFXO1lBQ1gsS0FBSyxFQUFFLFdBQVcsQ0FBQyxDQUFDLENBQUMsc0JBQXNCLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUU7WUFDN0QsZ0JBQWdCO1NBQ2pCLENBQUM7UUFDRixPQUFPLFFBQVEsQ0FBQztJQUNsQixDQUFDO0lBRU8sS0FBSyxDQUFDLHlCQUF5QixDQUFDLG9CQUE4QjtRQUNwRSxJQUFJLE1BQTJDLENBQUM7UUFDaEQsSUFBSSxhQUFpQyxDQUFDO1FBQ3RDLElBQUksQ0FBQztZQUNILE1BQU0sR0FBRyxNQUFNLElBQUksQ0FBQyx1QkFBdUIsQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDO1FBQ3BFLENBQUM7UUFBQyxPQUFPLEtBQWMsRUFBRSxDQUFDO1lBQ3hCLGFBQWEsR0FBRyx1Q0FBdUMsYUFBYSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUM7UUFDaEYsQ0FBQztRQUNELElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUNaLElBQUksQ0FBQyxNQUFNLEdBQUc7Z0JBQ1osTUFBTSxFQUFFLG9CQUFvQjtnQkFDNUIsTUFBTSxFQUFFLElBQUk7Z0JBQ1osWUFBWSxFQUFFLElBQUk7Z0JBQ2xCLFdBQVcsRUFBRSxJQUFJO2dCQUNqQixLQUFLLEVBQUUsRUFBRTtnQkFDVCxnQkFBZ0IsRUFBRSxDQUFDLGFBQWEsSUFBSSxnQ0FBZ0Msb0JBQW9CLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUM7YUFDdkcsQ0FBQztZQUNGLE9BQU8sU0FBUyxDQUFDO1FBQ25CLENBQUM7UUFDRCxNQUFNLE1BQU0sR0FBRyx1QkFBdUIsTUFBTSxDQUFDLE1BQU0sR0FBRyxDQUFDO1FBQ3ZELElBQUksQ0FBQyxNQUFNLEdBQUc7WUFDWixNQUFNLEVBQUUsb0JBQW9CO1lBQzVCLE1BQU07WUFDTixZQUFZLEVBQUUsTUFBTSxDQUFDLE1BQU07WUFDM0IsV0FBVyxFQUFFLE1BQU0sQ0FBQyxXQUFXO1lBQy9CLEtBQUssRUFBRSxNQUFNLENBQUMsS0FBSztZQUNuQixnQkFBZ0IsRUFBRSxJQUFJLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxXQUFXLEVBQUUsTUFBTSxDQUFDLE1BQU0sQ0FBQztTQUN2RSxDQUFDO1FBQ0YsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLENBQUMsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLENBQUMsTUFBTSxFQUFFLE1BQU0sRUFBRSxDQUFDO0lBQ3BFLENBQUM7SUFFTyxZQUFZLENBQUMsY0FBOEMsRUFBRSxTQUFpQjtRQUNwRixJQUFJLENBQUM7WUFDSCxPQUFPLGNBQWMsQ0FBQyxlQUFlLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQyxTQUFTLENBQUMsQ0FBQztnQkFDL0UsQ0FBQyxDQUFDLEVBQUU7Z0JBQ0osQ0FBQyxDQUFDLENBQUMsNENBQTRDLENBQUMsQ0FBQztRQUNyRCxDQUFDO1FBQUMsT0FBTyxLQUFjLEVBQUUsQ0FBQztZQUN4QixPQUFPLENBQUMsaUNBQWlDLGFBQWEsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLENBQUM7UUFDbkUsQ0FBQztJQUNILENBQUM7Q0FDRiJ9
|
package/dist_ts/email/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export * from './reconcile-mail-dns-singleton.js';
|
|
|
8
8
|
export * from './classes.mail-egress-coordinator.js';
|
|
9
9
|
export * from './classes.mail-edge-eligibility.js';
|
|
10
10
|
export * from './classes.email-settings.manager.js';
|
|
11
|
+
export * from './classes.email-tls-manager.js';
|
|
11
12
|
export * from './classes.gateway-mail-domain.store.js';
|
|
12
13
|
export * from './classes.coremail-gateway.manager.js';
|
|
13
14
|
export * from './classes.coremail-gateway.store.js';
|
package/dist_ts/email/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export * from './reconcile-mail-dns-singleton.js';
|
|
|
8
8
|
export * from './classes.mail-egress-coordinator.js';
|
|
9
9
|
export * from './classes.mail-edge-eligibility.js';
|
|
10
10
|
export * from './classes.email-settings.manager.js';
|
|
11
|
+
export * from './classes.email-tls-manager.js';
|
|
11
12
|
export * from './classes.gateway-mail-domain.store.js';
|
|
12
13
|
export * from './classes.coremail-gateway.manager.js';
|
|
13
14
|
export * from './classes.coremail-gateway.store.js';
|
|
@@ -24,4 +25,4 @@ export * from './gateway-mail-domain-authority.js';
|
|
|
24
25
|
export * from './email-dns-records.js';
|
|
25
26
|
export * from './helpers.email-log-traffic.js';
|
|
26
27
|
export * from './helpers.inbound-security.js';
|
|
27
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
28
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9lbWFpbC9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLG1DQUFtQyxDQUFDO0FBQ2xELGNBQWMsbUNBQW1DLENBQUM7QUFDbEQsY0FBYyxrQ0FBa0MsQ0FBQztBQUNqRCxjQUFjLDRCQUE0QixDQUFDO0FBQzNDLGNBQWMsMEJBQTBCLENBQUM7QUFDekMsY0FBYyxzQkFBc0IsQ0FBQztBQUNyQyxjQUFjLG1DQUFtQyxDQUFDO0FBQ2xELGNBQWMsc0NBQXNDLENBQUM7QUFDckQsY0FBYyxvQ0FBb0MsQ0FBQztBQUNuRCxjQUFjLHFDQUFxQyxDQUFDO0FBQ3BELGNBQWMsZ0NBQWdDLENBQUM7QUFDL0MsY0FBYyx3Q0FBd0MsQ0FBQztBQUN2RCxjQUFjLHVDQUF1QyxDQUFDO0FBQ3RELGNBQWMscUNBQXFDLENBQUM7QUFDcEQsY0FBYyx1Q0FBdUMsQ0FBQztBQUN0RCxjQUFjLGlDQUFpQyxDQUFDO0FBQ2hELGNBQWMsK0JBQStCLENBQUM7QUFDOUMsY0FBYyw0Q0FBNEMsQ0FBQztBQUMzRCxjQUFjLHVDQUF1QyxDQUFDO0FBQ3RELGNBQWMsOEJBQThCLENBQUM7QUFDN0MsY0FBYyxtQ0FBbUMsQ0FBQztBQUNsRCxjQUFjLGlCQUFpQixDQUFDO0FBQ2hDLGNBQWMsbUNBQW1DLENBQUM7QUFDbEQsY0FBYyxvQ0FBb0MsQ0FBQztBQUNuRCxjQUFjLHdCQUF3QixDQUFDO0FBQ3ZDLGNBQWMsZ0NBQWdDLENBQUM7QUFDL0MsY0FBYywrQkFBK0IsQ0FBQyJ9
|
|
@@ -40,10 +40,12 @@ export class EmailSettingsHandler {
|
|
|
40
40
|
const smtpListenerActive = dcRouter.emailServer
|
|
41
41
|
? dcRouter.emailServer.isSmtpListenerActive()
|
|
42
42
|
: undefined;
|
|
43
|
+
const tlsCertificate = dcRouter.getEmailTlsCertificateStatus();
|
|
43
44
|
const manager = dcRouter.emailSettingsManager;
|
|
44
45
|
if (manager) {
|
|
45
46
|
return {
|
|
46
47
|
...manager.getPublicSettings(),
|
|
48
|
+
...(tlsCertificate ? { tlsCertificate } : {}),
|
|
47
49
|
outboundEgress,
|
|
48
50
|
mailerBridgeState,
|
|
49
51
|
smtpAccountCount,
|
|
@@ -64,6 +66,8 @@ export class EmailSettingsHandler {
|
|
|
64
66
|
portMapping: emailPortConfig?.portMapping ? { ...emailPortConfig.portMapping } : null,
|
|
65
67
|
receivedEmailsPath: emailPortConfig?.receivedEmailsPath || null,
|
|
66
68
|
maxMessageSize: emailConfig?.maxMessageSize ?? null,
|
|
69
|
+
tlsSource: dcRouter.getEmailTlsSource(),
|
|
70
|
+
...(tlsCertificate ? { tlsCertificate } : {}),
|
|
67
71
|
domainCount: emailConfig?.domains?.length || 0,
|
|
68
72
|
routeCount: emailConfig?.routes?.length || 0,
|
|
69
73
|
authUserCount: emailConfig?.auth?.users?.length || 0,
|
|
@@ -74,4 +78,4 @@ export class EmailSettingsHandler {
|
|
|
74
78
|
};
|
|
75
79
|
}
|
|
76
80
|
}
|
|
77
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
81
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW1haWwtc2V0dGluZ3MuaGFuZGxlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3RzL29wc3NlcnZlci9oYW5kbGVycy9lbWFpbC1zZXR0aW5ncy5oYW5kbGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFFNUMsT0FBTyxLQUFLLFVBQVUsTUFBTSxpQ0FBaUMsQ0FBQztBQUM5RCxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFFcEQsTUFBTSxPQUFPLG9CQUFvQjtJQUdYO0lBRmIsV0FBVyxHQUFHLElBQUksT0FBTyxDQUFDLFlBQVksQ0FBQyxXQUFXLEVBQUUsQ0FBQztJQUU1RCxZQUFvQixZQUF1QjtRQUF2QixpQkFBWSxHQUFaLFlBQVksQ0FBVztRQUN6QyxJQUFJLENBQUMsWUFBWSxDQUFDLFdBQVcsQ0FBQyxjQUFjLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxDQUFDO1FBQy9ELElBQUksQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDO0lBQzFCLENBQUM7SUFFTyxnQkFBZ0I7UUFDdEIsSUFBSSxDQUFDLFdBQVcsQ0FBQyxlQUFlLENBQzlCLElBQUksT0FBTyxDQUFDLFlBQVksQ0FBQyxZQUFZLENBQ25DLHdCQUF3QixFQUN4QixLQUFLLEVBQUUsT0FBTyxFQUFFLEVBQUU7WUFDaEIsTUFBTSxjQUFjLENBQUMsSUFBSSxDQUFDLFlBQVksRUFBRSxPQUFPLEVBQUUsRUFBRSxLQUFLLEVBQUUsb0JBQTJCLEVBQUUsQ0FBQyxDQUFDO1lBQ3pGLE9BQU8sRUFBRSxRQUFRLEVBQUUsSUFBSSxDQUFDLFdBQVcsRUFBRSxFQUFFLENBQUM7UUFDMUMsQ0FBQyxDQUNGLENBQ0YsQ0FBQztRQUVGLElBQUksQ0FBQyxXQUFXLENBQUMsZUFBZSxDQUM5QixJQUFJLE9BQU8sQ0FBQyxZQUFZLENBQUMsWUFBWSxDQUNuQywyQkFBMkIsRUFDM0Is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
|
|
@@ -11,6 +11,38 @@ export interface IEmailOutboundEgressStatus {
|
|
|
11
11
|
}
|
|
12
12
|
/** Lifecycle state of the smartmta Rust mailer bridge. 'failed' and 'restarting' mean mail processing is degraded. */
|
|
13
13
|
export type TEmailBridgeState = 'idle' | 'starting' | 'running' | 'restarting' | 'failed' | 'stopped';
|
|
14
|
+
/**
|
|
15
|
+
* Where the SMTP listener's certificate (STARTTLS on 25/587, implicit TLS on
|
|
16
|
+
* the smartmta-terminated secure port) comes from.
|
|
17
|
+
* - 'managedCertificate': the stored ACME certificate that covers the mail
|
|
18
|
+
* hostname and every enabled mail-edge hostname. Configured `emailConfig.tls`
|
|
19
|
+
* material is ignored, and is removed when an operator switches to this
|
|
20
|
+
* source. Renewals reach the running listener without a restart.
|
|
21
|
+
* - 'configuredMaterial': the operator-managed `emailConfig.tls` material
|
|
22
|
+
* (inline `certPem`/`keyPem`, or `certPath`/`keyPath` read when the listener
|
|
23
|
+
* starts). ACME renewals never replace it.
|
|
24
|
+
* Settings persisted without a source keep their previous behaviour:
|
|
25
|
+
* 'configuredMaterial' while `emailConfig.tls` carries material, otherwise
|
|
26
|
+
* 'managedCertificate'.
|
|
27
|
+
*/
|
|
28
|
+
export type TEmailTlsSource = 'managedCertificate' | 'configuredMaterial';
|
|
29
|
+
/** The certificate the running SMTP listener serves, checked at read time. */
|
|
30
|
+
export interface IEmailTlsCertificateStatus {
|
|
31
|
+
source: TEmailTlsSource;
|
|
32
|
+
/** Where the served material was read from; null when no material was found. */
|
|
33
|
+
origin: string | null;
|
|
34
|
+
/** DNS subject alternative names of the served certificate. */
|
|
35
|
+
names: string[];
|
|
36
|
+
/** Epoch milliseconds; null when no parseable certificate is served. */
|
|
37
|
+
notBefore: number | null;
|
|
38
|
+
/** Epoch milliseconds; null when no parseable certificate is served. */
|
|
39
|
+
notAfter: number | null;
|
|
40
|
+
/** Mail hostname plus every enabled mail-edge hostname the certificate must cover. */
|
|
41
|
+
requiredHostnames: string[];
|
|
42
|
+
/** Empty when the served certificate is valid, matches its key and covers every required hostname. */
|
|
43
|
+
problems: string[];
|
|
44
|
+
checkedAt: number;
|
|
45
|
+
}
|
|
14
46
|
export interface IEmailPortConfig {
|
|
15
47
|
/** External to internal SMTP port mapping. */
|
|
16
48
|
portMapping?: Record<number, number>;
|
|
@@ -31,6 +63,10 @@ export interface IEmailServerSettings {
|
|
|
31
63
|
portMapping: Record<number, number> | null;
|
|
32
64
|
receivedEmailsPath: string | null;
|
|
33
65
|
maxMessageSize: number | null;
|
|
66
|
+
/** Effective SMTP certificate source (see TEmailTlsSource for the default of settings persisted without one). */
|
|
67
|
+
tlsSource: TEmailTlsSource;
|
|
68
|
+
/** Present when the email server is running: the certificate the SMTP listener serves. */
|
|
69
|
+
tlsCertificate?: IEmailTlsCertificateStatus;
|
|
34
70
|
domainCount: number;
|
|
35
71
|
routeCount: number;
|
|
36
72
|
authUserCount: number;
|
|
@@ -59,6 +95,13 @@ export type TEmailServerSettingsUpdate = {
|
|
|
59
95
|
portMapping?: Record<number, number> | null;
|
|
60
96
|
receivedEmailsPath?: string | null;
|
|
61
97
|
maxMessageSize?: number | null;
|
|
98
|
+
/**
|
|
99
|
+
* Switch the SMTP certificate source. 'managedCertificate' is refused while
|
|
100
|
+
* no valid stored certificate covers the mail hostname and every enabled
|
|
101
|
+
* mail-edge hostname; accepting it removes the configured `emailConfig.tls`
|
|
102
|
+
* material. 'configuredMaterial' is refused while none is persisted.
|
|
103
|
+
*/
|
|
104
|
+
tlsSource?: TEmailTlsSource;
|
|
62
105
|
/**
|
|
63
106
|
* Exact names of persisted legacy routes to remove (the internal-relay
|
|
64
107
|
* retirement lever). Fails loudly on unknown names and refuses generated
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@serve.zone/dcrouter',
|
|
6
|
-
version: '32.
|
|
6
|
+
version: '32.7.0',
|
|
7
7
|
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
|
8
8
|
};
|
|
9
9
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHNfd2ViLzAwX2NvbW1pdGluZm9fZGF0YS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7R0FFRztBQUNILE1BQU0sQ0FBQyxNQUFNLFVBQVUsR0FBRztJQUN4QixJQUFJLEVBQUUsc0JBQXNCO0lBQzVCLE9BQU8sRUFBRSxRQUFRO0lBQ2pCLFdBQVcsRUFBRSwwRUFBMEU7Q0FDeEYsQ0FBQSJ9
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@serve.zone/dcrouter",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "32.
|
|
4
|
+
"version": "32.7.0",
|
|
5
5
|
"description": "A multifaceted routing service handling mail and SMS delivery functions.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -15,16 +15,16 @@
|
|
|
15
15
|
"author": "Task Venture Capital GmbH",
|
|
16
16
|
"license": "MIT",
|
|
17
17
|
"devDependencies": {
|
|
18
|
-
"@git.zone/tsbuild": "^5.
|
|
19
|
-
"@git.zone/tsbundle": "^3.
|
|
20
|
-
"@git.zone/tsdeno": "^1.
|
|
21
|
-
"@git.zone/tsdocker": "^3.
|
|
18
|
+
"@git.zone/tsbuild": "^5.1.2",
|
|
19
|
+
"@git.zone/tsbundle": "^3.1.3",
|
|
20
|
+
"@git.zone/tsdeno": "^1.15.0",
|
|
21
|
+
"@git.zone/tsdocker": "^3.11.0",
|
|
22
22
|
"@git.zone/tsrun": "^3.0.2",
|
|
23
|
-
"@git.zone/tstest": "^
|
|
24
|
-
"@git.zone/tswatch": "^
|
|
23
|
+
"@git.zone/tstest": "^7.1.0",
|
|
24
|
+
"@git.zone/tswatch": "^6.0.1",
|
|
25
25
|
"@push.rocks/smartdb-v2": "npm:@push.rocks/smartdb@2.14.6",
|
|
26
26
|
"@push.rocks/smartdb-v5.2": "npm:@push.rocks/smartdb@5.2.2",
|
|
27
|
-
"@types/node": "26.6.
|
|
27
|
+
"@types/node": "26.6.3",
|
|
28
28
|
"@types/web-push": "3.6.4"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
package/readme.md
CHANGED
|
@@ -818,11 +818,14 @@ On upgrade, a migration converts legacy plaintext `emailConfig.auth.users` entri
|
|
|
818
818
|
|
|
819
819
|
SMTP AUTH is only ever offered on an encrypted transport. A submission port that has no TLS material and no upstream TLS terminator advertises neither `STARTTLS` nor `AUTH`, and refuses an `AUTH` command with `538 5.7.11` — credentials never cross a cleartext channel.
|
|
820
820
|
|
|
821
|
-
|
|
821
|
+
The SMTP listener's certificate therefore has to actually reach it. SmartMTA consumes PEM content and takes one certificate for every name the listener answers, so dcrouter resolves it from one of two sources, chosen by the email server setting `tlsSource`:
|
|
822
822
|
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
823
|
+
- `managedCertificate` — the stored ACME certificate that covers `emailConfig.hostname` and the `mailHostname` of every enabled mail edge (dcrouter issues it with the domain wildcard included, so one certificate covers them all). Every issuance or renewal of a stored certificate that covers one of those names is pushed to the running listener, including one stored under a wildcard or sibling route domain; SmartMTA restarts only the Rust listener when the PEM material changes.
|
|
824
|
+
- `configuredMaterial` — operator-managed material in `emailConfig.tls`: inline `certPem` / `keyPem`, or `certPath` / `keyPath` read when the listener starts. Both paths are required together; a path that cannot be read, or that holds an empty file, **fails startup** rather than silently leaving the listener without TLS. ACME renewals never replace this material.
|
|
825
|
+
|
|
826
|
+
Settings stored without a `tlsSource` keep their previous behaviour: `configuredMaterial` while `emailConfig.tls` carries material, otherwise `managedCertificate`. Operators switch with `updateEmailServerSettings { settings: { tlsSource } }`, which restarts the email components in-process. Switching to `managedCertificate` is refused, and the settings stay unchanged, while no currently valid stored certificate covers the mail hostname and every enabled mail-edge hostname; once accepted it removes the configured material from the persisted settings. Switching to `configuredMaterial` is refused while no material is persisted.
|
|
827
|
+
|
|
828
|
+
`getEmailServerSettings` reports the effective `tlsSource` and, while the listener runs, `tlsCertificate`: where the served material came from, its DNS names, `notBefore` / `notAfter`, the hostnames it must cover, and `problems`. The material is checked on every read, so a certificate that expires while the listener runs, does not cover a required hostname, or does not match its key is listed in `problems`; resolving such material also logs an error.
|
|
826
829
|
|
|
827
830
|
CoreTraffic terminates public TLS for port 465 and forwards the decrypted stream to the internal SMTP leg, so that leg is plaintext even though the client's channel was encrypted. dcrouter derives those ports from the generated email routes and declares them to SmartMTA as `smtp.tlsTerminatedPorts`, gated on the PROXY-protocol trust dcrouter already requires from `127.0.0.1`/`::1` — so implicit-TLS submission keeps authenticating while the plain ports require `STARTTLS` first.
|
|
828
831
|
|