import { logger } from '../../logger.js'; /** * SPF result qualifiers */ export enum SpfQualifier { PASS = '+', NEUTRAL = '?', SOFTFAIL = '~', FAIL = '-' } /** * SPF mechanism types */ export enum SpfMechanismType { ALL = 'all', INCLUDE = 'include', A = 'a', MX = 'mx', IP4 = 'ip4', IP6 = 'ip6', EXISTS = 'exists', REDIRECT = 'redirect', EXP = 'exp' } /** * SPF mechanism definition */ export interface SpfMechanism { qualifier: SpfQualifier; type: SpfMechanismType; value?: string; } /** * SPF record parsed data */ export interface SpfRecord { version: string; mechanisms: SpfMechanism[]; modifiers: Record; } /** * SPF verification result */ export interface SpfResult { result: 'pass' | 'neutral' | 'softfail' | 'fail' | 'temperror' | 'permerror' | 'none'; explanation?: string; domain: string; ip: string; record?: string; error?: string; } /** * 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?: any) { // dnsManager is no longer needed — Rust handles DNS lookups } /** * Parse SPF record from TXT record (pure string parsing, no DNS) */ public parseSpfRecord(record: string): SpfRecord | null { if (!record.startsWith('v=spf1')) { return null; } try { const spfRecord: 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] as SpfQualifier; mechanismText = term.substring(1); } const colonIndex = mechanismText.indexOf(':'); let type: SpfMechanismType; let value: string | undefined; if (colonIndex !== -1) { type = mechanismText.substring(0, colonIndex) as SpfMechanismType; value = mechanismText.substring(colonIndex + 1); } else { type = mechanismText as SpfMechanismType; } 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; } } }