feat(dcrouter): Implement integrated DcRouter with comprehensive SmartProxy configuration, enhanced SMTP processing, and robust store‐and‐forward email routing

This commit is contained in:
2025-05-07 23:45:19 +00:00
parent 04b7552b34
commit 5cf4c0f150
11 changed files with 2109 additions and 166 deletions

View File

@ -2,9 +2,14 @@ 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
/**
@ -31,27 +36,27 @@ export interface ISmtpForwardingConfig {
}>;
}
/**
* Simple domain-based routing configuration
*/
export interface IDomainRoutingConfig {
/** The domain name or pattern (e.g., example.com or *.example.com) */
domain: string;
/** Target server hostname or IP */
targetServer: string;
/** Target port */
targetPort: number;
/** Enable HTTPS/TLS for this route */
useTls?: boolean;
/** Allow incoming connections from these IP ranges (default: all) */
allowedIps?: string[];
}
import type { ISmtpConfig } from './classes.smtp.config.js';
export interface IDcRouterOptions {
/** HTTP/HTTPS domain-based routing */
httpDomainRoutes?: IDomainRoutingConfig[];
/**
* 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 forwarding configuration */
/**
* 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) */
@ -89,15 +94,21 @@ 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 smtpProxy?: 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/');
@ -112,13 +123,20 @@ export class DcRouter {
console.log('Starting DcRouter services...');
try {
// 1. Set up HTTP/HTTPS traffic handling with SmartProxy
await this.setupHttpProxy();
// Set up SmartProxy for HTTP/HTTPS and general TCP/SNI traffic
if (this.options.smartProxyConfig) {
await this.setupSmartProxy();
}
// 2. Set up MTA or SMTP forwarding
if (this.options.smtpForwarding?.enabled) {
// 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();
}
@ -139,60 +157,40 @@ export class DcRouter {
}
/**
* Set up SmartProxy for HTTP/HTTPS traffic
* Set up SmartProxy with direct configuration
*/
private async setupHttpProxy() {
if (!this.options.httpDomainRoutes || this.options.httpDomainRoutes.length === 0) {
console.log('No HTTP domain routes configured, skipping HTTP proxy setup');
private async setupSmartProxy(): Promise<void> {
if (!this.options.smartProxyConfig) {
return;
}
console.log('Setting up SmartProxy for HTTP/HTTPS traffic');
console.log('Setting up SmartProxy with direct configuration');
// Prepare SmartProxy configuration
const smartProxyConfig: plugins.smartproxy.ISmartProxyOptions = {
fromPort: 443,
toPort: this.options.httpDomainRoutes[0].targetPort,
targetIP: this.options.httpDomainRoutes[0].targetServer,
sniEnabled: true,
acme: {
port: 80,
enabled: true,
autoRenew: true,
useProduction: true,
renewThresholdDays: 30,
accountEmail: this.options.tls?.contactEmail || 'admin@example.com' // ACME requires an email
},
globalPortRanges: [{ from: 443, to: 443 }],
domainConfigs: []
};
// Create SmartProxy instance with full configuration
this.smartProxy = new plugins.smartproxy.SmartProxy(this.options.smartProxyConfig);
// Create domain configs from the HTTP routes
smartProxyConfig.domainConfigs = this.options.httpDomainRoutes.map(route => ({
domains: [route.domain],
targetIPs: [route.targetServer],
allowedIPs: route.allowedIps || ['0.0.0.0/0'],
// Skip certificate management for wildcard domains as it's not supported by HTTP-01 challenges
certificateManagement: !route.domain.includes('*')
}));
// Create and start the SmartProxy instance
this.smartProxy = new plugins.smartproxy.SmartProxy(smartProxyConfig);
// Listen for certificate events
this.smartProxy.on('certificate-issued', event => {
console.log(`Certificate issued for ${event.domain}, expires ${event.expiryDate}`);
// Set up event listeners
this.smartProxy.on('error', (err) => {
console.error('SmartProxy error:', err);
});
this.smartProxy.on('certificate-renewed', event => {
console.log(`Certificate renewed for ${event.domain}, expires ${event.expiryDate}`);
});
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(`HTTP/HTTPS proxy configured with ${smartProxyConfig.domainConfigs.length} domain routes`);
console.log('SmartProxy started successfully');
}
/**
* Set up the MTA service
*/
@ -227,7 +225,7 @@ export class DcRouter {
const smtpPorts = forwarding.ports || [25, 587, 465];
// Create SmartProxy instance for SMTP forwarding
this.smtpProxy = new plugins.smartproxy.SmartProxy({
const smtpProxyConfig: plugins.smartproxy.ISmartProxyOptions = {
// Listen on the first SMTP port
fromPort: smtpPorts[0],
// Forward to the default server
@ -245,10 +243,16 @@ export class DcRouter {
})) || [],
// 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 this.smtpProxy.start();
await smtpProxy.start();
// Store the SMTP proxy reference
this.smartProxy = smtpProxy;
console.log(`SMTP forwarding configured on ports ${smtpPorts.join(', ')}`);
}
@ -287,11 +291,11 @@ export class DcRouter {
try {
// Stop all services in parallel for faster shutdown
await Promise.all([
// Stop HTTP SmartProxy if running
this.smartProxy ? this.smartProxy.stop().catch(err => console.error('Error stopping HTTP SmartProxy:', err)) : Promise.resolve(),
// Stop SMTP components
this.stopSmtpComponents().catch(err => console.error('Error stopping SMTP components:', err)),
// Stop SMTP SmartProxy if running
this.smtpProxy ? this.smtpProxy.stop().catch(err => console.error('Error stopping SMTP SmartProxy:', err)) : Promise.resolve(),
// 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) ?
@ -312,23 +316,90 @@ export class DcRouter {
}
/**
* Update HTTP domain routes
* @param routes New HTTP domain routes
* Update SmartProxy configuration
* @param config New SmartProxy configuration
*/
public async updateHttpRoutes(routes: IDomainRoutingConfig[]): Promise<void> {
this.options.httpDomainRoutes = routes;
// If SmartProxy is already running, we need to restart it with the new configuration
public async updateSmartProxyConfig(config: plugins.smartproxy.ISmartProxyOptions): Promise<void> {
// Stop existing SmartProxy if running
if (this.smartProxy) {
// Stop the existing SmartProxy
await this.smartProxy.stop();
this.smartProxy = undefined;
// Start a new SmartProxy with the updated configuration
await this.setupHttpProxy();
}
console.log(`Updated HTTP routes with ${routes.length} domains`);
// 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<void> {
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;
}
}
/**
@ -336,14 +407,12 @@ export class DcRouter {
* @param config New SMTP forwarding configuration
*/
public async updateSmtpForwarding(config: ISmtpForwardingConfig): Promise<void> {
// Stop existing SMTP proxy if running
if (this.smtpProxy) {
await this.smtpProxy.stop();
this.smtpProxy = undefined;
}
// 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) {
@ -352,6 +421,50 @@ export class DcRouter {
console.log('SMTP forwarding configuration updated');
}
/**
* Update SMTP processing configuration
* @param config New SMTP config
*/
public async updateSmtpConfig(config: ISmtpConfig): Promise<void> {
// 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<void> {
// 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;