import * as plugins from '../plugins.js'; import * as paths from '../paths.js'; import { SmtpPortConfig, type ISmtpPortSettings } from './classes.smtp.portconfig.js'; import { EmailDomainRouter, type IEmailDomainRoutingConfig } from './classes.email.domainrouter.js'; import { type IMtaConfig, MtaService } from '../mta/classes.mta.js'; // Import SMTP store-and-forward components import { SmtpServer } from './classes.smtp.server.js'; import { EmailProcessor, type IProcessingResult } from './classes.email.processor.js'; import { DeliveryQueue } from './classes.delivery.queue.js'; import { DeliverySystem } from './classes.delivery.system.js'; // Certificate types are available via plugins.tsclass /** * Configuration for SMTP forwarding functionality */ export interface ISmtpForwardingConfig { /** Whether SMTP forwarding is enabled */ enabled?: boolean; /** SMTP ports to listen on */ ports?: number[]; /** Default SMTP server hostname */ defaultServer: string; /** Default SMTP server port */ defaultPort?: number; /** Whether to use TLS when connecting to the default server */ useTls?: boolean; /** Preserve source IP address when forwarding */ preserveSourceIp?: boolean; /** Domain-specific routing rules */ domainRoutes?: Array<{ domain: string; server: string; port?: number; }>; } import type { ISmtpConfig } from './classes.smtp.config.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; /** * SMTP store-and-forward configuration * This enables advanced email processing capabilities (complementary to smartProxyConfig) */ smtpConfig?: ISmtpConfig; /** * Legacy SMTP forwarding configuration * If smtpConfig is provided, this will be ignored */ smtpForwarding?: ISmtpForwardingConfig; /** MTA service configuration (if not using SMTP forwarding) */ mtaConfig?: IMtaConfig; /** Existing MTA service instance to use (if not using SMTP forwarding) */ mtaServiceInstance?: MtaService; /** 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; configs: plugins.smartproxy.IPortProxySettings['domainConfigs']; } export class DcRouter { public options: IDcRouterOptions; // Core services public smartProxy?: plugins.smartproxy.SmartProxy; public mta?: MtaService; public dnsServer?: plugins.smartdns.DnsServer; // SMTP store-and-forward components public smtpServer?: SmtpServer; public emailProcessor?: EmailProcessor; public deliveryQueue?: DeliveryQueue; public deliverySystem?: DeliverySystem; // 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 general TCP/SNI traffic if (this.options.smartProxyConfig) { await this.setupSmartProxy(); } // 2. Set up SMTP handling if (this.options.smtpConfig) { // Set up store-and-forward SMTP processing await this.setupSmtpProcessing(); } else if (this.options.smtpForwarding?.enabled) { // Fallback to simple SMTP forwarding for backward compatibility await this.setupSmtpForwarding(); } else { // Set up MTA service if no SMTP handling is configured await this.setupMtaService(); } // 3. 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 */ private async setupSmartProxy(): Promise { if (!this.options.smartProxyConfig) { return; } console.log('Setting up SmartProxy with direct configuration'); // Create SmartProxy instance with full configuration this.smartProxy = new plugins.smartproxy.SmartProxy(this.options.smartProxyConfig); // Set up event listeners this.smartProxy.on('error', (err) => { console.error('SmartProxy error:', err); }); if (this.options.smartProxyConfig.acme) { 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 successfully'); } /** * Set up the MTA service */ private async setupMtaService() { // Use existing MTA service if provided if (this.options.mtaServiceInstance) { this.mta = this.options.mtaServiceInstance; console.log('Using provided MTA service instance'); } else if (this.options.mtaConfig) { // Create new MTA service with the provided configuration this.mta = new MtaService(undefined, this.options.mtaConfig); console.log('Created new MTA service instance'); // Start the MTA service await this.mta.start(); console.log('MTA service started'); } } /** * Set up SMTP forwarding with SmartProxy */ private async setupSmtpForwarding() { if (!this.options.smtpForwarding) { return; } const forwarding = this.options.smtpForwarding; console.log('Setting up SMTP forwarding'); // Determine which ports to listen on const smtpPorts = forwarding.ports || [25, 587, 465]; // Create SmartProxy instance for SMTP forwarding const smtpProxyConfig: plugins.smartproxy.ISmartProxyOptions = { // Listen on the first SMTP port fromPort: smtpPorts[0], // Forward to the default server toPort: forwarding.defaultPort || 25, targetIP: forwarding.defaultServer, // Enable SNI if port 465 is included (implicit TLS) sniEnabled: smtpPorts.includes(465), // Preserve source IP if requested preserveSourceIP: forwarding.preserveSourceIp || false, // Create domain configs for SMTP routing domainConfigs: forwarding.domainRoutes?.map(route => ({ domains: [route.domain], allowedIPs: ['0.0.0.0/0'], // Allow from anywhere by default targetIPs: [route.server] })) || [], // Include all SMTP ports in the global port ranges globalPortRanges: smtpPorts.map(port => ({ from: port, to: port })) }; // Create a separate SmartProxy instance for SMTP const smtpProxy = new plugins.smartproxy.SmartProxy(smtpProxyConfig); // Start the SMTP proxy await smtpProxy.start(); // Store the SMTP proxy reference this.smartProxy = smtpProxy; console.log(`SMTP forwarding configured on ports ${smtpPorts.join(', ')}`); } /** * 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 SMTP components this.stopSmtpComponents().catch(err => console.error('Error stopping SMTP components:', err)), // Stop HTTP SmartProxy if running this.smartProxy ? this.smartProxy.stop().catch(err => console.error('Error stopping SmartProxy:', err)) : Promise.resolve(), // Stop MTA service if it's our own (not an external instance) (this.mta && !this.options.mtaServiceInstance) ? this.mta.stop().catch(err => console.error('Error stopping MTA service:', 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 await this.setupSmartProxy(); console.log('SmartProxy configuration updated'); } /** * Set up SMTP store-and-forward processing */ private async setupSmtpProcessing(): Promise { if (!this.options.smtpConfig) { return; } console.log('Setting up SMTP store-and-forward processing'); try { // 1. Create SMTP server this.smtpServer = new SmtpServer(this.options.smtpConfig); // 2. Create email processor this.emailProcessor = new EmailProcessor(this.options.smtpConfig); // 3. Create delivery queue this.deliveryQueue = new DeliveryQueue(this.options.smtpConfig.queue || {}); await this.deliveryQueue.initialize(); // 4. Create delivery system this.deliverySystem = new DeliverySystem(this.deliveryQueue); // 5. Connect components // When a message is received by the SMTP server, process it this.smtpServer.on('message', async ({ session, mail, rawData }) => { try { // Process the message const processingResult = await this.emailProcessor.processEmail(mail, rawData, session); // If action is queue, add to delivery queue if (processingResult.action === 'queue') { await this.deliveryQueue.enqueue(processingResult); } } catch (error) { console.error('Error processing message:', error); } }); // 6. Start components await this.smtpServer.start(); await this.deliverySystem.start(); console.log(`SMTP processing started on ports ${this.options.smtpConfig.ports.join(', ')}`); } catch (error) { console.error('Error setting up SMTP processing:', error); // Clean up any components that were started if (this.deliverySystem) { await this.deliverySystem.stop().catch(e => console.error('Error stopping delivery system:', e)); } if (this.deliveryQueue) { await this.deliveryQueue.shutdown().catch(e => console.error('Error shutting down delivery queue:', e)); } if (this.smtpServer) { await this.smtpServer.stop().catch(e => console.error('Error stopping SMTP server:', e)); } throw error; } } /** * Update SMTP forwarding configuration * @param config New SMTP forwarding configuration */ public async updateSmtpForwarding(config: ISmtpForwardingConfig): Promise { // Stop existing SMTP components await this.stopSmtpComponents(); // Update configuration this.options.smtpForwarding = config; this.options.smtpConfig = undefined; // Clear any store-and-forward config // Restart SMTP forwarding if enabled if (config.enabled) { await this.setupSmtpForwarding(); } console.log('SMTP forwarding configuration updated'); } /** * Update SMTP processing configuration * @param config New SMTP config */ public async updateSmtpConfig(config: ISmtpConfig): Promise { // Stop existing SMTP components await this.stopSmtpComponents(); // Update configuration this.options.smtpConfig = config; this.options.smtpForwarding = undefined; // Clear any forwarding config // Start SMTP processing await this.setupSmtpProcessing(); console.log('SMTP processing configuration updated'); } /** * Stop all SMTP components */ private async stopSmtpComponents(): Promise { // Stop delivery system if (this.deliverySystem) { await this.deliverySystem.stop().catch(e => console.error('Error stopping delivery system:', e)); this.deliverySystem = undefined; } // Stop delivery queue if (this.deliveryQueue) { await this.deliveryQueue.shutdown().catch(e => console.error('Error shutting down delivery queue:', e)); this.deliveryQueue = undefined; } // Stop SMTP server if (this.smtpServer) { await this.smtpServer.stop().catch(e => console.error('Error stopping SMTP server:', e)); this.smtpServer = undefined; } // For backward compatibility: legacy SMTP proxy implementation // This is no longer used with the new implementation } } export default DcRouter;