BREAKING CHANGE(smartmta): Rebrand package to @push.rocks/smartmta, add consolidated email security verification and IPC handler
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
} 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';
|
||||
// Deliverability types (IPWarmupManager and SenderReputationMonitor are optional external modules)
|
||||
interface IIPWarmupConfig {
|
||||
enabled?: boolean;
|
||||
@@ -192,7 +193,8 @@ export class UnifiedEmailServer extends EventEmitter {
|
||||
|
||||
// Add components needed for sending and securing emails
|
||||
public dkimCreator: DKIMCreator;
|
||||
private ipReputationChecker: IPReputationChecker; // TODO: Implement IP reputation checks in processEmailByMode
|
||||
private rustBridge: RustSecurityBridge;
|
||||
private ipReputationChecker: IPReputationChecker;
|
||||
private bounceManager: BounceManager;
|
||||
private ipWarmupManager: IPWarmupManager | null;
|
||||
private senderReputationMonitor: SenderReputationMonitor | null;
|
||||
@@ -217,6 +219,9 @@ export class UnifiedEmailServer extends EventEmitter {
|
||||
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);
|
||||
|
||||
@@ -360,7 +365,15 @@ export class UnifiedEmailServer extends EventEmitter {
|
||||
// Start the delivery system
|
||||
await this.deliverySystem.start();
|
||||
logger.log('info', 'Email delivery system started');
|
||||
|
||||
|
||||
// Start Rust security bridge (non-blocking — server works without it)
|
||||
const bridgeOk = await this.rustBridge.start();
|
||||
if (bridgeOk) {
|
||||
logger.log('info', 'Rust security bridge started — using Rust for DKIM/SPF/DMARC verification');
|
||||
} else {
|
||||
logger.log('warn', 'Rust security bridge unavailable — falling back to TypeScript security verification');
|
||||
}
|
||||
|
||||
// Set up DKIM for all domains
|
||||
await this.setupDkimForDomains();
|
||||
logger.log('info', 'DKIM configuration completed for all domains');
|
||||
@@ -417,12 +430,40 @@ export class UnifiedEmailServer extends EventEmitter {
|
||||
verifyDmarc: true
|
||||
}
|
||||
},
|
||||
// These will be implemented in the real integration:
|
||||
// Security verification delegated to the Rust bridge when available
|
||||
dkimVerifier: {
|
||||
verify: async () => ({ isValid: true, domain: '' })
|
||||
verify: async (rawMessage: string) => {
|
||||
if (this.rustBridge.running) {
|
||||
try {
|
||||
const results = await this.rustBridge.verifyDkim(rawMessage);
|
||||
const first = results[0];
|
||||
return { isValid: first?.is_valid ?? false, domain: first?.domain ?? '' };
|
||||
} catch (err) {
|
||||
logger.log('warn', `Rust DKIM verification failed, accepting: ${(err as Error).message}`);
|
||||
return { isValid: true, domain: '' };
|
||||
}
|
||||
}
|
||||
return { isValid: true, domain: '' }; // No bridge — accept
|
||||
}
|
||||
},
|
||||
spfVerifier: {
|
||||
verifyAndApply: async () => true
|
||||
verifyAndApply: async (session: any) => {
|
||||
if (this.rustBridge.running && session?.remoteAddress && session.remoteAddress !== '127.0.0.1') {
|
||||
try {
|
||||
const result = await this.rustBridge.checkSpf({
|
||||
ip: session.remoteAddress,
|
||||
heloDomain: session.clientHostname || '',
|
||||
hostname: this.options.hostname,
|
||||
mailFrom: session.envelope?.mailFrom?.address || session.mailFrom || '',
|
||||
});
|
||||
return result.result === 'pass' || result.result === 'none' || result.result === 'neutral';
|
||||
} catch (err) {
|
||||
logger.log('warn', `Rust SPF check failed, accepting: ${(err as Error).message}`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true; // No bridge or localhost — accept
|
||||
}
|
||||
},
|
||||
dmarcVerifier: {
|
||||
verify: async () => ({}),
|
||||
@@ -552,7 +593,10 @@ export class UnifiedEmailServer extends EventEmitter {
|
||||
try {
|
||||
// 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();
|
||||
@@ -588,6 +632,63 @@ export class UnifiedEmailServer extends EventEmitter {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Verify inbound email security (DKIM/SPF/DMARC) using the Rust bridge.
|
||||
* Falls back gracefully if the bridge is not running.
|
||||
*/
|
||||
private async verifyInboundSecurity(email: Email, session: IExtendedSmtpSession): Promise<void> {
|
||||
if (!this.rustBridge.running) {
|
||||
return; // Bridge not available — skip verification
|
||||
}
|
||||
|
||||
try {
|
||||
const rawMessage = session.emailData || email.toRFC822String();
|
||||
const 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.setHeader('X-DKIM-Result', dkimSummary);
|
||||
}
|
||||
|
||||
// Apply SPF result header
|
||||
if (result.spf) {
|
||||
email.setHeader('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.setHeader('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`);
|
||||
}
|
||||
}
|
||||
|
||||
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 as Error).message} — accepting email`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process email based on routing rules
|
||||
*/
|
||||
@@ -617,7 +718,12 @@ export class UnifiedEmailServer extends EventEmitter {
|
||||
} 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 || '';
|
||||
|
||||
Reference in New Issue
Block a user