feat(dcrouter): Implement integrated DcRouter with comprehensive SmartProxy configuration, enhanced SMTP processing, and robust store‐and‐forward email routing
This commit is contained in:
@ -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;
|
||||
|
Reference in New Issue
Block a user