import { logger } from '../../logger.js'; import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../../security/index.js'; import type { IEmailAction, IEmailContext } from './interfaces.js'; import { Email } from '../core/classes.email.js'; import { BounceManager } from '../core/classes.bouncemanager.js'; import { UnifiedDeliveryQueue } from '../delivery/classes.delivery.queue.js'; import type { ISmtpSendResult } from '../../security/classes.rustsecuritybridge.js'; /** * Dependencies injected from UnifiedEmailServer to avoid circular imports */ export interface IActionExecutorDeps { sendOutboundEmail: (host: string, port: number, email: Email, options?: { auth?: { user: string; pass: string }; dkimDomain?: string; dkimSelector?: string; tlsOpportunistic?: boolean; }) => Promise; bounceManager: BounceManager; deliveryQueue: UnifiedDeliveryQueue; } /** * Executes email routing actions (forward, process, deliver, reject) */ export class EmailActionExecutor { constructor(private deps: IActionExecutorDeps) {} async executeAction(action: IEmailAction, email: Email, context: IEmailContext): Promise { switch (action.type) { case 'forward': await this.handleForwardAction(action, email, context); break; case 'process': await this.handleProcessAction(action, email, context); break; case 'deliver': await this.handleDeliverAction(action, email, context); break; case 'reject': await this.handleRejectAction(action, email, context); break; default: throw new Error(`Unknown action type: ${(action as any).type}`); } } private async handleForwardAction(action: IEmailAction, email: Email, context: IEmailContext): Promise { if (!action.forward) { throw new Error('Forward action requires forward configuration'); } const { host, port = 25, auth, addHeaders } = action.forward; logger.log('info', `Forwarding email to ${host}:${port}`); // Add forwarding headers if (addHeaders) { for (const [key, value] of Object.entries(addHeaders)) { email.headers[key] = value; } } // Add standard forwarding headers email.headers['X-Forwarded-For'] = context.session.remoteAddress || 'unknown'; email.headers['X-Forwarded-To'] = email.to.join(', '); email.headers['X-Forwarded-Date'] = new Date().toISOString(); try { // Send email via Rust SMTP client await this.deps.sendOutboundEmail(host, port, email, { auth: auth as { user: string; pass: string } | undefined, }); logger.log('info', `Successfully forwarded email to ${host}:${port}`); SecurityLogger.getInstance().logEvent({ level: SecurityLogLevel.INFO, type: SecurityEventType.EMAIL_FORWARDING, message: 'Email forwarded successfully', ipAddress: context.session.remoteAddress, details: { sessionId: context.session.id, routeName: context.session.matchedRoute?.name, targetHost: host, targetPort: port, recipients: email.to }, success: true }); } catch (error) { logger.log('error', `Failed to forward email: ${error.message}`); SecurityLogger.getInstance().logEvent({ level: SecurityLogLevel.ERROR, type: SecurityEventType.EMAIL_FORWARDING, message: 'Email forwarding failed', ipAddress: context.session.remoteAddress, details: { sessionId: context.session.id, routeName: context.session.matchedRoute?.name, targetHost: host, targetPort: port, error: error.message }, success: false }); // Handle as bounce for (const recipient of email.getAllRecipients()) { await this.deps.bounceManager.processSmtpFailure(recipient, error.message, { sender: email.from, originalEmailId: email.headers['Message-ID'] as string }); } throw error; } } private async handleProcessAction(action: IEmailAction, email: Email, context: IEmailContext): Promise { logger.log('info', `Processing email with action options`); // Apply scanning if requested if (action.process?.scan) { logger.log('info', 'Content scanning requested'); } // Queue for delivery const queue = action.process?.queue || 'normal'; await this.deps.deliveryQueue.enqueue(email, 'process', context.session.matchedRoute!); logger.log('info', `Email queued for delivery in ${queue} queue`); } private async handleDeliverAction(_action: IEmailAction, email: Email, context: IEmailContext): Promise { logger.log('info', `Delivering email locally`); // Queue for local delivery await this.deps.deliveryQueue.enqueue(email, 'mta', context.session.matchedRoute!); logger.log('info', 'Email queued for local delivery'); } private async handleRejectAction(action: IEmailAction, _email: Email, context: IEmailContext): Promise { const code = action.reject?.code || 550; const message = action.reject?.message || 'Message rejected'; logger.log('info', `Rejecting email with code ${code}: ${message}`); SecurityLogger.getInstance().logEvent({ level: SecurityLogLevel.WARN, type: SecurityEventType.EMAIL_PROCESSING, message: 'Email rejected by routing rule', ipAddress: context.session.remoteAddress, details: { sessionId: context.session.id, routeName: context.session.matchedRoute?.name, rejectCode: code, rejectMessage: message, from: _email.from, to: _email.to }, success: false }); // Throw error with SMTP code and message const error = new Error(message); (error as any).responseCode = code; throw error; } }