import * as plugins from './plugins.js'; import * as paths from './paths.js'; // Certificate types are available via plugins.tsclass // Import the consolidated email config import type { IEmailConfig, IDomainRule } from './mail/routing/classes.email.config.js'; import { DomainRouter } from './mail/routing/classes.domain.router.js'; import { UnifiedEmailServer } from './mail/routing/classes.unified.email.server.js'; import { UnifiedDeliveryQueue, type IQueueOptions } from './mail/delivery/classes.delivery.queue.js'; import { MultiModeDeliverySystem, type IMultiModeDeliveryOptions } from './mail/delivery/classes.delivery.system.js'; import { UnifiedRateLimiter, type IHierarchicalRateLimits } from './mail/delivery/classes.unified.rate.limiter.js'; import { logger } from './logger.js'; export interface IDcRouterOptions { /** * Direct SmartProxy configuration - gives full control over HTTP/HTTPS and TCP/SNI traffic * This is the preferred way to configure HTTP/HTTPS and general TCP/SNI traffic */ smartProxyConfig?: plugins.smartproxy.ISmartProxyOptions; /** * Consolidated email configuration * This enables all email handling with pattern-based routing */ emailConfig?: IEmailConfig; /** TLS/certificate configuration */ tls?: { /** Contact email for ACME certificates */ contactEmail: string; /** Domain for main certificate */ domain?: string; /** Path to certificate file (if not using auto-provisioning) */ certPath?: string; /** Path to key file (if not using auto-provisioning) */ keyPath?: string; }; /** DNS server configuration */ dnsServerConfig?: plugins.smartdns.IDnsServerOptions; } /** * DcRouter can be run on ingress and egress to and from a datacenter site. */ /** * Context passed to HTTP routing rules */ /** * Context passed to port proxy (SmartProxy) routing rules */ export interface PortProxyRuleContext { proxy: plugins.smartproxy.SmartProxy; routes: plugins.smartproxy.IRouteConfig[]; } export class DcRouter { public options: IDcRouterOptions; // Core services public smartProxy?: plugins.smartproxy.SmartProxy; public dnsServer?: plugins.smartdns.DnsServer; // Unified email components public domainRouter?: DomainRouter; public unifiedEmailServer?: UnifiedEmailServer; public deliveryQueue?: UnifiedDeliveryQueue; public deliverySystem?: MultiModeDeliverySystem; public rateLimiter?: UnifiedRateLimiter; // Environment access private qenv = new plugins.qenv.Qenv('./', '.nogit/'); constructor(optionsArg: IDcRouterOptions) { // Set defaults in options this.options = { ...optionsArg }; } public async start() { console.log('Starting DcRouter services...'); try { // Set up SmartProxy for HTTP/HTTPS and all traffic including email routes await this.setupSmartProxy(); // Set up unified email handling if configured if (this.options.emailConfig) { await this.setupUnifiedEmailHandling(); } // Set up DNS server if configured if (this.options.dnsServerConfig) { this.dnsServer = new plugins.smartdns.DnsServer(this.options.dnsServerConfig); await this.dnsServer.start(); console.log('DNS server started'); } console.log('DcRouter started successfully'); } catch (error) { console.error('Error starting DcRouter:', error); // Try to clean up any services that may have started await this.stop(); throw error; } } /** * Set up SmartProxy with direct configuration and automatic email routes */ private async setupSmartProxy(): Promise { let routes: plugins.smartproxy.IRouteConfig[] = []; let acmeConfig: plugins.smartproxy.IAcmeOptions | undefined; // If user provides full SmartProxy config, use it directly if (this.options.smartProxyConfig) { routes = this.options.smartProxyConfig.routes || []; acmeConfig = this.options.smartProxyConfig.acme; } // If email config exists, automatically add email routes if (this.options.emailConfig) { const emailRoutes = this.generateEmailRoutes(this.options.emailConfig); routes = [...routes, ...emailRoutes]; } // Merge TLS/ACME configuration if provided at root level if (this.options.tls && !acmeConfig) { acmeConfig = { accountEmail: this.options.tls.contactEmail, enabled: true, useProduction: true, autoRenew: true, renewThresholdDays: 30 }; } // If we have routes or need a basic SmartProxy instance, create it if (routes.length > 0 || this.options.smartProxyConfig) { console.log('Setting up SmartProxy with combined configuration'); // Create SmartProxy configuration const smartProxyConfig: plugins.smartproxy.ISmartProxyOptions = { ...this.options.smartProxyConfig, routes, acme: acmeConfig }; // Create SmartProxy instance this.smartProxy = new plugins.smartproxy.SmartProxy(smartProxyConfig); // Set up event listeners this.smartProxy.on('error', (err) => { console.error('SmartProxy error:', err); }); if (acmeConfig) { this.smartProxy.on('certificate-issued', (event) => { console.log(`Certificate issued for ${event.domain}, expires ${event.expiryDate}`); }); this.smartProxy.on('certificate-renewed', (event) => { console.log(`Certificate renewed for ${event.domain}, expires ${event.expiryDate}`); }); } // Start SmartProxy await this.smartProxy.start(); console.log(`SmartProxy started with ${routes.length} routes`); } } /** * Generate SmartProxy routes for email configuration */ private generateEmailRoutes(emailConfig: IEmailConfig): plugins.smartproxy.IRouteConfig[] { const emailRoutes: plugins.smartproxy.IRouteConfig[] = []; // Create routes for each email port for (const port of emailConfig.ports) { // Handle different email ports differently switch (port) { case 25: // SMTP emailRoutes.push({ name: 'smtp-route', match: { ports: [25] }, action: { type: 'forward', target: { host: 'localhost', // Forward to internal email server port: 10025 // Internal email server port }, // No TLS termination for port 25 (STARTTLS handled by email server) tls: { mode: 'passthrough' } } }); break; case 587: // Submission emailRoutes.push({ name: 'submission-route', match: { ports: [587] }, action: { type: 'forward', target: { host: 'localhost', port: 10587 }, tls: { mode: 'passthrough' // STARTTLS handled by email server } } }); break; case 465: // SMTPS emailRoutes.push({ name: 'smtps-route', match: { ports: [465] }, action: { type: 'forward', target: { host: 'localhost', port: 10465 }, tls: { mode: 'terminate', // Terminate TLS and re-encrypt to email server certificate: 'auto' } } }); break; } } // Add domain-specific email routes if configured if (emailConfig.domainRules) { for (const rule of emailConfig.domainRules) { // Extract domain from pattern (e.g., "*@example.com" -> "example.com") const domain = rule.pattern.split('@')[1]; if (domain && rule.mode === 'forward' && rule.target) { emailRoutes.push({ name: `email-forward-${domain}`, match: { ports: emailConfig.ports, domains: [domain] }, action: { type: 'forward', target: { host: rule.target.server, port: rule.target.port || 25 }, tls: { mode: rule.target.useTls ? 'terminate-and-reencrypt' : 'passthrough' } } }); } } } return emailRoutes; } /** * Check if a domain matches a pattern (including wildcard support) * @param domain The domain to check * @param pattern The pattern to match against (e.g., "*.example.com") * @returns Whether the domain matches the pattern */ private isDomainMatch(domain: string, pattern: string): boolean { // Normalize inputs domain = domain.toLowerCase(); pattern = pattern.toLowerCase(); // Check for exact match if (domain === pattern) { return true; } // Check for wildcard match (*.example.com) if (pattern.startsWith('*.')) { const patternSuffix = pattern.slice(2); // Remove the "*." prefix // Check if domain ends with the pattern suffix and has at least one character before it return domain.endsWith(patternSuffix) && domain.length > patternSuffix.length; } // No match return false; } public async stop() { console.log('Stopping DcRouter services...'); try { // Stop all services in parallel for faster shutdown await Promise.all([ // Stop unified email components if running this.domainRouter ? this.stopUnifiedEmailComponents().catch(err => console.error('Error stopping unified email components:', err)) : Promise.resolve(), // Stop HTTP SmartProxy if running this.smartProxy ? this.smartProxy.stop().catch(err => console.error('Error stopping SmartProxy:', err)) : Promise.resolve(), // Stop DNS server if running this.dnsServer ? this.dnsServer.stop().catch(err => console.error('Error stopping DNS server:', err)) : Promise.resolve() ]); console.log('All DcRouter services stopped'); } catch (error) { console.error('Error during DcRouter shutdown:', error); throw error; } } /** * Update SmartProxy configuration * @param config New SmartProxy configuration */ public async updateSmartProxyConfig(config: plugins.smartproxy.ISmartProxyOptions): Promise { // Stop existing SmartProxy if running if (this.smartProxy) { await this.smartProxy.stop(); this.smartProxy = undefined; } // Update configuration this.options.smartProxyConfig = config; // Start new SmartProxy with updated configuration (will include email routes if configured) await this.setupSmartProxy(); console.log('SmartProxy configuration updated'); } /** * Set up unified email handling with pattern-based routing * This implements the consolidated emailConfig approach */ private async setupUnifiedEmailHandling(): Promise { logger.log('info', 'Setting up unified email handling with pattern-based routing'); if (!this.options.emailConfig) { throw new Error('Email configuration is required for unified email handling'); } const emailConfig = this.options.emailConfig; // Map external ports to internal ports const portMapping = { 25: 10025, // SMTP 587: 10587, // Submission 465: 10465 // SMTPS }; // Create internal email server configuration const internalEmailConfig: IEmailConfig = { ...emailConfig, ports: emailConfig.ports.map(port => portMapping[port] || port + 10000), hostname: 'localhost' // Listen on localhost for SmartProxy forwarding }; try { // Create domain router for pattern matching this.domainRouter = new DomainRouter({ domainRules: emailConfig.domainRules, defaultMode: emailConfig.defaultMode, defaultServer: emailConfig.defaultServer, defaultPort: emailConfig.defaultPort, defaultTls: emailConfig.defaultTls }); // Initialize the rate limiter this.rateLimiter = new UnifiedRateLimiter({ global: { maxMessagesPerMinute: 100, maxRecipientsPerMessage: 100, maxConnectionsPerIP: 20, maxErrorsPerIP: 10, maxAuthFailuresPerIP: 5 } }); // Initialize the unified delivery queue const queueOptions: IQueueOptions = { storageType: emailConfig.queue?.storageType || 'memory', persistentPath: emailConfig.queue?.persistentPath, maxRetries: emailConfig.queue?.maxRetries, baseRetryDelay: emailConfig.queue?.baseRetryDelay, maxRetryDelay: emailConfig.queue?.maxRetryDelay }; this.deliveryQueue = new UnifiedDeliveryQueue(queueOptions); await this.deliveryQueue.initialize(); // Initialize the delivery system const deliveryOptions: IMultiModeDeliveryOptions = { globalRateLimit: 100, // Default to 100 emails per minute concurrentDeliveries: 10 }; this.deliverySystem = new MultiModeDeliverySystem(this.deliveryQueue, deliveryOptions); await this.deliverySystem.start(); // Initialize the unified email server with internal configuration this.unifiedEmailServer = new UnifiedEmailServer({ ports: internalEmailConfig.ports, hostname: internalEmailConfig.hostname, maxMessageSize: emailConfig.maxMessageSize, auth: emailConfig.auth, tls: emailConfig.tls, domainRules: emailConfig.domainRules, defaultMode: emailConfig.defaultMode, defaultServer: emailConfig.defaultServer, defaultPort: emailConfig.defaultPort, defaultTls: emailConfig.defaultTls }); // Set up event listeners this.unifiedEmailServer.on('error', (err) => { logger.log('error', `UnifiedEmailServer error: ${err.message}`); }); // Connect the unified email server with the delivery queue this.unifiedEmailServer.on('emailProcessed', (email, mode, rule) => { this.deliveryQueue!.enqueue(email, mode, rule).catch(err => { logger.log('error', `Failed to enqueue email: ${err.message}`); }); }); // Start the unified email server await this.unifiedEmailServer.start(); logger.log('info', `Unified email handling configured with ${emailConfig.domainRules.length} domain rules on internal ports`); logger.log('info', `Email server listening on ports: ${internalEmailConfig.ports.join(', ')}`); } catch (error) { logger.log('error', `Error setting up unified email handling: ${error.message}`); throw error; } } /** * Update the unified email configuration * @param config New email configuration */ public async updateEmailConfig(config: IEmailConfig): Promise { // Stop existing email components await this.stopUnifiedEmailComponents(); // Update configuration this.options.emailConfig = config; // Start email handling with new configuration await this.setupUnifiedEmailHandling(); console.log('Unified email configuration updated'); } /** * Stop all unified email components */ private async stopUnifiedEmailComponents(): Promise { try { // Stop all components in the correct order // 1. Stop the unified email server first if (this.unifiedEmailServer) { await this.unifiedEmailServer.stop(); logger.log('info', 'Unified email server stopped'); this.unifiedEmailServer = undefined; } // 2. Stop the delivery system if (this.deliverySystem) { await this.deliverySystem.stop(); logger.log('info', 'Delivery system stopped'); this.deliverySystem = undefined; } // 3. Stop the delivery queue if (this.deliveryQueue) { await this.deliveryQueue.shutdown(); logger.log('info', 'Delivery queue shut down'); this.deliveryQueue = undefined; } // 4. Stop the rate limiter if (this.rateLimiter) { this.rateLimiter.stop(); logger.log('info', 'Rate limiter stopped'); this.rateLimiter = undefined; } // 5. Clear the domain router this.domainRouter = undefined; logger.log('info', 'All unified email components stopped'); } catch (error) { logger.log('error', `Error stopping unified email components: ${error.message}`); throw error; } } /** * Update domain rules for email routing * @param rules New domain rules to apply */ public async updateDomainRules(rules: IDomainRule[]): Promise { // Validate that email config exists if (!this.options.emailConfig) { throw new Error('Email configuration is required before updating domain rules'); } // Update the configuration this.options.emailConfig.domainRules = rules; // Update the domain router if it exists if (this.domainRouter) { this.domainRouter.updateRules(rules); } // Update the unified email server if it exists if (this.unifiedEmailServer) { this.unifiedEmailServer.updateDomainRules(rules); } console.log(`Domain rules updated with ${rules.length} rules`); } /** * Get statistics from all components */ public getStats(): any { const stats: any = { unifiedEmailServer: this.unifiedEmailServer?.getStats(), deliveryQueue: this.deliveryQueue?.getStats(), deliverySystem: this.deliverySystem?.getStats(), rateLimiter: this.rateLimiter?.getStats() }; return stats; } } export default DcRouter;