2025-05-08 01:13:54 +00:00
import * as plugins from './plugins.js' ;
import * as paths from './paths.js' ;
2025-05-07 23:45:19 +00:00
2025-05-07 22:06:55 +00:00
// Certificate types are available via plugins.tsclass
2025-05-07 14:33:20 +00:00
2025-05-27 19:28:12 +00:00
// Import the email server and its configuration
import { UnifiedEmailServer , type IUnifiedEmailServerOptions } from './mail/routing/classes.unified.email.server.js' ;
2025-05-28 13:23:45 +00:00
import type { IEmailRoute } from './mail/routing/interfaces.js' ;
2025-05-08 01:13:54 +00:00
import { logger } from './logger.js' ;
2025-05-21 00:12:49 +00:00
// Import the email configuration helpers directly from mail/delivery
import { configureEmailStorage , configureEmailServer } from './mail/delivery/index.js' ;
2025-05-30 05:30:06 +00:00
// Import storage manager
import { StorageManager , type IStorageConfig } from './storage/index.js' ;
2025-05-07 23:04:54 +00:00
export interface IDcRouterOptions {
2025-05-07 23:45:19 +00:00
/ * *
* 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 ;
/ * *
2025-05-27 19:28:12 +00:00
* Email server configuration
2025-05-08 00:12:36 +00:00
* This enables all email handling with pattern - based routing
2025-05-07 23:45:19 +00:00
* /
2025-05-27 19:28:12 +00:00
emailConfig? : IUnifiedEmailServerOptions ;
2025-05-07 23:04:54 +00:00
2025-05-20 11:04:09 +00:00
/ * *
* Custom email port configuration
* Allows configuring specific ports for email handling
* This overrides the default port mapping in the emailConfig
* /
emailPortConfig ? : {
/** External to internal port mapping */
portMapping? : Record < number , number > ;
/** Custom port configuration for specific ports */
portSettings? : Record < number , any > ;
/** Path to store received emails */
receivedEmailsPath? : string ;
} ;
2025-05-07 23:04:54 +00:00
/** 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 ;
2025-05-21 00:12:49 +00:00
/** Path to CA certificate file (for custom CAs) */
caPath? : string ;
2025-05-07 23:04:54 +00:00
} ;
2025-05-30 15:04:12 +00:00
/ * *
2025-05-30 20:11:44 +00:00
* The nameserver domains ( e . g . , [ 'ns1.example.com' , 'ns2.example.com' ] )
* These will automatically get A records pointing to publicIp or proxyIps [ 0 ]
2025-05-30 15:04:12 +00:00
* These are what go in the NS records for ALL domains in dnsScopes
* /
dnsNsDomains? : string [ ] ;
/ * *
2025-05-30 20:11:44 +00:00
* Domains this DNS server is authoritative for ( e . g . , [ 'example.com' , 'mail.example.org' ] )
2025-05-30 15:04:12 +00:00
* NS records will be auto - generated for these domains
* Any DNS record outside these scopes will trigger a warning
* Email domains with ` internal-dns ` mode must be included here
2025-05-30 10:34:50 +00:00
* /
2025-05-30 15:04:12 +00:00
dnsScopes? : string [ ] ;
2025-05-19 17:34:48 +00:00
2025-05-30 10:34:50 +00:00
/ * *
2025-05-30 15:04:12 +00:00
* IPs of proxies that forward traffic to your server ( optional )
* When defined AND useIngressProxy is true , A records with server IP are replaced with proxy IPs
* If not defined or empty , all A records use the real server IP
* Helps hide real server IP for security / privacy
* /
proxyIps? : string [ ] ;
2025-05-30 20:11:44 +00:00
/ * *
* Public IP address for nameserver A records ( required if proxyIps not set )
* This is the IP that will be used for the nameserver domains ( dnsNsDomains )
* If proxyIps is set , the first proxy IP will be used instead
* /
publicIp? : string ;
2025-05-30 15:04:12 +00:00
/ * *
* DNS records to register
* Must be within the defined dnsScopes ( or receive warning )
2025-05-30 19:54:48 +00:00
* Only need A , CNAME , TXT , MX records ( NS records auto - generated , SOA handled by smartdns )
2025-05-30 15:04:12 +00:00
* Can use ` useIngressProxy: false ` to expose real server IP ( defaults to true )
2025-05-30 10:34:50 +00:00
* /
dnsRecords? : Array < {
name : string ;
type : 'A' | 'AAAA' | 'CNAME' | 'MX' | 'TXT' | 'NS' | 'SOA' ;
value : string ;
ttl? : number ;
2025-05-30 15:04:12 +00:00
useIngressProxy? : boolean ; // Whether to replace server IP with proxy IP (default: true)
2025-05-30 10:34:50 +00:00
} > ;
2025-05-19 17:34:48 +00:00
/** DNS challenge configuration for ACME (optional) */
dnsChallenge ? : {
/** Cloudflare API key for DNS challenges */
cloudflareApiKey? : string ;
/** Other DNS providers can be added here */
} ;
2025-05-30 05:30:06 +00:00
/** Storage configuration */
storage? : IStorageConfig ;
2025-05-04 10:10:07 +00:00
}
/ * *
* DcRouter can be run on ingress and egress to and from a datacenter site .
* /
2025-05-07 14:33:20 +00:00
/ * *
* Context passed to HTTP routing rules
* /
/ * *
* Context passed to port proxy ( SmartProxy ) routing rules
* /
export interface PortProxyRuleContext {
proxy : plugins.smartproxy.SmartProxy ;
2025-05-16 15:50:46 +00:00
routes : plugins.smartproxy.IRouteConfig [ ] ;
2025-05-07 14:33:20 +00:00
}
2025-05-07 23:45:19 +00:00
2025-05-04 10:10:07 +00:00
export class DcRouter {
2025-05-07 14:33:20 +00:00
public options : IDcRouterOptions ;
2025-05-07 23:04:54 +00:00
// Core services
2025-05-07 14:33:20 +00:00
public smartProxy? : plugins.smartproxy.SmartProxy ;
2025-05-29 16:26:19 +00:00
public dnsServer? : plugins.smartdns.dnsServerMod.DnsServer ;
2025-05-27 14:06:22 +00:00
public emailServer? : UnifiedEmailServer ;
2025-05-30 05:30:06 +00:00
public storageManager : StorageManager ;
2025-05-07 23:45:19 +00:00
2025-05-20 11:04:09 +00:00
2025-05-07 23:04:54 +00:00
// Environment access
private qenv = new plugins . qenv . Qenv ( './' , '.nogit/' ) ;
2025-05-21 02:17:18 +00:00
constructor ( optionsArg : IDcRouterOptions ) {
2025-05-07 22:06:55 +00:00
// Set defaults in options
this . options = {
. . . optionsArg
} ;
2025-05-20 11:04:09 +00:00
2025-05-30 05:30:06 +00:00
// Initialize storage manager
this . storageManager = new StorageManager ( this . options . storage ) ;
2025-05-07 14:33:20 +00:00
}
public async start() {
2025-05-07 23:04:54 +00:00
console . log ( 'Starting DcRouter services...' ) ;
try {
2025-05-16 15:50:46 +00:00
// Set up SmartProxy for HTTP/HTTPS and all traffic including email routes
await this . setupSmartProxy ( ) ;
2025-05-07 23:04:54 +00:00
2025-05-08 00:12:36 +00:00
// Set up unified email handling if configured
if ( this . options . emailConfig ) {
await this . setupUnifiedEmailHandling ( ) ;
2025-05-20 11:04:09 +00:00
// Apply custom email storage configuration if available
2025-05-27 14:06:22 +00:00
if ( this . emailServer && this . options . emailPortConfig ? . receivedEmailsPath ) {
2025-05-20 11:04:09 +00:00
logger . log ( 'info' , 'Applying custom email storage configuration' ) ;
2025-05-27 14:06:22 +00:00
configureEmailStorage ( this . emailServer , this . options ) ;
2025-05-20 11:04:09 +00:00
}
2025-05-07 23:04:54 +00:00
}
2025-05-30 15:04:12 +00:00
// Set up DNS server if configured with nameservers and scopes
if ( this . options . dnsNsDomains && this . options . dnsNsDomains . length > 0 &&
this . options . dnsScopes && this . options . dnsScopes . length > 0 ) {
2025-05-29 16:26:19 +00:00
await this . setupDnsWithSocketHandler ( ) ;
2025-05-07 23:04:54 +00:00
}
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 ;
}
}
/ * *
2025-05-16 15:50:46 +00:00
* Set up SmartProxy with direct configuration and automatic email routes
2025-05-07 23:04:54 +00:00
* /
2025-05-07 23:45:19 +00:00
private async setupSmartProxy ( ) : Promise < void > {
2025-05-19 17:34:48 +00:00
console . log ( '[DcRouter] Setting up SmartProxy...' ) ;
2025-05-16 15:50:46 +00:00
let routes : plugins.smartproxy.IRouteConfig [ ] = [ ] ;
let acmeConfig : plugins.smartproxy.IAcmeOptions | undefined ;
2025-05-07 23:04:54 +00:00
2025-05-16 15:50:46 +00:00
// If user provides full SmartProxy config, use it directly
if ( this . options . smartProxyConfig ) {
routes = this . options . smartProxyConfig . routes || [ ] ;
acmeConfig = this . options . smartProxyConfig . acme ;
2025-05-19 17:34:48 +00:00
console . log ( ` [DcRouter] Found ${ routes . length } routes in config ` ) ;
console . log ( ` [DcRouter] ACME config present: ${ ! ! acmeConfig } ` ) ;
2025-05-16 15:50:46 +00:00
}
2025-05-07 23:04:54 +00:00
2025-05-16 15:50:46 +00:00
// If email config exists, automatically add email routes
if ( this . options . emailConfig ) {
const emailRoutes = this . generateEmailRoutes ( this . options . emailConfig ) ;
2025-05-20 19:46:59 +00:00
console . log ( ` Email Routes are: ` )
console . log ( emailRoutes )
2025-05-21 00:12:49 +00:00
routes = [ . . . routes , . . . emailRoutes ] ; // Enable email routing through SmartProxy
2025-05-16 15:50:46 +00:00
}
2025-05-07 23:04:54 +00:00
2025-05-30 15:04:12 +00:00
// If DNS is configured, add DNS routes
if ( this . options . dnsNsDomains && this . options . dnsNsDomains . length > 0 ) {
2025-05-29 16:26:19 +00:00
const dnsRoutes = this . generateDnsRoutes ( ) ;
2025-05-30 15:04:12 +00:00
console . log ( ` DNS Routes for nameservers ${ this . options . dnsNsDomains . join ( ', ' ) } : ` , dnsRoutes ) ;
2025-05-29 16:26:19 +00:00
routes = [ . . . routes , . . . dnsRoutes ] ;
}
2025-05-16 15:50:46 +00:00
// 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
} ;
}
2025-05-07 23:04:54 +00:00
2025-05-19 17:34:48 +00:00
// Configure DNS challenge if available
let challengeHandlers : any [ ] = [ ] ;
if ( this . options . dnsChallenge ? . cloudflareApiKey ) {
console . log ( 'Configuring Cloudflare DNS challenge for ACME' ) ;
const cloudflareAccount = new plugins . cloudflare . CloudflareAccount ( this . options . dnsChallenge . cloudflareApiKey ) ;
const dns01Handler = new plugins . smartacme . handlers . Dns01Handler ( cloudflareAccount ) ;
challengeHandlers . push ( dns01Handler ) ;
}
2025-05-16 15:50:46 +00:00
// 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
} ;
2025-05-19 17:34:48 +00:00
// If we have DNS challenge handlers, enhance the config
if ( challengeHandlers . length > 0 ) {
// We'll need to pass this to SmartProxy somehow
// For now, we'll set it as a property
( smartProxyConfig as any ) . acmeChallengeHandlers = challengeHandlers ;
( smartProxyConfig as any ) . acmeChallengePriority = [ 'dns-01' , 'http-01' ] ;
}
2025-05-16 15:50:46 +00:00
// Create SmartProxy instance
2025-05-19 17:34:48 +00:00
console . log ( '[DcRouter] Creating SmartProxy instance with config:' , JSON . stringify ( {
routeCount : smartProxyConfig.routes?.length ,
acmeEnabled : smartProxyConfig.acme?.enabled ,
acmeEmail : smartProxyConfig.acme?.email ,
certProvisionFunction : ! ! smartProxyConfig . certProvisionFunction
} , null , 2 ) ) ;
2025-05-16 15:50:46 +00:00
this . smartProxy = new plugins . smartproxy . SmartProxy ( smartProxyConfig ) ;
2025-05-07 23:45:19 +00:00
2025-05-16 15:50:46 +00:00
// Set up event listeners
this . smartProxy . on ( 'error' , ( err ) = > {
2025-05-19 17:34:48 +00:00
console . error ( '[DcRouter] SmartProxy error:' , err ) ;
console . error ( '[DcRouter] Error stack:' , err . stack ) ;
2025-05-07 23:45:19 +00:00
} ) ;
2025-05-16 15:50:46 +00:00
if ( acmeConfig ) {
this . smartProxy . on ( 'certificate-issued' , ( event ) = > {
2025-05-19 17:34:48 +00:00
console . log ( ` [DcRouter] Certificate issued for ${ event . domain } , expires ${ event . expiryDate } ` ) ;
2025-05-16 15:50:46 +00:00
} ) ;
this . smartProxy . on ( 'certificate-renewed' , ( event ) = > {
2025-05-19 17:34:48 +00:00
console . log ( ` [DcRouter] Certificate renewed for ${ event . domain } , expires ${ event . expiryDate } ` ) ;
} ) ;
this . smartProxy . on ( 'certificate-failed' , ( event ) = > {
console . error ( ` [DcRouter] Certificate failed for ${ event . domain } : ` , event . error ) ;
2025-05-16 15:50:46 +00:00
} ) ;
}
// Start SmartProxy
2025-05-19 17:34:48 +00:00
console . log ( '[DcRouter] Starting SmartProxy...' ) ;
2025-05-16 15:50:46 +00:00
await this . smartProxy . start ( ) ;
2025-05-19 17:34:48 +00:00
console . log ( '[DcRouter] SmartProxy started successfully' ) ;
2025-05-16 15:50:46 +00:00
console . log ( ` SmartProxy started with ${ routes . length } routes ` ) ;
2025-05-07 23:45:19 +00:00
}
2025-05-07 23:04:54 +00:00
}
2025-05-07 23:45:19 +00:00
2025-05-04 10:10:07 +00:00
2025-05-16 15:50:46 +00:00
/ * *
* Generate SmartProxy routes for email configuration
* /
2025-05-27 19:28:12 +00:00
private generateEmailRoutes ( emailConfig : IUnifiedEmailServerOptions ) : plugins . smartproxy . IRouteConfig [ ] {
2025-05-16 15:50:46 +00:00
const emailRoutes : plugins.smartproxy.IRouteConfig [ ] = [ ] ;
// Create routes for each email port
for ( const port of emailConfig . ports ) {
2025-05-20 11:04:09 +00:00
// Create a descriptive name for the route based on the port
let routeName = 'email-route' ;
let tlsMode = 'passthrough' ;
2025-05-16 15:50:46 +00:00
// Handle different email ports differently
switch ( port ) {
case 25 : // SMTP
2025-05-20 11:04:09 +00:00
routeName = 'smtp-route' ;
tlsMode = 'passthrough' ; // STARTTLS handled by email server
2025-05-16 15:50:46 +00:00
break ;
case 587 : // Submission
2025-05-20 11:04:09 +00:00
routeName = 'submission-route' ;
tlsMode = 'passthrough' ; // STARTTLS handled by email server
2025-05-16 15:50:46 +00:00
break ;
case 465 : // SMTPS
2025-05-20 11:04:09 +00:00
routeName = 'smtps-route' ;
tlsMode = 'terminate' ; // Terminate TLS and re-encrypt to email server
break ;
default :
routeName = ` email-port- ${ port } -route ` ;
tlsMode = 'passthrough' ;
// Check if we have specific settings for this port
if ( this . options . emailPortConfig ? . portSettings &&
this . options . emailPortConfig . portSettings [ port ] ) {
const portSettings = this . options . emailPortConfig . portSettings [ port ] ;
// If this port requires TLS termination, set the mode accordingly
if ( portSettings . terminateTls ) {
tlsMode = 'terminate' ;
2025-05-16 15:50:46 +00:00
}
2025-05-20 11:04:09 +00:00
// Override the route name if specified
if ( portSettings . routeName ) {
routeName = portSettings . routeName ;
}
}
2025-05-16 15:50:46 +00:00
break ;
}
2025-05-20 11:04:09 +00:00
2025-05-29 16:26:19 +00:00
// Create action based on mode
let action : any ;
if ( emailConfig . useSocketHandler ) {
// Socket-handler mode
action = {
type : 'socket-handler' as any ,
socketHandler : this.createMailSocketHandler ( port )
} ;
} else {
// Traditional forwarding mode
const defaultPortMapping = {
25 : 10025 , // SMTP
587 : 10587 , // Submission
465 : 10465 // SMTPS
} ;
const portMapping = this . options . emailPortConfig ? . portMapping || defaultPortMapping ;
const internalPort = portMapping [ port ] || port + 10000 ;
action = {
2025-05-20 11:04:09 +00:00
type : 'forward' ,
target : {
host : 'localhost' , // Forward to internal email server
port : internalPort
} ,
tls : {
mode : tlsMode as any
}
2025-05-29 16:26:19 +00:00
} ;
}
2025-05-20 11:04:09 +00:00
// For TLS terminate mode, add certificate info
2025-05-29 16:26:19 +00:00
if ( tlsMode === 'terminate' && action . tls ) {
action . tls . certificate = 'auto' ;
2025-05-20 11:04:09 +00:00
}
2025-05-29 16:26:19 +00:00
// Create the route configuration
const routeConfig : plugins.smartproxy.IRouteConfig = {
name : routeName ,
match : {
ports : [ port ]
} ,
action : action
} ;
2025-05-20 11:04:09 +00:00
// Add the route to our list
emailRoutes . push ( routeConfig ) ;
2025-05-16 15:50:46 +00:00
}
2025-05-29 16:26:19 +00:00
// Add email domain-based routes if configured
2025-05-28 13:23:45 +00:00
if ( emailConfig . routes ) {
for ( const route of emailConfig . routes ) {
emailRoutes . push ( {
name : route.name ,
match : {
ports : emailConfig.ports ,
domains : route.match.recipients ? [ route . match . recipients . toString ( ) . split ( '@' ) [ 1 ] ] : [ ]
} ,
action : {
type : 'forward' ,
target : route.action.type === 'forward' && route . action . forward ? {
host : route.action.forward.host ,
port : route.action.forward.port || 25
} : undefined ,
tls : {
mode : 'passthrough'
2025-05-16 15:50:46 +00:00
}
2025-05-28 13:23:45 +00:00
}
} ) ;
2025-05-16 15:50:46 +00:00
}
}
return emailRoutes ;
}
2025-05-29 16:26:19 +00:00
/ * *
* Generate SmartProxy routes for DNS configuration
* /
private generateDnsRoutes ( ) : plugins . smartproxy . IRouteConfig [ ] {
2025-05-30 15:04:12 +00:00
if ( ! this . options . dnsNsDomains || this . options . dnsNsDomains . length === 0 ) {
2025-05-29 16:26:19 +00:00
return [ ] ;
}
const dnsRoutes : plugins.smartproxy.IRouteConfig [ ] = [ ] ;
// Create routes for DNS-over-HTTPS paths
const dohPaths = [ '/dns-query' , '/resolve' ] ;
2025-05-30 15:04:12 +00:00
// Use the first nameserver domain for DoH routes
const primaryNameserver = this . options . dnsNsDomains [ 0 ] ;
2025-05-29 16:26:19 +00:00
for ( const path of dohPaths ) {
const dohRoute : plugins.smartproxy.IRouteConfig = {
name : ` dns-over-https- ${ path . replace ( '/' , '' ) } ` ,
match : {
ports : [ 443 ] , // HTTPS port for DoH
2025-05-30 15:04:12 +00:00
domains : [ primaryNameserver ] ,
2025-05-29 16:26:19 +00:00
path : path
} ,
action : {
type : 'socket-handler' as any ,
socketHandler : this.createDnsSocketHandler ( )
} as any
} ;
dnsRoutes . push ( dohRoute ) ;
}
return dnsRoutes ;
}
2025-05-16 15:50:46 +00:00
2025-05-07 22:06:55 +00:00
/ * *
2025-05-07 23:04:54 +00:00
* 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
2025-05-07 22:06:55 +00:00
* /
2025-05-07 23:04:54 +00:00
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 ;
}
2025-05-07 22:06:55 +00:00
2025-05-07 23:04:54 +00:00
public async stop() {
console . log ( 'Stopping DcRouter services...' ) ;
2025-05-07 22:06:55 +00:00
try {
2025-05-07 23:04:54 +00:00
// Stop all services in parallel for faster shutdown
await Promise . all ( [
2025-05-27 14:06:22 +00:00
// Stop unified email server if running
this . emailServer ? this . emailServer . stop ( ) . catch ( err = > console . error ( 'Error stopping email server:' , err ) ) : Promise . resolve ( ) ,
2025-05-07 23:04:54 +00:00
2025-05-07 23:45:19 +00:00
// Stop HTTP SmartProxy if running
this . smartProxy ? this . smartProxy . stop ( ) . catch ( err = > console . error ( 'Error stopping SmartProxy:' , err ) ) : Promise . resolve ( ) ,
2025-05-07 23:04:54 +00:00
// 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' ) ;
2025-05-07 22:06:55 +00:00
} catch ( error ) {
2025-05-07 23:04:54 +00:00
console . error ( 'Error during DcRouter shutdown:' , error ) ;
throw error ;
2025-05-07 22:06:55 +00:00
}
}
2025-05-07 23:04:54 +00:00
/ * *
2025-05-07 23:45:19 +00:00
* Update SmartProxy configuration
* @param config New SmartProxy configuration
2025-05-07 23:04:54 +00:00
* /
2025-05-07 23:45:19 +00:00
public async updateSmartProxyConfig ( config : plugins.smartproxy.ISmartProxyOptions ) : Promise < void > {
// Stop existing SmartProxy if running
2025-05-04 10:10:07 +00:00
if ( this . smartProxy ) {
await this . smartProxy . stop ( ) ;
2025-05-07 23:04:54 +00:00
this . smartProxy = undefined ;
2025-05-04 10:10:07 +00:00
}
2025-05-07 22:06:55 +00:00
2025-05-07 23:45:19 +00:00
// Update configuration
this . options . smartProxyConfig = config ;
2025-05-16 15:50:46 +00:00
// Start new SmartProxy with updated configuration (will include email routes if configured)
2025-05-07 23:45:19 +00:00
await this . setupSmartProxy ( ) ;
console . log ( 'SmartProxy configuration updated' ) ;
}
2025-05-08 00:12:36 +00:00
2025-05-07 23:45:19 +00:00
/ * *
2025-05-08 00:12:36 +00:00
* Set up unified email handling with pattern - based routing
* This implements the consolidated emailConfig approach
2025-05-07 23:45:19 +00:00
* /
2025-05-08 00:12:36 +00:00
private async setupUnifiedEmailHandling ( ) : Promise < void > {
if ( ! this . options . emailConfig ) {
throw new Error ( 'Email configuration is required for unified email handling' ) ;
}
2025-05-16 15:50:46 +00:00
2025-05-27 19:28:12 +00:00
// Apply port mapping if behind SmartProxy
2025-05-27 18:00:14 +00:00
const portMapping = this . options . emailPortConfig ? . portMapping || {
2025-05-16 15:50:46 +00:00
25 : 10025 , // SMTP
587 : 10587 , // Submission
465 : 10465 // SMTPS
} ;
2025-05-27 19:28:12 +00:00
// Create config with mapped ports
const emailConfig : IUnifiedEmailServerOptions = {
. . . this . options . emailConfig ,
ports : this.options.emailConfig.ports.map ( port = > portMapping [ port ] || port + 10000 ) ,
2025-05-16 15:50:46 +00:00
hostname : 'localhost' // Listen on localhost for SmartProxy forwarding
2025-05-27 19:28:12 +00:00
} ;
// Create unified email server
this . emailServer = new UnifiedEmailServer ( this , emailConfig ) ;
2025-05-07 23:45:19 +00:00
2025-05-27 18:00:14 +00:00
// Set up error handling
this . emailServer . on ( 'error' , ( err : Error ) = > {
logger . log ( 'error' , ` UnifiedEmailServer error: ${ err . message } ` ) ;
} ) ;
2025-05-20 11:04:09 +00:00
2025-05-27 18:00:14 +00:00
// Start the server
await this . emailServer . start ( ) ;
2025-05-20 11:04:09 +00:00
2025-05-27 19:28:12 +00:00
logger . log ( 'info' , ` Email server started on ports: ${ emailConfig . ports . join ( ', ' ) } ` ) ;
2025-05-07 14:33:20 +00:00
}
2025-05-07 23:04:54 +00:00
2025-05-07 14:33:20 +00:00
/ * *
2025-05-08 00:12:36 +00:00
* Update the unified email configuration
* @param config New email configuration
2025-05-07 14:33:20 +00:00
* /
2025-05-27 19:28:12 +00:00
public async updateEmailConfig ( config : IUnifiedEmailServerOptions ) : Promise < void > {
2025-05-08 00:12:36 +00:00
// Stop existing email components
await this . stopUnifiedEmailComponents ( ) ;
2025-05-07 23:04:54 +00:00
// Update configuration
2025-05-08 00:12:36 +00:00
this . options . emailConfig = config ;
2025-05-07 23:04:54 +00:00
2025-05-08 00:12:36 +00:00
// Start email handling with new configuration
await this . setupUnifiedEmailHandling ( ) ;
2025-05-07 23:04:54 +00:00
2025-05-08 00:12:36 +00:00
console . log ( 'Unified email configuration updated' ) ;
2025-05-04 10:10:07 +00:00
}
2025-05-07 23:45:19 +00:00
/ * *
2025-05-08 00:12:36 +00:00
* Stop all unified email components
2025-05-07 23:45:19 +00:00
* /
2025-05-08 00:12:36 +00:00
private async stopUnifiedEmailComponents ( ) : Promise < void > {
2025-05-08 00:39:43 +00:00
try {
2025-05-27 14:06:22 +00:00
// Stop the unified email server which contains all components
if ( this . emailServer ) {
await this . emailServer . stop ( ) ;
2025-05-08 00:39:43 +00:00
logger . log ( 'info' , 'Unified email server stopped' ) ;
2025-05-27 14:06:22 +00:00
this . emailServer = undefined ;
2025-05-08 00:39:43 +00:00
}
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
* /
2025-05-28 13:23:45 +00:00
public async updateEmailRoutes ( routes : IEmailRoute [ ] ) : Promise < void > {
2025-05-08 00:39:43 +00:00
// Validate that email config exists
if ( ! this . options . emailConfig ) {
2025-05-28 13:23:45 +00:00
throw new Error ( 'Email configuration is required before updating routes' ) ;
2025-05-08 00:39:43 +00:00
}
// Update the configuration
2025-05-28 13:23:45 +00:00
this . options . emailConfig . routes = routes ;
2025-05-08 00:39:43 +00:00
// Update the unified email server if it exists
2025-05-27 14:06:22 +00:00
if ( this . emailServer ) {
2025-05-28 13:23:45 +00:00
this . emailServer . updateRoutes ( routes ) ;
2025-05-08 00:39:43 +00:00
}
2025-05-07 23:45:19 +00:00
2025-05-28 13:23:45 +00:00
console . log ( ` Email routes updated with ${ routes . length } routes ` ) ;
2025-05-07 23:45:19 +00:00
}
2025-05-08 00:39:43 +00:00
/ * *
* Get statistics from all components
* /
public getStats ( ) : any {
const stats : any = {
2025-05-27 14:06:22 +00:00
emailServer : this.emailServer?.getStats ( )
2025-05-08 00:39:43 +00:00
} ;
return stats ;
}
2025-05-20 19:46:59 +00:00
/ * *
* Configure MTA for email handling with custom port and storage settings
* @param config Configuration for the MTA service
* /
public async configureEmailMta ( config : {
internalPort : number ;
host? : string ;
secure? : boolean ;
storagePath? : string ;
portMapping? : Record < number , number > ;
} ) : Promise < boolean > {
logger . log ( 'info' , 'Configuring MTA service with custom settings' ) ;
// Update email port configuration
if ( ! this . options . emailPortConfig ) {
this . options . emailPortConfig = { } ;
}
// Configure storage paths for received emails
if ( config . storagePath ) {
// Set the storage path for received emails
this . options . emailPortConfig . receivedEmailsPath = config . storagePath ;
}
// Apply port mapping if provided
if ( config . portMapping ) {
this . options . emailPortConfig . portMapping = {
. . . this . options . emailPortConfig . portMapping ,
. . . config . portMapping
} ;
logger . log ( 'info' , ` Updated MTA port mappings: ${ JSON . stringify ( this . options . emailPortConfig . portMapping ) } ` ) ;
}
2025-05-21 00:12:49 +00:00
// Use the dedicated helper to configure the email server
// Pass through the options specified by the implementation
2025-05-27 14:06:22 +00:00
if ( this . emailServer ) {
configureEmailServer ( this . emailServer , {
2025-05-21 00:12:49 +00:00
ports : [ config . internalPort ] , // Use whatever port the implementation specifies
hostname : config.host ,
tls : config.secure ? {
// Basic TLS settings if secure mode is enabled
certPath : this.options.tls?.certPath ,
keyPath : this.options.tls?.keyPath ,
caPath : this.options.tls?.caPath
} : undefined ,
storagePath : config.storagePath
} ) ;
}
2025-05-20 19:46:59 +00:00
// If email handling is already set up, restart it to apply changes
2025-05-27 14:06:22 +00:00
if ( this . emailServer ) {
2025-05-20 19:46:59 +00:00
logger . log ( 'info' , 'Restarting unified email handling to apply MTA configuration changes' ) ;
await this . stopUnifiedEmailComponents ( ) ;
await this . setupUnifiedEmailHandling ( ) ;
}
return true ;
}
2025-05-28 18:07:07 +00:00
/ * *
* Register DNS records with the DNS server
* @param records Array of DNS records to register
* /
private registerDnsRecords ( records : Array < { name : string ; type : string ; value : string ; ttl? : number } > ) : void {
if ( ! this . dnsServer ) return ;
2025-05-30 16:44:10 +00:00
// Register a separate handler for each record
// This ensures multiple records of the same type (like NS records) are all served
2025-05-28 18:07:07 +00:00
for ( const record of records ) {
2025-05-30 16:44:10 +00:00
// Register handler for this specific record
this . dnsServer . registerHandler ( record . name , [ record . type ] , ( question ) = > {
// Check if this handler matches the question
if ( question . name === record . name && question . type === record . type ) {
2025-05-28 18:07:07 +00:00
return {
2025-05-30 16:44:10 +00:00
name : record.name ,
type : record . type ,
2025-05-28 18:07:07 +00:00
class : 'IN' ,
2025-05-30 16:44:10 +00:00
ttl : record.ttl || 300 ,
data : this.parseDnsRecordData ( record . type , record . value )
2025-05-28 18:07:07 +00:00
} ;
}
return null ;
} ) ;
}
2025-05-30 16:44:10 +00:00
logger . log ( 'info' , ` Registered ${ records . length } DNS handlers (one per record) ` ) ;
2025-05-28 18:07:07 +00:00
}
/ * *
* Parse DNS record data based on record type
* @param type DNS record type
* @param value DNS record value
* @returns Parsed data for the DNS response
* /
private parseDnsRecordData ( type : string , value : string ) : any {
switch ( type ) {
case 'A' :
return value ; // IP address as string
case 'MX' :
const [ priority , exchange ] = value . split ( ' ' ) ;
return { priority : parseInt ( priority ) , exchange } ;
case 'TXT' :
return value ;
case 'NS' :
return value ;
2025-05-30 15:04:12 +00:00
case 'SOA' :
// SOA format: primary-ns admin-email serial refresh retry expire minimum
const parts = value . split ( ' ' ) ;
return {
mname : parts [ 0 ] ,
rname : parts [ 1 ] ,
serial : parseInt ( parts [ 2 ] ) ,
refresh : parseInt ( parts [ 3 ] ) ,
retry : parseInt ( parts [ 4 ] ) ,
expire : parseInt ( parts [ 5 ] ) ,
minimum : parseInt ( parts [ 6 ] )
} ;
2025-05-28 18:07:07 +00:00
default :
return value ;
}
}
2025-05-29 16:26:19 +00:00
/ * *
* Set up DNS server with socket handler for DoH
* /
private async setupDnsWithSocketHandler ( ) : Promise < void > {
2025-05-30 15:04:12 +00:00
if ( ! this . options . dnsNsDomains || this . options . dnsNsDomains . length === 0 ) {
throw new Error ( 'dnsNsDomains is required for DNS server setup' ) ;
}
if ( ! this . options . dnsScopes || this . options . dnsScopes . length === 0 ) {
throw new Error ( 'dnsScopes is required for DNS server setup' ) ;
2025-05-29 16:26:19 +00:00
}
2025-05-30 15:04:12 +00:00
const primaryNameserver = this . options . dnsNsDomains [ 0 ] ;
logger . log ( 'info' , ` Setting up DNS server with primary nameserver: ${ primaryNameserver } ` ) ;
2025-05-29 16:26:19 +00:00
// Get VM IP address for UDP binding
const networkInterfaces = plugins . os . networkInterfaces ( ) ;
let vmIpAddress = '0.0.0.0' ; // Default to all interfaces
// Try to find the VM's internal IP address
2025-05-30 15:04:12 +00:00
for ( const [ _name , interfaces ] of Object . entries ( networkInterfaces ) ) {
2025-05-29 16:26:19 +00:00
if ( interfaces ) {
for ( const iface of interfaces ) {
if ( ! iface . internal && iface . family === 'IPv4' ) {
vmIpAddress = iface . address ;
break ;
}
}
}
}
// Create DNS server instance with manual HTTPS mode
this . dnsServer = new plugins . smartdns . dnsServerMod . DnsServer ( {
udpPort : 53 ,
udpBindInterface : vmIpAddress ,
httpsPort : 443 , // Required but won't bind due to manual mode
manualHttpsMode : true , // Enable manual HTTPS socket handling
2025-05-30 15:04:12 +00:00
dnssecZone : primaryNameserver ,
2025-05-30 19:54:48 +00:00
primaryNameserver : primaryNameserver , // Automatically generates correct SOA records
2025-05-29 16:26:19 +00:00
// For now, use self-signed cert until we integrate with Let's Encrypt
httpsKey : '' ,
httpsCert : ''
} ) ;
// Start the DNS server (UDP only)
await this . dnsServer . start ( ) ;
logger . log ( 'info' , ` DNS server started on UDP ${ vmIpAddress } :53 ` ) ;
2025-05-30 10:34:50 +00:00
2025-05-30 15:04:12 +00:00
// Validate DNS configuration
await this . validateDnsConfiguration ( ) ;
// Generate and register authoritative records
const authoritativeRecords = await this . generateAuthoritativeRecords ( ) ;
// Generate email DNS records
const emailDnsRecords = await this . generateEmailDnsRecords ( ) ;
// Combine all records: authoritative, email, and user-defined
const allRecords = [ . . . authoritativeRecords , . . . emailDnsRecords ] ;
2025-05-30 10:34:50 +00:00
if ( this . options . dnsRecords && this . options . dnsRecords . length > 0 ) {
2025-05-30 15:04:12 +00:00
allRecords . push ( . . . this . options . dnsRecords ) ;
}
// Apply proxy IP replacement if configured
2025-05-30 20:11:44 +00:00
await this . applyProxyIpReplacement ( allRecords ) ;
2025-05-30 15:04:12 +00:00
// Register all DNS records
if ( allRecords . length > 0 ) {
this . registerDnsRecords ( allRecords ) ;
logger . log ( 'info' , ` Registered ${ allRecords . length } DNS records ( ${ authoritativeRecords . length } authoritative, ${ emailDnsRecords . length } email, ${ this . options . dnsRecords ? . length || 0 } user-defined) ` ) ;
2025-05-30 10:34:50 +00:00
}
2025-05-29 16:26:19 +00:00
}
/ * *
* Create DNS socket handler for DoH
* /
private createDnsSocketHandler ( ) : ( socket : plugins.net.Socket ) = > Promise < void > {
return async ( socket : plugins.net.Socket ) = > {
if ( ! this . dnsServer ) {
logger . log ( 'error' , 'DNS socket handler called but DNS server not initialized' ) ;
socket . end ( ) ;
return ;
}
logger . log ( 'debug' , 'DNS socket handler: passing socket to DnsServer' ) ;
try {
// Use the built-in socket handler from smartdns
// This handles HTTP/2, DoH protocol, etc.
await ( this . dnsServer as any ) . handleHttpsSocket ( socket ) ;
} catch ( error ) {
logger . log ( 'error' , ` DNS socket handler error: ${ error . message } ` ) ;
socket . destroy ( ) ;
}
} ;
}
2025-05-30 15:04:12 +00:00
/ * *
* Validate DNS configuration
* /
private async validateDnsConfiguration ( ) : Promise < void > {
if ( ! this . options . dnsNsDomains || ! this . options . dnsScopes ) {
return ;
}
logger . log ( 'info' , 'Validating DNS configuration...' ) ;
// Check if email domains with internal-dns are in dnsScopes
if ( this . options . emailConfig ? . domains ) {
for ( const domainConfig of this . options . emailConfig . domains ) {
if ( domainConfig . dnsMode === 'internal-dns' &&
! this . options . dnsScopes . includes ( domainConfig . domain ) ) {
logger . log ( 'warn' , ` Email domain ' ${ domainConfig . domain } ' with internal-dns mode is not in dnsScopes. It should be added to dnsScopes. ` ) ;
}
}
}
// Validate user-provided DNS records are within scopes
if ( this . options . dnsRecords ) {
for ( const record of this . options . dnsRecords ) {
const recordDomain = this . extractDomain ( record . name ) ;
const isInScope = this . options . dnsScopes . some ( scope = >
recordDomain === scope || recordDomain . endsWith ( ` . ${ scope } ` )
) ;
if ( ! isInScope ) {
logger . log ( 'warn' , ` DNS record for ' ${ record . name } ' is outside defined scopes [ ${ this . options . dnsScopes . join ( ', ' ) } ] ` ) ;
}
}
}
}
/ * *
* Generate email DNS records for domains with internal - dns mode
* /
private async generateEmailDnsRecords ( ) : Promise < Array < { name : string ; type : string ; value : string ; ttl ? : number } > > {
const records : Array < { name : string ; type : string ; value : string ; ttl? : number } > = [ ] ;
if ( ! this . options . emailConfig ? . domains ) {
return records ;
}
// Filter domains with internal-dns mode
const internalDnsDomains = this . options . emailConfig . domains . filter (
domain = > domain . dnsMode === 'internal-dns'
) ;
for ( const domainConfig of internalDnsDomains ) {
const domain = domainConfig . domain ;
const ttl = domainConfig . dns ? . internal ? . ttl || 3600 ;
const mxPriority = domainConfig . dns ? . internal ? . mxPriority || 10 ;
// MX record - points to the domain itself for email handling
records . push ( {
name : domain ,
type : 'MX' ,
value : ` ${ mxPriority } ${ domain } ` ,
ttl
} ) ;
// SPF record - using sensible defaults
const spfRecord = 'v=spf1 a mx ~all' ;
records . push ( {
name : domain ,
type : 'TXT' ,
value : spfRecord ,
ttl
} ) ;
// DMARC record - using sensible defaults
const dmarcPolicy = 'none' ; // Start with 'none' policy for monitoring
const dmarcEmail = ` dmarc@ ${ domain } ` ;
records . push ( {
name : ` _dmarc. ${ domain } ` ,
type : 'TXT' ,
value : ` v=DMARC1; p= ${ dmarcPolicy } ; rua=mailto: ${ dmarcEmail } ` ,
ttl
} ) ;
// Note: DKIM records will be generated later when DKIM keys are available
// They require the DKIMCreator which is part of the email server
}
logger . log ( 'info' , ` Generated ${ records . length } email DNS records for ${ internalDnsDomains . length } internal-dns domains ` ) ;
return records ;
}
/ * *
2025-05-30 19:54:48 +00:00
* Generate authoritative DNS records ( NS only ) for all domains in dnsScopes
* SOA records are now automatically generated by smartdns with primaryNameserver setting
2025-05-30 15:04:12 +00:00
* /
private async generateAuthoritativeRecords ( ) : Promise < Array < { name : string ; type : string ; value : string ; ttl ? : number } > > {
const records : Array < { name : string ; type : string ; value : string ; ttl? : number } > = [ ] ;
if ( ! this . options . dnsNsDomains || ! this . options . dnsScopes ) {
return records ;
}
2025-05-30 20:11:44 +00:00
// Determine the public IP for nameserver A records
let publicIp : string | null = null ;
// Use proxy IPs if configured (these should be public IPs)
if ( this . options . proxyIps && this . options . proxyIps . length > 0 ) {
publicIp = this . options . proxyIps [ 0 ] ; // Use first proxy IP
logger . log ( 'info' , ` Using proxy IP for nameserver A records: ${ publicIp } ` ) ;
} else if ( this . options . publicIp ) {
// Use explicitly configured public IP
publicIp = this . options . publicIp ;
logger . log ( 'info' , ` Using configured public IP for nameserver A records: ${ publicIp } ` ) ;
} else {
// Auto-discover public IP using smartnetwork
try {
logger . log ( 'info' , 'Auto-discovering public IP address...' ) ;
const smartNetwork = new plugins . smartnetwork . SmartNetwork ( ) ;
const publicIps = await smartNetwork . getPublicIps ( ) ;
if ( publicIps . v4 ) {
publicIp = publicIps . v4 ;
logger . log ( 'info' , ` Auto-discovered public IPv4: ${ publicIp } ` ) ;
} else {
logger . log ( 'warn' , 'Could not auto-discover public IPv4 address' ) ;
}
} catch ( error ) {
logger . log ( 'error' , ` Failed to auto-discover public IP: ${ error . message } ` ) ;
}
if ( ! publicIp ) {
logger . log ( 'warn' , 'No public IP available. Nameserver A records require either proxyIps, publicIp, or successful auto-discovery.' ) ;
}
}
// Generate A records for nameservers if we have a public IP
if ( publicIp ) {
for ( const nsDomain of this . options . dnsNsDomains ) {
records . push ( {
name : nsDomain ,
type : 'A' ,
value : publicIp ,
ttl : 3600
} ) ;
}
logger . log ( 'info' , ` Generated A records for ${ this . options . dnsNsDomains . length } nameservers ` ) ;
}
2025-05-30 19:54:48 +00:00
// Generate NS records for each domain in scopes
2025-05-30 15:04:12 +00:00
for ( const domain of this . options . dnsScopes ) {
// Add NS records for all nameservers
for ( const nsDomain of this . options . dnsNsDomains ) {
records . push ( {
name : domain ,
type : 'NS' ,
value : nsDomain ,
ttl : 3600
} ) ;
}
2025-05-30 19:54:48 +00:00
// SOA records are now automatically generated by smartdns DnsServer
// with the primaryNameserver configuration option
2025-05-30 15:04:12 +00:00
}
2025-05-30 20:11:44 +00:00
logger . log ( 'info' , ` Generated ${ records . length } total records (A + NS) for ${ this . options . dnsScopes . length } domains ` ) ;
2025-05-30 15:04:12 +00:00
return records ;
}
/ * *
* Extract the base domain from a DNS record name
* /
private extractDomain ( recordName : string ) : string {
// Handle wildcards
if ( recordName . startsWith ( '*.' ) ) {
recordName = recordName . substring ( 2 ) ;
}
return recordName ;
}
/ * *
* Apply proxy IP replacement logic to DNS records
* /
2025-05-30 20:11:44 +00:00
private async applyProxyIpReplacement ( records : Array < { name : string ; type : string ; value : string ; ttl? : number ; useIngressProxy? : boolean } > ) : Promise < void > {
2025-05-30 15:04:12 +00:00
if ( ! this . options . proxyIps || this . options . proxyIps . length === 0 ) {
return ; // No proxy IPs configured, skip replacement
}
2025-05-30 20:11:44 +00:00
// Get server's public IP
const serverIp = await this . detectServerPublicIp ( ) ;
2025-05-30 15:04:12 +00:00
if ( ! serverIp ) {
logger . log ( 'warn' , 'Could not detect server public IP, skipping proxy IP replacement' ) ;
return ;
}
logger . log ( 'info' , ` Applying proxy IP replacement. Server IP: ${ serverIp } , Proxy IPs: ${ this . options . proxyIps . join ( ', ' ) } ` ) ;
let proxyIndex = 0 ;
for ( const record of records ) {
if ( record . type === 'A' &&
record . value === serverIp &&
record . useIngressProxy !== false ) {
// Round-robin through proxy IPs
const proxyIp = this . options . proxyIps [ proxyIndex % this . options . proxyIps . length ] ;
logger . log ( 'info' , ` Replacing A record for ${ record . name } : ${ record . value } → ${ proxyIp } ` ) ;
record . value = proxyIp ;
proxyIndex ++ ;
}
}
}
/ * *
* Detect the server ' s public IP address
* /
2025-05-30 20:11:44 +00:00
private async detectServerPublicIp ( ) : Promise < string | null > {
try {
const smartNetwork = new plugins . smartnetwork . SmartNetwork ( ) ;
const publicIps = await smartNetwork . getPublicIps ( ) ;
if ( publicIps . v4 ) {
return publicIps . v4 ;
}
return null ;
} catch ( error ) {
logger . log ( 'warn' , ` Failed to detect public IP: ${ error . message } ` ) ;
return null ;
}
2025-05-30 15:04:12 +00:00
}
2025-05-29 16:26:19 +00:00
/ * *
* Create mail socket handler for email traffic
* /
private createMailSocketHandler ( port : number ) : ( socket : plugins.net.Socket ) = > Promise < void > {
return async ( socket : plugins.net.Socket ) = > {
if ( ! this . emailServer ) {
logger . log ( 'error' , 'Mail socket handler called but email server not initialized' ) ;
socket . end ( ) ;
return ;
}
logger . log ( 'debug' , ` Mail socket handler: handling connection for port ${ port } ` ) ;
try {
// Port 465 requires immediate TLS
if ( port === 465 ) {
// Wrap the socket in TLS
const tlsOptions = {
isServer : true ,
key : this.options.tls?.keyPath ? plugins . fs . readFileSync ( this . options . tls . keyPath , 'utf8' ) : undefined ,
cert : this.options.tls?.certPath ? plugins . fs . readFileSync ( this . options . tls . certPath , 'utf8' ) : undefined
} ;
const tlsSocket = new plugins . tls . TLSSocket ( socket , tlsOptions ) ;
tlsSocket . on ( 'secure' , ( ) = > {
// Pass the secure socket to the email server
this . emailServer ! . handleSocket ( tlsSocket , port ) ;
} ) ;
tlsSocket . on ( 'error' , ( err ) = > {
logger . log ( 'error' , ` TLS handshake error on port ${ port } : ${ err . message } ` ) ;
socket . destroy ( ) ;
} ) ;
} else {
// For ports 25 and 587, pass raw socket (STARTTLS handled by email server)
await this . emailServer . handleSocket ( socket , port ) ;
}
} catch ( error ) {
logger . log ( 'error' , ` Mail socket handler error on port ${ port } : ${ error . message } ` ) ;
socket . destroy ( ) ;
}
} ;
}
2025-05-04 10:10:07 +00:00
}
2025-05-27 19:28:12 +00:00
// Re-export email server types for convenience
2025-05-28 14:12:50 +00:00
export type { IUnifiedEmailServerOptions } ;
2025-05-24 01:00:30 +00:00
2025-05-04 10:10:07 +00:00
export default DcRouter ;