Phase 3 of the Rust migration: the Rust security bridge is now mandatory and all TypeScript security fallback implementations have been removed. - UnifiedEmailServer.start() throws if Rust bridge fails to start - SpfVerifier gutted to thin wrapper (parseSpfRecord stays in TS) - DKIMVerifier gutted to thin wrapper delegating to bridge.verifyDkim() - IPReputationChecker delegates to bridge.checkIpReputation(), keeps LRU cache - DmarcVerifier keeps alignment logic (works with pre-computed results) - DKIM signing via bridge.signDkim() in all 4 locations - Removed mailauth and ip packages from plugins.ts (~1,200 lines deleted)
846 lines
70 KiB
JavaScript
846 lines
70 KiB
JavaScript
import * as plugins from '../../plugins.js';
|
|
import { EventEmitter } from 'node:events';
|
|
import * as net from 'node:net';
|
|
import * as tls from 'node:tls';
|
|
import { logger } from '../../logger.js';
|
|
import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../../security/index.js';
|
|
import { UnifiedDeliveryQueue } from './classes.delivery.queue.js';
|
|
import { RustSecurityBridge } from '../../security/classes.rustsecuritybridge.js';
|
|
/**
|
|
* Delivery status enumeration
|
|
*/
|
|
export var DeliveryStatus;
|
|
(function (DeliveryStatus) {
|
|
DeliveryStatus["PENDING"] = "pending";
|
|
DeliveryStatus["DELIVERING"] = "delivering";
|
|
DeliveryStatus["DELIVERED"] = "delivered";
|
|
DeliveryStatus["DEFERRED"] = "deferred";
|
|
DeliveryStatus["FAILED"] = "failed";
|
|
})(DeliveryStatus || (DeliveryStatus = {}));
|
|
/**
|
|
* Handles delivery for all email processing modes
|
|
*/
|
|
export class MultiModeDeliverySystem extends EventEmitter {
|
|
queue;
|
|
options;
|
|
stats;
|
|
deliveryTimes = [];
|
|
activeDeliveries = new Set();
|
|
running = false;
|
|
throttled = false;
|
|
rateLimitLastCheck = Date.now();
|
|
rateLimitCounter = 0;
|
|
emailServer;
|
|
/**
|
|
* Create a new multi-mode delivery system
|
|
* @param queue Unified delivery queue
|
|
* @param options Delivery options
|
|
* @param emailServer Optional reference to unified email server for SmtpClient access
|
|
*/
|
|
constructor(queue, options, emailServer) {
|
|
super();
|
|
this.queue = queue;
|
|
this.emailServer = emailServer;
|
|
// Set default options
|
|
this.options = {
|
|
connectionPoolSize: options.connectionPoolSize || 10,
|
|
socketTimeout: options.socketTimeout || 30000, // 30 seconds
|
|
concurrentDeliveries: options.concurrentDeliveries || 10,
|
|
sendTimeout: options.sendTimeout || 60000, // 1 minute
|
|
verifyCertificates: options.verifyCertificates !== false, // Default to true
|
|
tlsMinVersion: options.tlsMinVersion || 'TLSv1.2',
|
|
forwardHandler: options.forwardHandler || {
|
|
deliver: this.handleForwardDelivery.bind(this)
|
|
},
|
|
deliveryHandler: options.deliveryHandler || {
|
|
deliver: this.handleMtaDelivery.bind(this)
|
|
},
|
|
processHandler: options.processHandler || {
|
|
deliver: this.handleProcessDelivery.bind(this)
|
|
},
|
|
globalRateLimit: options.globalRateLimit || 100, // 100 emails per minute
|
|
perPatternRateLimit: options.perPatternRateLimit || {},
|
|
processBounces: options.processBounces !== false, // Default to true
|
|
bounceHandler: options.bounceHandler || null,
|
|
onDeliveryStart: options.onDeliveryStart || (async () => { }),
|
|
onDeliverySuccess: options.onDeliverySuccess || (async () => { }),
|
|
onDeliveryFailed: options.onDeliveryFailed || (async () => { })
|
|
};
|
|
// Initialize statistics
|
|
this.stats = {
|
|
activeDeliveries: 0,
|
|
totalSuccessful: 0,
|
|
totalFailed: 0,
|
|
avgDeliveryTime: 0,
|
|
byMode: {
|
|
forward: {
|
|
successful: 0,
|
|
failed: 0
|
|
},
|
|
mta: {
|
|
successful: 0,
|
|
failed: 0
|
|
},
|
|
process: {
|
|
successful: 0,
|
|
failed: 0
|
|
}
|
|
},
|
|
rateLimiting: {
|
|
currentRate: 0,
|
|
globalLimit: this.options.globalRateLimit,
|
|
throttled: 0
|
|
}
|
|
};
|
|
// Set up event listeners
|
|
this.queue.on('itemsReady', this.processItems.bind(this));
|
|
}
|
|
/**
|
|
* Start the delivery system
|
|
*/
|
|
async start() {
|
|
logger.log('info', 'Starting MultiModeDeliverySystem');
|
|
if (this.running) {
|
|
logger.log('warn', 'MultiModeDeliverySystem is already running');
|
|
return;
|
|
}
|
|
this.running = true;
|
|
// Emit started event
|
|
this.emit('started');
|
|
logger.log('info', 'MultiModeDeliverySystem started successfully');
|
|
}
|
|
/**
|
|
* Stop the delivery system
|
|
*/
|
|
async stop() {
|
|
logger.log('info', 'Stopping MultiModeDeliverySystem');
|
|
if (!this.running) {
|
|
logger.log('warn', 'MultiModeDeliverySystem is already stopped');
|
|
return;
|
|
}
|
|
this.running = false;
|
|
// Wait for active deliveries to complete
|
|
if (this.activeDeliveries.size > 0) {
|
|
logger.log('info', `Waiting for ${this.activeDeliveries.size} active deliveries to complete`);
|
|
// Wait for a maximum of 30 seconds
|
|
await new Promise(resolve => {
|
|
const checkInterval = setInterval(() => {
|
|
if (this.activeDeliveries.size === 0) {
|
|
clearInterval(checkInterval);
|
|
clearTimeout(forceTimeout);
|
|
resolve();
|
|
}
|
|
}, 1000);
|
|
// Force resolve after 30 seconds
|
|
const forceTimeout = setTimeout(() => {
|
|
clearInterval(checkInterval);
|
|
resolve();
|
|
}, 30000);
|
|
});
|
|
}
|
|
// Emit stopped event
|
|
this.emit('stopped');
|
|
logger.log('info', 'MultiModeDeliverySystem stopped successfully');
|
|
}
|
|
/**
|
|
* Process ready items from the queue
|
|
* @param items Queue items ready for processing
|
|
*/
|
|
async processItems(items) {
|
|
if (!this.running) {
|
|
return;
|
|
}
|
|
// Check if we're already at max concurrent deliveries
|
|
if (this.activeDeliveries.size >= this.options.concurrentDeliveries) {
|
|
logger.log('debug', `Already at max concurrent deliveries (${this.activeDeliveries.size})`);
|
|
return;
|
|
}
|
|
// Check rate limiting
|
|
if (this.checkRateLimit()) {
|
|
logger.log('debug', 'Rate limit exceeded, throttling deliveries');
|
|
return;
|
|
}
|
|
// Calculate how many more deliveries we can start
|
|
const availableSlots = this.options.concurrentDeliveries - this.activeDeliveries.size;
|
|
const itemsToProcess = items.slice(0, availableSlots);
|
|
if (itemsToProcess.length === 0) {
|
|
return;
|
|
}
|
|
logger.log('info', `Processing ${itemsToProcess.length} items for delivery`);
|
|
// Process each item
|
|
for (const item of itemsToProcess) {
|
|
// Mark as processing
|
|
await this.queue.markProcessing(item.id);
|
|
// Add to active deliveries
|
|
this.activeDeliveries.add(item.id);
|
|
this.stats.activeDeliveries = this.activeDeliveries.size;
|
|
// Deliver asynchronously
|
|
this.deliverItem(item).catch(err => {
|
|
logger.log('error', `Unhandled error in delivery: ${err.message}`);
|
|
});
|
|
}
|
|
// Update statistics
|
|
this.emit('statsUpdated', this.stats);
|
|
}
|
|
/**
|
|
* Deliver an item from the queue
|
|
* @param item Queue item to deliver
|
|
*/
|
|
async deliverItem(item) {
|
|
const startTime = Date.now();
|
|
try {
|
|
// Call delivery start hook
|
|
await this.options.onDeliveryStart(item);
|
|
// Emit delivery start event
|
|
this.emit('deliveryStart', item);
|
|
logger.log('info', `Starting delivery of item ${item.id}, mode: ${item.processingMode}`);
|
|
// Choose the appropriate handler based on mode
|
|
let result;
|
|
switch (item.processingMode) {
|
|
case 'forward':
|
|
result = await this.options.forwardHandler.deliver(item);
|
|
break;
|
|
case 'mta':
|
|
result = await this.options.deliveryHandler.deliver(item);
|
|
break;
|
|
case 'process':
|
|
result = await this.options.processHandler.deliver(item);
|
|
break;
|
|
default:
|
|
throw new Error(`Unknown processing mode: ${item.processingMode}`);
|
|
}
|
|
// Mark as delivered
|
|
await this.queue.markDelivered(item.id);
|
|
// Update statistics
|
|
this.stats.totalSuccessful++;
|
|
this.stats.byMode[item.processingMode].successful++;
|
|
// Calculate delivery time
|
|
const deliveryTime = Date.now() - startTime;
|
|
this.deliveryTimes.push(deliveryTime);
|
|
this.updateDeliveryTimeStats();
|
|
// Call delivery success hook
|
|
await this.options.onDeliverySuccess(item, result);
|
|
// Emit delivery success event
|
|
this.emit('deliverySuccess', item, result);
|
|
logger.log('info', `Item ${item.id} delivered successfully in ${deliveryTime}ms`);
|
|
SecurityLogger.getInstance().logEvent({
|
|
level: SecurityLogLevel.INFO,
|
|
type: SecurityEventType.EMAIL_DELIVERY,
|
|
message: 'Email delivery successful',
|
|
details: {
|
|
itemId: item.id,
|
|
mode: item.processingMode,
|
|
routeName: item.route?.name || 'unknown',
|
|
deliveryTime
|
|
},
|
|
success: true
|
|
});
|
|
}
|
|
catch (error) {
|
|
// Calculate delivery attempt time even for failures
|
|
const deliveryTime = Date.now() - startTime;
|
|
// Mark as failed
|
|
await this.queue.markFailed(item.id, error.message);
|
|
// Update statistics
|
|
this.stats.totalFailed++;
|
|
this.stats.byMode[item.processingMode].failed++;
|
|
// Call delivery failed hook
|
|
await this.options.onDeliveryFailed(item, error.message);
|
|
// Process as bounce if enabled and we have a bounce handler
|
|
if (this.options.processBounces && this.options.bounceHandler) {
|
|
try {
|
|
const email = item.processingResult;
|
|
// Extract recipient and error message
|
|
// For multiple recipients, we'd need more sophisticated parsing
|
|
const recipient = email.to.length > 0 ? email.to[0] : '';
|
|
if (recipient) {
|
|
logger.log('info', `Processing delivery failure as bounce for recipient ${recipient}`);
|
|
// Process SMTP failure through bounce handler
|
|
await this.options.bounceHandler.processSmtpFailure(recipient, error.message, {
|
|
sender: email.from,
|
|
originalEmailId: item.id,
|
|
headers: email.headers
|
|
});
|
|
logger.log('info', `Bounce record created for failed delivery to ${recipient}`);
|
|
}
|
|
}
|
|
catch (bounceError) {
|
|
logger.log('error', `Failed to process bounce: ${bounceError.message}`);
|
|
}
|
|
}
|
|
// Emit delivery failed event
|
|
this.emit('deliveryFailed', item, error);
|
|
logger.log('error', `Item ${item.id} delivery failed: ${error.message}`);
|
|
SecurityLogger.getInstance().logEvent({
|
|
level: SecurityLogLevel.ERROR,
|
|
type: SecurityEventType.EMAIL_DELIVERY,
|
|
message: 'Email delivery failed',
|
|
details: {
|
|
itemId: item.id,
|
|
mode: item.processingMode,
|
|
routeName: item.route?.name || 'unknown',
|
|
error: error.message,
|
|
deliveryTime
|
|
},
|
|
success: false
|
|
});
|
|
}
|
|
finally {
|
|
// Remove from active deliveries
|
|
this.activeDeliveries.delete(item.id);
|
|
this.stats.activeDeliveries = this.activeDeliveries.size;
|
|
// Update statistics
|
|
this.emit('statsUpdated', this.stats);
|
|
}
|
|
}
|
|
/**
|
|
* Default handler for forward mode delivery
|
|
* @param item Queue item
|
|
*/
|
|
async handleForwardDelivery(item) {
|
|
logger.log('info', `Forward delivery for item ${item.id}`);
|
|
const email = item.processingResult;
|
|
const route = item.route;
|
|
// Get target server information
|
|
const targetServer = route?.action.forward?.host;
|
|
const targetPort = route?.action.forward?.port || 25;
|
|
const useTls = false; // TLS configuration can be enhanced later
|
|
if (!targetServer) {
|
|
throw new Error('No target server configured for forward mode');
|
|
}
|
|
logger.log('info', `Forwarding email to ${targetServer}:${targetPort}, TLS: ${useTls}`);
|
|
try {
|
|
// Get SMTP client from email server if available
|
|
if (!this.emailServer) {
|
|
// Fall back to raw socket implementation if no email server
|
|
logger.log('warn', 'No email server available, falling back to raw socket implementation');
|
|
return this.handleForwardDeliveryLegacy(item);
|
|
}
|
|
// Get SMTP client from UnifiedEmailServer
|
|
const smtpClient = this.emailServer.getSmtpClient(targetServer, targetPort);
|
|
// Apply DKIM signing if configured in the route
|
|
if (item.route?.action.options?.mtaOptions?.dkimSign) {
|
|
await this.applyDkimSigning(email, item.route.action.options.mtaOptions);
|
|
}
|
|
// Send the email using SmtpClient
|
|
const result = await smtpClient.sendMail(email);
|
|
if (result.success) {
|
|
logger.log('info', `Email forwarded successfully to ${targetServer}:${targetPort}`);
|
|
return {
|
|
targetServer: targetServer,
|
|
targetPort: targetPort,
|
|
recipients: result.acceptedRecipients.length,
|
|
messageId: result.messageId,
|
|
rejectedRecipients: result.rejectedRecipients
|
|
};
|
|
}
|
|
else {
|
|
throw new Error(result.error?.message || 'Failed to forward email');
|
|
}
|
|
}
|
|
catch (error) {
|
|
logger.log('error', `Failed to forward email: ${error.message}`);
|
|
throw error;
|
|
}
|
|
}
|
|
/**
|
|
* Legacy forward delivery using raw sockets (fallback)
|
|
* @param item Queue item
|
|
*/
|
|
async handleForwardDeliveryLegacy(item) {
|
|
const email = item.processingResult;
|
|
const route = item.route;
|
|
// Get target server information
|
|
const targetServer = route?.action.forward?.host;
|
|
const targetPort = route?.action.forward?.port || 25;
|
|
const useTls = false; // TLS configuration can be enhanced later
|
|
if (!targetServer) {
|
|
throw new Error('No target server configured for forward mode');
|
|
}
|
|
// Create a socket connection to the target server
|
|
const socket = new net.Socket();
|
|
// Set timeout
|
|
socket.setTimeout(this.options.socketTimeout);
|
|
try {
|
|
// Connect to the target server
|
|
await new Promise((resolve, reject) => {
|
|
// Handle connection events
|
|
socket.on('connect', () => {
|
|
logger.log('debug', `Connected to ${targetServer}:${targetPort}`);
|
|
resolve();
|
|
});
|
|
socket.on('timeout', () => {
|
|
reject(new Error(`Connection timeout to ${targetServer}:${targetPort}`));
|
|
});
|
|
socket.on('error', (err) => {
|
|
reject(new Error(`Connection error to ${targetServer}:${targetPort}: ${err.message}`));
|
|
});
|
|
// Connect to the server
|
|
socket.connect({
|
|
host: targetServer,
|
|
port: targetPort
|
|
});
|
|
});
|
|
// Send EHLO
|
|
await this.smtpCommand(socket, `EHLO ${route?.action.options?.mtaOptions?.domain || 'localhost'}`);
|
|
// Start TLS if required
|
|
if (useTls) {
|
|
await this.smtpCommand(socket, 'STARTTLS');
|
|
// Upgrade to TLS
|
|
const tlsSocket = await this.upgradeTls(socket, targetServer);
|
|
// Send EHLO again after STARTTLS
|
|
await this.smtpCommand(tlsSocket, `EHLO ${route?.action.options?.mtaOptions?.domain || 'localhost'}`);
|
|
// Use tlsSocket for remaining commands
|
|
return this.completeSMTPExchange(tlsSocket, email, route);
|
|
}
|
|
// Complete the SMTP exchange
|
|
return this.completeSMTPExchange(socket, email, route);
|
|
}
|
|
catch (error) {
|
|
logger.log('error', `Failed to forward email: ${error.message}`);
|
|
// Close the connection
|
|
socket.destroy();
|
|
throw error;
|
|
}
|
|
}
|
|
/**
|
|
* Complete the SMTP exchange after connection and initial setup
|
|
* @param socket Network socket
|
|
* @param email Email to send
|
|
* @param rule Domain rule
|
|
*/
|
|
async completeSMTPExchange(socket, email, route) {
|
|
try {
|
|
// Authenticate if credentials provided
|
|
if (route?.action?.forward?.auth?.user && route?.action?.forward?.auth?.pass) {
|
|
// Send AUTH LOGIN
|
|
await this.smtpCommand(socket, 'AUTH LOGIN');
|
|
// Send username (base64)
|
|
const username = Buffer.from(route.action.forward.auth.user).toString('base64');
|
|
await this.smtpCommand(socket, username);
|
|
// Send password (base64)
|
|
const password = Buffer.from(route.action.forward.auth.pass).toString('base64');
|
|
await this.smtpCommand(socket, password);
|
|
}
|
|
// Send MAIL FROM
|
|
await this.smtpCommand(socket, `MAIL FROM:<${email.from}>`);
|
|
// Send RCPT TO for each recipient
|
|
for (const recipient of email.getAllRecipients()) {
|
|
await this.smtpCommand(socket, `RCPT TO:<${recipient}>`);
|
|
}
|
|
// Send DATA
|
|
await this.smtpCommand(socket, 'DATA');
|
|
// Send email content (simplified)
|
|
const emailContent = await this.getFormattedEmail(email);
|
|
await this.smtpData(socket, emailContent);
|
|
// Send QUIT
|
|
await this.smtpCommand(socket, 'QUIT');
|
|
// Close the connection
|
|
socket.end();
|
|
logger.log('info', `Email forwarded successfully to ${route?.action?.forward?.host}:${route?.action?.forward?.port || 25}`);
|
|
return {
|
|
targetServer: route?.action?.forward?.host,
|
|
targetPort: route?.action?.forward?.port || 25,
|
|
recipients: email.getAllRecipients().length
|
|
};
|
|
}
|
|
catch (error) {
|
|
logger.log('error', `Failed to forward email: ${error.message}`);
|
|
// Close the connection
|
|
socket.destroy();
|
|
throw error;
|
|
}
|
|
}
|
|
/**
|
|
* Default handler for MTA mode delivery
|
|
* @param item Queue item
|
|
*/
|
|
async handleMtaDelivery(item) {
|
|
logger.log('info', `MTA delivery for item ${item.id}`);
|
|
const email = item.processingResult;
|
|
const route = item.route;
|
|
try {
|
|
// Apply DKIM signing if configured in the route
|
|
if (item.route?.action.options?.mtaOptions?.dkimSign) {
|
|
await this.applyDkimSigning(email, item.route.action.options.mtaOptions);
|
|
}
|
|
// In a full implementation, this would use the MTA service
|
|
// For now, we'll simulate a successful delivery
|
|
logger.log('info', `Email processed by MTA: ${email.subject} to ${email.getAllRecipients().join(', ')}`);
|
|
// Note: The MTA implementation would handle actual local delivery
|
|
// Simulate successful delivery
|
|
return {
|
|
recipients: email.getAllRecipients().length,
|
|
subject: email.subject,
|
|
dkimSigned: !!item.route?.action.options?.mtaOptions?.dkimSign
|
|
};
|
|
}
|
|
catch (error) {
|
|
logger.log('error', `Failed to process email in MTA mode: ${error.message}`);
|
|
throw error;
|
|
}
|
|
}
|
|
/**
|
|
* Default handler for process mode delivery
|
|
* @param item Queue item
|
|
*/
|
|
async handleProcessDelivery(item) {
|
|
logger.log('info', `Process delivery for item ${item.id}`);
|
|
const email = item.processingResult;
|
|
const route = item.route;
|
|
try {
|
|
// Apply content scanning if enabled
|
|
if (route?.action.options?.contentScanning && route?.action.options?.scanners && route.action.options.scanners.length > 0) {
|
|
logger.log('info', 'Performing content scanning');
|
|
// Apply each scanner
|
|
for (const scanner of route.action.options.scanners) {
|
|
switch (scanner.type) {
|
|
case 'spam':
|
|
logger.log('info', 'Scanning for spam content');
|
|
// Implement spam scanning
|
|
break;
|
|
case 'virus':
|
|
logger.log('info', 'Scanning for virus content');
|
|
// Implement virus scanning
|
|
break;
|
|
case 'attachment':
|
|
logger.log('info', 'Scanning attachments');
|
|
// Check for blocked extensions
|
|
if (scanner.blockedExtensions && scanner.blockedExtensions.length > 0) {
|
|
for (const attachment of email.attachments) {
|
|
const ext = this.getFileExtension(attachment.filename);
|
|
if (scanner.blockedExtensions.includes(ext)) {
|
|
if (scanner.action === 'reject') {
|
|
throw new Error(`Blocked attachment type: ${ext}`);
|
|
}
|
|
else { // tag
|
|
email.addHeader('X-Attachment-Warning', `Potentially unsafe attachment: ${attachment.filename}`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// Apply transformations if defined
|
|
if (route?.action.options?.transformations && route?.action.options?.transformations.length > 0) {
|
|
logger.log('info', 'Applying email transformations');
|
|
for (const transform of route.action.options.transformations) {
|
|
switch (transform.type) {
|
|
case 'addHeader':
|
|
if (transform.header && transform.value) {
|
|
email.addHeader(transform.header, transform.value);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// Apply DKIM signing if configured (after all transformations)
|
|
if (item.route?.action.options?.mtaOptions?.dkimSign || item.route?.action.process?.dkim) {
|
|
await this.applyDkimSigning(email, item.route.action.options?.mtaOptions || {});
|
|
}
|
|
logger.log('info', `Email successfully processed in store-and-forward mode`);
|
|
// Simulate successful delivery
|
|
return {
|
|
recipients: email.getAllRecipients().length,
|
|
subject: email.subject,
|
|
scanned: !!route?.action.options?.contentScanning,
|
|
transformed: !!(route?.action.options?.transformations && route?.action.options?.transformations.length > 0),
|
|
dkimSigned: !!(item.route?.action.options?.mtaOptions?.dkimSign || item.route?.action.process?.dkim)
|
|
};
|
|
}
|
|
catch (error) {
|
|
logger.log('error', `Failed to process email: ${error.message}`);
|
|
throw error;
|
|
}
|
|
}
|
|
/**
|
|
* Get file extension from filename
|
|
*/
|
|
getFileExtension(filename) {
|
|
return filename.substring(filename.lastIndexOf('.')).toLowerCase();
|
|
}
|
|
/**
|
|
* Apply DKIM signing to an email
|
|
*/
|
|
async applyDkimSigning(email, mtaOptions) {
|
|
if (!this.emailServer) {
|
|
logger.log('warn', 'Cannot apply DKIM signing without email server reference');
|
|
return;
|
|
}
|
|
const domainName = mtaOptions.dkimOptions?.domainName || email.from.split('@')[1];
|
|
const keySelector = mtaOptions.dkimOptions?.keySelector || 'default';
|
|
try {
|
|
// Ensure DKIM keys exist for the domain
|
|
await this.emailServer.dkimCreator.handleDKIMKeysForDomain(domainName);
|
|
// Get the private key
|
|
const dkimPrivateKey = (await this.emailServer.dkimCreator.readDKIMKeys(domainName)).privateKey;
|
|
// Convert Email to raw format for signing
|
|
const rawEmail = email.toRFC822String();
|
|
// Sign via Rust bridge
|
|
const bridge = RustSecurityBridge.getInstance();
|
|
const signResult = await bridge.signDkim({
|
|
rawMessage: rawEmail,
|
|
domain: domainName,
|
|
selector: keySelector,
|
|
privateKey: dkimPrivateKey,
|
|
});
|
|
if (signResult.header) {
|
|
email.addHeader('DKIM-Signature', signResult.header);
|
|
logger.log('info', `Successfully added DKIM signature for ${domainName}`);
|
|
}
|
|
}
|
|
catch (error) {
|
|
logger.log('error', `Failed to apply DKIM signature: ${error.message}`);
|
|
// Don't throw - allow email to be sent without DKIM if signing fails
|
|
}
|
|
}
|
|
/**
|
|
* Format email for SMTP transmission
|
|
* @param email Email to format
|
|
*/
|
|
async getFormattedEmail(email) {
|
|
// This is a simplified implementation
|
|
// In a full implementation, this would use proper MIME formatting
|
|
let content = '';
|
|
// Add headers
|
|
content += `From: ${email.from}\r\n`;
|
|
content += `To: ${email.to.join(', ')}\r\n`;
|
|
content += `Subject: ${email.subject}\r\n`;
|
|
// Add additional headers
|
|
for (const [name, value] of Object.entries(email.headers || {})) {
|
|
content += `${name}: ${value}\r\n`;
|
|
}
|
|
// Add content type for multipart
|
|
if (email.attachments && email.attachments.length > 0) {
|
|
const boundary = `----_=_NextPart_${Math.random().toString(36).substr(2)}`;
|
|
content += `MIME-Version: 1.0\r\n`;
|
|
content += `Content-Type: multipart/mixed; boundary="${boundary}"\r\n`;
|
|
content += `\r\n`;
|
|
// Add text part
|
|
content += `--${boundary}\r\n`;
|
|
content += `Content-Type: text/plain; charset="UTF-8"\r\n`;
|
|
content += `\r\n`;
|
|
content += `${email.text}\r\n`;
|
|
// Add HTML part if present
|
|
if (email.html) {
|
|
content += `--${boundary}\r\n`;
|
|
content += `Content-Type: text/html; charset="UTF-8"\r\n`;
|
|
content += `\r\n`;
|
|
content += `${email.html}\r\n`;
|
|
}
|
|
// Add attachments
|
|
for (const attachment of email.attachments) {
|
|
content += `--${boundary}\r\n`;
|
|
content += `Content-Type: ${attachment.contentType || 'application/octet-stream'}; name="${attachment.filename}"\r\n`;
|
|
content += `Content-Disposition: attachment; filename="${attachment.filename}"\r\n`;
|
|
content += `Content-Transfer-Encoding: base64\r\n`;
|
|
content += `\r\n`;
|
|
// Add base64 encoded content
|
|
const base64Content = attachment.content.toString('base64');
|
|
// Split into lines of 76 characters
|
|
for (let i = 0; i < base64Content.length; i += 76) {
|
|
content += base64Content.substring(i, i + 76) + '\r\n';
|
|
}
|
|
}
|
|
// End boundary
|
|
content += `--${boundary}--\r\n`;
|
|
}
|
|
else {
|
|
// Simple email with just text
|
|
content += `Content-Type: text/plain; charset="UTF-8"\r\n`;
|
|
content += `\r\n`;
|
|
content += `${email.text}\r\n`;
|
|
}
|
|
return content;
|
|
}
|
|
/**
|
|
* Send SMTP command and wait for response
|
|
* @param socket Socket connection
|
|
* @param command SMTP command to send
|
|
*/
|
|
async smtpCommand(socket, command) {
|
|
return new Promise((resolve, reject) => {
|
|
const onData = (data) => {
|
|
const response = data.toString().trim();
|
|
// Clean up listeners
|
|
socket.removeListener('data', onData);
|
|
socket.removeListener('error', onError);
|
|
socket.removeListener('timeout', onTimeout);
|
|
// Check response code
|
|
if (response.charAt(0) === '2' || response.charAt(0) === '3') {
|
|
resolve(response);
|
|
}
|
|
else {
|
|
reject(new Error(`SMTP error: ${response}`));
|
|
}
|
|
};
|
|
const onError = (err) => {
|
|
// Clean up listeners
|
|
socket.removeListener('data', onData);
|
|
socket.removeListener('error', onError);
|
|
socket.removeListener('timeout', onTimeout);
|
|
reject(err);
|
|
};
|
|
const onTimeout = () => {
|
|
// Clean up listeners
|
|
socket.removeListener('data', onData);
|
|
socket.removeListener('error', onError);
|
|
socket.removeListener('timeout', onTimeout);
|
|
reject(new Error('SMTP command timeout'));
|
|
};
|
|
// Set up listeners
|
|
socket.once('data', onData);
|
|
socket.once('error', onError);
|
|
socket.once('timeout', onTimeout);
|
|
// Send command
|
|
socket.write(command + '\r\n');
|
|
});
|
|
}
|
|
/**
|
|
* Send SMTP DATA command with content
|
|
* @param socket Socket connection
|
|
* @param data Email content to send
|
|
*/
|
|
async smtpData(socket, data) {
|
|
return new Promise((resolve, reject) => {
|
|
const onData = (responseData) => {
|
|
const response = responseData.toString().trim();
|
|
// Clean up listeners
|
|
socket.removeListener('data', onData);
|
|
socket.removeListener('error', onError);
|
|
socket.removeListener('timeout', onTimeout);
|
|
// Check response code
|
|
if (response.charAt(0) === '2') {
|
|
resolve(response);
|
|
}
|
|
else {
|
|
reject(new Error(`SMTP error: ${response}`));
|
|
}
|
|
};
|
|
const onError = (err) => {
|
|
// Clean up listeners
|
|
socket.removeListener('data', onData);
|
|
socket.removeListener('error', onError);
|
|
socket.removeListener('timeout', onTimeout);
|
|
reject(err);
|
|
};
|
|
const onTimeout = () => {
|
|
// Clean up listeners
|
|
socket.removeListener('data', onData);
|
|
socket.removeListener('error', onError);
|
|
socket.removeListener('timeout', onTimeout);
|
|
reject(new Error('SMTP data timeout'));
|
|
};
|
|
// Set up listeners
|
|
socket.once('data', onData);
|
|
socket.once('error', onError);
|
|
socket.once('timeout', onTimeout);
|
|
// Send data and end with CRLF.CRLF
|
|
socket.write(data + '\r\n.\r\n');
|
|
});
|
|
}
|
|
/**
|
|
* Upgrade socket to TLS
|
|
* @param socket Socket connection
|
|
* @param hostname Target hostname for TLS
|
|
*/
|
|
async upgradeTls(socket, hostname) {
|
|
return new Promise((resolve, reject) => {
|
|
const tlsOptions = {
|
|
socket,
|
|
servername: hostname,
|
|
rejectUnauthorized: this.options.verifyCertificates,
|
|
minVersion: this.options.tlsMinVersion
|
|
};
|
|
const tlsSocket = tls.connect(tlsOptions);
|
|
tlsSocket.once('secureConnect', () => {
|
|
resolve(tlsSocket);
|
|
});
|
|
tlsSocket.once('error', (err) => {
|
|
reject(new Error(`TLS error: ${err.message}`));
|
|
});
|
|
tlsSocket.setTimeout(this.options.socketTimeout);
|
|
tlsSocket.once('timeout', () => {
|
|
reject(new Error('TLS connection timeout'));
|
|
});
|
|
});
|
|
}
|
|
/**
|
|
* Update delivery time statistics
|
|
*/
|
|
updateDeliveryTimeStats() {
|
|
if (this.deliveryTimes.length === 0)
|
|
return;
|
|
// Keep only the last 1000 delivery times
|
|
if (this.deliveryTimes.length > 1000) {
|
|
this.deliveryTimes = this.deliveryTimes.slice(-1000);
|
|
}
|
|
// Calculate average
|
|
const sum = this.deliveryTimes.reduce((acc, time) => acc + time, 0);
|
|
this.stats.avgDeliveryTime = sum / this.deliveryTimes.length;
|
|
}
|
|
/**
|
|
* Check if rate limit is exceeded
|
|
* @returns True if rate limited, false otherwise
|
|
*/
|
|
checkRateLimit() {
|
|
const now = Date.now();
|
|
const elapsed = now - this.rateLimitLastCheck;
|
|
// Reset counter if more than a minute has passed
|
|
if (elapsed >= 60000) {
|
|
this.rateLimitLastCheck = now;
|
|
this.rateLimitCounter = 0;
|
|
this.throttled = false;
|
|
this.stats.rateLimiting.currentRate = 0;
|
|
return false;
|
|
}
|
|
// Check if we're already throttled
|
|
if (this.throttled) {
|
|
return true;
|
|
}
|
|
// Increment counter
|
|
this.rateLimitCounter++;
|
|
// Calculate current rate (emails per minute)
|
|
const rate = (this.rateLimitCounter / elapsed) * 60000;
|
|
this.stats.rateLimiting.currentRate = rate;
|
|
// Check if rate limit is exceeded
|
|
if (rate > this.options.globalRateLimit) {
|
|
this.throttled = true;
|
|
this.stats.rateLimiting.throttled++;
|
|
// Schedule throttle reset
|
|
const resetDelay = 60000 - elapsed;
|
|
setTimeout(() => {
|
|
this.throttled = false;
|
|
this.rateLimitLastCheck = Date.now();
|
|
this.rateLimitCounter = 0;
|
|
this.stats.rateLimiting.currentRate = 0;
|
|
}, resetDelay);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* Update delivery options
|
|
* @param options New options
|
|
*/
|
|
updateOptions(options) {
|
|
this.options = {
|
|
...this.options,
|
|
...options
|
|
};
|
|
// Update rate limit statistics
|
|
if (options.globalRateLimit) {
|
|
this.stats.rateLimiting.globalLimit = options.globalRateLimit;
|
|
}
|
|
logger.log('info', 'MultiModeDeliverySystem options updated');
|
|
}
|
|
/**
|
|
* Get delivery statistics
|
|
*/
|
|
getStats() {
|
|
return { ...this.stats };
|
|
}
|
|
}
|
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5kZWxpdmVyeS5zeXN0ZW0uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9tYWlsL2RlbGl2ZXJ5L2NsYXNzZXMuZGVsaXZlcnkuc3lzdGVtLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFDNUMsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUMzQyxPQUFPLEtBQUssR0FBRyxNQUFNLFVBQVUsQ0FBQztBQUNoQyxPQUFPLEtBQUssR0FBRyxNQUFNLFVBQVUsQ0FBQztBQUNoQyxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDekMsT0FBTyxFQUNMLGNBQWMsRUFDZCxnQkFBZ0IsRUFDaEIsaUJBQWlCLEVBQ2xCLE1BQU0seUJBQXlCLENBQUM7QUFDakMsT0FBTyxFQUFFLG9CQUFvQixFQUFtQixNQUFNLDZCQUE2QixDQUFDO0FBSXBGLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLDhDQUE4QyxDQUFDO0FBRWxGOztHQUVHO0FBQ0gsTUFBTSxDQUFOLElBQVksY0FNWDtBQU5ELFdBQVksY0FBYztJQUN4QixxQ0FBbUIsQ0FBQTtJQUNuQiwyQ0FBeUIsQ0FBQTtJQUN6Qix5Q0FBdUIsQ0FBQTtJQUN2Qix1Q0FBcUIsQ0FBQTtJQUNyQixtQ0FBaUIsQ0FBQTtBQUNuQixDQUFDLEVBTlcsY0FBYyxLQUFkLGNBQWMsUUFNekI7QUEyRUQ7O0dBRUc7QUFDSCxNQUFNLE9BQU8sdUJBQXdCLFNBQVEsWUFBWTtJQUMvQyxLQUFLLENBQXVCO0lBQzVCLE9BQU8sQ0FBc0M7SUFDN0MsS0FBSyxDQUFpQjtJQUN0QixhQUFhLEdBQWEsRUFBRSxDQUFDO0lBQzdCLGdCQUFnQixHQUFnQixJQUFJLEdBQUcsRUFBRSxDQUFDO0lBQzFDLE9BQU8sR0FBWSxLQUFLLENBQUM7SUFDekIsU0FBUyxHQUFZLEtBQUssQ0FBQztJ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
|