127 lines
2.8 KiB
TypeScript
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;
|
|
}
|
|
}
|
|
}
|