@serve.zone/dcrouter 11.0.5 → 11.0.6
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/dist_serve/bundle.js +1 -1
- package/dist_ts/00_commitinfo_data.d.ts +8 -0
- package/dist_ts/00_commitinfo_data.js +9 -0
- package/dist_ts/cache/classes.cache.cleaner.d.ts +47 -0
- package/dist_ts/cache/documents/index.d.ts +2 -0
- package/dist_ts/cache/documents/index.js +3 -0
- package/dist_ts/cache/index.d.ts +4 -0
- package/dist_ts/cache/index.js +7 -0
- package/dist_ts/classes.cert-provision-scheduler.d.ts +53 -0
- package/dist_ts/classes.cert-provision-scheduler.js +110 -0
- package/dist_ts/classes.dcrouter.d.ts +337 -0
- package/dist_ts/classes.dcrouter.js +1405 -0
- package/dist_ts/classes.storage-cert-manager.d.ts +18 -0
- package/dist_ts/classes.storage-cert-manager.js +43 -0
- package/dist_ts/config/classes.api-token-manager.d.ts +46 -0
- package/dist_ts/config/classes.api-token-manager.js +150 -0
- package/dist_ts/config/classes.route-config-manager.d.ts +35 -0
- package/dist_ts/config/classes.route-config-manager.js +231 -0
- package/dist_ts/config/index.d.ts +3 -0
- package/dist_ts/config/index.js +5 -0
- package/dist_ts/config/validator.d.ts +104 -0
- package/dist_ts/config/validator.js +152 -0
- package/dist_ts/errors/base.errors.d.ts +224 -0
- package/dist_ts/errors/base.errors.js +320 -0
- package/dist_ts/errors/error-handler.d.ts +98 -0
- package/dist_ts/errors/error-handler.js +282 -0
- package/dist_ts/errors/error.codes.d.ts +115 -0
- package/dist_ts/errors/error.codes.js +136 -0
- package/dist_ts/errors/index.d.ts +54 -0
- package/dist_ts/errors/index.js +136 -0
- package/dist_ts/errors/reputation.errors.d.ts +183 -0
- package/dist_ts/errors/reputation.errors.js +292 -0
- package/dist_ts/index.d.ts +7 -0
- package/dist_ts/index.js +11 -0
- package/dist_ts/logger.d.ts +21 -0
- package/dist_ts/logger.js +81 -0
- package/dist_ts/monitoring/classes.metricscache.d.ts +32 -0
- package/dist_ts/monitoring/classes.metricscache.js +63 -0
- package/dist_ts/monitoring/classes.metricsmanager.d.ts +178 -0
- package/dist_ts/monitoring/classes.metricsmanager.js +642 -0
- package/dist_ts/monitoring/index.d.ts +1 -0
- package/dist_ts/monitoring/index.js +2 -0
- package/dist_ts/opsserver/classes.opsserver.d.ts +37 -0
- package/dist_ts/opsserver/classes.opsserver.js +85 -0
- package/dist_ts/opsserver/handlers/admin.handler.d.ts +31 -0
- package/dist_ts/opsserver/handlers/admin.handler.js +180 -0
- package/dist_ts/opsserver/handlers/api-token.handler.d.ts +6 -0
- package/dist_ts/opsserver/handlers/api-token.handler.js +62 -0
- package/dist_ts/opsserver/handlers/certificate.handler.d.ts +32 -0
- package/dist_ts/opsserver/handlers/certificate.handler.js +421 -0
- package/dist_ts/opsserver/handlers/config.handler.d.ts +7 -0
- package/dist_ts/opsserver/handlers/config.handler.js +192 -0
- package/dist_ts/opsserver/handlers/email-ops.handler.d.ts +30 -0
- package/dist_ts/opsserver/handlers/email-ops.handler.js +227 -0
- package/dist_ts/opsserver/handlers/index.d.ts +11 -0
- package/dist_ts/opsserver/handlers/index.js +12 -0
- package/dist_ts/opsserver/handlers/logs.handler.d.ts +25 -0
- package/dist_ts/opsserver/handlers/logs.handler.js +256 -0
- package/dist_ts/opsserver/handlers/radius.handler.d.ts +6 -0
- package/dist_ts/opsserver/handlers/radius.handler.js +295 -0
- package/dist_ts/opsserver/handlers/remoteingress.handler.d.ts +6 -0
- package/dist_ts/opsserver/handlers/remoteingress.handler.js +156 -0
- package/dist_ts/opsserver/handlers/route-management.handler.d.ts +14 -0
- package/dist_ts/opsserver/handlers/route-management.handler.js +117 -0
- package/dist_ts/opsserver/handlers/security.handler.d.ts +9 -0
- package/dist_ts/opsserver/handlers/security.handler.js +231 -0
- package/dist_ts/opsserver/handlers/stats.handler.d.ts +11 -0
- package/dist_ts/opsserver/handlers/stats.handler.js +399 -0
- package/dist_ts/opsserver/helpers/guards.d.ts +27 -0
- package/dist_ts/opsserver/helpers/guards.js +43 -0
- package/dist_ts/opsserver/index.d.ts +1 -0
- package/dist_ts/opsserver/index.js +2 -0
- package/dist_ts/paths.d.ts +26 -0
- package/dist_ts/paths.js +45 -0
- package/dist_ts/plugins.d.ts +79 -0
- package/dist_ts/plugins.js +113 -0
- package/dist_ts/radius/classes.accounting.manager.d.ts +218 -0
- package/dist_ts/radius/classes.accounting.manager.js +417 -0
- package/dist_ts/radius/classes.radius.server.d.ts +171 -0
- package/dist_ts/radius/classes.radius.server.js +385 -0
- package/dist_ts/radius/classes.vlan.manager.d.ts +128 -0
- package/dist_ts/radius/classes.vlan.manager.js +279 -0
- package/dist_ts/radius/index.d.ts +13 -0
- package/dist_ts/radius/index.js +14 -0
- package/dist_ts/remoteingress/classes.remoteingress-manager.d.ts +82 -0
- package/dist_ts/remoteingress/classes.remoteingress-manager.js +227 -0
- package/dist_ts/remoteingress/classes.tunnel-manager.d.ts +59 -0
- package/dist_ts/remoteingress/classes.tunnel-manager.js +165 -0
- package/dist_ts/remoteingress/index.d.ts +2 -0
- package/dist_ts/remoteingress/index.js +3 -0
- package/dist_ts/security/classes.contentscanner.d.ts +164 -0
- package/dist_ts/security/classes.contentscanner.js +642 -0
- package/dist_ts/security/classes.ipreputationchecker.d.ts +160 -0
- package/dist_ts/security/classes.ipreputationchecker.js +537 -0
- package/dist_ts/security/classes.securitylogger.d.ts +144 -0
- package/dist_ts/security/classes.securitylogger.js +233 -0
- package/dist_ts/security/index.d.ts +3 -0
- package/dist_ts/security/index.js +4 -0
- package/dist_ts/sms/classes.smsservice.d.ts +15 -0
- package/dist_ts/sms/classes.smsservice.js +72 -0
- package/dist_ts/sms/config/sms.config.d.ts +93 -0
- package/dist_ts/sms/config/sms.config.js +2 -0
- package/dist_ts/sms/config/sms.schema.d.ts +5 -0
- package/dist_ts/sms/config/sms.schema.js +121 -0
- package/dist_ts/sms/index.d.ts +1 -0
- package/dist_ts/sms/index.js +2 -0
- package/dist_ts/storage/classes.storagemanager.d.ts +83 -0
- package/dist_ts/storage/classes.storagemanager.js +350 -0
- package/dist_ts/storage/index.d.ts +1 -0
- package/dist_ts/storage/index.js +3 -0
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/package.json +19 -11
|
@@ -0,0 +1,1405 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import * as paths from './paths.js';
|
|
3
|
+
// Certificate types are available via plugins.tsclass
|
|
4
|
+
// Import the email server and its configuration from smartmta
|
|
5
|
+
import { UnifiedEmailServer, } from '@push.rocks/smartmta';
|
|
6
|
+
import { logger } from './logger.js';
|
|
7
|
+
// Import storage manager
|
|
8
|
+
import { StorageManager } from './storage/index.js';
|
|
9
|
+
import { StorageBackedCertManager } from './classes.storage-cert-manager.js';
|
|
10
|
+
import { CertProvisionScheduler } from './classes.cert-provision-scheduler.js';
|
|
11
|
+
// Import cache system
|
|
12
|
+
import { CacheDb, CacheCleaner } from './cache/index.js';
|
|
13
|
+
import { OpsServer } from './opsserver/index.js';
|
|
14
|
+
import { MetricsManager } from './monitoring/index.js';
|
|
15
|
+
import { RadiusServer } from './radius/index.js';
|
|
16
|
+
import { RemoteIngressManager, TunnelManager } from './remoteingress/index.js';
|
|
17
|
+
import { RouteConfigManager, ApiTokenManager } from './config/index.js';
|
|
18
|
+
import { SecurityLogger, ContentScanner, IPReputationChecker } from './security/index.js';
|
|
19
|
+
export class DcRouter {
|
|
20
|
+
options;
|
|
21
|
+
resolvedPaths;
|
|
22
|
+
// Core services
|
|
23
|
+
smartProxy;
|
|
24
|
+
smartAcme;
|
|
25
|
+
dnsServer;
|
|
26
|
+
emailServer;
|
|
27
|
+
radiusServer;
|
|
28
|
+
storageManager;
|
|
29
|
+
opsServer;
|
|
30
|
+
metricsManager;
|
|
31
|
+
// Cache system (smartdata + LocalTsmDb)
|
|
32
|
+
cacheDb;
|
|
33
|
+
cacheCleaner;
|
|
34
|
+
// Remote Ingress
|
|
35
|
+
remoteIngressManager;
|
|
36
|
+
tunnelManager;
|
|
37
|
+
// Programmatic config API
|
|
38
|
+
routeConfigManager;
|
|
39
|
+
apiTokenManager;
|
|
40
|
+
// Auto-discovered public IP (populated by generateAuthoritativeRecords)
|
|
41
|
+
detectedPublicIp = null;
|
|
42
|
+
// DNS query logging rate limiter state
|
|
43
|
+
dnsLogWindowSecond = 0; // epoch second of current window
|
|
44
|
+
dnsLogWindowCount = 0; // queries logged this second
|
|
45
|
+
dnsBatchCount = 0;
|
|
46
|
+
dnsBatchTimer = null;
|
|
47
|
+
// Certificate status tracking from SmartProxy events (keyed by domain)
|
|
48
|
+
certificateStatusMap = new Map();
|
|
49
|
+
// Certificate provisioning scheduler with per-domain backoff
|
|
50
|
+
certProvisionScheduler;
|
|
51
|
+
// TypedRouter for API endpoints
|
|
52
|
+
typedrouter = new plugins.typedrequest.TypedRouter();
|
|
53
|
+
// Cached constructor routes (computed once during setupSmartProxy, used by RouteConfigManager)
|
|
54
|
+
constructorRoutes = [];
|
|
55
|
+
// Environment access
|
|
56
|
+
qenv = new plugins.qenv.Qenv('./', '.nogit/');
|
|
57
|
+
constructor(optionsArg) {
|
|
58
|
+
// Set defaults in options
|
|
59
|
+
this.options = {
|
|
60
|
+
...optionsArg
|
|
61
|
+
};
|
|
62
|
+
// Resolve all data paths from baseDir
|
|
63
|
+
this.resolvedPaths = paths.resolvePaths(this.options.baseDir);
|
|
64
|
+
// Default storage to filesystem if not configured
|
|
65
|
+
if (!this.options.storage) {
|
|
66
|
+
this.options.storage = {
|
|
67
|
+
fsPath: this.resolvedPaths.defaultStoragePath,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
// Initialize storage manager
|
|
71
|
+
this.storageManager = new StorageManager(this.options.storage);
|
|
72
|
+
}
|
|
73
|
+
async start() {
|
|
74
|
+
logger.log('info', 'Starting DcRouter Services');
|
|
75
|
+
this.opsServer = new OpsServer(this);
|
|
76
|
+
await this.opsServer.start();
|
|
77
|
+
try {
|
|
78
|
+
// Initialize cache database if enabled (default: enabled)
|
|
79
|
+
if (this.options.cacheConfig?.enabled !== false) {
|
|
80
|
+
await this.setupCacheDb();
|
|
81
|
+
}
|
|
82
|
+
// Initialize MetricsManager
|
|
83
|
+
this.metricsManager = new MetricsManager(this);
|
|
84
|
+
await this.metricsManager.start();
|
|
85
|
+
// Set up SmartProxy for HTTP/HTTPS and all traffic including email routes
|
|
86
|
+
await this.setupSmartProxy();
|
|
87
|
+
// Initialize programmatic config API managers
|
|
88
|
+
this.routeConfigManager = new RouteConfigManager(this.storageManager, () => this.getConstructorRoutes(), () => this.smartProxy);
|
|
89
|
+
this.apiTokenManager = new ApiTokenManager(this.storageManager);
|
|
90
|
+
await this.apiTokenManager.initialize();
|
|
91
|
+
await this.routeConfigManager.initialize();
|
|
92
|
+
// Set up unified email handling if configured
|
|
93
|
+
if (this.options.emailConfig) {
|
|
94
|
+
await this.setupUnifiedEmailHandling();
|
|
95
|
+
}
|
|
96
|
+
// Set up DNS server if configured with nameservers and scopes
|
|
97
|
+
if (this.options.dnsNsDomains && this.options.dnsNsDomains.length > 0 &&
|
|
98
|
+
this.options.dnsScopes && this.options.dnsScopes.length > 0) {
|
|
99
|
+
await this.setupDnsWithSocketHandler();
|
|
100
|
+
}
|
|
101
|
+
// Set up RADIUS server if configured
|
|
102
|
+
if (this.options.radiusConfig) {
|
|
103
|
+
await this.setupRadiusServer();
|
|
104
|
+
}
|
|
105
|
+
// Set up Remote Ingress hub if configured
|
|
106
|
+
if (this.options.remoteIngressConfig?.enabled) {
|
|
107
|
+
await this.setupRemoteIngress();
|
|
108
|
+
}
|
|
109
|
+
this.logStartupSummary();
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
logger.log('error', 'Error starting DcRouter', { error: String(error) });
|
|
113
|
+
// Try to clean up any services that may have started
|
|
114
|
+
await this.stop();
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Log comprehensive startup summary
|
|
120
|
+
*/
|
|
121
|
+
logStartupSummary() {
|
|
122
|
+
logger.log('info', 'DcRouter Started Successfully');
|
|
123
|
+
// Metrics summary
|
|
124
|
+
if (this.metricsManager) {
|
|
125
|
+
logger.log('info', 'Metrics Service: SmartMetrics active, SmartProxy stats active, real-time tracking enabled');
|
|
126
|
+
}
|
|
127
|
+
// SmartProxy summary
|
|
128
|
+
if (this.smartProxy) {
|
|
129
|
+
const routeCount = this.options.smartProxyConfig?.routes?.length || 0;
|
|
130
|
+
const acmeEnabled = this.options.smartProxyConfig?.acme?.enabled || false;
|
|
131
|
+
const acmeMode = acmeEnabled
|
|
132
|
+
? `email=${this.options.smartProxyConfig.acme.email || 'not set'}, mode=${this.options.smartProxyConfig.acme.useProduction ? 'production' : 'staging'}`
|
|
133
|
+
: 'disabled';
|
|
134
|
+
logger.log('info', `SmartProxy Service: ${routeCount} routes, ACME: ${acmeMode}`);
|
|
135
|
+
}
|
|
136
|
+
// Email service summary
|
|
137
|
+
if (this.emailServer && this.options.emailConfig) {
|
|
138
|
+
const ports = this.options.emailConfig.ports || [];
|
|
139
|
+
const domainCount = this.options.emailConfig.domains?.length || 0;
|
|
140
|
+
const domainNames = this.options.emailConfig.domains?.map(d => `${d.domain} (${d.dnsMode || 'default'})`).join(', ') || 'none';
|
|
141
|
+
logger.log('info', `Email Service: ports=[${ports.join(', ')}], hostname=${this.options.emailConfig.hostname || 'localhost'}, domains=${domainCount} [${domainNames}], DKIM initialized`);
|
|
142
|
+
}
|
|
143
|
+
// DNS service summary
|
|
144
|
+
if (this.dnsServer && this.options.dnsNsDomains && this.options.dnsScopes) {
|
|
145
|
+
logger.log('info', `DNS Service: nameservers=[${this.options.dnsNsDomains.join(', ')}], authoritative for ${this.options.dnsScopes.length} domains [${this.options.dnsScopes.join(', ')}], UDP:53, DoH enabled`);
|
|
146
|
+
}
|
|
147
|
+
// RADIUS service summary
|
|
148
|
+
if (this.radiusServer && this.options.radiusConfig) {
|
|
149
|
+
const vlanStats = this.radiusServer.getVlanManager().getStats();
|
|
150
|
+
logger.log('info', `RADIUS Service: auth=${this.options.radiusConfig.authPort || 1812}, acct=${this.options.radiusConfig.acctPort || 1813}, clients=${this.options.radiusConfig.clients?.length || 0}, VLANs=${vlanStats.totalMappings}, accounting=${this.options.radiusConfig.accounting?.enabled ? 'enabled' : 'disabled'}`);
|
|
151
|
+
}
|
|
152
|
+
// Remote Ingress summary
|
|
153
|
+
if (this.tunnelManager && this.options.remoteIngressConfig?.enabled) {
|
|
154
|
+
const edgeCount = this.remoteIngressManager?.getAllEdges().length || 0;
|
|
155
|
+
const connectedCount = this.tunnelManager.getConnectedCount();
|
|
156
|
+
logger.log('info', `Remote Ingress: tunnel port=${this.options.remoteIngressConfig.tunnelPort || 8443}, edges=${edgeCount} registered/${connectedCount} connected`);
|
|
157
|
+
}
|
|
158
|
+
// Storage summary
|
|
159
|
+
if (this.storageManager && this.options.storage) {
|
|
160
|
+
logger.log('info', `Storage: path=${this.options.storage.fsPath || 'default'}`);
|
|
161
|
+
}
|
|
162
|
+
// Cache database summary
|
|
163
|
+
if (this.cacheDb) {
|
|
164
|
+
logger.log('info', `Cache Database: storage=${this.cacheDb.getStoragePath()}, db=${this.cacheDb.getDbName()}, cleaner=${this.cacheCleaner?.isActive() ? 'active' : 'inactive'} (${(this.options.cacheConfig?.cleanupIntervalHours || 1)}h interval)`);
|
|
165
|
+
}
|
|
166
|
+
logger.log('info', 'All services are running');
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Set up the cache database (smartdata + LocalTsmDb)
|
|
170
|
+
*/
|
|
171
|
+
async setupCacheDb() {
|
|
172
|
+
logger.log('info', 'Setting up CacheDb...');
|
|
173
|
+
const cacheConfig = this.options.cacheConfig || {};
|
|
174
|
+
// Initialize CacheDb singleton
|
|
175
|
+
this.cacheDb = CacheDb.getInstance({
|
|
176
|
+
storagePath: cacheConfig.storagePath || this.resolvedPaths.defaultTsmDbPath,
|
|
177
|
+
dbName: cacheConfig.dbName || 'dcrouter',
|
|
178
|
+
debug: false,
|
|
179
|
+
});
|
|
180
|
+
await this.cacheDb.start();
|
|
181
|
+
// Start the cache cleaner
|
|
182
|
+
const cleanupIntervalMs = (cacheConfig.cleanupIntervalHours || 1) * 60 * 60 * 1000;
|
|
183
|
+
this.cacheCleaner = new CacheCleaner(this.cacheDb, {
|
|
184
|
+
intervalMs: cleanupIntervalMs,
|
|
185
|
+
verbose: false,
|
|
186
|
+
});
|
|
187
|
+
this.cacheCleaner.start();
|
|
188
|
+
logger.log('info', `CacheDb initialized at ${this.cacheDb.getStoragePath()}`);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Set up SmartProxy with direct configuration and automatic email routes
|
|
192
|
+
*/
|
|
193
|
+
async setupSmartProxy() {
|
|
194
|
+
logger.log('info', 'Setting up SmartProxy...');
|
|
195
|
+
let routes = [];
|
|
196
|
+
let acmeConfig;
|
|
197
|
+
// If user provides full SmartProxy config, use it directly
|
|
198
|
+
if (this.options.smartProxyConfig) {
|
|
199
|
+
routes = this.options.smartProxyConfig.routes || [];
|
|
200
|
+
acmeConfig = this.options.smartProxyConfig.acme;
|
|
201
|
+
logger.log('info', `Found ${routes.length} routes in config, ACME config present: ${!!acmeConfig}`);
|
|
202
|
+
}
|
|
203
|
+
// If email config exists, automatically add email routes
|
|
204
|
+
if (this.options.emailConfig) {
|
|
205
|
+
const emailRoutes = this.generateEmailRoutes(this.options.emailConfig);
|
|
206
|
+
logger.log('debug', 'Email routes generated', { routes: JSON.stringify(emailRoutes) });
|
|
207
|
+
routes = [...routes, ...emailRoutes]; // Enable email routing through SmartProxy
|
|
208
|
+
}
|
|
209
|
+
// If DNS is configured, add DNS routes
|
|
210
|
+
if (this.options.dnsNsDomains && this.options.dnsNsDomains.length > 0) {
|
|
211
|
+
const dnsRoutes = this.generateDnsRoutes();
|
|
212
|
+
logger.log('debug', `DNS routes for nameservers ${this.options.dnsNsDomains.join(', ')}`, { routes: JSON.stringify(dnsRoutes) });
|
|
213
|
+
routes = [...routes, ...dnsRoutes];
|
|
214
|
+
}
|
|
215
|
+
// Merge TLS/ACME configuration if provided at root level
|
|
216
|
+
if (this.options.tls && !acmeConfig) {
|
|
217
|
+
acmeConfig = {
|
|
218
|
+
accountEmail: this.options.tls.contactEmail,
|
|
219
|
+
enabled: true,
|
|
220
|
+
useProduction: true,
|
|
221
|
+
autoRenew: true,
|
|
222
|
+
renewThresholdDays: 30
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
// Configure DNS challenge if available
|
|
226
|
+
let challengeHandlers = [];
|
|
227
|
+
if (this.options.dnsChallenge?.cloudflareApiKey) {
|
|
228
|
+
logger.log('info', 'Configuring Cloudflare DNS challenge for ACME');
|
|
229
|
+
const cloudflareAccount = new plugins.cloudflare.CloudflareAccount(this.options.dnsChallenge.cloudflareApiKey);
|
|
230
|
+
const dns01Handler = new plugins.smartacme.handlers.Dns01Handler(cloudflareAccount);
|
|
231
|
+
challengeHandlers.push(dns01Handler);
|
|
232
|
+
}
|
|
233
|
+
// Cache constructor routes for RouteConfigManager
|
|
234
|
+
this.constructorRoutes = [...routes];
|
|
235
|
+
// If we have routes or need a basic SmartProxy instance, create it
|
|
236
|
+
if (routes.length > 0 || this.options.smartProxyConfig) {
|
|
237
|
+
logger.log('info', 'Setting up SmartProxy with combined configuration');
|
|
238
|
+
// Track cert entries loaded from cert store so we can populate certificateStatusMap after start
|
|
239
|
+
const loadedCertEntries = [];
|
|
240
|
+
// Create SmartProxy configuration
|
|
241
|
+
const smartProxyConfig = {
|
|
242
|
+
...this.options.smartProxyConfig,
|
|
243
|
+
routes,
|
|
244
|
+
acme: acmeConfig,
|
|
245
|
+
certStore: {
|
|
246
|
+
loadAll: async () => {
|
|
247
|
+
const keys = await this.storageManager.list('/proxy-certs/');
|
|
248
|
+
const certs = [];
|
|
249
|
+
for (const key of keys) {
|
|
250
|
+
const data = await this.storageManager.getJSON(key);
|
|
251
|
+
if (data) {
|
|
252
|
+
certs.push(data);
|
|
253
|
+
loadedCertEntries.push({ domain: data.domain, publicKey: data.publicKey, validUntil: data.validUntil, validFrom: data.validFrom });
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return certs;
|
|
257
|
+
},
|
|
258
|
+
save: async (domain, publicKey, privateKey, ca) => {
|
|
259
|
+
let validUntil;
|
|
260
|
+
let validFrom;
|
|
261
|
+
try {
|
|
262
|
+
const x509 = new plugins.crypto.X509Certificate(publicKey);
|
|
263
|
+
validUntil = new Date(x509.validTo).getTime();
|
|
264
|
+
validFrom = new Date(x509.validFrom).getTime();
|
|
265
|
+
}
|
|
266
|
+
catch { /* PEM parsing failed */ }
|
|
267
|
+
await this.storageManager.setJSON(`/proxy-certs/${domain}`, {
|
|
268
|
+
domain, publicKey, privateKey, ca, validUntil, validFrom,
|
|
269
|
+
});
|
|
270
|
+
},
|
|
271
|
+
remove: async (domain) => {
|
|
272
|
+
await this.storageManager.delete(`/proxy-certs/${domain}`);
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
// Initialize cert provision scheduler
|
|
277
|
+
this.certProvisionScheduler = new CertProvisionScheduler(this.storageManager);
|
|
278
|
+
// If we have DNS challenge handlers, create SmartAcme and wire to certProvisionFunction
|
|
279
|
+
if (challengeHandlers.length > 0) {
|
|
280
|
+
// Stop old SmartAcme if it exists (e.g., during updateSmartProxyConfig)
|
|
281
|
+
if (this.smartAcme) {
|
|
282
|
+
await this.smartAcme.stop().catch(err => logger.log('error', 'Error stopping old SmartAcme', { error: String(err) }));
|
|
283
|
+
}
|
|
284
|
+
this.smartAcme = new plugins.smartacme.SmartAcme({
|
|
285
|
+
accountEmail: acmeConfig?.accountEmail || this.options.tls?.contactEmail || 'admin@example.com',
|
|
286
|
+
certManager: new StorageBackedCertManager(this.storageManager),
|
|
287
|
+
environment: 'production',
|
|
288
|
+
challengeHandlers: challengeHandlers,
|
|
289
|
+
challengePriority: ['dns-01'],
|
|
290
|
+
});
|
|
291
|
+
await this.smartAcme.start();
|
|
292
|
+
const scheduler = this.certProvisionScheduler;
|
|
293
|
+
smartProxyConfig.certProvisionFunction = async (domain, eventComms) => {
|
|
294
|
+
// Check backoff before attempting provision
|
|
295
|
+
if (await scheduler.isInBackoff(domain)) {
|
|
296
|
+
const info = await scheduler.getBackoffInfo(domain);
|
|
297
|
+
const msg = `Domain ${domain} is in backoff (${info?.failures} failures), retry after ${info?.retryAfter}`;
|
|
298
|
+
eventComms.warn(msg);
|
|
299
|
+
throw new Error(msg);
|
|
300
|
+
}
|
|
301
|
+
try {
|
|
302
|
+
// smartacme v9 handles concurrency, per-domain dedup, and rate limiting internally
|
|
303
|
+
eventComms.log(`Attempting DNS-01 via SmartAcme for ${domain}`);
|
|
304
|
+
eventComms.setSource('smartacme-dns-01');
|
|
305
|
+
const isWildcardDomain = domain.startsWith('*.');
|
|
306
|
+
const cert = await this.smartAcme.getCertificateForDomain(domain, {
|
|
307
|
+
includeWildcard: !isWildcardDomain,
|
|
308
|
+
});
|
|
309
|
+
if (cert.validUntil) {
|
|
310
|
+
eventComms.setExpiryDate(new Date(cert.validUntil));
|
|
311
|
+
}
|
|
312
|
+
const result = {
|
|
313
|
+
id: cert.id,
|
|
314
|
+
domainName: cert.domainName,
|
|
315
|
+
created: cert.created,
|
|
316
|
+
validUntil: cert.validUntil,
|
|
317
|
+
privateKey: cert.privateKey,
|
|
318
|
+
publicKey: cert.publicKey,
|
|
319
|
+
csr: cert.csr,
|
|
320
|
+
};
|
|
321
|
+
// Success — clear any backoff
|
|
322
|
+
await scheduler.clearBackoff(domain);
|
|
323
|
+
return result;
|
|
324
|
+
}
|
|
325
|
+
catch (err) {
|
|
326
|
+
// Record failure for backoff tracking
|
|
327
|
+
await scheduler.recordFailure(domain, err.message);
|
|
328
|
+
eventComms.warn(`SmartAcme DNS-01 failed for ${domain}: ${err.message}, falling back to http-01`);
|
|
329
|
+
return 'http01';
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
// When remoteIngress is enabled, the hub binary forwards tunneled connections
|
|
334
|
+
// to SmartProxy with PROXY protocol v1 headers to preserve client IPs.
|
|
335
|
+
if (this.options.remoteIngressConfig?.enabled) {
|
|
336
|
+
smartProxyConfig.acceptProxyProtocol = true;
|
|
337
|
+
smartProxyConfig.proxyIPs = ['127.0.0.1'];
|
|
338
|
+
}
|
|
339
|
+
// Create SmartProxy instance
|
|
340
|
+
logger.log('info', `Creating SmartProxy instance: routes=${smartProxyConfig.routes?.length}, acme=${smartProxyConfig.acme?.enabled}, certProvisionFunction=${!!smartProxyConfig.certProvisionFunction}`);
|
|
341
|
+
this.smartProxy = new plugins.smartproxy.SmartProxy(smartProxyConfig);
|
|
342
|
+
// Set up event listeners
|
|
343
|
+
this.smartProxy.on('error', (err) => {
|
|
344
|
+
logger.log('error', `SmartProxy error: ${err.message}`, { stack: err.stack });
|
|
345
|
+
});
|
|
346
|
+
// Always listen for certificate events — emitted by both ACME and certProvisionFunction paths
|
|
347
|
+
// Events are keyed by domain for domain-centric certificate tracking
|
|
348
|
+
this.smartProxy.on('certificate-issued', (event) => {
|
|
349
|
+
logger.log('info', `Certificate issued for ${event.domain} via ${event.source}, expires ${event.expiryDate}`);
|
|
350
|
+
const routeNames = this.findRouteNamesForDomain(event.domain);
|
|
351
|
+
this.certificateStatusMap.set(event.domain, {
|
|
352
|
+
status: 'valid', routeNames,
|
|
353
|
+
expiryDate: event.expiryDate, issuedAt: new Date().toISOString(),
|
|
354
|
+
source: event.source,
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
this.smartProxy.on('certificate-renewed', (event) => {
|
|
358
|
+
logger.log('info', `Certificate renewed for ${event.domain} via ${event.source}, expires ${event.expiryDate}`);
|
|
359
|
+
const routeNames = this.findRouteNamesForDomain(event.domain);
|
|
360
|
+
this.certificateStatusMap.set(event.domain, {
|
|
361
|
+
status: 'valid', routeNames,
|
|
362
|
+
expiryDate: event.expiryDate, issuedAt: new Date().toISOString(),
|
|
363
|
+
source: event.source,
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
this.smartProxy.on('certificate-failed', (event) => {
|
|
367
|
+
logger.log('error', `Certificate failed for ${event.domain} (${event.source}): ${event.error}`);
|
|
368
|
+
const routeNames = this.findRouteNamesForDomain(event.domain);
|
|
369
|
+
this.certificateStatusMap.set(event.domain, {
|
|
370
|
+
status: 'failed', routeNames, error: event.error,
|
|
371
|
+
source: event.source,
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
// Start SmartProxy
|
|
375
|
+
logger.log('info', 'Starting SmartProxy...');
|
|
376
|
+
await this.smartProxy.start();
|
|
377
|
+
logger.log('info', 'SmartProxy started successfully');
|
|
378
|
+
// Populate certificateStatusMap for certs loaded from store at startup
|
|
379
|
+
for (const entry of loadedCertEntries) {
|
|
380
|
+
if (!this.certificateStatusMap.has(entry.domain)) {
|
|
381
|
+
const routeNames = this.findRouteNamesForDomain(entry.domain);
|
|
382
|
+
let expiryDate;
|
|
383
|
+
let issuedAt;
|
|
384
|
+
// Use validUntil/validFrom from stored proxy-certs data if available
|
|
385
|
+
if (entry.validUntil) {
|
|
386
|
+
expiryDate = new Date(entry.validUntil).toISOString();
|
|
387
|
+
}
|
|
388
|
+
if (entry.validFrom) {
|
|
389
|
+
issuedAt = new Date(entry.validFrom).toISOString();
|
|
390
|
+
}
|
|
391
|
+
// Try SmartAcme /certs/ metadata as secondary source
|
|
392
|
+
if (!expiryDate) {
|
|
393
|
+
try {
|
|
394
|
+
const cleanDomain = entry.domain.replace(/^\*\.?/, '');
|
|
395
|
+
const certMeta = await this.storageManager.getJSON(`/certs/${cleanDomain}`);
|
|
396
|
+
if (certMeta?.validUntil) {
|
|
397
|
+
expiryDate = new Date(certMeta.validUntil).toISOString();
|
|
398
|
+
}
|
|
399
|
+
if (certMeta?.created && !issuedAt) {
|
|
400
|
+
issuedAt = new Date(certMeta.created).toISOString();
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
catch { /* no metadata available */ }
|
|
404
|
+
}
|
|
405
|
+
// Fallback: parse X509 from PEM to get expiry
|
|
406
|
+
if (!expiryDate && entry.publicKey) {
|
|
407
|
+
try {
|
|
408
|
+
const x509 = new plugins.crypto.X509Certificate(entry.publicKey);
|
|
409
|
+
expiryDate = new Date(x509.validTo).toISOString();
|
|
410
|
+
if (!issuedAt) {
|
|
411
|
+
issuedAt = new Date(x509.validFrom).toISOString();
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
catch { /* PEM parsing failed */ }
|
|
415
|
+
}
|
|
416
|
+
this.certificateStatusMap.set(entry.domain, {
|
|
417
|
+
status: 'valid',
|
|
418
|
+
routeNames,
|
|
419
|
+
expiryDate,
|
|
420
|
+
issuedAt,
|
|
421
|
+
source: 'cert-store',
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
if (loadedCertEntries.length > 0) {
|
|
426
|
+
logger.log('info', `Populated certificate status for ${loadedCertEntries.length} store-loaded domain(s)`);
|
|
427
|
+
}
|
|
428
|
+
logger.log('info', `SmartProxy started with ${routes.length} routes`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Generate SmartProxy routes for email configuration
|
|
433
|
+
*/
|
|
434
|
+
generateEmailRoutes(emailConfig) {
|
|
435
|
+
const emailRoutes = [];
|
|
436
|
+
// Create routes for each email port
|
|
437
|
+
for (const port of emailConfig.ports) {
|
|
438
|
+
// Create a descriptive name for the route based on the port
|
|
439
|
+
let routeName = 'email-route';
|
|
440
|
+
let tlsMode = 'passthrough';
|
|
441
|
+
// Handle different email ports differently
|
|
442
|
+
switch (port) {
|
|
443
|
+
case 25: // SMTP
|
|
444
|
+
routeName = 'smtp-route';
|
|
445
|
+
tlsMode = 'passthrough'; // STARTTLS handled by email server
|
|
446
|
+
break;
|
|
447
|
+
case 587: // Submission
|
|
448
|
+
routeName = 'submission-route';
|
|
449
|
+
tlsMode = 'passthrough'; // STARTTLS handled by email server
|
|
450
|
+
break;
|
|
451
|
+
case 465: // SMTPS
|
|
452
|
+
routeName = 'smtps-route';
|
|
453
|
+
tlsMode = 'terminate'; // Terminate TLS and re-encrypt to email server
|
|
454
|
+
break;
|
|
455
|
+
default:
|
|
456
|
+
routeName = `email-port-${port}-route`;
|
|
457
|
+
tlsMode = 'passthrough';
|
|
458
|
+
// Check if we have specific settings for this port
|
|
459
|
+
if (this.options.emailPortConfig?.portSettings &&
|
|
460
|
+
this.options.emailPortConfig.portSettings[port]) {
|
|
461
|
+
const portSettings = this.options.emailPortConfig.portSettings[port];
|
|
462
|
+
// If this port requires TLS termination, set the mode accordingly
|
|
463
|
+
if (portSettings.terminateTls) {
|
|
464
|
+
tlsMode = 'terminate';
|
|
465
|
+
}
|
|
466
|
+
// Override the route name if specified
|
|
467
|
+
if (portSettings.routeName) {
|
|
468
|
+
routeName = portSettings.routeName;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
// Create forward action to route to internal email server ports
|
|
474
|
+
const defaultPortMapping = {
|
|
475
|
+
25: 10025, // SMTP
|
|
476
|
+
587: 10587, // Submission
|
|
477
|
+
465: 10465 // SMTPS
|
|
478
|
+
};
|
|
479
|
+
const portMapping = this.options.emailPortConfig?.portMapping || defaultPortMapping;
|
|
480
|
+
const internalPort = portMapping[port] || port + 10000;
|
|
481
|
+
let action = {
|
|
482
|
+
type: 'forward',
|
|
483
|
+
targets: [{
|
|
484
|
+
host: 'localhost', // Forward to internal email server
|
|
485
|
+
port: internalPort
|
|
486
|
+
}],
|
|
487
|
+
tls: {
|
|
488
|
+
mode: tlsMode
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
// For TLS terminate mode, add certificate info
|
|
492
|
+
if (tlsMode === 'terminate' && action.tls) {
|
|
493
|
+
action.tls.certificate = 'auto';
|
|
494
|
+
}
|
|
495
|
+
// Create the route configuration
|
|
496
|
+
const routeConfig = {
|
|
497
|
+
name: routeName,
|
|
498
|
+
match: {
|
|
499
|
+
ports: [port]
|
|
500
|
+
},
|
|
501
|
+
action: action
|
|
502
|
+
};
|
|
503
|
+
// Add the route to our list
|
|
504
|
+
emailRoutes.push(routeConfig);
|
|
505
|
+
}
|
|
506
|
+
return emailRoutes;
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Generate SmartProxy routes for DNS configuration
|
|
510
|
+
*/
|
|
511
|
+
generateDnsRoutes() {
|
|
512
|
+
if (!this.options.dnsNsDomains || this.options.dnsNsDomains.length === 0) {
|
|
513
|
+
return [];
|
|
514
|
+
}
|
|
515
|
+
const dnsRoutes = [];
|
|
516
|
+
// Create routes for DNS-over-HTTPS paths
|
|
517
|
+
const dohPaths = ['/dns-query', '/resolve'];
|
|
518
|
+
// Use the first nameserver domain for DoH routes
|
|
519
|
+
const primaryNameserver = this.options.dnsNsDomains[0];
|
|
520
|
+
for (const path of dohPaths) {
|
|
521
|
+
const dohRoute = {
|
|
522
|
+
name: `dns-over-https-${path.replace('/', '')}`,
|
|
523
|
+
match: {
|
|
524
|
+
ports: [443], // HTTPS port for DoH
|
|
525
|
+
domains: [primaryNameserver],
|
|
526
|
+
path: path
|
|
527
|
+
},
|
|
528
|
+
action: {
|
|
529
|
+
type: 'socket-handler',
|
|
530
|
+
socketHandler: this.createDnsSocketHandler()
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
dnsRoutes.push(dohRoute);
|
|
534
|
+
}
|
|
535
|
+
return dnsRoutes;
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Check if a domain matches a pattern (including wildcard support)
|
|
539
|
+
* @param domain The domain to check
|
|
540
|
+
* @param pattern The pattern to match against (e.g., "*.example.com")
|
|
541
|
+
* @returns Whether the domain matches the pattern
|
|
542
|
+
*/
|
|
543
|
+
isDomainMatch(domain, pattern) {
|
|
544
|
+
domain = domain.toLowerCase();
|
|
545
|
+
pattern = pattern.toLowerCase();
|
|
546
|
+
if (domain === pattern)
|
|
547
|
+
return true;
|
|
548
|
+
// Routing-glob: *example.com matches example.com, sub.example.com, *.example.com
|
|
549
|
+
if (pattern.startsWith('*') && !pattern.startsWith('*.')) {
|
|
550
|
+
const baseDomain = pattern.slice(1); // *nevermind.cloud → nevermind.cloud
|
|
551
|
+
if (domain === baseDomain || domain === `*.${baseDomain}`)
|
|
552
|
+
return true;
|
|
553
|
+
if (domain.endsWith(baseDomain) && domain.length > baseDomain.length)
|
|
554
|
+
return true;
|
|
555
|
+
}
|
|
556
|
+
// Standard wildcard: *.example.com matches sub.example.com and example.com
|
|
557
|
+
if (pattern.startsWith('*.')) {
|
|
558
|
+
const suffix = pattern.slice(2);
|
|
559
|
+
if (domain === suffix)
|
|
560
|
+
return true;
|
|
561
|
+
return domain.endsWith(suffix) && domain.length > suffix.length;
|
|
562
|
+
}
|
|
563
|
+
return false;
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Find the first route name that matches a given domain
|
|
567
|
+
*/
|
|
568
|
+
findRouteNameForDomain(domain) {
|
|
569
|
+
if (!this.smartProxy)
|
|
570
|
+
return undefined;
|
|
571
|
+
for (const route of this.smartProxy.routeManager.getRoutes()) {
|
|
572
|
+
if (!route.match.domains || !route.name)
|
|
573
|
+
continue;
|
|
574
|
+
const routeDomains = Array.isArray(route.match.domains)
|
|
575
|
+
? route.match.domains
|
|
576
|
+
: [route.match.domains];
|
|
577
|
+
for (const pattern of routeDomains) {
|
|
578
|
+
if (this.isDomainMatch(domain, pattern))
|
|
579
|
+
return route.name;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
return undefined;
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Find ALL route names that match a given domain
|
|
586
|
+
*/
|
|
587
|
+
findRouteNamesForDomain(domain) {
|
|
588
|
+
if (!this.smartProxy)
|
|
589
|
+
return [];
|
|
590
|
+
const names = [];
|
|
591
|
+
for (const route of this.smartProxy.routeManager.getRoutes()) {
|
|
592
|
+
if (!route.match.domains || !route.name)
|
|
593
|
+
continue;
|
|
594
|
+
const routeDomains = Array.isArray(route.match.domains)
|
|
595
|
+
? route.match.domains
|
|
596
|
+
: [route.match.domains];
|
|
597
|
+
for (const pattern of routeDomains) {
|
|
598
|
+
if (this.isDomainMatch(domain, pattern)) {
|
|
599
|
+
names.push(route.name);
|
|
600
|
+
break; // This route already matched, no need to check other patterns
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
return names;
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Get the routes derived from constructor config (smartProxy + email + DNS).
|
|
608
|
+
* Used by RouteConfigManager as the "hardcoded" base.
|
|
609
|
+
*/
|
|
610
|
+
getConstructorRoutes() {
|
|
611
|
+
return this.constructorRoutes;
|
|
612
|
+
}
|
|
613
|
+
async stop() {
|
|
614
|
+
logger.log('info', 'Stopping DcRouter services...');
|
|
615
|
+
// Flush pending DNS batch log
|
|
616
|
+
if (this.dnsBatchTimer) {
|
|
617
|
+
clearTimeout(this.dnsBatchTimer);
|
|
618
|
+
if (this.dnsBatchCount > 0) {
|
|
619
|
+
logger.log('info', `DNS: ${this.dnsBatchCount} queries processed (rate limited, final flush)`, { zone: 'dns' });
|
|
620
|
+
}
|
|
621
|
+
this.dnsBatchTimer = null;
|
|
622
|
+
this.dnsBatchCount = 0;
|
|
623
|
+
this.dnsLogWindowSecond = 0;
|
|
624
|
+
this.dnsLogWindowCount = 0;
|
|
625
|
+
}
|
|
626
|
+
await this.opsServer.stop();
|
|
627
|
+
try {
|
|
628
|
+
// Remove event listeners before stopping services to prevent leaks
|
|
629
|
+
if (this.smartProxy) {
|
|
630
|
+
this.smartProxy.removeAllListeners();
|
|
631
|
+
}
|
|
632
|
+
if (this.emailServer) {
|
|
633
|
+
if (this.emailServer.deliverySystem) {
|
|
634
|
+
this.emailServer.deliverySystem.removeAllListeners();
|
|
635
|
+
}
|
|
636
|
+
this.emailServer.removeAllListeners();
|
|
637
|
+
}
|
|
638
|
+
if (this.dnsServer) {
|
|
639
|
+
this.dnsServer.removeAllListeners();
|
|
640
|
+
}
|
|
641
|
+
// Stop all services in parallel for faster shutdown
|
|
642
|
+
await Promise.all([
|
|
643
|
+
// Stop cache cleaner if running
|
|
644
|
+
this.cacheCleaner ? Promise.resolve(this.cacheCleaner.stop()) : Promise.resolve(),
|
|
645
|
+
// Stop metrics manager if running
|
|
646
|
+
this.metricsManager ? this.metricsManager.stop().catch(err => logger.log('error', 'Error stopping MetricsManager', { error: String(err) })) : Promise.resolve(),
|
|
647
|
+
// Stop unified email server if running
|
|
648
|
+
this.emailServer ? this.emailServer.stop().catch(err => logger.log('error', 'Error stopping email server', { error: String(err) })) : Promise.resolve(),
|
|
649
|
+
// Stop SmartAcme if running
|
|
650
|
+
this.smartAcme ? this.smartAcme.stop().catch(err => logger.log('error', 'Error stopping SmartAcme', { error: String(err) })) : Promise.resolve(),
|
|
651
|
+
// Stop HTTP SmartProxy if running
|
|
652
|
+
this.smartProxy ? this.smartProxy.stop().catch(err => logger.log('error', 'Error stopping SmartProxy', { error: String(err) })) : Promise.resolve(),
|
|
653
|
+
// Stop DNS server if running
|
|
654
|
+
this.dnsServer ?
|
|
655
|
+
this.dnsServer.stop().catch(err => logger.log('error', 'Error stopping DNS server', { error: String(err) })) :
|
|
656
|
+
Promise.resolve(),
|
|
657
|
+
// Stop RADIUS server if running
|
|
658
|
+
this.radiusServer ?
|
|
659
|
+
this.radiusServer.stop().catch(err => logger.log('error', 'Error stopping RADIUS server', { error: String(err) })) :
|
|
660
|
+
Promise.resolve(),
|
|
661
|
+
// Stop Remote Ingress tunnel manager if running
|
|
662
|
+
this.tunnelManager ?
|
|
663
|
+
this.tunnelManager.stop().catch(err => logger.log('error', 'Error stopping TunnelManager', { error: String(err) })) :
|
|
664
|
+
Promise.resolve()
|
|
665
|
+
]);
|
|
666
|
+
// Stop cache database after other services (they may need it during shutdown)
|
|
667
|
+
if (this.cacheDb) {
|
|
668
|
+
await this.cacheDb.stop().catch(err => logger.log('error', 'Error stopping CacheDb', { error: String(err) }));
|
|
669
|
+
CacheDb.resetInstance();
|
|
670
|
+
}
|
|
671
|
+
// Clear backoff cache in cert scheduler
|
|
672
|
+
if (this.certProvisionScheduler) {
|
|
673
|
+
this.certProvisionScheduler.clear();
|
|
674
|
+
}
|
|
675
|
+
// Allow GC of stopped services by nulling references
|
|
676
|
+
this.smartProxy = undefined;
|
|
677
|
+
this.emailServer = undefined;
|
|
678
|
+
this.dnsServer = undefined;
|
|
679
|
+
this.metricsManager = undefined;
|
|
680
|
+
this.cacheCleaner = undefined;
|
|
681
|
+
this.cacheDb = undefined;
|
|
682
|
+
this.tunnelManager = undefined;
|
|
683
|
+
this.radiusServer = undefined;
|
|
684
|
+
this.smartAcme = undefined;
|
|
685
|
+
this.certProvisionScheduler = undefined;
|
|
686
|
+
this.remoteIngressManager = undefined;
|
|
687
|
+
this.routeConfigManager = undefined;
|
|
688
|
+
this.apiTokenManager = undefined;
|
|
689
|
+
this.certificateStatusMap.clear();
|
|
690
|
+
// Reset security singletons to allow GC
|
|
691
|
+
SecurityLogger.resetInstance();
|
|
692
|
+
ContentScanner.resetInstance();
|
|
693
|
+
IPReputationChecker.resetInstance();
|
|
694
|
+
logger.log('info', 'All DcRouter services stopped');
|
|
695
|
+
}
|
|
696
|
+
catch (error) {
|
|
697
|
+
logger.log('error', 'Error during DcRouter shutdown', { error: String(error) });
|
|
698
|
+
throw error;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Update SmartProxy configuration
|
|
703
|
+
* @param config New SmartProxy configuration
|
|
704
|
+
*/
|
|
705
|
+
async updateSmartProxyConfig(config) {
|
|
706
|
+
// Stop existing SmartProxy if running
|
|
707
|
+
if (this.smartProxy) {
|
|
708
|
+
this.smartProxy.removeAllListeners();
|
|
709
|
+
await this.smartProxy.stop();
|
|
710
|
+
this.smartProxy = undefined;
|
|
711
|
+
}
|
|
712
|
+
// Update configuration
|
|
713
|
+
this.options.smartProxyConfig = config;
|
|
714
|
+
// Update routes on RemoteIngressManager so derived ports stay in sync
|
|
715
|
+
if (this.remoteIngressManager && config.routes) {
|
|
716
|
+
this.remoteIngressManager.setRoutes(config.routes);
|
|
717
|
+
}
|
|
718
|
+
// Start new SmartProxy with updated configuration (will include email routes if configured)
|
|
719
|
+
await this.setupSmartProxy();
|
|
720
|
+
// Re-apply programmatic routes and overrides after SmartProxy restart
|
|
721
|
+
if (this.routeConfigManager) {
|
|
722
|
+
await this.routeConfigManager.initialize();
|
|
723
|
+
}
|
|
724
|
+
logger.log('info', 'SmartProxy configuration updated');
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Set up unified email handling with pattern-based routing
|
|
728
|
+
* This implements the consolidated emailConfig approach
|
|
729
|
+
*/
|
|
730
|
+
async setupUnifiedEmailHandling() {
|
|
731
|
+
if (!this.options.emailConfig) {
|
|
732
|
+
throw new Error('Email configuration is required for unified email handling');
|
|
733
|
+
}
|
|
734
|
+
// Apply port mapping if behind SmartProxy
|
|
735
|
+
const portMapping = this.options.emailPortConfig?.portMapping || {
|
|
736
|
+
25: 10025, // SMTP
|
|
737
|
+
587: 10587, // Submission
|
|
738
|
+
465: 10465 // SMTPS
|
|
739
|
+
};
|
|
740
|
+
// Transform domains if they are provided as strings
|
|
741
|
+
let transformedDomains = this.options.emailConfig.domains;
|
|
742
|
+
if (transformedDomains && transformedDomains.length > 0) {
|
|
743
|
+
// Check if domains are strings (for backward compatibility)
|
|
744
|
+
if (typeof transformedDomains[0] === 'string') {
|
|
745
|
+
transformedDomains = transformedDomains.map((domain) => ({
|
|
746
|
+
domain,
|
|
747
|
+
dnsMode: 'external-dns',
|
|
748
|
+
dkim: {
|
|
749
|
+
selector: 'default',
|
|
750
|
+
keySize: 2048,
|
|
751
|
+
rotateKeys: false,
|
|
752
|
+
rotationInterval: 90
|
|
753
|
+
}
|
|
754
|
+
}));
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
// Create config with mapped ports
|
|
758
|
+
const emailConfig = {
|
|
759
|
+
...this.options.emailConfig,
|
|
760
|
+
domains: transformedDomains,
|
|
761
|
+
ports: this.options.emailConfig.ports.map(port => portMapping[port] || port + 10000),
|
|
762
|
+
hostname: 'localhost' // Listen on localhost for SmartProxy forwarding
|
|
763
|
+
};
|
|
764
|
+
// Create unified email server
|
|
765
|
+
this.emailServer = new UnifiedEmailServer(this, emailConfig);
|
|
766
|
+
// Set up error handling
|
|
767
|
+
this.emailServer.on('error', (err) => {
|
|
768
|
+
logger.log('error', `UnifiedEmailServer error: ${err.message}`);
|
|
769
|
+
});
|
|
770
|
+
// Start the server
|
|
771
|
+
await this.emailServer.start();
|
|
772
|
+
// Wire delivery events to MetricsManager and logger
|
|
773
|
+
if (this.metricsManager && this.emailServer.deliverySystem) {
|
|
774
|
+
this.emailServer.deliverySystem.on('deliveryStart', (item) => {
|
|
775
|
+
this.metricsManager.trackEmailReceived(item?.from);
|
|
776
|
+
logger.log('info', `Email delivery started: ${item?.from} → ${item?.to}`, { zone: 'email' });
|
|
777
|
+
});
|
|
778
|
+
this.emailServer.deliverySystem.on('deliverySuccess', (item) => {
|
|
779
|
+
this.metricsManager.trackEmailSent(item?.to);
|
|
780
|
+
logger.log('info', `Email delivered to ${item?.to}`, { zone: 'email' });
|
|
781
|
+
});
|
|
782
|
+
this.emailServer.deliverySystem.on('deliveryFailed', (item, error) => {
|
|
783
|
+
this.metricsManager.trackEmailFailed(item?.to, error?.message);
|
|
784
|
+
logger.log('warn', `Email delivery failed to ${item?.to}: ${error?.message}`, { zone: 'email' });
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
if (this.metricsManager && this.emailServer) {
|
|
788
|
+
this.emailServer.on('bounceProcessed', () => {
|
|
789
|
+
this.metricsManager.trackEmailBounced();
|
|
790
|
+
logger.log('warn', 'Email bounce processed', { zone: 'email' });
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
logger.log('info', `Email server started on ports: ${emailConfig.ports.join(', ')}`);
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Update the unified email configuration
|
|
797
|
+
* @param config New email configuration
|
|
798
|
+
*/
|
|
799
|
+
async updateEmailConfig(config) {
|
|
800
|
+
// Stop existing email components
|
|
801
|
+
await this.stopUnifiedEmailComponents();
|
|
802
|
+
// Update configuration
|
|
803
|
+
this.options.emailConfig = config;
|
|
804
|
+
// Start email handling with new configuration
|
|
805
|
+
await this.setupUnifiedEmailHandling();
|
|
806
|
+
logger.log('info', 'Unified email configuration updated');
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Stop all unified email components
|
|
810
|
+
*/
|
|
811
|
+
async stopUnifiedEmailComponents() {
|
|
812
|
+
try {
|
|
813
|
+
// Stop the unified email server which contains all components
|
|
814
|
+
if (this.emailServer) {
|
|
815
|
+
// Remove listeners before stopping to prevent leaks on config update cycles
|
|
816
|
+
if (this.emailServer.deliverySystem) {
|
|
817
|
+
this.emailServer.deliverySystem.removeAllListeners();
|
|
818
|
+
}
|
|
819
|
+
this.emailServer.removeAllListeners();
|
|
820
|
+
await this.emailServer.stop();
|
|
821
|
+
logger.log('info', 'Unified email server stopped');
|
|
822
|
+
this.emailServer = undefined;
|
|
823
|
+
}
|
|
824
|
+
logger.log('info', 'All unified email components stopped');
|
|
825
|
+
}
|
|
826
|
+
catch (error) {
|
|
827
|
+
logger.log('error', `Error stopping unified email components: ${error.message}`);
|
|
828
|
+
throw error;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Update domain rules for email routing
|
|
833
|
+
* @param rules New domain rules to apply
|
|
834
|
+
*/
|
|
835
|
+
async updateEmailRoutes(routes) {
|
|
836
|
+
// Validate that email config exists
|
|
837
|
+
if (!this.options.emailConfig) {
|
|
838
|
+
throw new Error('Email configuration is required before updating routes');
|
|
839
|
+
}
|
|
840
|
+
// Update the configuration
|
|
841
|
+
this.options.emailConfig.routes = routes;
|
|
842
|
+
// Update the unified email server if it exists
|
|
843
|
+
if (this.emailServer) {
|
|
844
|
+
this.emailServer.updateEmailRoutes(routes);
|
|
845
|
+
}
|
|
846
|
+
logger.log('info', `Email routes updated with ${routes.length} routes`);
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Get statistics from all components
|
|
850
|
+
*/
|
|
851
|
+
getStats() {
|
|
852
|
+
const stats = {
|
|
853
|
+
emailServer: this.emailServer?.getStats()
|
|
854
|
+
};
|
|
855
|
+
return stats;
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* Register DNS records with the DNS server
|
|
859
|
+
* @param records Array of DNS records to register
|
|
860
|
+
*/
|
|
861
|
+
registerDnsRecords(records) {
|
|
862
|
+
if (!this.dnsServer)
|
|
863
|
+
return;
|
|
864
|
+
// Register a separate handler for each record
|
|
865
|
+
// This ensures multiple records of the same type (like NS records) are all served
|
|
866
|
+
for (const record of records) {
|
|
867
|
+
// Register handler for this specific record
|
|
868
|
+
this.dnsServer.registerHandler(record.name, [record.type], (question) => {
|
|
869
|
+
// Check if this handler matches the question
|
|
870
|
+
if (question.name === record.name && question.type === record.type) {
|
|
871
|
+
return {
|
|
872
|
+
name: record.name,
|
|
873
|
+
type: record.type,
|
|
874
|
+
class: 'IN',
|
|
875
|
+
ttl: record.ttl || 300,
|
|
876
|
+
data: this.parseDnsRecordData(record.type, record.value)
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
return null;
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
logger.log('info', `Registered ${records.length} DNS handlers (one per record)`);
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* Parse DNS record data based on record type
|
|
886
|
+
* @param type DNS record type
|
|
887
|
+
* @param value DNS record value
|
|
888
|
+
* @returns Parsed data for the DNS response
|
|
889
|
+
*/
|
|
890
|
+
parseDnsRecordData(type, value) {
|
|
891
|
+
switch (type) {
|
|
892
|
+
case 'A':
|
|
893
|
+
return value; // IP address as string
|
|
894
|
+
case 'MX':
|
|
895
|
+
const [priority, exchange] = value.split(' ');
|
|
896
|
+
return { priority: parseInt(priority), exchange };
|
|
897
|
+
case 'TXT':
|
|
898
|
+
return value;
|
|
899
|
+
case 'NS':
|
|
900
|
+
return value;
|
|
901
|
+
case 'SOA':
|
|
902
|
+
// SOA format: primary-ns admin-email serial refresh retry expire minimum
|
|
903
|
+
const parts = value.split(' ');
|
|
904
|
+
return {
|
|
905
|
+
mname: parts[0],
|
|
906
|
+
rname: parts[1],
|
|
907
|
+
serial: parseInt(parts[2]),
|
|
908
|
+
refresh: parseInt(parts[3]),
|
|
909
|
+
retry: parseInt(parts[4]),
|
|
910
|
+
expire: parseInt(parts[5]),
|
|
911
|
+
minimum: parseInt(parts[6])
|
|
912
|
+
};
|
|
913
|
+
default:
|
|
914
|
+
return value;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Set up DNS server with socket handler for DoH
|
|
919
|
+
*/
|
|
920
|
+
async setupDnsWithSocketHandler() {
|
|
921
|
+
if (!this.options.dnsNsDomains || this.options.dnsNsDomains.length === 0) {
|
|
922
|
+
throw new Error('dnsNsDomains is required for DNS server setup');
|
|
923
|
+
}
|
|
924
|
+
if (!this.options.dnsScopes || this.options.dnsScopes.length === 0) {
|
|
925
|
+
throw new Error('dnsScopes is required for DNS server setup');
|
|
926
|
+
}
|
|
927
|
+
const primaryNameserver = this.options.dnsNsDomains[0];
|
|
928
|
+
logger.log('info', `Setting up DNS server with primary nameserver: ${primaryNameserver}`);
|
|
929
|
+
// Get VM IP address for UDP binding
|
|
930
|
+
const networkInterfaces = plugins.os.networkInterfaces();
|
|
931
|
+
let vmIpAddress = '0.0.0.0'; // Default to all interfaces
|
|
932
|
+
// Try to find the VM's internal IP address
|
|
933
|
+
for (const [_name, interfaces] of Object.entries(networkInterfaces)) {
|
|
934
|
+
if (interfaces) {
|
|
935
|
+
for (const iface of interfaces) {
|
|
936
|
+
if (!iface.internal && iface.family === 'IPv4') {
|
|
937
|
+
vmIpAddress = iface.address;
|
|
938
|
+
break;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
// Create DNS server instance with manual HTTPS mode
|
|
944
|
+
this.dnsServer = new plugins.smartdns.dnsServerMod.DnsServer({
|
|
945
|
+
udpPort: 53,
|
|
946
|
+
udpBindInterface: vmIpAddress,
|
|
947
|
+
httpsPort: 443, // Required but won't bind due to manual mode
|
|
948
|
+
manualHttpsMode: true, // Enable manual HTTPS socket handling
|
|
949
|
+
dnssecZone: primaryNameserver,
|
|
950
|
+
primaryNameserver: primaryNameserver, // Automatically generates correct SOA records
|
|
951
|
+
// For now, use self-signed cert until we integrate with Let's Encrypt
|
|
952
|
+
httpsKey: '',
|
|
953
|
+
httpsCert: ''
|
|
954
|
+
});
|
|
955
|
+
// Start the DNS server (UDP only)
|
|
956
|
+
await this.dnsServer.start();
|
|
957
|
+
logger.log('info', `DNS server started on UDP ${vmIpAddress}:53`);
|
|
958
|
+
// Wire DNS query events to MetricsManager and logger with adaptive rate limiting
|
|
959
|
+
if (this.metricsManager && this.dnsServer) {
|
|
960
|
+
const flushDnsBatch = () => {
|
|
961
|
+
if (this.dnsBatchCount > 0) {
|
|
962
|
+
logger.log('info', `DNS: ${this.dnsBatchCount} queries processed (rate limited)`, { zone: 'dns' });
|
|
963
|
+
this.dnsBatchCount = 0;
|
|
964
|
+
}
|
|
965
|
+
this.dnsBatchTimer = null;
|
|
966
|
+
};
|
|
967
|
+
this.dnsServer.on('query', (event) => {
|
|
968
|
+
// Metrics tracking
|
|
969
|
+
for (const question of event.questions) {
|
|
970
|
+
this.metricsManager.trackDnsQuery(question.type, question.name, false, event.responseTimeMs, event.answered);
|
|
971
|
+
}
|
|
972
|
+
// Adaptive logging: individual logs up to 2/sec, then batch
|
|
973
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
974
|
+
if (nowSec !== this.dnsLogWindowSecond) {
|
|
975
|
+
this.dnsLogWindowSecond = nowSec;
|
|
976
|
+
this.dnsLogWindowCount = 0;
|
|
977
|
+
}
|
|
978
|
+
if (this.dnsLogWindowCount < 2) {
|
|
979
|
+
this.dnsLogWindowCount++;
|
|
980
|
+
const summary = event.questions.map(q => `${q.type} ${q.name}`).join(', ');
|
|
981
|
+
logger.log('info', `DNS query: ${summary} (${event.responseTimeMs}ms, ${event.answered ? 'answered' : 'unanswered'})`, { zone: 'dns' });
|
|
982
|
+
}
|
|
983
|
+
else {
|
|
984
|
+
this.dnsBatchCount++;
|
|
985
|
+
if (!this.dnsBatchTimer) {
|
|
986
|
+
this.dnsBatchTimer = setTimeout(flushDnsBatch, 5000);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
// Validate DNS configuration
|
|
992
|
+
await this.validateDnsConfiguration();
|
|
993
|
+
// Generate and register authoritative records
|
|
994
|
+
const authoritativeRecords = await this.generateAuthoritativeRecords();
|
|
995
|
+
// Generate email DNS records
|
|
996
|
+
const emailDnsRecords = await this.generateEmailDnsRecords();
|
|
997
|
+
// Initialize DKIM for all email domains
|
|
998
|
+
await this.initializeDkimForEmailDomains();
|
|
999
|
+
// Load DKIM records from JSON files (they should now exist)
|
|
1000
|
+
const dkimRecords = await this.loadDkimRecords();
|
|
1001
|
+
// Combine all records: authoritative, email, DKIM, and user-defined
|
|
1002
|
+
const allRecords = [...authoritativeRecords, ...emailDnsRecords, ...dkimRecords];
|
|
1003
|
+
if (this.options.dnsRecords && this.options.dnsRecords.length > 0) {
|
|
1004
|
+
allRecords.push(...this.options.dnsRecords);
|
|
1005
|
+
}
|
|
1006
|
+
// Apply proxy IP replacement if configured
|
|
1007
|
+
await this.applyProxyIpReplacement(allRecords);
|
|
1008
|
+
// Register all DNS records
|
|
1009
|
+
if (allRecords.length > 0) {
|
|
1010
|
+
this.registerDnsRecords(allRecords);
|
|
1011
|
+
logger.log('info', `Registered ${allRecords.length} DNS records (${authoritativeRecords.length} authoritative, ${emailDnsRecords.length} email, ${dkimRecords.length} DKIM, ${this.options.dnsRecords?.length || 0} user-defined)`);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Create DNS socket handler for DoH
|
|
1016
|
+
*/
|
|
1017
|
+
createDnsSocketHandler() {
|
|
1018
|
+
return async (socket) => {
|
|
1019
|
+
if (!this.dnsServer) {
|
|
1020
|
+
logger.log('error', 'DNS socket handler called but DNS server not initialized');
|
|
1021
|
+
socket.end();
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
// Prevent uncaught exception from socket 'error' events
|
|
1025
|
+
socket.on('error', (err) => {
|
|
1026
|
+
logger.log('error', `DNS socket error: ${err.message}`);
|
|
1027
|
+
if (!socket.destroyed) {
|
|
1028
|
+
socket.destroy();
|
|
1029
|
+
}
|
|
1030
|
+
});
|
|
1031
|
+
logger.log('debug', 'DNS socket handler: passing socket to DnsServer');
|
|
1032
|
+
try {
|
|
1033
|
+
// Use the built-in socket handler from smartdns
|
|
1034
|
+
// This handles HTTP/2, DoH protocol, etc.
|
|
1035
|
+
await this.dnsServer.handleHttpsSocket(socket);
|
|
1036
|
+
}
|
|
1037
|
+
catch (error) {
|
|
1038
|
+
logger.log('error', `DNS socket handler error: ${error.message}`);
|
|
1039
|
+
if (!socket.destroyed) {
|
|
1040
|
+
socket.destroy();
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Validate DNS configuration
|
|
1047
|
+
*/
|
|
1048
|
+
async validateDnsConfiguration() {
|
|
1049
|
+
if (!this.options.dnsNsDomains || !this.options.dnsScopes) {
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
logger.log('info', 'Validating DNS configuration...');
|
|
1053
|
+
// Check if email domains with internal-dns are in dnsScopes
|
|
1054
|
+
if (this.options.emailConfig?.domains) {
|
|
1055
|
+
for (const domainConfig of this.options.emailConfig.domains) {
|
|
1056
|
+
if (domainConfig.dnsMode === 'internal-dns' &&
|
|
1057
|
+
!this.options.dnsScopes.includes(domainConfig.domain)) {
|
|
1058
|
+
logger.log('warn', `Email domain '${domainConfig.domain}' with internal-dns mode is not in dnsScopes. It should be added to dnsScopes.`);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
// Validate user-provided DNS records are within scopes
|
|
1063
|
+
if (this.options.dnsRecords) {
|
|
1064
|
+
for (const record of this.options.dnsRecords) {
|
|
1065
|
+
const recordDomain = this.extractDomain(record.name);
|
|
1066
|
+
const isInScope = this.options.dnsScopes.some(scope => recordDomain === scope || recordDomain.endsWith(`.${scope}`));
|
|
1067
|
+
if (!isInScope) {
|
|
1068
|
+
logger.log('warn', `DNS record for '${record.name}' is outside defined scopes [${this.options.dnsScopes.join(', ')}]`);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* Generate email DNS records for domains with internal-dns mode
|
|
1075
|
+
*/
|
|
1076
|
+
async generateEmailDnsRecords() {
|
|
1077
|
+
const records = [];
|
|
1078
|
+
if (!this.options.emailConfig?.domains) {
|
|
1079
|
+
return records;
|
|
1080
|
+
}
|
|
1081
|
+
// Filter domains with internal-dns mode
|
|
1082
|
+
const internalDnsDomains = this.options.emailConfig.domains.filter(domain => domain.dnsMode === 'internal-dns');
|
|
1083
|
+
for (const domainConfig of internalDnsDomains) {
|
|
1084
|
+
const domain = domainConfig.domain;
|
|
1085
|
+
const ttl = domainConfig.dns?.internal?.ttl || 3600;
|
|
1086
|
+
const mxPriority = domainConfig.dns?.internal?.mxPriority || 10;
|
|
1087
|
+
// MX record - points to the domain itself for email handling
|
|
1088
|
+
records.push({
|
|
1089
|
+
name: domain,
|
|
1090
|
+
type: 'MX',
|
|
1091
|
+
value: `${mxPriority} ${domain}`,
|
|
1092
|
+
ttl
|
|
1093
|
+
});
|
|
1094
|
+
// SPF record - using sensible defaults
|
|
1095
|
+
const spfRecord = 'v=spf1 a mx ~all';
|
|
1096
|
+
records.push({
|
|
1097
|
+
name: domain,
|
|
1098
|
+
type: 'TXT',
|
|
1099
|
+
value: spfRecord,
|
|
1100
|
+
ttl
|
|
1101
|
+
});
|
|
1102
|
+
// DMARC record - using sensible defaults
|
|
1103
|
+
const dmarcPolicy = 'none'; // Start with 'none' policy for monitoring
|
|
1104
|
+
const dmarcEmail = `dmarc@${domain}`;
|
|
1105
|
+
records.push({
|
|
1106
|
+
name: `_dmarc.${domain}`,
|
|
1107
|
+
type: 'TXT',
|
|
1108
|
+
value: `v=DMARC1; p=${dmarcPolicy}; rua=mailto:${dmarcEmail}`,
|
|
1109
|
+
ttl
|
|
1110
|
+
});
|
|
1111
|
+
// Note: DKIM records will be generated later when DKIM keys are available
|
|
1112
|
+
// They require the DKIMCreator which is part of the email server
|
|
1113
|
+
}
|
|
1114
|
+
logger.log('info', `Generated ${records.length} email DNS records for ${internalDnsDomains.length} internal-dns domains`);
|
|
1115
|
+
return records;
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Load DKIM records from JSON files
|
|
1119
|
+
* Reads all *.dkimrecord.json files from the DNS records directory
|
|
1120
|
+
*/
|
|
1121
|
+
async loadDkimRecords() {
|
|
1122
|
+
const records = [];
|
|
1123
|
+
try {
|
|
1124
|
+
// Ensure paths are imported
|
|
1125
|
+
const dnsDir = this.resolvedPaths.dnsRecordsDir;
|
|
1126
|
+
// Check if directory exists
|
|
1127
|
+
if (!plugins.fs.existsSync(dnsDir)) {
|
|
1128
|
+
logger.log('debug', 'No DNS records directory found, skipping DKIM record loading');
|
|
1129
|
+
return records;
|
|
1130
|
+
}
|
|
1131
|
+
// Read all files in the directory
|
|
1132
|
+
const files = plugins.fs.readdirSync(dnsDir);
|
|
1133
|
+
const dkimFiles = files.filter(f => f.endsWith('.dkimrecord.json'));
|
|
1134
|
+
logger.log('info', `Found ${dkimFiles.length} DKIM record files`);
|
|
1135
|
+
// Load each DKIM record
|
|
1136
|
+
for (const file of dkimFiles) {
|
|
1137
|
+
try {
|
|
1138
|
+
const filePath = plugins.path.join(dnsDir, file);
|
|
1139
|
+
const fileContent = plugins.fs.readFileSync(filePath, 'utf8');
|
|
1140
|
+
const dkimRecord = JSON.parse(fileContent);
|
|
1141
|
+
// Validate record structure
|
|
1142
|
+
if (dkimRecord.name && dkimRecord.type === 'TXT' && dkimRecord.value) {
|
|
1143
|
+
records.push({
|
|
1144
|
+
name: dkimRecord.name,
|
|
1145
|
+
type: 'TXT',
|
|
1146
|
+
value: dkimRecord.value,
|
|
1147
|
+
ttl: 3600 // Standard DKIM TTL
|
|
1148
|
+
});
|
|
1149
|
+
logger.log('info', `Loaded DKIM record for ${dkimRecord.name}`);
|
|
1150
|
+
}
|
|
1151
|
+
else {
|
|
1152
|
+
logger.log('warn', `Invalid DKIM record structure in ${file}`);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
catch (error) {
|
|
1156
|
+
logger.log('error', `Failed to load DKIM record from ${file}: ${error.message}`);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
catch (error) {
|
|
1161
|
+
logger.log('error', `Failed to load DKIM records: ${error.message}`);
|
|
1162
|
+
}
|
|
1163
|
+
return records;
|
|
1164
|
+
}
|
|
1165
|
+
/**
|
|
1166
|
+
* Initialize DKIM keys for all configured email domains
|
|
1167
|
+
* This ensures DKIM records are available immediately at startup
|
|
1168
|
+
*/
|
|
1169
|
+
async initializeDkimForEmailDomains() {
|
|
1170
|
+
if (!this.options.emailConfig?.domains || !this.emailServer) {
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
logger.log('info', 'Initializing DKIM keys for email domains...');
|
|
1174
|
+
// Get DKIMCreator instance from email server (public in smartmta)
|
|
1175
|
+
const dkimCreator = this.emailServer.dkimCreator;
|
|
1176
|
+
if (!dkimCreator) {
|
|
1177
|
+
logger.log('warn', 'DKIMCreator not available, skipping DKIM initialization');
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
// Ensure necessary directories exist
|
|
1181
|
+
paths.ensureDataDirectories(this.resolvedPaths);
|
|
1182
|
+
// Generate DKIM keys for each email domain
|
|
1183
|
+
for (const domainConfig of this.options.emailConfig.domains) {
|
|
1184
|
+
try {
|
|
1185
|
+
// Generate DKIM keys for all domains, regardless of DNS mode
|
|
1186
|
+
// This ensures keys are ready even if DNS mode changes later
|
|
1187
|
+
await dkimCreator.handleDKIMKeysForDomain(domainConfig.domain);
|
|
1188
|
+
logger.log('info', `DKIM keys initialized for ${domainConfig.domain}`);
|
|
1189
|
+
}
|
|
1190
|
+
catch (error) {
|
|
1191
|
+
logger.log('error', `Failed to initialize DKIM for ${domainConfig.domain}: ${error.message}`);
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
logger.log('info', 'DKIM initialization complete');
|
|
1195
|
+
}
|
|
1196
|
+
/**
|
|
1197
|
+
* Generate authoritative DNS records (NS only) for all domains in dnsScopes
|
|
1198
|
+
* SOA records are now automatically generated by smartdns with primaryNameserver setting
|
|
1199
|
+
*/
|
|
1200
|
+
async generateAuthoritativeRecords() {
|
|
1201
|
+
const records = [];
|
|
1202
|
+
if (!this.options.dnsNsDomains || !this.options.dnsScopes) {
|
|
1203
|
+
return records;
|
|
1204
|
+
}
|
|
1205
|
+
// Determine the public IP for nameserver A records
|
|
1206
|
+
let publicIp = null;
|
|
1207
|
+
// Use proxy IPs if configured (these should be public IPs)
|
|
1208
|
+
if (this.options.proxyIps && this.options.proxyIps.length > 0) {
|
|
1209
|
+
publicIp = this.options.proxyIps[0]; // Use first proxy IP
|
|
1210
|
+
logger.log('info', `Using proxy IP for nameserver A records: ${publicIp}`);
|
|
1211
|
+
}
|
|
1212
|
+
else if (this.options.publicIp) {
|
|
1213
|
+
// Use explicitly configured public IP
|
|
1214
|
+
publicIp = this.options.publicIp;
|
|
1215
|
+
this.detectedPublicIp = publicIp;
|
|
1216
|
+
logger.log('info', `Using configured public IP for nameserver A records: ${publicIp}`);
|
|
1217
|
+
}
|
|
1218
|
+
else {
|
|
1219
|
+
// Auto-discover public IP using smartnetwork
|
|
1220
|
+
try {
|
|
1221
|
+
logger.log('info', 'Auto-discovering public IP address...');
|
|
1222
|
+
const smartNetwork = new plugins.smartnetwork.SmartNetwork();
|
|
1223
|
+
const publicIps = await smartNetwork.getPublicIps();
|
|
1224
|
+
if (publicIps.v4) {
|
|
1225
|
+
publicIp = publicIps.v4;
|
|
1226
|
+
this.detectedPublicIp = publicIp;
|
|
1227
|
+
logger.log('info', `Auto-discovered public IPv4: ${publicIp}`);
|
|
1228
|
+
}
|
|
1229
|
+
else {
|
|
1230
|
+
logger.log('warn', 'Could not auto-discover public IPv4 address');
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
catch (error) {
|
|
1234
|
+
logger.log('error', `Failed to auto-discover public IP: ${error.message}`);
|
|
1235
|
+
}
|
|
1236
|
+
if (!publicIp) {
|
|
1237
|
+
logger.log('warn', 'No public IP available. Nameserver A records require either proxyIps, publicIp, or successful auto-discovery.');
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
// Generate A records for nameservers if we have a public IP
|
|
1241
|
+
if (publicIp) {
|
|
1242
|
+
for (const nsDomain of this.options.dnsNsDomains) {
|
|
1243
|
+
records.push({
|
|
1244
|
+
name: nsDomain,
|
|
1245
|
+
type: 'A',
|
|
1246
|
+
value: publicIp,
|
|
1247
|
+
ttl: 3600
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
logger.log('info', `Generated A records for ${this.options.dnsNsDomains.length} nameservers`);
|
|
1251
|
+
}
|
|
1252
|
+
// Generate NS records for each domain in scopes
|
|
1253
|
+
for (const domain of this.options.dnsScopes) {
|
|
1254
|
+
// Add NS records for all nameservers
|
|
1255
|
+
for (const nsDomain of this.options.dnsNsDomains) {
|
|
1256
|
+
records.push({
|
|
1257
|
+
name: domain,
|
|
1258
|
+
type: 'NS',
|
|
1259
|
+
value: nsDomain,
|
|
1260
|
+
ttl: 3600
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
// SOA records are now automatically generated by smartdns DnsServer
|
|
1264
|
+
// with the primaryNameserver configuration option
|
|
1265
|
+
}
|
|
1266
|
+
logger.log('info', `Generated ${records.length} total records (A + NS) for ${this.options.dnsScopes.length} domains`);
|
|
1267
|
+
return records;
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* Extract the base domain from a DNS record name
|
|
1271
|
+
*/
|
|
1272
|
+
extractDomain(recordName) {
|
|
1273
|
+
// Handle wildcards
|
|
1274
|
+
if (recordName.startsWith('*.')) {
|
|
1275
|
+
recordName = recordName.substring(2);
|
|
1276
|
+
}
|
|
1277
|
+
return recordName;
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* Apply proxy IP replacement logic to DNS records
|
|
1281
|
+
*/
|
|
1282
|
+
async applyProxyIpReplacement(records) {
|
|
1283
|
+
if (!this.options.proxyIps || this.options.proxyIps.length === 0) {
|
|
1284
|
+
return; // No proxy IPs configured, skip replacement
|
|
1285
|
+
}
|
|
1286
|
+
// Get server's public IP
|
|
1287
|
+
const serverIp = await this.detectServerPublicIp();
|
|
1288
|
+
if (!serverIp) {
|
|
1289
|
+
logger.log('warn', 'Could not detect server public IP, skipping proxy IP replacement');
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
logger.log('info', `Applying proxy IP replacement. Server IP: ${serverIp}, Proxy IPs: ${this.options.proxyIps.join(', ')}`);
|
|
1293
|
+
let proxyIndex = 0;
|
|
1294
|
+
for (const record of records) {
|
|
1295
|
+
if (record.type === 'A' &&
|
|
1296
|
+
record.value === serverIp &&
|
|
1297
|
+
record.useIngressProxy !== false) {
|
|
1298
|
+
// Round-robin through proxy IPs
|
|
1299
|
+
const proxyIp = this.options.proxyIps[proxyIndex % this.options.proxyIps.length];
|
|
1300
|
+
logger.log('info', `Replacing A record for ${record.name}: ${record.value} → ${proxyIp}`);
|
|
1301
|
+
record.value = proxyIp;
|
|
1302
|
+
proxyIndex++;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
/**
|
|
1307
|
+
* Detect the server's public IP address
|
|
1308
|
+
*/
|
|
1309
|
+
async detectServerPublicIp() {
|
|
1310
|
+
try {
|
|
1311
|
+
const smartNetwork = new plugins.smartnetwork.SmartNetwork();
|
|
1312
|
+
const publicIps = await smartNetwork.getPublicIps();
|
|
1313
|
+
if (publicIps.v4) {
|
|
1314
|
+
return publicIps.v4;
|
|
1315
|
+
}
|
|
1316
|
+
return null;
|
|
1317
|
+
}
|
|
1318
|
+
catch (error) {
|
|
1319
|
+
logger.log('warn', `Failed to detect public IP: ${error.message}`);
|
|
1320
|
+
return null;
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
/**
|
|
1324
|
+
* Set up Remote Ingress hub for edge tunnel connections
|
|
1325
|
+
*/
|
|
1326
|
+
async setupRemoteIngress() {
|
|
1327
|
+
if (!this.options.remoteIngressConfig?.enabled) {
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
logger.log('info', 'Setting up Remote Ingress hub...');
|
|
1331
|
+
// Initialize the edge registration manager
|
|
1332
|
+
this.remoteIngressManager = new RemoteIngressManager(this.storageManager);
|
|
1333
|
+
await this.remoteIngressManager.initialize();
|
|
1334
|
+
// Pass current routes so the manager can derive edge ports from remoteIngress-tagged routes
|
|
1335
|
+
const currentRoutes = this.options.smartProxyConfig?.routes || [];
|
|
1336
|
+
this.remoteIngressManager.setRoutes(currentRoutes);
|
|
1337
|
+
// Resolve TLS certs for tunnel: explicit paths > ACME for hubDomain > self-signed (Rust default)
|
|
1338
|
+
const riCfg = this.options.remoteIngressConfig;
|
|
1339
|
+
let tlsConfig;
|
|
1340
|
+
// Priority 1: Explicit cert/key file paths
|
|
1341
|
+
if (riCfg.tls?.certPath && riCfg.tls?.keyPath) {
|
|
1342
|
+
try {
|
|
1343
|
+
const certPem = plugins.fs.readFileSync(riCfg.tls.certPath, 'utf8');
|
|
1344
|
+
const keyPem = plugins.fs.readFileSync(riCfg.tls.keyPath, 'utf8');
|
|
1345
|
+
tlsConfig = { certPem, keyPem };
|
|
1346
|
+
logger.log('info', 'Using explicit TLS cert/key for RemoteIngress tunnel');
|
|
1347
|
+
}
|
|
1348
|
+
catch (err) {
|
|
1349
|
+
logger.log('warn', `Failed to read RemoteIngress TLS cert/key files: ${err.message}`);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
// Priority 2: Existing cert from SmartProxy cert store for hubDomain
|
|
1353
|
+
if (!tlsConfig && riCfg.hubDomain) {
|
|
1354
|
+
try {
|
|
1355
|
+
const stored = await this.storageManager.getJSON(`/proxy-certs/${riCfg.hubDomain}`);
|
|
1356
|
+
if (stored?.publicKey && stored?.privateKey) {
|
|
1357
|
+
tlsConfig = { certPem: stored.publicKey, keyPem: stored.privateKey };
|
|
1358
|
+
logger.log('info', `Using stored ACME cert for RemoteIngress tunnel TLS: ${riCfg.hubDomain}`);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
catch { /* no stored cert, fall through */ }
|
|
1362
|
+
}
|
|
1363
|
+
if (!tlsConfig) {
|
|
1364
|
+
logger.log('info', 'No TLS cert configured for RemoteIngress tunnel — using auto-generated self-signed');
|
|
1365
|
+
}
|
|
1366
|
+
// Create and start the tunnel manager
|
|
1367
|
+
this.tunnelManager = new TunnelManager(this.remoteIngressManager, {
|
|
1368
|
+
tunnelPort: riCfg.tunnelPort ?? 8443,
|
|
1369
|
+
targetHost: '127.0.0.1',
|
|
1370
|
+
tls: tlsConfig,
|
|
1371
|
+
});
|
|
1372
|
+
await this.tunnelManager.start();
|
|
1373
|
+
const edgeCount = this.remoteIngressManager.getAllEdges().length;
|
|
1374
|
+
logger.log('info', `Remote Ingress hub started on port ${this.options.remoteIngressConfig.tunnelPort || 8443} with ${edgeCount} registered edge(s)`);
|
|
1375
|
+
}
|
|
1376
|
+
/**
|
|
1377
|
+
* Set up RADIUS server for network authentication
|
|
1378
|
+
*/
|
|
1379
|
+
async setupRadiusServer() {
|
|
1380
|
+
if (!this.options.radiusConfig) {
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
logger.log('info', 'Setting up RADIUS server...');
|
|
1384
|
+
this.radiusServer = new RadiusServer(this.options.radiusConfig, this.storageManager);
|
|
1385
|
+
await this.radiusServer.start();
|
|
1386
|
+
logger.log('info', `RADIUS server started on ports ${this.options.radiusConfig.authPort || 1812} (auth) and ${this.options.radiusConfig.acctPort || 1813} (acct)`);
|
|
1387
|
+
}
|
|
1388
|
+
/**
|
|
1389
|
+
* Update RADIUS configuration at runtime
|
|
1390
|
+
*/
|
|
1391
|
+
async updateRadiusConfig(config) {
|
|
1392
|
+
// Stop existing RADIUS server if running
|
|
1393
|
+
if (this.radiusServer) {
|
|
1394
|
+
await this.radiusServer.stop();
|
|
1395
|
+
this.radiusServer = undefined;
|
|
1396
|
+
}
|
|
1397
|
+
// Update configuration
|
|
1398
|
+
this.options.radiusConfig = config;
|
|
1399
|
+
// Start with new configuration
|
|
1400
|
+
await this.setupRadiusServer();
|
|
1401
|
+
logger.log('info', 'RADIUS configuration updated');
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
export default DcRouter;
|
|
1405
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5kY3JvdXRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL2NsYXNzZXMuZGNyb3V0ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFDeEMsT0FBTyxLQUFLLEtBQUssTUFBTSxZQUFZLENBQUM7QUFFcEMsc0RBQXNEO0FBRXRELDhEQUE4RDtBQUM5RCxPQUFPLEVBQ0wsa0JBQWtCLEdBSW5CLE1BQU0sc0JBQXNCLENBQUM7QUFDOUIsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUNyQyx5QkFBeUI7QUFDekIsT0FBTyxFQUFFLGNBQWMsRUFBdUIsTUFBTSxvQkFBb0IsQ0FBQztBQUN6RSxPQUFPLEVBQUUsd0JBQXdCLEVBQUUsTUFBTSxtQ0FBbUMsQ0FBQztBQUM3RSxPQUFPLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSx1Q0FBdUMsQ0FBQztBQUMvRSxzQkFBc0I7QUFDdEIsT0FBTyxFQUFFLE9BQU8sRUFBRSxZQUFZLEVBQXdCLE1BQU0sa0JBQWtCLENBQUM7QUFFL0UsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBQ2pELE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUN2RCxPQUFPLEVBQUUsWUFBWSxFQUE0QixNQUFNLG1CQUFtQixDQUFDO0FBQzNFLE9BQU8sRUFBRSxvQkFBb0IsRUFBRSxhQUFhLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUMvRSxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsZUFBZSxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDeEUsT0FBTyxFQUFFLGNBQWMsRUFBRSxjQUFjLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQXlLMUYsTUFBTSxPQUFPLFFBQVE7SUFDWixPQUFPLENBQW1CO0lBQzFCLGFBQWEsQ0FBd0M7SUFFNUQsZ0JBQWdCO0lBQ1Qs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
|