import * as plugins from '../../plugins.js'; import { logger } from '../../logger.js'; import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../../security/index.js'; import { RustSecurityBridge } from '../../security/classes.rustsecuritybridge.js'; /** * SPF result qualifiers */ export var SpfQualifier; (function (SpfQualifier) { SpfQualifier["PASS"] = "+"; SpfQualifier["NEUTRAL"] = "?"; SpfQualifier["SOFTFAIL"] = "~"; SpfQualifier["FAIL"] = "-"; })(SpfQualifier || (SpfQualifier = {})); /** * SPF mechanism types */ export var SpfMechanismType; (function (SpfMechanismType) { SpfMechanismType["ALL"] = "all"; SpfMechanismType["INCLUDE"] = "include"; SpfMechanismType["A"] = "a"; SpfMechanismType["MX"] = "mx"; SpfMechanismType["IP4"] = "ip4"; SpfMechanismType["IP6"] = "ip6"; SpfMechanismType["EXISTS"] = "exists"; SpfMechanismType["REDIRECT"] = "redirect"; SpfMechanismType["EXP"] = "exp"; })(SpfMechanismType || (SpfMechanismType = {})); /** * Class for verifying SPF records. * Delegates actual SPF evaluation to the Rust security bridge. * Retains parseSpfRecord() for lightweight local parsing. */ export class SpfVerifier { constructor(_dnsManager) { // dnsManager is no longer needed — Rust handles DNS lookups } /** * Parse SPF record from TXT record (pure string parsing, no DNS) */ parseSpfRecord(record) { if (!record.startsWith('v=spf1')) { return null; } try { const spfRecord = { version: 'spf1', mechanisms: [], modifiers: {} }; const terms = record.split(' ').filter(term => term.length > 0); for (let i = 1; i < terms.length; i++) { const term = terms[i]; if (term.includes('=')) { const [name, value] = term.split('='); spfRecord.modifiers[name] = value; continue; } let qualifier = SpfQualifier.PASS; let mechanismText = term; if (term.startsWith('+') || term.startsWith('-') || term.startsWith('~') || term.startsWith('?')) { qualifier = term[0]; mechanismText = term.substring(1); } const colonIndex = mechanismText.indexOf(':'); let type; let value; if (colonIndex !== -1) { type = mechanismText.substring(0, colonIndex); value = mechanismText.substring(colonIndex + 1); } else { type = mechanismText; } spfRecord.mechanisms.push({ qualifier, type, value }); } return spfRecord; } catch (error) { logger.log('error', `Error parsing SPF record: ${error.message}`, { record, error: error.message }); return null; } } /** * Verify SPF for a given email — delegates to Rust bridge */ async verify(email, ip, heloDomain) { const securityLogger = SecurityLogger.getInstance(); const mailFrom = email.from || ''; const domain = mailFrom.split('@')[1] || ''; try { const bridge = RustSecurityBridge.getInstance(); const result = await bridge.checkSpf({ ip, heloDomain, hostname: plugins.os.hostname(), mailFrom, }); const spfResult = { result: result.result, domain: result.domain, ip: result.ip, explanation: result.explanation ?? undefined, }; securityLogger.logEvent({ level: spfResult.result === 'pass' ? SecurityLogLevel.INFO : (spfResult.result === 'fail' ? SecurityLogLevel.WARN : SecurityLogLevel.INFO), type: SecurityEventType.SPF, message: `SPF ${spfResult.result} for ${spfResult.domain} from IP ${ip}`, domain: spfResult.domain, details: { ip, heloDomain, result: spfResult.result, explanation: spfResult.explanation }, success: spfResult.result === 'pass' }); return spfResult; } catch (error) { logger.log('error', `SPF verification error: ${error.message}`, { domain, ip, error: error.message }); securityLogger.logEvent({ level: SecurityLogLevel.ERROR, type: SecurityEventType.SPF, message: `SPF verification error for ${domain}`, domain, details: { ip, error: error.message }, success: false }); return { result: 'temperror', explanation: `Error verifying SPF: ${error.message}`, domain, ip, error: error.message }; } } /** * Check if email passes SPF verification and apply headers */ async verifyAndApply(email, ip, heloDomain) { const result = await this.verify(email, ip, heloDomain); email.headers['Received-SPF'] = `${result.result} (${result.domain}: ${result.explanation || ''}) client-ip=${ip}; envelope-from=${email.getEnvelopeFrom()}; helo=${heloDomain};`; switch (result.result) { case 'fail': email.mightBeSpam = true; logger.log('warn', `SPF failed for ${result.domain} from ${ip}: ${result.explanation}`); return false; case 'softfail': email.mightBeSpam = true; logger.log('info', `SPF softfailed for ${result.domain} from ${ip}: ${result.explanation}`); return true; case 'neutral': case 'none': logger.log('info', `SPF ${result.result} for ${result.domain} from ${ip}: ${result.explanation}`); return true; case 'pass': logger.log('info', `SPF passed for ${result.domain} from ${ip}: ${result.explanation}`); return true; case 'temperror': case 'permerror': logger.log('error', `SPF error for ${result.domain} from ${ip}: ${result.explanation}`); return true; default: return true; } } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5zcGZ2ZXJpZmllci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3RzL21haWwvc2VjdXJpdHkvY2xhc3Nlcy5zcGZ2ZXJpZmllci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGtCQUFrQixDQUFDO0FBQzVDLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUN6QyxPQUFPLEVBQUUsY0FBYyxFQUFFLGdCQUFnQixFQUFFLGlCQUFpQixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDOUYsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sOENBQThDLENBQUM7QUFHbEY7O0dBRUc7QUFDSCxNQUFNLENBQU4sSUFBWSxZQUtYO0FBTEQsV0FBWSxZQUFZO0lBQ3RCLDBCQUFVLENBQUE7SUFDViw2QkFBYSxDQUFBO0lBQ2IsOEJBQWMsQ0FBQTtJQUNkLDBCQUFVLENBQUE7QUFDWixDQUFDLEVBTFcsWUFBWSxLQUFaLFlBQVksUUFLdkI7QUFFRDs7R0FFRztBQUNILE1BQU0sQ0FBTixJQUFZLGdCQVVYO0FBVkQsV0FBWSxnQkFBZ0I7SUFDMUIsK0JBQVcsQ0FBQTtJQUNYLHVDQUFtQixDQUFBO0lBQ25CLDJCQUFPLENBQUE7SUFDUCw2QkFBUyxDQUFBO0lBQ1QsK0JBQVcsQ0FBQTtJQUNYLCtCQUFXLENBQUE7SUFDWCxxQ0FBaUIsQ0FBQTtJQUNqQix5Q0FBcUIsQ0FBQTtJQUNyQiwrQkFBVyxDQUFBO0FBQ2IsQ0FBQyxFQVZXLGdCQUFnQixLQUFoQixnQkFBZ0IsUUFVM0I7QUFnQ0Q7Ozs7R0FJRztBQUNILE1BQU0sT0FBTyxXQUFXO0lBQ3RCLFlBQVksV0FBaUI7UUFDM0IsNERBQTREO0lBQzlELENBQUM7SUFFRDs7T0FFRztJQUNJLGNBQWMsQ0FBQyxNQUFjO1FBQ2xDLElBQUksQ0FBQyxNQUFNLENBQUMsVUFBVSxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUM7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