1555 lines
126 KiB
JavaScript
1555 lines
126 KiB
JavaScript
import * as plugins from '../../plugins.js';
|
|
import * as paths from '../../paths.js';
|
|
import { EventEmitter } from 'events';
|
|
import { logger } from '../../logger.js';
|
|
import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../../security/index.js';
|
|
import { DKIMCreator } from '../security/classes.dkimcreator.js';
|
|
import { IPReputationChecker } from '../../security/classes.ipreputationchecker.js';
|
|
import { RustSecurityBridge } from '../../security/classes.rustsecuritybridge.js';
|
|
import { EmailRouter } from './classes.email.router.js';
|
|
import { Email } from '../core/classes.email.js';
|
|
import { DomainRegistry } from './classes.domain.registry.js';
|
|
import { DnsManager } from './classes.dns.manager.js';
|
|
import { BounceManager, BounceType, BounceCategory } from '../core/classes.bouncemanager.js';
|
|
import { createPooledSmtpClient } from '../delivery/smtpclient/create-client.js';
|
|
import { MultiModeDeliverySystem } from '../delivery/classes.delivery.system.js';
|
|
import { UnifiedDeliveryQueue } from '../delivery/classes.delivery.queue.js';
|
|
import { UnifiedRateLimiter } from '../delivery/classes.unified.rate.limiter.js';
|
|
import { SmtpState } from '../delivery/interfaces.js';
|
|
/**
|
|
* Unified email server that handles all email traffic with pattern-based routing
|
|
*/
|
|
export class UnifiedEmailServer extends EventEmitter {
|
|
dcRouter;
|
|
options;
|
|
emailRouter;
|
|
domainRegistry;
|
|
servers = [];
|
|
stats;
|
|
// Add components needed for sending and securing emails
|
|
dkimCreator;
|
|
rustBridge;
|
|
ipReputationChecker;
|
|
bounceManager;
|
|
ipWarmupManager;
|
|
senderReputationMonitor;
|
|
deliveryQueue;
|
|
deliverySystem;
|
|
rateLimiter; // TODO: Implement rate limiting in SMTP server handlers
|
|
dkimKeys = new Map(); // domain -> private key
|
|
smtpClients = new Map(); // host:port -> client
|
|
constructor(dcRouter, options) {
|
|
super();
|
|
this.dcRouter = dcRouter;
|
|
// Set default options
|
|
this.options = {
|
|
...options,
|
|
banner: options.banner || `${options.hostname} ESMTP UnifiedEmailServer`,
|
|
maxMessageSize: options.maxMessageSize || 10 * 1024 * 1024, // 10MB
|
|
maxClients: options.maxClients || 100,
|
|
maxConnections: options.maxConnections || 1000,
|
|
connectionTimeout: options.connectionTimeout || 60000, // 1 minute
|
|
socketTimeout: options.socketTimeout || 60000 // 1 minute
|
|
};
|
|
// Initialize Rust security bridge (singleton)
|
|
this.rustBridge = RustSecurityBridge.getInstance();
|
|
// Initialize DKIM creator with storage manager
|
|
this.dkimCreator = new DKIMCreator(paths.keysDir, dcRouter.storageManager);
|
|
// Initialize IP reputation checker with storage manager
|
|
this.ipReputationChecker = IPReputationChecker.getInstance({
|
|
enableLocalCache: true,
|
|
enableDNSBL: true,
|
|
enableIPInfo: true
|
|
}, dcRouter.storageManager);
|
|
// Initialize bounce manager with storage manager
|
|
this.bounceManager = new BounceManager({
|
|
maxCacheSize: 10000,
|
|
cacheTTL: 30 * 24 * 60 * 60 * 1000, // 30 days
|
|
storageManager: dcRouter.storageManager
|
|
});
|
|
// IP warmup manager and sender reputation monitor are optional
|
|
// They will be initialized when the deliverability module is available
|
|
this.ipWarmupManager = null;
|
|
this.senderReputationMonitor = null;
|
|
// Initialize domain registry
|
|
this.domainRegistry = new DomainRegistry(options.domains, options.defaults);
|
|
// Initialize email router with routes and storage manager
|
|
this.emailRouter = new EmailRouter(options.routes || [], {
|
|
storageManager: dcRouter.storageManager,
|
|
persistChanges: true
|
|
});
|
|
// Initialize rate limiter
|
|
this.rateLimiter = new UnifiedRateLimiter(options.rateLimits || {
|
|
global: {
|
|
maxConnectionsPerIP: 10,
|
|
maxMessagesPerMinute: 100,
|
|
maxRecipientsPerMessage: 50,
|
|
maxErrorsPerIP: 10,
|
|
maxAuthFailuresPerIP: 5,
|
|
blockDuration: 300000 // 5 minutes
|
|
}
|
|
});
|
|
// Initialize delivery components
|
|
const queueOptions = {
|
|
storageType: 'memory', // Default to memory storage
|
|
maxRetries: 3,
|
|
baseRetryDelay: 300000, // 5 minutes
|
|
maxRetryDelay: 3600000 // 1 hour
|
|
};
|
|
this.deliveryQueue = new UnifiedDeliveryQueue(queueOptions);
|
|
const deliveryOptions = {
|
|
globalRateLimit: 100, // Default to 100 emails per minute
|
|
concurrentDeliveries: 10,
|
|
processBounces: true,
|
|
bounceHandler: {
|
|
processSmtpFailure: this.processSmtpFailure.bind(this)
|
|
},
|
|
onDeliverySuccess: async (item, _result) => {
|
|
// Record delivery success event for reputation monitoring
|
|
const email = item.processingResult;
|
|
const senderDomain = email.from.split('@')[1];
|
|
if (senderDomain) {
|
|
this.recordReputationEvent(senderDomain, {
|
|
type: 'delivered',
|
|
count: email.to.length
|
|
});
|
|
}
|
|
}
|
|
};
|
|
this.deliverySystem = new MultiModeDeliverySystem(this.deliveryQueue, deliveryOptions, this);
|
|
// Initialize statistics
|
|
this.stats = {
|
|
startTime: new Date(),
|
|
connections: {
|
|
current: 0,
|
|
total: 0
|
|
},
|
|
messages: {
|
|
processed: 0,
|
|
delivered: 0,
|
|
failed: 0
|
|
},
|
|
processingTime: {
|
|
avg: 0,
|
|
max: 0,
|
|
min: 0
|
|
}
|
|
};
|
|
// We'll create the SMTP servers during the start() method
|
|
}
|
|
/**
|
|
* Get or create an SMTP client for the given host and port
|
|
* Uses connection pooling for efficiency
|
|
*/
|
|
getSmtpClient(host, port = 25) {
|
|
const clientKey = `${host}:${port}`;
|
|
// Check if we already have a client for this destination
|
|
let client = this.smtpClients.get(clientKey);
|
|
if (!client) {
|
|
// Create a new pooled SMTP client
|
|
client = createPooledSmtpClient({
|
|
host,
|
|
port,
|
|
secure: port === 465,
|
|
connectionTimeout: this.options.outbound?.connectionTimeout || 30000,
|
|
socketTimeout: this.options.outbound?.socketTimeout || 120000,
|
|
maxConnections: this.options.outbound?.maxConnections || 10,
|
|
maxMessages: 1000, // Messages per connection before reconnect
|
|
pool: true,
|
|
debug: false
|
|
});
|
|
this.smtpClients.set(clientKey, client);
|
|
logger.log('info', `Created new SMTP client pool for ${clientKey}`);
|
|
}
|
|
return client;
|
|
}
|
|
/**
|
|
* Start the unified email server
|
|
*/
|
|
async start() {
|
|
logger.log('info', `Starting UnifiedEmailServer on ports: ${this.options.ports.join(', ')}`);
|
|
try {
|
|
// Initialize the delivery queue
|
|
await this.deliveryQueue.initialize();
|
|
logger.log('info', 'Email delivery queue initialized');
|
|
// Start the delivery system
|
|
await this.deliverySystem.start();
|
|
logger.log('info', 'Email delivery system started');
|
|
// Start Rust security bridge — required for all security operations
|
|
const bridgeOk = await this.rustBridge.start();
|
|
if (!bridgeOk) {
|
|
throw new Error('Rust security bridge failed to start. The mailer-bin binary is required. Run "pnpm build" to compile it.');
|
|
}
|
|
logger.log('info', 'Rust security bridge started — Rust is the primary security backend');
|
|
// Set up DKIM for all domains
|
|
await this.setupDkimForDomains();
|
|
logger.log('info', 'DKIM configuration completed for all domains');
|
|
// Create DNS manager and ensure all DNS records are created
|
|
const dnsManager = new DnsManager(this.dcRouter);
|
|
await dnsManager.ensureDnsRecords(this.domainRegistry.getAllConfigs(), this.dkimCreator);
|
|
logger.log('info', 'DNS records ensured for all configured domains');
|
|
// Apply per-domain rate limits
|
|
this.applyDomainRateLimits();
|
|
logger.log('info', 'Per-domain rate limits configured');
|
|
// Check and rotate DKIM keys if needed
|
|
await this.checkAndRotateDkimKeys();
|
|
logger.log('info', 'DKIM key rotation check completed');
|
|
// Ensure we have the necessary TLS options
|
|
const hasTlsConfig = this.options.tls?.keyPath && this.options.tls?.certPath;
|
|
// Prepare the certificate and key if available
|
|
let tlsCertPem;
|
|
let tlsKeyPem;
|
|
if (hasTlsConfig) {
|
|
try {
|
|
tlsKeyPem = plugins.fs.readFileSync(this.options.tls.keyPath, 'utf8');
|
|
tlsCertPem = plugins.fs.readFileSync(this.options.tls.certPath, 'utf8');
|
|
logger.log('info', 'TLS certificates loaded successfully');
|
|
}
|
|
catch (error) {
|
|
logger.log('warn', `Failed to load TLS certificates: ${error.message}`);
|
|
}
|
|
}
|
|
// --- Start Rust SMTP server ---
|
|
// Register event handlers for email reception and auth
|
|
this.rustBridge.onEmailReceived(async (data) => {
|
|
try {
|
|
await this.handleRustEmailReceived(data);
|
|
}
|
|
catch (err) {
|
|
logger.log('error', `Error handling email from Rust SMTP: ${err.message}`);
|
|
// Send rejection back to Rust
|
|
await this.rustBridge.sendEmailProcessingResult({
|
|
correlationId: data.correlationId,
|
|
accepted: false,
|
|
smtpCode: 451,
|
|
smtpMessage: 'Internal processing error',
|
|
});
|
|
}
|
|
});
|
|
this.rustBridge.onAuthRequest(async (data) => {
|
|
try {
|
|
await this.handleRustAuthRequest(data);
|
|
}
|
|
catch (err) {
|
|
logger.log('error', `Error handling auth from Rust SMTP: ${err.message}`);
|
|
await this.rustBridge.sendAuthResult({
|
|
correlationId: data.correlationId,
|
|
success: false,
|
|
message: 'Internal auth error',
|
|
});
|
|
}
|
|
});
|
|
// Determine which ports need STARTTLS and which need implicit TLS
|
|
const smtpPorts = this.options.ports.filter(p => p !== 465);
|
|
const securePort = this.options.ports.find(p => p === 465);
|
|
const started = await this.rustBridge.startSmtpServer({
|
|
hostname: this.options.hostname,
|
|
ports: smtpPorts,
|
|
securePort: securePort,
|
|
tlsCertPem,
|
|
tlsKeyPem,
|
|
maxMessageSize: this.options.maxMessageSize || 10 * 1024 * 1024,
|
|
maxConnections: this.options.maxConnections || this.options.maxClients || 100,
|
|
maxRecipients: 100,
|
|
connectionTimeoutSecs: this.options.connectionTimeout ? Math.floor(this.options.connectionTimeout / 1000) : 30,
|
|
dataTimeoutSecs: 60,
|
|
authEnabled: !!this.options.auth?.required || !!(this.options.auth?.users?.length),
|
|
maxAuthFailures: 3,
|
|
socketTimeoutSecs: this.options.socketTimeout ? Math.floor(this.options.socketTimeout / 1000) : 300,
|
|
processingTimeoutSecs: 30,
|
|
rateLimits: this.options.rateLimits ? {
|
|
maxConnectionsPerIp: this.options.rateLimits.global?.maxConnectionsPerIP || 50,
|
|
maxMessagesPerSender: this.options.rateLimits.global?.maxMessagesPerMinute || 100,
|
|
maxAuthFailuresPerIp: this.options.rateLimits.global?.maxAuthFailuresPerIP || 5,
|
|
windowSecs: 60,
|
|
} : undefined,
|
|
});
|
|
if (!started) {
|
|
throw new Error('Failed to start Rust SMTP server');
|
|
}
|
|
logger.log('info', `Rust SMTP server listening on ports: ${smtpPorts.join(', ')}${securePort ? ` + ${securePort} (TLS)` : ''}`);
|
|
logger.log('info', 'UnifiedEmailServer started successfully');
|
|
this.emit('started');
|
|
}
|
|
catch (error) {
|
|
logger.log('error', `Failed to start UnifiedEmailServer: ${error.message}`);
|
|
throw error;
|
|
}
|
|
}
|
|
/**
|
|
* Stop the unified email server
|
|
*/
|
|
async stop() {
|
|
logger.log('info', 'Stopping UnifiedEmailServer');
|
|
try {
|
|
// Stop the Rust SMTP server first
|
|
try {
|
|
await this.rustBridge.stopSmtpServer();
|
|
logger.log('info', 'Rust SMTP server stopped');
|
|
}
|
|
catch (err) {
|
|
logger.log('warn', `Error stopping Rust SMTP server: ${err.message}`);
|
|
}
|
|
// Clear the servers array - servers will be garbage collected
|
|
this.servers = [];
|
|
// Stop Rust security bridge
|
|
await this.rustBridge.stop();
|
|
// Stop the delivery system
|
|
if (this.deliverySystem) {
|
|
await this.deliverySystem.stop();
|
|
logger.log('info', 'Email delivery system stopped');
|
|
}
|
|
// Shut down the delivery queue
|
|
if (this.deliveryQueue) {
|
|
await this.deliveryQueue.shutdown();
|
|
logger.log('info', 'Email delivery queue shut down');
|
|
}
|
|
// Close all SMTP client connections
|
|
for (const [clientKey, client] of this.smtpClients) {
|
|
try {
|
|
await client.close();
|
|
logger.log('info', `Closed SMTP client pool for ${clientKey}`);
|
|
}
|
|
catch (error) {
|
|
logger.log('warn', `Error closing SMTP client for ${clientKey}: ${error.message}`);
|
|
}
|
|
}
|
|
this.smtpClients.clear();
|
|
logger.log('info', 'UnifiedEmailServer stopped successfully');
|
|
this.emit('stopped');
|
|
}
|
|
catch (error) {
|
|
logger.log('error', `Error stopping UnifiedEmailServer: ${error.message}`);
|
|
throw error;
|
|
}
|
|
}
|
|
// -----------------------------------------------------------------------
|
|
// Rust SMTP server event handlers
|
|
// -----------------------------------------------------------------------
|
|
/**
|
|
* Handle an emailReceived event from the Rust SMTP server.
|
|
* Decodes the email data, processes it through the routing system,
|
|
* and sends back the result via the correlation-ID callback.
|
|
*/
|
|
async handleRustEmailReceived(data) {
|
|
const { correlationId, mailFrom, rcptTo, remoteAddr, clientHostname, secure, authenticatedUser } = data;
|
|
logger.log('info', `Rust SMTP received email from=${mailFrom} to=${rcptTo.join(',')} remote=${remoteAddr}`);
|
|
try {
|
|
// Decode the email data
|
|
let rawMessageBuffer;
|
|
if (data.data.type === 'inline' && data.data.base64) {
|
|
rawMessageBuffer = Buffer.from(data.data.base64, 'base64');
|
|
}
|
|
else if (data.data.type === 'file' && data.data.path) {
|
|
rawMessageBuffer = plugins.fs.readFileSync(data.data.path);
|
|
// Clean up temp file
|
|
try {
|
|
plugins.fs.unlinkSync(data.data.path);
|
|
}
|
|
catch {
|
|
// Ignore cleanup errors
|
|
}
|
|
}
|
|
else {
|
|
throw new Error('Invalid email data transport');
|
|
}
|
|
// Build a session-like object for processEmailByMode
|
|
const session = {
|
|
id: data.sessionId || 'rust-' + Math.random().toString(36).substring(2),
|
|
state: SmtpState.FINISHED,
|
|
mailFrom: mailFrom,
|
|
rcptTo: rcptTo,
|
|
emailData: rawMessageBuffer.toString('utf8'),
|
|
useTLS: secure,
|
|
connectionEnded: false,
|
|
remoteAddress: remoteAddr,
|
|
clientHostname: clientHostname || '',
|
|
secure: secure,
|
|
authenticated: !!authenticatedUser,
|
|
envelope: {
|
|
mailFrom: { address: mailFrom, args: {} },
|
|
rcptTo: rcptTo.map(addr => ({ address: addr, args: {} })),
|
|
},
|
|
};
|
|
if (authenticatedUser) {
|
|
session.user = { username: authenticatedUser };
|
|
}
|
|
// Attach pre-computed security results from Rust in-process pipeline
|
|
if (data.securityResults) {
|
|
session._precomputedSecurityResults = data.securityResults;
|
|
}
|
|
// Process the email through the routing system
|
|
await this.processEmailByMode(rawMessageBuffer, session);
|
|
// Send acceptance back to Rust
|
|
await this.rustBridge.sendEmailProcessingResult({
|
|
correlationId,
|
|
accepted: true,
|
|
smtpCode: 250,
|
|
smtpMessage: '2.0.0 Message accepted for delivery',
|
|
});
|
|
}
|
|
catch (err) {
|
|
logger.log('error', `Failed to process email from Rust SMTP: ${err.message}`);
|
|
await this.rustBridge.sendEmailProcessingResult({
|
|
correlationId,
|
|
accepted: false,
|
|
smtpCode: 550,
|
|
smtpMessage: `5.0.0 Processing failed: ${err.message}`,
|
|
});
|
|
}
|
|
}
|
|
/**
|
|
* Handle an authRequest event from the Rust SMTP server.
|
|
* Validates credentials and sends back the result.
|
|
*/
|
|
async handleRustAuthRequest(data) {
|
|
const { correlationId, username, password, remoteAddr } = data;
|
|
logger.log('info', `Rust SMTP auth request for user=${username} from=${remoteAddr}`);
|
|
// Check against configured users
|
|
const users = this.options.auth?.users || [];
|
|
const matched = users.find(u => u.username === username && u.password === password);
|
|
if (matched) {
|
|
await this.rustBridge.sendAuthResult({
|
|
correlationId,
|
|
success: true,
|
|
});
|
|
}
|
|
else {
|
|
logger.log('warn', `Auth failed for user=${username} from=${remoteAddr}`);
|
|
await this.rustBridge.sendAuthResult({
|
|
correlationId,
|
|
success: false,
|
|
message: 'Invalid credentials',
|
|
});
|
|
}
|
|
}
|
|
/**
|
|
* Verify inbound email security (DKIM/SPF/DMARC) using pre-computed Rust results
|
|
* or falling back to IPC call if no pre-computed results are available.
|
|
*/
|
|
async verifyInboundSecurity(email, session) {
|
|
try {
|
|
// Check for pre-computed results from Rust in-process security pipeline
|
|
const precomputed = session._precomputedSecurityResults;
|
|
let result;
|
|
if (precomputed) {
|
|
logger.log('info', 'Using pre-computed security results from Rust in-process pipeline');
|
|
result = precomputed;
|
|
}
|
|
else {
|
|
// Fallback: IPC round-trip to Rust (for backward compat / handleSocket mode)
|
|
const rawMessage = session.emailData || email.toRFC822String();
|
|
result = await this.rustBridge.verifyEmail({
|
|
rawMessage,
|
|
ip: session.remoteAddress,
|
|
heloDomain: session.clientHostname || '',
|
|
hostname: this.options.hostname,
|
|
mailFrom: session.envelope?.mailFrom?.address || session.mailFrom || '',
|
|
});
|
|
}
|
|
// Apply DKIM result headers
|
|
if (result.dkim && result.dkim.length > 0) {
|
|
const dkimSummary = result.dkim
|
|
.map((d) => `${d.status}${d.domain ? ` (${d.domain})` : ''}`)
|
|
.join(', ');
|
|
email.addHeader('X-DKIM-Result', dkimSummary);
|
|
}
|
|
// Apply SPF result header
|
|
if (result.spf) {
|
|
email.addHeader('Received-SPF', `${result.spf.result} (domain: ${result.spf.domain}, ip: ${result.spf.ip})`);
|
|
// Mark as spam on SPF hard fail
|
|
if (result.spf.result === 'fail') {
|
|
email.mightBeSpam = true;
|
|
logger.log('warn', `SPF fail for ${session.remoteAddress} — marking as potential spam`);
|
|
}
|
|
}
|
|
// Apply DMARC result header and policy
|
|
if (result.dmarc) {
|
|
email.addHeader('X-DMARC-Result', `${result.dmarc.action} (policy=${result.dmarc.policy}, dkim=${result.dmarc.dkim_result}, spf=${result.dmarc.spf_result})`);
|
|
if (result.dmarc.action === 'reject') {
|
|
email.mightBeSpam = true;
|
|
logger.log('warn', `DMARC reject for domain ${result.dmarc.domain} — marking as spam`);
|
|
}
|
|
else if (result.dmarc.action === 'quarantine') {
|
|
email.mightBeSpam = true;
|
|
logger.log('info', `DMARC quarantine for domain ${result.dmarc.domain} — marking as potential spam`);
|
|
}
|
|
}
|
|
// Apply content scan results (from pre-computed pipeline)
|
|
if (result.contentScan) {
|
|
const scan = result.contentScan;
|
|
if (scan.threatScore > 0) {
|
|
email.addHeader('X-Spam-Score', String(scan.threatScore));
|
|
if (scan.threatType) {
|
|
email.addHeader('X-Spam-Type', scan.threatType);
|
|
}
|
|
if (scan.threatScore >= 50) {
|
|
email.mightBeSpam = true;
|
|
logger.log('warn', `Content scan threat score ${scan.threatScore} (${scan.threatType}) — marking as potential spam`);
|
|
}
|
|
}
|
|
}
|
|
// Apply IP reputation results (from pre-computed pipeline)
|
|
if (result.ipReputation) {
|
|
const rep = result.ipReputation;
|
|
email.addHeader('X-IP-Reputation-Score', String(rep.score));
|
|
if (rep.is_spam) {
|
|
email.mightBeSpam = true;
|
|
logger.log('warn', `IP ${rep.ip} flagged by reputation check (score=${rep.score}) — marking as potential spam`);
|
|
}
|
|
}
|
|
logger.log('info', `Inbound security verified for email from ${session.remoteAddress}: DKIM=${result.dkim?.[0]?.status ?? 'none'}, SPF=${result.spf?.result ?? 'none'}, DMARC=${result.dmarc?.action ?? 'none'}`);
|
|
}
|
|
catch (err) {
|
|
logger.log('warn', `Inbound security verification failed: ${err.message} — accepting email`);
|
|
}
|
|
}
|
|
/**
|
|
* Process email based on routing rules
|
|
*/
|
|
async processEmailByMode(emailData, session) {
|
|
// Convert Buffer to Email if needed
|
|
let email;
|
|
if (Buffer.isBuffer(emailData)) {
|
|
// Parse the email data buffer into an Email object
|
|
try {
|
|
const parsed = await plugins.mailparser.simpleParser(emailData);
|
|
email = new Email({
|
|
from: parsed.from?.value[0]?.address || session.envelope.mailFrom.address,
|
|
to: session.envelope.rcptTo[0]?.address || '',
|
|
subject: parsed.subject || '',
|
|
text: parsed.text || '',
|
|
html: parsed.html || undefined,
|
|
attachments: parsed.attachments?.map(att => ({
|
|
filename: att.filename || '',
|
|
content: att.content,
|
|
contentType: att.contentType
|
|
})) || []
|
|
});
|
|
}
|
|
catch (error) {
|
|
logger.log('error', `Error parsing email data: ${error.message}`);
|
|
throw new Error(`Error parsing email data: ${error.message}`);
|
|
}
|
|
}
|
|
else {
|
|
email = emailData;
|
|
}
|
|
// Run inbound security verification (DKIM/SPF/DMARC) via Rust bridge
|
|
if (session.remoteAddress && session.remoteAddress !== '127.0.0.1') {
|
|
await this.verifyInboundSecurity(email, session);
|
|
}
|
|
// First check if this is a bounce notification email
|
|
// Look for common bounce notification subject patterns
|
|
const subject = email.subject || '';
|
|
const isBounceLike = /mail delivery|delivery (failed|status|notification)|failure notice|returned mail|undeliverable|delivery problem/i.test(subject);
|
|
if (isBounceLike) {
|
|
logger.log('info', `Email subject matches bounce notification pattern: "${subject}"`);
|
|
// Try to process as a bounce
|
|
const isBounce = await this.processBounceNotification(email);
|
|
if (isBounce) {
|
|
logger.log('info', 'Successfully processed as bounce notification, skipping regular processing');
|
|
return email;
|
|
}
|
|
logger.log('info', 'Not a valid bounce notification, continuing with regular processing');
|
|
}
|
|
// Find matching route
|
|
const context = { email, session };
|
|
const route = await this.emailRouter.evaluateRoutes(context);
|
|
if (!route) {
|
|
// No matching route - reject
|
|
throw new Error('No matching route for email');
|
|
}
|
|
// Store matched route in session
|
|
session.matchedRoute = route;
|
|
// Execute action based on route
|
|
await this.executeAction(route.action, email, context);
|
|
// Return the processed email
|
|
return email;
|
|
}
|
|
/**
|
|
* Execute action based on route configuration
|
|
*/
|
|
async executeAction(action, email, context) {
|
|
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.type}`);
|
|
}
|
|
}
|
|
/**
|
|
* Handle forward action
|
|
*/
|
|
async handleForwardAction(_action, email, context) {
|
|
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();
|
|
// Get SMTP client
|
|
const client = this.getSmtpClient(host, port);
|
|
try {
|
|
// Send email
|
|
await client.sendMail(email);
|
|
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.bounceManager.processSmtpFailure(recipient, error.message, {
|
|
sender: email.from,
|
|
originalEmailId: email.headers['Message-ID']
|
|
});
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
/**
|
|
* Handle process action
|
|
*/
|
|
async handleProcessAction(action, email, context) {
|
|
logger.log('info', `Processing email with action options`);
|
|
// Apply scanning if requested
|
|
if (action.process?.scan) {
|
|
// Use existing content scanner
|
|
// Note: ContentScanner integration would go here
|
|
logger.log('info', 'Content scanning requested');
|
|
}
|
|
// Note: DKIM signing will be applied at delivery time to ensure signature validity
|
|
// Queue for delivery
|
|
const queue = action.process?.queue || 'normal';
|
|
await this.deliveryQueue.enqueue(email, 'process', context.session.matchedRoute);
|
|
logger.log('info', `Email queued for delivery in ${queue} queue`);
|
|
}
|
|
/**
|
|
* Handle deliver action
|
|
*/
|
|
async handleDeliverAction(_action, email, context) {
|
|
logger.log('info', `Delivering email locally`);
|
|
// Queue for local delivery
|
|
await this.deliveryQueue.enqueue(email, 'mta', context.session.matchedRoute);
|
|
logger.log('info', 'Email queued for local delivery');
|
|
}
|
|
/**
|
|
* Handle reject action
|
|
*/
|
|
async handleRejectAction(action, email, context) {
|
|
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.responseCode = code;
|
|
throw error;
|
|
}
|
|
/**
|
|
* Handle email in MTA mode (programmatic processing)
|
|
*/
|
|
async _handleMtaMode(email, session) {
|
|
logger.log('info', `Handling email in MTA mode for session ${session.id}`);
|
|
try {
|
|
// Apply MTA rule options if provided
|
|
if (session.matchedRoute?.action.options?.mtaOptions) {
|
|
const options = session.matchedRoute.action.options.mtaOptions;
|
|
// Apply DKIM signing if enabled
|
|
if (options.dkimSign && options.dkimOptions) {
|
|
const dkimDomain = options.dkimOptions.domainName;
|
|
const dkimSelector = options.dkimOptions.keySelector || 'mta';
|
|
logger.log('info', `Signing email with DKIM for domain ${dkimDomain}`);
|
|
await this.handleDkimSigning(email, dkimDomain, dkimSelector);
|
|
}
|
|
}
|
|
// Get email content for logging/processing
|
|
const subject = email.subject;
|
|
const recipients = email.getAllRecipients().join(', ');
|
|
logger.log('info', `Email processed by MTA: ${subject} to ${recipients}`);
|
|
SecurityLogger.getInstance().logEvent({
|
|
level: SecurityLogLevel.INFO,
|
|
type: SecurityEventType.EMAIL_PROCESSING,
|
|
message: 'Email processed by MTA',
|
|
ipAddress: session.remoteAddress,
|
|
details: {
|
|
sessionId: session.id,
|
|
ruleName: session.matchedRoute?.name || 'default',
|
|
subject,
|
|
recipients
|
|
},
|
|
success: true
|
|
});
|
|
}
|
|
catch (error) {
|
|
logger.log('error', `Failed to process email in MTA mode: ${error.message}`);
|
|
SecurityLogger.getInstance().logEvent({
|
|
level: SecurityLogLevel.ERROR,
|
|
type: SecurityEventType.EMAIL_PROCESSING,
|
|
message: 'MTA processing failed',
|
|
ipAddress: session.remoteAddress,
|
|
details: {
|
|
sessionId: session.id,
|
|
ruleName: session.matchedRoute?.name || 'default',
|
|
error: error.message
|
|
},
|
|
success: false
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
/**
|
|
* Handle email in process mode (store-and-forward with scanning)
|
|
*/
|
|
async _handleProcessMode(email, session) {
|
|
logger.log('info', `Handling email in process mode for session ${session.id}`);
|
|
try {
|
|
const route = session.matchedRoute;
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
logger.log('info', `Email successfully processed in store-and-forward mode`);
|
|
SecurityLogger.getInstance().logEvent({
|
|
level: SecurityLogLevel.INFO,
|
|
type: SecurityEventType.EMAIL_PROCESSING,
|
|
message: 'Email processed and queued',
|
|
ipAddress: session.remoteAddress,
|
|
details: {
|
|
sessionId: session.id,
|
|
ruleName: route?.name || 'default',
|
|
contentScanning: route?.action.options?.contentScanning || false,
|
|
subject: email.subject
|
|
},
|
|
success: true
|
|
});
|
|
}
|
|
catch (error) {
|
|
logger.log('error', `Failed to process email: ${error.message}`);
|
|
SecurityLogger.getInstance().logEvent({
|
|
level: SecurityLogLevel.ERROR,
|
|
type: SecurityEventType.EMAIL_PROCESSING,
|
|
message: 'Email processing failed',
|
|
ipAddress: session.remoteAddress,
|
|
details: {
|
|
sessionId: session.id,
|
|
ruleName: session.matchedRoute?.name || 'default',
|
|
error: error.message
|
|
},
|
|
success: false
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
/**
|
|
* Get file extension from filename
|
|
*/
|
|
getFileExtension(filename) {
|
|
return filename.substring(filename.lastIndexOf('.')).toLowerCase();
|
|
}
|
|
/**
|
|
* Set up DKIM configuration for all domains
|
|
*/
|
|
async setupDkimForDomains() {
|
|
const domainConfigs = this.domainRegistry.getAllConfigs();
|
|
if (domainConfigs.length === 0) {
|
|
logger.log('warn', 'No domains configured for DKIM');
|
|
return;
|
|
}
|
|
for (const domainConfig of domainConfigs) {
|
|
const domain = domainConfig.domain;
|
|
const selector = domainConfig.dkim?.selector || 'default';
|
|
try {
|
|
// Check if DKIM keys already exist for this domain
|
|
let keyPair;
|
|
try {
|
|
// Try to read existing keys
|
|
keyPair = await this.dkimCreator.readDKIMKeys(domain);
|
|
logger.log('info', `Using existing DKIM keys for domain: ${domain}`);
|
|
}
|
|
catch (error) {
|
|
// Generate new keys if they don't exist
|
|
keyPair = await this.dkimCreator.createDKIMKeys();
|
|
// Store them for future use
|
|
await this.dkimCreator.createAndStoreDKIMKeys(domain);
|
|
logger.log('info', `Generated new DKIM keys for domain: ${domain}`);
|
|
}
|
|
// Store the private key for signing
|
|
this.dkimKeys.set(domain, keyPair.privateKey);
|
|
// DNS record creation is now handled by DnsManager
|
|
logger.log('info', `DKIM keys loaded for domain: ${domain} with selector: ${selector}`);
|
|
}
|
|
catch (error) {
|
|
logger.log('error', `Failed to set up DKIM for domain ${domain}: ${error.message}`);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Apply per-domain rate limits from domain configurations
|
|
*/
|
|
applyDomainRateLimits() {
|
|
const domainConfigs = this.domainRegistry.getAllConfigs();
|
|
for (const domainConfig of domainConfigs) {
|
|
if (domainConfig.rateLimits) {
|
|
const domain = domainConfig.domain;
|
|
const rateLimitConfig = {};
|
|
// Convert domain-specific rate limits to the format expected by UnifiedRateLimiter
|
|
if (domainConfig.rateLimits.outbound) {
|
|
if (domainConfig.rateLimits.outbound.messagesPerMinute) {
|
|
rateLimitConfig.maxMessagesPerMinute = domainConfig.rateLimits.outbound.messagesPerMinute;
|
|
}
|
|
// Note: messagesPerHour and messagesPerDay would need additional implementation in rate limiter
|
|
}
|
|
if (domainConfig.rateLimits.inbound) {
|
|
if (domainConfig.rateLimits.inbound.messagesPerMinute) {
|
|
rateLimitConfig.maxMessagesPerMinute = domainConfig.rateLimits.inbound.messagesPerMinute;
|
|
}
|
|
if (domainConfig.rateLimits.inbound.connectionsPerIp) {
|
|
rateLimitConfig.maxConnectionsPerIP = domainConfig.rateLimits.inbound.connectionsPerIp;
|
|
}
|
|
if (domainConfig.rateLimits.inbound.recipientsPerMessage) {
|
|
rateLimitConfig.maxRecipientsPerMessage = domainConfig.rateLimits.inbound.recipientsPerMessage;
|
|
}
|
|
}
|
|
// Apply the rate limits if we have any
|
|
if (Object.keys(rateLimitConfig).length > 0) {
|
|
this.rateLimiter.applyDomainLimits(domain, rateLimitConfig);
|
|
logger.log('info', `Applied rate limits for domain ${domain}:`, rateLimitConfig);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Check and rotate DKIM keys if needed
|
|
*/
|
|
async checkAndRotateDkimKeys() {
|
|
const domainConfigs = this.domainRegistry.getAllConfigs();
|
|
for (const domainConfig of domainConfigs) {
|
|
const domain = domainConfig.domain;
|
|
const selector = domainConfig.dkim?.selector || 'default';
|
|
const rotateKeys = domainConfig.dkim?.rotateKeys || false;
|
|
const rotationInterval = domainConfig.dkim?.rotationInterval || 90;
|
|
const keySize = domainConfig.dkim?.keySize || 2048;
|
|
if (!rotateKeys) {
|
|
logger.log('debug', `DKIM key rotation disabled for ${domain}`);
|
|
continue;
|
|
}
|
|
try {
|
|
// Check if keys need rotation
|
|
const needsRotation = await this.dkimCreator.needsRotation(domain, selector, rotationInterval);
|
|
if (needsRotation) {
|
|
logger.log('info', `DKIM keys need rotation for ${domain} (selector: ${selector})`);
|
|
// Rotate the keys
|
|
const newSelector = await this.dkimCreator.rotateDkimKeys(domain, selector, keySize);
|
|
// Update the domain config with new selector
|
|
domainConfig.dkim = {
|
|
...domainConfig.dkim,
|
|
selector: newSelector
|
|
};
|
|
// Re-register DNS handler for new selector if internal-dns mode
|
|
if (domainConfig.dnsMode === 'internal-dns' && this.dcRouter.dnsServer) {
|
|
// Get new public key
|
|
const keyPair = await this.dkimCreator.readDKIMKeysForSelector(domain, newSelector);
|
|
const publicKeyBase64 = keyPair.publicKey
|
|
.replace(/-----BEGIN PUBLIC KEY-----/g, '')
|
|
.replace(/-----END PUBLIC KEY-----/g, '')
|
|
.replace(/\s/g, '');
|
|
const ttl = domainConfig.dns?.internal?.ttl || 3600;
|
|
// Register new selector
|
|
this.dcRouter.dnsServer.registerHandler(`${newSelector}._domainkey.${domain}`, ['TXT'], () => ({
|
|
name: `${newSelector}._domainkey.${domain}`,
|
|
type: 'TXT',
|
|
class: 'IN',
|
|
ttl: ttl,
|
|
data: `v=DKIM1; k=rsa; p=${publicKeyBase64}`
|
|
}));
|
|
logger.log('info', `DKIM DNS handler registered for new selector: ${newSelector}._domainkey.${domain}`);
|
|
// Store the updated public key in storage
|
|
await this.dcRouter.storageManager.set(`/email/dkim/${domain}/public.key`, keyPair.publicKey);
|
|
}
|
|
// Clean up old keys after grace period (async, don't wait)
|
|
this.dkimCreator.cleanupOldKeys(domain, 30).catch(error => {
|
|
logger.log('warn', `Failed to cleanup old DKIM keys for ${domain}: ${error.message}`);
|
|
});
|
|
}
|
|
else {
|
|
logger.log('debug', `DKIM keys for ${domain} are up to date`);
|
|
}
|
|
}
|
|
catch (error) {
|
|
logger.log('error', `Failed to check/rotate DKIM keys for ${domain}: ${error.message}`);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Generate SmartProxy routes for email ports
|
|
*/
|
|
generateProxyRoutes(portMapping) {
|
|
const routes = [];
|
|
const defaultPortMapping = {
|
|
25: 10025,
|
|
587: 10587,
|
|
465: 10465
|
|
};
|
|
const actualPortMapping = portMapping || defaultPortMapping;
|
|
// Generate routes for each configured port
|
|
for (const externalPort of this.options.ports) {
|
|
const internalPort = actualPortMapping[externalPort] || externalPort + 10000;
|
|
let routeName = 'email-route';
|
|
let tlsMode = 'passthrough';
|
|
// Configure based on port
|
|
switch (externalPort) {
|
|
case 25:
|
|
routeName = 'smtp-route';
|
|
tlsMode = 'passthrough'; // STARTTLS
|
|
break;
|
|
case 587:
|
|
routeName = 'submission-route';
|
|
tlsMode = 'passthrough'; // STARTTLS
|
|
break;
|
|
case 465:
|
|
routeName = 'smtps-route';
|
|
tlsMode = 'terminate'; // Implicit TLS
|
|
break;
|
|
default:
|
|
routeName = `email-port-${externalPort}-route`;
|
|
}
|
|
routes.push({
|
|
name: routeName,
|
|
match: {
|
|
ports: [externalPort]
|
|
},
|
|
action: {
|
|
type: 'forward',
|
|
target: {
|
|
host: 'localhost',
|
|
port: internalPort
|
|
},
|
|
tls: {
|
|
mode: tlsMode
|
|
}
|
|
}
|
|
});
|
|
}
|
|
return routes;
|
|
}
|
|
/**
|
|
* Update server configuration
|
|
*/
|
|
updateOptions(options) {
|
|
// Stop the server if changing ports
|
|
const portsChanged = options.ports &&
|
|
(!this.options.ports ||
|
|
JSON.stringify(options.ports) !== JSON.stringify(this.options.ports));
|
|
if (portsChanged) {
|
|
this.stop().then(() => {
|
|
this.options = { ...this.options, ...options };
|
|
this.start();
|
|
});
|
|
}
|
|
else {
|
|
// Update options without restart
|
|
this.options = { ...this.options, ...options };
|
|
// Update domain registry if domains changed
|
|
if (options.domains) {
|
|
this.domainRegistry = new DomainRegistry(options.domains, options.defaults || this.options.defaults);
|
|
}
|
|
// Update email router if routes changed
|
|
if (options.routes) {
|
|
this.emailRouter.updateRoutes(options.routes);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Update email routes
|
|
*/
|
|
updateEmailRoutes(routes) {
|
|
this.options.routes = routes;
|
|
this.emailRouter.updateRoutes(routes);
|
|
}
|
|
/**
|
|
* Get server statistics
|
|
*/
|
|
getStats() {
|
|
return { ...this.stats };
|
|
}
|
|
/**
|
|
* Get domain registry
|
|
*/
|
|
getDomainRegistry() {
|
|
return this.domainRegistry;
|
|
}
|
|
/**
|
|
* Update email routes dynamically
|
|
*/
|
|
updateRoutes(routes) {
|
|
this.emailRouter.setRoutes(routes);
|
|
logger.log('info', `Updated email routes with ${routes.length} routes`);
|
|
}
|
|
/**
|
|
* Send an email through the delivery system
|
|
* @param email The email to send
|
|
* @param mode The processing mode to use
|
|
* @param rule Optional rule to apply
|
|
* @param options Optional sending options
|
|
* @returns The ID of the queued email
|
|
*/
|
|
async sendEmail(email, mode = 'mta', route, options) {
|
|
logger.log('info', `Sending email: ${email.subject} to ${email.to.join(', ')}`);
|
|
try {
|
|
// Validate the email
|
|
if (!email.from) {
|
|
throw new Error('Email must have a sender address');
|
|
}
|
|
if (!email.to || email.to.length === 0) {
|
|
throw new Error('Email must have at least one recipient');
|
|
}
|
|
// Check if any recipients are on the suppression list (unless explicitly skipped)
|
|
if (!options?.skipSuppressionCheck) {
|
|
const suppressedRecipients = email.to.filter(recipient => this.isEmailSuppressed(recipient));
|
|
if (suppressedRecipients.length > 0) {
|
|
// Filter out suppressed recipients
|
|
const originalCount = email.to.length;
|
|
const suppressed = suppressedRecipients.map(recipient => {
|
|
const info = this.getSuppressionInfo(recipient);
|
|
return {
|
|
email: recipient,
|
|
reason: info?.reason || 'Unknown',
|
|
until: info?.expiresAt ? new Date(info.expiresAt).toISOString() : 'permanent'
|
|
};
|
|
});
|
|
logger.log('warn', `Filtering out ${suppressedRecipients.length} suppressed recipient(s)`, { suppressed });
|
|
// If all recipients are suppressed, throw an error
|
|
if (suppressedRecipients.length === originalCount) {
|
|
throw new Error('All recipients are on the suppression list');
|
|
}
|
|
// Filter the recipients list to only include non-suppressed addresses
|
|
email.to = email.to.filter(recipient => !this.isEmailSuppressed(recipient));
|
|
}
|
|
}
|
|
// IP warmup handling
|
|
let ipAddress = options?.ipAddress;
|
|
// If no specific IP was provided, use IP warmup manager to find the best IP
|
|
if (!ipAddress) {
|
|
const domain = email.from.split('@')[1];
|
|
ipAddress = this.getBestIPForSending({
|
|
from: email.from,
|
|
to: email.to,
|
|
domain,
|
|
isTransactional: options?.isTransactional
|
|
});
|
|
if (ipAddress) {
|
|
logger.log('info', `Selected IP ${ipAddress} for sending based on warmup status`);
|
|
}
|
|
}
|
|
// If an IP is provided or selected by warmup manager, check its capacity
|
|
if (ipAddress) {
|
|
// Check if the IP can send more today
|
|
if (!this.canIPSendMoreToday(ipAddress)) {
|
|
logger.log('warn', `IP ${ipAddress} has reached its daily sending limit, email will be queued for later delivery`);
|
|
}
|
|
// Check if the IP can send more this hour
|
|
if (!this.canIPSendMoreThisHour(ipAddress)) {
|
|
logger.log('warn', `IP ${ipAddress} has reached its hourly sending limit, email will be queued for later delivery`);
|
|
}
|
|
// Record the send for IP warmup tracking
|
|
this.recordIPSend(ipAddress);
|
|
// Add IP header to the email
|
|
email.addHeader('X-Sending-IP', ipAddress);
|
|
}
|
|
// Check if the sender domain has DKIM keys and sign the email if needed
|
|
if (mode === 'mta' && route?.action.options?.mtaOptions?.dkimSign) {
|
|
const domain = email.from.split('@')[1];
|
|
await this.handleDkimSigning(email, domain, route.action.options.mtaOptions.dkimOptions?.keySelector || 'mta');
|
|
}
|
|
// Generate a unique ID for this email
|
|
const id = plugins.uuid.v4();
|
|
// Queue the email for delivery
|
|
await this.deliveryQueue.enqueue(email, mode, route);
|
|
// Record 'sent' event for domain reputation monitoring
|
|
const senderDomain = email.from.split('@')[1];
|
|
if (senderDomain) {
|
|
this.recordReputationEvent(senderDomain, {
|
|
type: 'sent',
|
|
count: email.to.length
|
|
});
|
|
}
|
|
logger.log('info', `Email queued with ID: ${id}`);
|
|
return id;
|
|
}
|
|
catch (error) {
|
|
logger.log('error', `Failed to send email: ${error.message}`);
|
|
throw error;
|
|
}
|
|
}
|
|
/**
|
|
* Handle DKIM signing for an email
|
|
* @param email The email to sign
|
|
* @param domain The domain to sign with
|
|
* @param selector The DKIM selector
|
|
*/
|
|
async handleDkimSigning(email, domain, selector) {
|
|
try {
|
|
// Ensure we have DKIM keys for this domain
|
|
await this.dkimCreator.handleDKIMKeysForDomain(domain);
|
|
// Get the private key
|
|
const { privateKey } = await this.dkimCreator.readDKIMKeys(domain);
|
|
// Convert Email to raw format for signing
|
|
const rawEmail = email.toRFC822String();
|
|
// Sign the email via Rust bridge
|
|
const signResult = await this.rustBridge.signDkim({
|
|
rawMessage: rawEmail,
|
|
domain,
|
|
selector,
|
|
privateKey,
|
|
});
|
|
if (signResult.header) {
|
|
email.addHeader('DKIM-Signature', signResult.header);
|
|
logger.log('info', `Successfully added DKIM signature for ${domain}`);
|
|
}
|
|
}
|
|
catch (error) {
|
|
logger.log('error', `Failed to sign email with DKIM: ${error.message}`);
|
|
// Continue without DKIM rather than failing the send
|
|
}
|
|
}
|
|
/**
|
|
* Process a bounce notification email
|
|
* @param bounceEmail The email containing bounce notification information
|
|
* @returns Processed bounce record or null if not a bounce
|
|
*/
|
|
async processBounceNotification(bounceEmail) {
|
|
logger.log('info', 'Processing potential bounce notification email');
|
|
try {
|
|
// Process as a bounce notification (no conversion needed anymore)
|
|
const bounceRecord = await this.bounceManager.processBounceEmail(bounceEmail);
|
|
if (bounceRecord) {
|
|
logger.log('info', `Successfully processed bounce notification for ${bounceRecord.recipient}`, {
|
|
bounceType: bounceRecord.bounceType,
|
|
bounceCategory: bounceRecord.bounceCategory
|
|
});
|
|
// Notify any registered listeners about the bounce
|
|
this.emit('bounceProcessed', bounceRecord);
|
|
// Record bounce event for domain reputation tracking
|
|
if (bounceRecord.domain) {
|
|
this.recordReputationEvent(bounceRecord.domain, {
|
|
type: 'bounce',
|
|
hardBounce: bounceRecord.bounceCategory === BounceCategory.HARD,
|
|
receivingDomain: bounceRecord.recipient.split('@')[1]
|
|
});
|
|
}
|
|
// Log security event
|
|
SecurityLogger.getInstance().logEvent({
|
|
level: SecurityLogLevel.INFO,
|
|
type: SecurityEventType.EMAIL_VALIDATION,
|
|
message: `Bounce notification processed for recipient`,
|
|
domain: bounceRecord.domain,
|
|
details: {
|
|
recipient: bounceRecord.recipient,
|
|
bounceType: bounceRecord.bounceType,
|
|
bounceCategory: bounceRecord.bounceCategory
|
|
},
|
|
success: true
|
|
});
|
|
return true;
|
|
}
|
|
else {
|
|
logger.log('info', 'Email not recognized as a bounce notification');
|
|
return false;
|
|
}
|
|
}
|
|
catch (error) {
|
|
logger.log('error', `Error processing bounce notification: ${error.message}`);
|
|
SecurityLogger.getInstance().logEvent({
|
|
level: SecurityLogLevel.ERROR,
|
|
type: SecurityEventType.EMAIL_VALIDATION,
|
|
message: 'Failed to process bounce notification',
|
|
details: {
|
|
error: error.message,
|
|
subject: bounceEmail.subject
|
|
},
|
|
success: false
|
|
});
|
|
return false;
|
|
}
|
|
}
|
|
/**
|
|
* Process an SMTP failure as a bounce
|
|
* @param recipient Recipient email that failed
|
|
* @param smtpResponse SMTP error response
|
|
* @param options Additional options for bounce processing
|
|
* @returns Processed bounce record
|
|
*/
|
|
async processSmtpFailure(recipient, smtpResponse, options = {}) {
|
|
logger.log('info', `Processing SMTP failure for ${recipient}: ${smtpResponse}`);
|
|
try {
|
|
// Process the SMTP failure through the bounce manager
|
|
const bounceRecord = await this.bounceManager.processSmtpFailure(recipient, smtpResponse, options);
|
|
logger.log('info', `Successfully processed SMTP failure for ${recipient} as ${bounceRecord.bounceCategory} bounce`, {
|
|
bounceType: bounceRecord.bounceType
|
|
});
|
|
// Notify any registered listeners about the bounce
|
|
this.emit('bounceProcessed', bounceRecord);
|
|
// Record bounce event for domain reputation tracking
|
|
if (bounceRecord.domain) {
|
|
this.recordReputationEvent(bounceRecord.domain, {
|
|
type: 'bounce',
|
|
hardBounce: bounceRecord.bounceCategory === BounceCategory.HARD,
|
|
receivingDomain: bounceRecord.recipient.split('@')[1]
|
|
});
|
|
}
|
|
// Log security event
|
|
SecurityLogger.getInstance().logEvent({
|
|
level: SecurityLogLevel.INFO,
|
|
type: SecurityEventType.EMAIL_VALIDATION,
|
|
message: `SMTP failure processed for recipient`,
|
|
domain: bounceRecord.domain,
|
|
details: {
|
|
recipient: bounceRecord.recipient,
|
|
bounceType: bounceRecord.bounceType,
|
|
bounceCategory: bounceRecord.bounceCategory,
|
|
smtpResponse
|
|
},
|
|
success: true
|
|
});
|
|
return true;
|
|
}
|
|
catch (error) {
|
|
logger.log('error', `Error processing SMTP failure: ${error.message}`);
|
|
SecurityLogger.getInstance().logEvent({
|
|
level: SecurityLogLevel.ERROR,
|
|
type: SecurityEventType.EMAIL_VALIDATION,
|
|
message: 'Failed to process SMTP failure',
|
|
details: {
|
|
recipient,
|
|
smtpResponse,
|
|
error: error.message
|
|
},
|
|
success: false
|
|
});
|
|
return false;
|
|
}
|
|
}
|
|
/**
|
|
* Check if an email address is suppressed (has bounced previously)
|
|
* @param email Email address to check
|
|
* @returns Whether the email is suppressed
|
|
*/
|
|
isEmailSuppressed(email) {
|
|
return this.bounceManager.isEmailSuppressed(email);
|
|
}
|
|
/**
|
|
* Get suppression information for an email
|
|
* @param email Email address to check
|
|
* @returns Suppression information or null if not suppressed
|
|
*/
|
|
getSuppressionInfo(email) {
|
|
return this.bounceManager.getSuppressionInfo(email);
|
|
}
|
|
/**
|
|
* Get bounce history information for an email
|
|
* @param email Email address to check
|
|
* @returns Bounce history or null if no bounces
|
|
*/
|
|
getBounceHistory(email) {
|
|
return this.bounceManager.getBounceInfo(email);
|
|
}
|
|
/**
|
|
* Get all suppressed email addresses
|
|
* @returns Array of suppressed email addresses
|
|
*/
|
|
getSuppressionList() {
|
|
return this.bounceManager.getSuppressionList();
|
|
}
|
|
/**
|
|
* Get all hard bounced email addresses
|
|
* @returns Array of hard bounced email addresses
|
|
*/
|
|
getHardBouncedAddresses() {
|
|
return this.bounceManager.getHardBouncedAddresses();
|
|
}
|
|
/**
|
|
* Add an email to the suppression list
|
|
* @param email Email address to suppress
|
|
* @param reason Reason for suppression
|
|
* @param expiresAt Optional expiration time (undefined for permanent)
|
|
*/
|
|
addToSuppressionList(email, reason, expiresAt) {
|
|
this.bounceManager.addToSuppressionList(email, reason, expiresAt);
|
|
logger.log('info', `Added ${email} to suppression list: ${reason}`);
|
|
}
|
|
/**
|
|
* Remove an email from the suppression list
|
|
* @param email Email address to remove from suppression
|
|
*/
|
|
removeFromSuppressionList(email) {
|
|
this.bounceManager.removeFromSuppressionList(email);
|
|
logger.log('info', `Removed ${email} from suppression list`);
|
|
}
|
|
/**
|
|
* Get the status of IP warmup process
|
|
* @param ipAddress Optional specific IP to check
|
|
* @returns Status of IP warmup
|
|
*/
|
|
getIPWarmupStatus(ipAddress) {
|
|
return this.ipWarmupManager.getWarmupStatus(ipAddress);
|
|
}
|
|
/**
|
|
* Add a new IP address to the warmup process
|
|
* @param ipAddress IP address to add
|
|
*/
|
|
addIPToWarmup(ipAddress) {
|
|
this.ipWarmupManager.addIPToWarmup(ipAddress);
|
|
}
|
|
/**
|
|
* Remove an IP address from the warmup process
|
|
* @param ipAddress IP address to remove
|
|
*/
|
|
removeIPFromWarmup(ipAddress) {
|
|
this.ipWarmupManager.removeIPFromWarmup(ipAddress);
|
|
}
|
|
/**
|
|
* Update metrics for an IP in the warmup process
|
|
* @param ipAddress IP address
|
|
* @param metrics Metrics to update
|
|
*/
|
|
updateIPWarmupMetrics(ipAddress, metrics) {
|
|
this.ipWarmupManager.updateMetrics(ipAddress, metrics);
|
|
}
|
|
/**
|
|
* Check if an IP can send more emails today
|
|
* @param ipAddress IP address to check
|
|
* @returns Whether the IP can send more today
|
|
*/
|
|
canIPSendMoreToday(ipAddress) {
|
|
return this.ipWarmupManager.canSendMoreToday(ipAddress);
|
|
}
|
|
/**
|
|
* Check if an IP can send more emails in the current hour
|
|
* @param ipAddress IP address to check
|
|
* @returns Whether the IP can send more this hour
|
|
*/
|
|
canIPSendMoreThisHour(ipAddress) {
|
|
return this.ipWarmupManager.canSendMoreThisHour(ipAddress);
|
|
}
|
|
/**
|
|
* Get the best IP to use for sending an email based on warmup status
|
|
* @param emailInfo Information about the email being sent
|
|
* @returns Best IP to use or null
|
|
*/
|
|
getBestIPForSending(emailInfo) {
|
|
return this.ipWarmupManager.getBestIPForSending(emailInfo);
|
|
}
|
|
/**
|
|
* Set the active IP allocation policy for warmup
|
|
* @param policyName Name of the policy to set
|
|
*/
|
|
setIPAllocationPolicy(policyName) {
|
|
this.ipWarmupManager.setActiveAllocationPolicy(policyName);
|
|
}
|
|
/**
|
|
* Record that an email was sent using a specific IP
|
|
* @param ipAddress IP address used for sending
|
|
*/
|
|
recordIPSend(ipAddress) {
|
|
this.ipWarmupManager.recordSend(ipAddress);
|
|
}
|
|
/**
|
|
* Get reputation data for a domain
|
|
* @param domain Domain to get reputation for
|
|
* @returns Domain reputation metrics
|
|
*/
|
|
getDomainReputationData(domain) {
|
|
return this.senderReputationMonitor.getReputationData(domain);
|
|
}
|
|
/**
|
|
* Get summary reputation data for all monitored domains
|
|
* @returns Summary data for all domains
|
|
*/
|
|
getReputationSummary() {
|
|
return this.senderReputationMonitor.getReputationSummary();
|
|
}
|
|
/**
|
|
* Add a domain to the reputation monitoring system
|
|
* @param domain Domain to add
|
|
*/
|
|
addDomainToMonitoring(domain) {
|
|
this.senderReputationMonitor.addDomain(domain);
|
|
}
|
|
/**
|
|
* Remove a domain from the reputation monitoring system
|
|
* @param domain Domain to remove
|
|
*/
|
|
removeDomainFromMonitoring(domain) {
|
|
this.senderReputationMonitor.removeDomain(domain);
|
|
}
|
|
/**
|
|
* Record an email event for domain reputation tracking
|
|
* @param domain Domain sending the email
|
|
* @param event Event details
|
|
*/
|
|
recordReputationEvent(domain, event) {
|
|
this.senderReputationMonitor.recordSendEvent(domain, event);
|
|
}
|
|
/**
|
|
* Check if DKIM key exists for a domain
|
|
* @param domain Domain to check
|
|
*/
|
|
hasDkimKey(domain) {
|
|
return this.dkimKeys.has(domain);
|
|
}
|
|
/**
|
|
* Record successful email delivery
|
|
* @param domain Sending domain
|
|
*/
|
|
recordDelivery(domain) {
|
|
this.recordReputationEvent(domain, {
|
|
type: 'delivered',
|
|
count: 1
|
|
});
|
|
}
|
|
/**
|
|
* Record email bounce
|
|
* @param domain Sending domain
|
|
* @param receivingDomain Receiving domain that bounced
|
|
* @param bounceType Type of bounce (hard/soft)
|
|
* @param reason Bounce reason
|
|
*/
|
|
recordBounce(domain, receivingDomain, bounceType, reason) {
|
|
// Record bounce in bounce manager
|
|
const bounceRecord = {
|
|
id: `bounce_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
|
|
recipient: `user@${receivingDomain}`,
|
|
sender: `user@${domain}`,
|
|
domain: domain,
|
|
bounceType: bounceType === 'hard' ? BounceType.INVALID_RECIPIENT : BounceType.TEMPORARY_FAILURE,
|
|
bounceCategory: bounceType === 'hard' ? BounceCategory.HARD : BounceCategory.SOFT,
|
|
timestamp: Date.now(),
|
|
smtpResponse: reason,
|
|
diagnosticCode: reason,
|
|
statusCode: bounceType === 'hard' ? '550' : '450',
|
|
processed: false
|
|
};
|
|
// Process the bounce
|
|
this.bounceManager.processBounce(bounceRecord);
|
|
// Record reputation event
|
|
this.recordReputationEvent(domain, {
|
|
type: 'bounce',
|
|
count: 1,
|
|
hardBounce: bounceType === 'hard',
|
|
receivingDomain
|
|
});
|
|
}
|
|
/**
|
|
* Get the rate limiter instance
|
|
* @returns The unified rate limiter
|
|
*/
|
|
getRateLimiter() {
|
|
return this.rateLimiter;
|
|
}
|
|
}
|
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy51bmlmaWVkLmVtYWlsLnNlcnZlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3RzL21haWwvcm91dGluZy9jbGFzc2VzLnVuaWZpZWQuZW1haWwuc2VydmVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFDNUMsT0FBTyxLQUFLLEtBQUssTUFBTSxnQkFBZ0IsQ0FBQztBQUN4QyxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sUUFBUSxDQUFDO0FBQ3RDLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUN6QyxPQUFPLEVBQ0wsY0FBYyxFQUNkLGdCQUFnQixFQUNoQixpQkFBaUIsRUFDbEIsTUFBTSx5QkFBeUIsQ0FBQztBQUNqQyxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sb0NBQW9DLENBQUM7QUFDakUsT0FBTyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sK0NBQStDLENBQUM7QUFDcEYsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sOENBQThDLENBQUM7QUErQmxGLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUV4RCxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDakQsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBQzlELE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUN0RCxPQUFPLEVBQUUsYUFBYSxFQUFFLFVBQVUsRUFBRSxjQUFjLEVBQUUsTUFBTSxrQ0FBa0MsQ0FBQztBQUM3RixPQUFPLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSx5Q0FBeUMsQ0FBQztBQUVqRixPQUFPLEVBQUUsdUJBQXVCLEVBQWtDLE1BQU0sd0NBQXdDLENBQUM7QUFDakgsT0FBTyxFQUFFLG9CQUFvQixFQUFzQixNQUFNLHVDQUF1QyxDQUFDO0FB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
|