import * as plugins from '../plugins.js'; import * as paths from '../paths.js'; import { logger } from '../logger.js'; // --------------------------------------------------------------------------- // IPC command type map — mirrors the methods in mailer-bin's management mode // --------------------------------------------------------------------------- interface IDkimVerificationResult { is_valid: boolean; domain: string | null; selector: string | null; status: string; details: string | null; } interface ISpfResult { result: string; domain: string; ip: string; explanation: string | null; } interface IDmarcResult { passed: boolean; policy: string; domain: string; dkim_result: string; spf_result: string; action: string; details: string | null; } interface IEmailSecurityResult { dkim: IDkimVerificationResult[]; spf: ISpfResult | null; dmarc: IDmarcResult | null; } interface IValidationResult { valid: boolean; formatValid: boolean; score: number; error: string | null; } interface IBounceDetection { bounce_type: string; category: string; } interface IReputationResult { ip: string; score: number; risk_level: string; ip_type: string; dnsbl_results: Array<{ server: string; listed: boolean; response: string | null }>; listed_count: number; total_checked: number; } interface IContentScanResult { threatScore: number; threatType: string | null; threatDetails: string | null; scannedElements: string[]; } interface IVersionInfo { bin: string; core: string; security: string; smtp: string; } /** * Type-safe command map for the mailer-bin IPC bridge. */ type TMailerCommands = { ping: { params: Record; result: { pong: boolean }; }; version: { params: Record; result: IVersionInfo; }; validateEmail: { params: { email: string }; result: IValidationResult; }; detectBounce: { params: { smtpResponse?: string; diagnosticCode?: string; statusCode?: string }; result: IBounceDetection; }; checkIpReputation: { params: { ip: string }; result: IReputationResult; }; verifyDkim: { params: { rawMessage: string }; result: IDkimVerificationResult[]; }; signDkim: { params: { rawMessage: string; domain: string; selector?: string; privateKey: string }; result: { header: string; signedMessage: string }; }; checkSpf: { params: { ip: string; heloDomain: string; hostname?: string; mailFrom: string }; result: ISpfResult; }; scanContent: { params: { subject?: string; textBody?: string; htmlBody?: string; attachmentNames?: string[]; }; result: IContentScanResult; }; verifyEmail: { params: { rawMessage: string; ip: string; heloDomain: string; hostname?: string; mailFrom: string; }; result: IEmailSecurityResult; }; }; // --------------------------------------------------------------------------- // RustSecurityBridge — singleton wrapper around smartrust.RustBridge // --------------------------------------------------------------------------- /** * Bridge between TypeScript and the Rust `mailer-bin` binary. * * Uses `@push.rocks/smartrust` for JSON-over-stdin/stdout IPC. * Singleton — access via `RustSecurityBridge.getInstance()`. */ export class RustSecurityBridge { private static instance: RustSecurityBridge | null = null; private bridge: InstanceType>; private _running = false; private constructor() { this.bridge = new plugins.smartrust.RustBridge({ binaryName: 'mailer-bin', cliArgs: ['--management'], requestTimeoutMs: 30_000, readyTimeoutMs: 10_000, localPaths: [ plugins.path.join(paths.packageDir, 'dist_rust', 'mailer-bin'), plugins.path.join(paths.packageDir, 'rust', 'target', 'release', 'mailer-bin'), plugins.path.join(paths.packageDir, 'rust', 'target', 'debug', 'mailer-bin'), ], searchSystemPath: false, }); // Forward lifecycle events this.bridge.on('ready', () => { this._running = true; logger.log('info', 'Rust security bridge is ready'); }); this.bridge.on('exit', (code: number | null, signal: string | null) => { this._running = false; logger.log('warn', `Rust security bridge exited (code=${code}, signal=${signal})`); }); this.bridge.on('stderr', (line: string) => { logger.log('debug', `[rust-bridge] ${line}`); }); } /** Get or create the singleton instance. */ public static getInstance(): RustSecurityBridge { if (!RustSecurityBridge.instance) { RustSecurityBridge.instance = new RustSecurityBridge(); } return RustSecurityBridge.instance; } /** Whether the Rust process is currently running and accepting commands. */ public get running(): boolean { return this._running; } // ----------------------------------------------------------------------- // Lifecycle // ----------------------------------------------------------------------- /** * Spawn the Rust binary and wait for the ready signal. * @returns `true` if the binary started successfully, `false` otherwise. */ public async start(): Promise { if (this._running) { return true; } try { const ok = await this.bridge.spawn(); this._running = ok; if (ok) { logger.log('info', 'Rust security bridge started'); } else { logger.log('warn', 'Rust security bridge failed to start (binary not found or timeout)'); } return ok; } catch (err) { logger.log('error', `Failed to start Rust security bridge: ${(err as Error).message}`); return false; } } /** Kill the Rust process. */ public async stop(): Promise { if (!this._running) { return; } try { this.bridge.kill(); this._running = false; logger.log('info', 'Rust security bridge stopped'); } catch (err) { logger.log('error', `Error stopping Rust security bridge: ${(err as Error).message}`); } } // ----------------------------------------------------------------------- // Commands — thin typed wrappers over sendCommand // ----------------------------------------------------------------------- /** Ping the Rust process. */ public async ping(): Promise { const res = await this.bridge.sendCommand('ping', {} as any); return res?.pong === true; } /** Get version information for all Rust crates. */ public async getVersion(): Promise { return this.bridge.sendCommand('version', {} as any); } /** Validate an email address. */ public async validateEmail(email: string): Promise { return this.bridge.sendCommand('validateEmail', { email }); } /** Detect bounce type from SMTP response / diagnostic code. */ public async detectBounce(opts: { smtpResponse?: string; diagnosticCode?: string; statusCode?: string; }): Promise { return this.bridge.sendCommand('detectBounce', opts); } /** Scan email content for threats (phishing, spam, malware, etc.). */ public async scanContent(opts: { subject?: string; textBody?: string; htmlBody?: string; attachmentNames?: string[]; }): Promise { return this.bridge.sendCommand('scanContent', opts); } /** Check IP reputation via DNSBL. */ public async checkIpReputation(ip: string): Promise { return this.bridge.sendCommand('checkIpReputation', { ip }); } /** Verify DKIM signatures on a raw email message. */ public async verifyDkim(rawMessage: string): Promise { return this.bridge.sendCommand('verifyDkim', { rawMessage }); } /** Sign an email with DKIM. */ public async signDkim(opts: { rawMessage: string; domain: string; selector?: string; privateKey: string; }): Promise<{ header: string; signedMessage: string }> { return this.bridge.sendCommand('signDkim', opts); } /** Check SPF for a sender. */ public async checkSpf(opts: { ip: string; heloDomain: string; hostname?: string; mailFrom: string; }): Promise { return this.bridge.sendCommand('checkSpf', opts); } /** * Compound email security verification: DKIM + SPF + DMARC in one IPC call. * * This is the preferred method for inbound email verification — it avoids * 3 sequential round-trips and correctly passes raw mail-auth types internally. */ public async verifyEmail(opts: { rawMessage: string; ip: string; heloDomain: string; hostname?: string; mailFrom: string; }): Promise { return this.bridge.sendCommand('verifyEmail', opts); } } // Re-export interfaces for consumers export type { IDkimVerificationResult, ISpfResult, IDmarcResult, IEmailSecurityResult, IValidationResult, IBounceDetection, IContentScanResult, IReputationResult as IRustReputationResult, IVersionInfo, };