Files
smartmta/ts/mail/security/classes.spfverifier.ts

127 lines
2.8 KiB
TypeScript

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<string, string>;
}
/**
* 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;
}
}
}