563 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			563 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
|  | import * as plugins from '../../plugins.ts'; | ||
|  | import type { IEmailDomainConfig } from './interfaces.ts'; | ||
|  | import { logger } from '../../logger.ts'; | ||
|  | import type { DcRouter } from '../../classes.mailer.ts'; | ||
|  | import type { StorageManager } from '../../storage/index.ts'; | ||
|  | 
 | ||
|  | /** | ||
|  |  * DNS validation result | ||
|  |  */ | ||
|  | export interface IDnsValidationResult { | ||
|  |   valid: boolean; | ||
|  |   errors: string[]; | ||
|  |   warnings: string[]; | ||
|  |   requiredChanges: string[]; | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * DNS records found for a domain | ||
|  |  */ | ||
|  | interface IDnsRecords { | ||
|  |   mx?: string[]; | ||
|  |   spf?: string; | ||
|  |   dkim?: string; | ||
|  |   dmarc?: string; | ||
|  |   ns?: string[]; | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Manages DNS configuration for email domains | ||
|  |  * Handles both validation and creation of DNS records | ||
|  |  */ | ||
|  | export class DnsManager { | ||
|  |   private dcRouter: DcRouter; | ||
|  |   private storageManager: StorageManager; | ||
|  |    | ||
|  |   constructor(dcRouter: DcRouter) { | ||
|  |     this.dcRouter = dcRouter; | ||
|  |     this.storageManager = dcRouter.storageManager; | ||
|  |   } | ||
|  |    | ||
|  |   /** | ||
|  |    * Validate all domain configurations | ||
|  |    */ | ||
|  |   async validateAllDomains(domainConfigs: IEmailDomainConfig[]): Promise<Map<string, IDnsValidationResult>> { | ||
|  |     const results = new Map<string, IDnsValidationResult>(); | ||
|  |      | ||
|  |     for (const config of domainConfigs) { | ||
|  |       const result = await this.validateDomain(config); | ||
|  |       results.set(config.domain, result); | ||
|  |     } | ||
|  |      | ||
|  |     return results; | ||
|  |   } | ||
|  |    | ||
|  |   /** | ||
|  |    * Validate a single domain configuration | ||
|  |    */ | ||
|  |   async validateDomain(config: IEmailDomainConfig): Promise<IDnsValidationResult> { | ||
|  |     switch (config.dnsMode) { | ||
|  |       case 'forward': | ||
|  |         return this.validateForwardMode(config); | ||
|  |       case 'internal-dns': | ||
|  |         return this.validateInternalDnsMode(config); | ||
|  |       case 'external-dns': | ||
|  |         return this.validateExternalDnsMode(config); | ||
|  |       default: | ||
|  |         return { | ||
|  |           valid: false, | ||
|  |           errors: [`Unknown DNS mode: ${config.dnsMode}`], | ||
|  |           warnings: [], | ||
|  |           requiredChanges: [] | ||
|  |         }; | ||
|  |     } | ||
|  |   } | ||
|  |    | ||
|  |   /** | ||
|  |    * Validate forward mode configuration | ||
|  |    */ | ||
|  |   private async validateForwardMode(config: IEmailDomainConfig): Promise<IDnsValidationResult> { | ||
|  |     const result: IDnsValidationResult = { | ||
|  |       valid: true, | ||
|  |       errors: [], | ||
|  |       warnings: [], | ||
|  |       requiredChanges: [] | ||
|  |     }; | ||
|  |      | ||
|  |     // Forward mode doesn't require DNS validation by default
 | ||
|  |     if (!config.dns?.forward?.skipDnsValidation) { | ||
|  |       logger.log('info', `DNS validation skipped for forward mode domain: ${config.domain}`); | ||
|  |     } | ||
|  |      | ||
|  |     // DKIM keys are still generated for consistency
 | ||
|  |     result.warnings.push( | ||
|  |       `Domain "${config.domain}" uses forward mode. DKIM keys will be generated but signing only happens if email is processed.` | ||
|  |     ); | ||
|  |      | ||
|  |     return result; | ||
|  |   } | ||
|  |    | ||
|  |   /** | ||
|  |    * Validate internal DNS mode configuration | ||
|  |    */ | ||
|  |   private async validateInternalDnsMode(config: IEmailDomainConfig): Promise<IDnsValidationResult> { | ||
|  |     const result: IDnsValidationResult = { | ||
|  |       valid: true, | ||
|  |       errors: [], | ||
|  |       warnings: [], | ||
|  |       requiredChanges: [] | ||
|  |     }; | ||
|  |      | ||
|  |     // Check if DNS configuration is set up
 | ||
|  |     const dnsNsDomains = this.dcRouter.options?.dnsNsDomains; | ||
|  |     const dnsScopes = this.dcRouter.options?.dnsScopes; | ||
|  |      | ||
|  |     if (!dnsNsDomains || dnsNsDomains.length === 0) { | ||
|  |       result.valid = false; | ||
|  |       result.errors.push( | ||
|  |         `Domain "${config.domain}" is configured to use internal DNS, but dnsNsDomains is not set in DcRouter configuration.` | ||
|  |       ); | ||
|  |       console.error( | ||
|  |         `❌ ERROR: Domain "${config.domain}" is configured to use internal DNS,\n` + | ||
|  |         '   but dnsNsDomains is not set in DcRouter configuration.\n' + | ||
|  |         '   Please configure dnsNsDomains to enable the DNS server.\n' + | ||
|  |         '   Example: dnsNsDomains: ["ns1.myservice.com", "ns2.myservice.com"]' | ||
|  |       ); | ||
|  |       return result; | ||
|  |     } | ||
|  |      | ||
|  |     if (!dnsScopes || dnsScopes.length === 0) { | ||
|  |       result.valid = false; | ||
|  |       result.errors.push( | ||
|  |         `Domain "${config.domain}" is configured to use internal DNS, but dnsScopes is not set in DcRouter configuration.` | ||
|  |       ); | ||
|  |       console.error( | ||
|  |         `❌ ERROR: Domain "${config.domain}" is configured to use internal DNS,\n` + | ||
|  |         '   but dnsScopes is not set in DcRouter configuration.\n' + | ||
|  |         '   Please configure dnsScopes to define authoritative domains.\n' + | ||
|  |         '   Example: dnsScopes: ["myservice.com", "mail.myservice.com"]' | ||
|  |       ); | ||
|  |       return result; | ||
|  |     } | ||
|  |      | ||
|  |     // Check if the email domain is in dnsScopes
 | ||
|  |     if (!dnsScopes.includes(config.domain)) { | ||
|  |       result.valid = false; | ||
|  |       result.errors.push( | ||
|  |         `Domain "${config.domain}" is configured to use internal DNS, but is not included in dnsScopes.` | ||
|  |       ); | ||
|  |       console.error( | ||
|  |         `❌ ERROR: Domain "${config.domain}" is configured to use internal DNS,\n` + | ||
|  |         `   but is not included in dnsScopes: [${dnsScopes.join(', ')}].\n` + | ||
|  |         '   Please add this domain to dnsScopes to enable internal DNS.\n' + | ||
|  |         `   Example: dnsScopes: [..., "${config.domain}"]` | ||
|  |       ); | ||
|  |       return result; | ||
|  |     } | ||
|  |      | ||
|  |     const primaryNameserver = dnsNsDomains[0]; | ||
|  |      | ||
|  |     // Check NS delegation
 | ||
|  |     try { | ||
|  |       const nsRecords = await this.resolveNs(config.domain); | ||
|  |       const delegatedNameservers = dnsNsDomains.filter(ns => nsRecords.includes(ns)); | ||
|  |       const isDelegated = delegatedNameservers.length > 0; | ||
|  |        | ||
|  |       if (!isDelegated) { | ||
|  |         result.warnings.push( | ||
|  |           `NS delegation not found for ${config.domain}. Please add NS records at your registrar.` | ||
|  |         ); | ||
|  |         dnsNsDomains.forEach(ns => { | ||
|  |           result.requiredChanges.push( | ||
|  |             `Add NS record: ${config.domain}. NS ${ns}.` | ||
|  |           ); | ||
|  |         }); | ||
|  |          | ||
|  |         console.log( | ||
|  |           `📋 DNS Delegation Required for ${config.domain}:\n` + | ||
|  |           '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' + | ||
|  |           'Please add these NS records at your domain registrar:\n' + | ||
|  |           dnsNsDomains.map(ns => `  ${config.domain}. NS ${ns}.`).join('\n') + '\n' + | ||
|  |           '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' + | ||
|  |           'This delegation is required for internal DNS mode to work.' | ||
|  |         ); | ||
|  |       } else { | ||
|  |         console.log( | ||
|  |           `✅ NS delegation verified: ${config.domain} -> [${delegatedNameservers.join(', ')}]` | ||
|  |         ); | ||
|  |       } | ||
|  |     } catch (error) { | ||
|  |       result.warnings.push( | ||
|  |         `Could not verify NS delegation for ${config.domain}: ${error.message}` | ||
|  |       ); | ||
|  |     } | ||
|  |      | ||
|  |     return result; | ||
|  |   } | ||
|  |    | ||
|  |   /** | ||
|  |    * Validate external DNS mode configuration | ||
|  |    */ | ||
|  |   private async validateExternalDnsMode(config: IEmailDomainConfig): Promise<IDnsValidationResult> { | ||
|  |     const result: IDnsValidationResult = { | ||
|  |       valid: true, | ||
|  |       errors: [], | ||
|  |       warnings: [], | ||
|  |       requiredChanges: [] | ||
|  |     }; | ||
|  |      | ||
|  |     try { | ||
|  |       // Get current DNS records
 | ||
|  |       const records = await this.checkDnsRecords(config); | ||
|  |       const requiredRecords = config.dns?.external?.requiredRecords || ['MX', 'SPF', 'DKIM', 'DMARC']; | ||
|  |        | ||
|  |       // Check MX record
 | ||
|  |       if (requiredRecords.includes('MX') && !records.mx?.length) { | ||
|  |         result.requiredChanges.push( | ||
|  |           `Add MX record: ${this.getBaseDomain(config.domain)} -> ${config.domain} (priority 10)` | ||
|  |         ); | ||
|  |       } | ||
|  |        | ||
|  |       // Check SPF record
 | ||
|  |       if (requiredRecords.includes('SPF') && !records.spf) { | ||
|  |         result.requiredChanges.push( | ||
|  |           `Add TXT record: ${this.getBaseDomain(config.domain)} -> "v=spf1 a mx ~all"` | ||
|  |         ); | ||
|  |       } | ||
|  |        | ||
|  |       // Check DKIM record
 | ||
|  |       if (requiredRecords.includes('DKIM') && !records.dkim) { | ||
|  |         const selector = config.dkim?.selector || 'default'; | ||
|  |         const dkimPublicKey = await this.storageManager.get(`/email/dkim/${config.domain}/public.key`); | ||
|  |          | ||
|  |         if (dkimPublicKey) { | ||
|  |           const publicKeyBase64 = dkimPublicKey | ||
|  |             .replace(/-----BEGIN PUBLIC KEY-----/g, '') | ||
|  |             .replace(/-----END PUBLIC KEY-----/g, '') | ||
|  |             .replace(/\s/g, ''); | ||
|  |              | ||
|  |           result.requiredChanges.push( | ||
|  |             `Add TXT record: ${selector}._domainkey.${config.domain} -> "v=DKIM1; k=rsa; p=${publicKeyBase64}"` | ||
|  |           ); | ||
|  |         } else { | ||
|  |           result.warnings.push( | ||
|  |             `DKIM public key not found for ${config.domain}. It will be generated on first use.` | ||
|  |           ); | ||
|  |         } | ||
|  |       } | ||
|  |        | ||
|  |       // Check DMARC record
 | ||
|  |       if (requiredRecords.includes('DMARC') && !records.dmarc) { | ||
|  |         result.requiredChanges.push( | ||
|  |           `Add TXT record: _dmarc.${this.getBaseDomain(config.domain)} -> "v=DMARC1; p=none; rua=mailto:dmarc@${config.domain}"` | ||
|  |         ); | ||
|  |       } | ||
|  |        | ||
|  |       // Show setup instructions if needed
 | ||
|  |       if (result.requiredChanges.length > 0) { | ||
|  |         console.log( | ||
|  |           `📋 DNS Configuration Required for ${config.domain}:\n` + | ||
|  |           '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' + | ||
|  |           result.requiredChanges.map((change, i) => `${i + 1}. ${change}`).join('\n') + | ||
|  |           '\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━' | ||
|  |         ); | ||
|  |       } | ||
|  |        | ||
|  |     } catch (error) { | ||
|  |       result.errors.push(`DNS validation failed: ${error.message}`); | ||
|  |       result.valid = false; | ||
|  |     } | ||
|  |      | ||
|  |     return result; | ||
|  |   } | ||
|  |    | ||
|  |   /** | ||
|  |    * Check DNS records for a domain | ||
|  |    */ | ||
|  |   private async checkDnsRecords(config: IEmailDomainConfig): Promise<IDnsRecords> { | ||
|  |     const records: IDnsRecords = {}; | ||
|  |     const baseDomain = this.getBaseDomain(config.domain); | ||
|  |     const selector = config.dkim?.selector || 'default'; | ||
|  |      | ||
|  |     // Use custom DNS servers if specified
 | ||
|  |     const resolver = new plugins.dns.promises.Resolver(); | ||
|  |     if (config.dns?.external?.servers?.length) { | ||
|  |       resolver.setServers(config.dns.external.servers); | ||
|  |     } | ||
|  |      | ||
|  |     // Check MX records
 | ||
|  |     try { | ||
|  |       const mxRecords = await resolver.resolveMx(baseDomain); | ||
|  |       records.mx = mxRecords.map(mx => mx.exchange); | ||
|  |     } catch (error) { | ||
|  |       logger.log('debug', `No MX records found for ${baseDomain}`); | ||
|  |     } | ||
|  |      | ||
|  |     // Check SPF record
 | ||
|  |     try { | ||
|  |       const txtRecords = await resolver.resolveTxt(baseDomain); | ||
|  |       const spfRecord = txtRecords.find(records =>  | ||
|  |         records.some(record => record.startsWith('v=spf1')) | ||
|  |       ); | ||
|  |       if (spfRecord) { | ||
|  |         records.spf = spfRecord.join(''); | ||
|  |       } | ||
|  |     } catch (error) { | ||
|  |       logger.log('debug', `No SPF record found for ${baseDomain}`); | ||
|  |     } | ||
|  |      | ||
|  |     // Check DKIM record
 | ||
|  |     try { | ||
|  |       const dkimRecords = await resolver.resolveTxt(`${selector}._domainkey.${config.domain}`); | ||
|  |       const dkimRecord = dkimRecords.find(records =>  | ||
|  |         records.some(record => record.includes('v=DKIM1')) | ||
|  |       ); | ||
|  |       if (dkimRecord) { | ||
|  |         records.dkim = dkimRecord.join(''); | ||
|  |       } | ||
|  |     } catch (error) { | ||
|  |       logger.log('debug', `No DKIM record found for ${selector}._domainkey.${config.domain}`); | ||
|  |     } | ||
|  |      | ||
|  |     // Check DMARC record
 | ||
|  |     try { | ||
|  |       const dmarcRecords = await resolver.resolveTxt(`_dmarc.${baseDomain}`); | ||
|  |       const dmarcRecord = dmarcRecords.find(records =>  | ||
|  |         records.some(record => record.startsWith('v=DMARC1')) | ||
|  |       ); | ||
|  |       if (dmarcRecord) { | ||
|  |         records.dmarc = dmarcRecord.join(''); | ||
|  |       } | ||
|  |     } catch (error) { | ||
|  |       logger.log('debug', `No DMARC record found for _dmarc.${baseDomain}`); | ||
|  |     } | ||
|  |      | ||
|  |     return records; | ||
|  |   } | ||
|  |    | ||
|  |   /** | ||
|  |    * Resolve NS records for a domain | ||
|  |    */ | ||
|  |   private async resolveNs(domain: string): Promise<string[]> { | ||
|  |     try { | ||
|  |       const resolver = new plugins.dns.promises.Resolver(); | ||
|  |       const nsRecords = await resolver.resolveNs(domain); | ||
|  |       return nsRecords; | ||
|  |     } catch (error) { | ||
|  |       logger.log('warn', `Failed to resolve NS records for ${domain}: ${error.message}`); | ||
|  |       return []; | ||
|  |     } | ||
|  |   } | ||
|  |    | ||
|  |   /** | ||
|  |    * Get base domain from email domain (e.g., mail.example.com -> example.com) | ||
|  |    */ | ||
|  |   private getBaseDomain(domain: string): string { | ||
|  |     const parts = domain.split('.'); | ||
|  |     if (parts.length <= 2) { | ||
|  |       return domain; | ||
|  |     } | ||
|  |      | ||
|  |     // For subdomains like mail.example.com, return example.com
 | ||
|  |     // But preserve domain structure for longer TLDs like .co.uk
 | ||
|  |     if (parts[parts.length - 2].length <= 3 && parts[parts.length - 1].length === 2) { | ||
|  |       // Likely a country code TLD like .co.uk
 | ||
|  |       return parts.slice(-3).join('.'); | ||
|  |     } | ||
|  |      | ||
|  |     return parts.slice(-2).join('.'); | ||
|  |   } | ||
|  |    | ||
|  |   /** | ||
|  |    * Ensure all DNS records are created for configured domains | ||
|  |    * This is the main entry point for DNS record management | ||
|  |    */ | ||
|  |   async ensureDnsRecords(domainConfigs: IEmailDomainConfig[], dkimCreator?: any): Promise<void> { | ||
|  |     logger.log('info', `Ensuring DNS records for ${domainConfigs.length} domains`); | ||
|  |      | ||
|  |     // First, validate all domains
 | ||
|  |     const validationResults = await this.validateAllDomains(domainConfigs); | ||
|  |      | ||
|  |     // Then create records for internal-dns domains
 | ||
|  |     const internalDnsDomains = domainConfigs.filter(config => config.dnsMode === 'internal-dns'); | ||
|  |     if (internalDnsDomains.length > 0) { | ||
|  |       await this.createInternalDnsRecords(internalDnsDomains); | ||
|  |        | ||
|  |       // Create DKIM records if DKIMCreator is provided
 | ||
|  |       if (dkimCreator) { | ||
|  |         await this.createDkimRecords(domainConfigs, dkimCreator); | ||
|  |       } | ||
|  |     } | ||
|  |      | ||
|  |     // Log validation results for external-dns domains
 | ||
|  |     for (const [domain, result] of validationResults) { | ||
|  |       const config = domainConfigs.find(c => c.domain === domain); | ||
|  |       if (config?.dnsMode === 'external-dns' && result.requiredChanges.length > 0) { | ||
|  |         logger.log('warn', `External DNS configuration required for ${domain}`); | ||
|  |       } | ||
|  |     } | ||
|  |   } | ||
|  |    | ||
|  |   /** | ||
|  |    * Create DNS records for internal-dns mode domains | ||
|  |    */ | ||
|  |   private async createInternalDnsRecords(domainConfigs: IEmailDomainConfig[]): Promise<void> { | ||
|  |     // Check if DNS server is available
 | ||
|  |     if (!this.dcRouter.dnsServer) { | ||
|  |       logger.log('warn', 'DNS server not available, skipping internal DNS record creation'); | ||
|  |       return; | ||
|  |     } | ||
|  |      | ||
|  |     logger.log('info', `Creating DNS records for ${domainConfigs.length} internal-dns domains`); | ||
|  |      | ||
|  |     for (const domainConfig of domainConfigs) { | ||
|  |       const domain = domainConfig.domain; | ||
|  |       const ttl = domainConfig.dns?.internal?.ttl || 3600; | ||
|  |       const mxPriority = domainConfig.dns?.internal?.mxPriority || 10; | ||
|  |        | ||
|  |       try { | ||
|  |         // 1. Register MX record - points to the email domain itself
 | ||
|  |         this.dcRouter.dnsServer.registerHandler( | ||
|  |           domain, | ||
|  |           ['MX'], | ||
|  |           () => ({ | ||
|  |             name: domain, | ||
|  |             type: 'MX', | ||
|  |             class: 'IN', | ||
|  |             ttl: ttl, | ||
|  |             data: { | ||
|  |               priority: mxPriority, | ||
|  |               exchange: domain | ||
|  |             } | ||
|  |           }) | ||
|  |         ); | ||
|  |         logger.log('info', `MX record registered for ${domain} -> ${domain} (priority ${mxPriority})`); | ||
|  |          | ||
|  |         // Store MX record in StorageManager
 | ||
|  |         await this.storageManager.set( | ||
|  |           `/email/dns/${domain}/mx`, | ||
|  |           JSON.stringify({ | ||
|  |             type: 'MX', | ||
|  |             priority: mxPriority, | ||
|  |             exchange: domain, | ||
|  |             ttl: ttl | ||
|  |           }) | ||
|  |         ); | ||
|  |          | ||
|  |         // 2. Register SPF record - allows the domain to send emails
 | ||
|  |         const spfRecord = `v=spf1 a mx ~all`; | ||
|  |         this.dcRouter.dnsServer.registerHandler( | ||
|  |           domain, | ||
|  |           ['TXT'], | ||
|  |           () => ({ | ||
|  |             name: domain, | ||
|  |             type: 'TXT', | ||
|  |             class: 'IN', | ||
|  |             ttl: ttl, | ||
|  |             data: spfRecord | ||
|  |           }) | ||
|  |         ); | ||
|  |         logger.log('info', `SPF record registered for ${domain}: "${spfRecord}"`); | ||
|  |          | ||
|  |         // Store SPF record in StorageManager
 | ||
|  |         await this.storageManager.set( | ||
|  |           `/email/dns/${domain}/spf`, | ||
|  |           JSON.stringify({ | ||
|  |             type: 'TXT', | ||
|  |             data: spfRecord, | ||
|  |             ttl: ttl | ||
|  |           }) | ||
|  |         ); | ||
|  |          | ||
|  |         // 3. Register DMARC record - policy for handling email authentication
 | ||
|  |         const dmarcRecord = `v=DMARC1; p=none; rua=mailto:dmarc@${domain}`; | ||
|  |         this.dcRouter.dnsServer.registerHandler( | ||
|  |           `_dmarc.${domain}`, | ||
|  |           ['TXT'], | ||
|  |           () => ({ | ||
|  |             name: `_dmarc.${domain}`, | ||
|  |             type: 'TXT', | ||
|  |             class: 'IN', | ||
|  |             ttl: ttl, | ||
|  |             data: dmarcRecord | ||
|  |           }) | ||
|  |         ); | ||
|  |         logger.log('info', `DMARC record registered for _dmarc.${domain}: "${dmarcRecord}"`); | ||
|  |          | ||
|  |         // Store DMARC record in StorageManager
 | ||
|  |         await this.storageManager.set( | ||
|  |           `/email/dns/${domain}/dmarc`, | ||
|  |           JSON.stringify({ | ||
|  |             type: 'TXT', | ||
|  |             name: `_dmarc.${domain}`, | ||
|  |             data: dmarcRecord, | ||
|  |             ttl: ttl | ||
|  |           }) | ||
|  |         ); | ||
|  |          | ||
|  |         // Log summary of DNS records created
 | ||
|  |         logger.log('info', `✅ DNS records created for ${domain}:
 | ||
|  |   - MX: ${domain} (priority ${mxPriority}) | ||
|  |   - SPF: ${spfRecord} | ||
|  |   - DMARC: ${dmarcRecord} | ||
|  |   - DKIM: Will be created when keys are generated`);
 | ||
|  |          | ||
|  |       } catch (error) { | ||
|  |         logger.log('error', `Failed to create DNS records for ${domain}: ${error.message}`); | ||
|  |       } | ||
|  |     } | ||
|  |   } | ||
|  |    | ||
|  |   /** | ||
|  |    * Create DKIM DNS records for all domains | ||
|  |    */ | ||
|  |   private async createDkimRecords(domainConfigs: IEmailDomainConfig[], dkimCreator: any): Promise<void> { | ||
|  |     for (const domainConfig of domainConfigs) { | ||
|  |       const domain = domainConfig.domain; | ||
|  |       const selector = domainConfig.dkim?.selector || 'default'; | ||
|  |        | ||
|  |       try { | ||
|  |         // Get DKIM DNS record from DKIMCreator
 | ||
|  |         const dnsRecord = await dkimCreator.getDNSRecordForDomain(domain); | ||
|  |          | ||
|  |         // For internal-dns domains, register the DNS handler
 | ||
|  |         if (domainConfig.dnsMode === 'internal-dns' && this.dcRouter.dnsServer) { | ||
|  |           const ttl = domainConfig.dns?.internal?.ttl || 3600; | ||
|  |            | ||
|  |           this.dcRouter.dnsServer.registerHandler( | ||
|  |             `${selector}._domainkey.${domain}`, | ||
|  |             ['TXT'], | ||
|  |             () => ({ | ||
|  |               name: `${selector}._domainkey.${domain}`, | ||
|  |               type: 'TXT', | ||
|  |               class: 'IN', | ||
|  |               ttl: ttl, | ||
|  |               data: dnsRecord.value | ||
|  |             }) | ||
|  |           ); | ||
|  |            | ||
|  |           logger.log('info', `DKIM DNS record registered for ${selector}._domainkey.${domain}`); | ||
|  |            | ||
|  |           // Store DKIM record in StorageManager
 | ||
|  |           await this.storageManager.set( | ||
|  |             `/email/dns/${domain}/dkim`, | ||
|  |             JSON.stringify({ | ||
|  |               type: 'TXT', | ||
|  |               name: `${selector}._domainkey.${domain}`, | ||
|  |               data: dnsRecord.value, | ||
|  |               ttl: ttl | ||
|  |             }) | ||
|  |           ); | ||
|  |         } | ||
|  |          | ||
|  |         // For external-dns domains, just log what should be configured
 | ||
|  |         if (domainConfig.dnsMode === 'external-dns') { | ||
|  |           logger.log('info', `DKIM record for external DNS: ${dnsRecord.name} -> "${dnsRecord.value}"`); | ||
|  |         } | ||
|  |          | ||
|  |       } catch (error) { | ||
|  |         logger.log('warn', `Could not create DKIM DNS record for ${domain}: ${error.message}`); | ||
|  |       } | ||
|  |     } | ||
|  |   } | ||
|  | } |