import * as plugins from '../../plugins.js'; import { logger } from '../../logger.js'; import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../../security/index.js'; /** * DMARC policy types */ export var DmarcPolicy; (function (DmarcPolicy) { DmarcPolicy["NONE"] = "none"; DmarcPolicy["QUARANTINE"] = "quarantine"; DmarcPolicy["REJECT"] = "reject"; })(DmarcPolicy || (DmarcPolicy = {})); /** * DMARC alignment modes */ export var DmarcAlignment; (function (DmarcAlignment) { DmarcAlignment["RELAXED"] = "r"; DmarcAlignment["STRICT"] = "s"; })(DmarcAlignment || (DmarcAlignment = {})); /** * Class for verifying and enforcing DMARC policies */ export class DmarcVerifier { // DNS Manager reference for verifying records dnsManager; constructor(dnsManager) { this.dnsManager = dnsManager; } /** * Parse a DMARC record from a TXT record string * @param record DMARC TXT record string * @returns Parsed DMARC record or null if invalid */ parseDmarcRecord(record) { if (!record.startsWith('v=DMARC1')) { return null; } try { // Initialize record with default values const dmarcRecord = { version: 'DMARC1', policy: DmarcPolicy.NONE, pct: 100, adkim: DmarcAlignment.RELAXED, aspf: DmarcAlignment.RELAXED }; // Split the record into tag/value pairs const parts = record.split(';').map(part => part.trim()); for (const part of parts) { if (!part || !part.includes('=')) continue; const [tag, value] = part.split('=').map(p => p.trim()); // Process based on tag switch (tag.toLowerCase()) { case 'v': dmarcRecord.version = value; break; case 'p': dmarcRecord.policy = value; break; case 'sp': dmarcRecord.subdomainPolicy = value; break; case 'pct': const pctValue = parseInt(value, 10); if (!isNaN(pctValue) && pctValue >= 0 && pctValue <= 100) { dmarcRecord.pct = pctValue; } break; case 'adkim': dmarcRecord.adkim = value; break; case 'aspf': dmarcRecord.aspf = value; break; case 'ri': const interval = parseInt(value, 10); if (!isNaN(interval) && interval > 0) { dmarcRecord.reportInterval = interval; } break; case 'fo': dmarcRecord.failureOptions = value; break; case 'rua': dmarcRecord.reportUriAggregate = value.split(',').map(uri => { if (uri.startsWith('mailto:')) { return uri.substring(7).trim(); } return uri.trim(); }); break; case 'ruf': dmarcRecord.reportUriForensic = value.split(',').map(uri => { if (uri.startsWith('mailto:')) { return uri.substring(7).trim(); } return uri.trim(); }); break; } } // Ensure subdomain policy is set if not explicitly provided if (!dmarcRecord.subdomainPolicy) { dmarcRecord.subdomainPolicy = dmarcRecord.policy; } return dmarcRecord; } catch (error) { logger.log('error', `Error parsing DMARC record: ${error.message}`, { record, error: error.message }); return null; } } /** * Check if domains are aligned according to DMARC policy * @param headerDomain Domain from header (From) * @param authDomain Domain from authentication (SPF, DKIM) * @param alignment Alignment mode * @returns Whether the domains are aligned */ isDomainAligned(headerDomain, authDomain, alignment) { if (!headerDomain || !authDomain) { return false; } // For strict alignment, domains must match exactly if (alignment === DmarcAlignment.STRICT) { return headerDomain.toLowerCase() === authDomain.toLowerCase(); } // For relaxed alignment, the authenticated domain must be a subdomain of the header domain // or the same as the header domain const headerParts = headerDomain.toLowerCase().split('.'); const authParts = authDomain.toLowerCase().split('.'); // Ensures we have at least two parts (domain and TLD) if (headerParts.length < 2 || authParts.length < 2) { return false; } // Get organizational domain (last two parts) const headerOrgDomain = headerParts.slice(-2).join('.'); const authOrgDomain = authParts.slice(-2).join('.'); return headerOrgDomain === authOrgDomain; } /** * Extract domain from an email address * @param email Email address * @returns Domain part of the email */ getDomainFromEmail(email) { if (!email) return ''; // Handle name + email format: "John Doe " const matches = email.match(/<([^>]+)>/); const address = matches ? matches[1] : email; const parts = address.split('@'); return parts.length > 1 ? parts[1] : ''; } /** * Check if DMARC verification should be applied based on percentage * @param record DMARC record * @returns Whether DMARC verification should be applied */ shouldApplyDmarc(record) { if (record.pct === undefined || record.pct === 100) { return true; } // Apply DMARC randomly based on percentage const random = Math.floor(Math.random() * 100) + 1; return random <= record.pct; } /** * Determine the action to take based on DMARC policy * @param policy DMARC policy * @returns Action to take */ determineAction(policy) { switch (policy) { case DmarcPolicy.REJECT: return 'reject'; case DmarcPolicy.QUARANTINE: return 'quarantine'; case DmarcPolicy.NONE: default: return 'pass'; } } /** * Verify DMARC for an incoming email * @param email Email to verify * @param spfResult SPF verification result * @param dkimResult DKIM verification result * @returns DMARC verification result */ async verify(email, spfResult, dkimResult) { const securityLogger = SecurityLogger.getInstance(); // Initialize result const result = { hasDmarc: false, spfDomainAligned: false, dkimDomainAligned: false, spfPassed: spfResult.result, dkimPassed: dkimResult.result, policyEvaluated: DmarcPolicy.NONE, actualPolicy: DmarcPolicy.NONE, appliedPercentage: 100, action: 'pass', details: 'DMARC not configured' }; try { // Extract From domain const fromHeader = email.getFromEmail(); const fromDomain = this.getDomainFromEmail(fromHeader); if (!fromDomain) { result.error = 'Invalid From domain'; return result; } // Check alignment result.spfDomainAligned = this.isDomainAligned(fromDomain, spfResult.domain, DmarcAlignment.RELAXED); result.dkimDomainAligned = this.isDomainAligned(fromDomain, dkimResult.domain, DmarcAlignment.RELAXED); // Lookup DMARC record const dmarcVerificationResult = this.dnsManager ? await this.dnsManager.verifyDmarcRecord(fromDomain) : { found: false, valid: false, error: 'DNS Manager not available' }; // If DMARC record exists and is valid if (dmarcVerificationResult.found && dmarcVerificationResult.valid) { result.hasDmarc = true; // Parse DMARC record const parsedRecord = this.parseDmarcRecord(dmarcVerificationResult.value); if (parsedRecord) { result.record = parsedRecord; result.actualPolicy = parsedRecord.policy; result.appliedPercentage = parsedRecord.pct || 100; // Override alignment modes if specified in record if (parsedRecord.adkim) { result.dkimDomainAligned = this.isDomainAligned(fromDomain, dkimResult.domain, parsedRecord.adkim); } if (parsedRecord.aspf) { result.spfDomainAligned = this.isDomainAligned(fromDomain, spfResult.domain, parsedRecord.aspf); } // Determine DMARC compliance const spfAligned = result.spfPassed && result.spfDomainAligned; const dkimAligned = result.dkimPassed && result.dkimDomainAligned; // Email passes DMARC if either SPF or DKIM passes with alignment const dmarcPass = spfAligned || dkimAligned; // Use record percentage to determine if policy should be applied const applyPolicy = this.shouldApplyDmarc(parsedRecord); if (!dmarcPass) { // DMARC failed, apply policy result.policyEvaluated = applyPolicy ? parsedRecord.policy : DmarcPolicy.NONE; result.action = this.determineAction(result.policyEvaluated); result.details = `DMARC failed: SPF aligned=${spfAligned}, DKIM aligned=${dkimAligned}, policy=${result.policyEvaluated}`; } else { result.policyEvaluated = DmarcPolicy.NONE; result.action = 'pass'; result.details = `DMARC passed: SPF aligned=${spfAligned}, DKIM aligned=${dkimAligned}`; } } else { result.error = 'Invalid DMARC record format'; result.details = 'DMARC record invalid'; } } else { // No DMARC record found or invalid result.details = dmarcVerificationResult.error || 'No DMARC record found'; } // Log the DMARC verification securityLogger.logEvent({ level: result.action === 'pass' ? SecurityLogLevel.INFO : SecurityLogLevel.WARN, type: SecurityEventType.DMARC, message: result.details, domain: fromDomain, details: { fromDomain, spfDomain: spfResult.domain, dkimDomain: dkimResult.domain, spfPassed: result.spfPassed, dkimPassed: result.dkimPassed, spfAligned: result.spfDomainAligned, dkimAligned: result.dkimDomainAligned, dmarcPolicy: result.policyEvaluated, action: result.action }, success: result.action === 'pass' }); return result; } catch (error) { logger.log('error', `Error verifying DMARC: ${error.message}`, { error: error.message, emailId: email.getMessageId() }); result.error = `DMARC verification error: ${error.message}`; // Log error securityLogger.logEvent({ level: SecurityLogLevel.ERROR, type: SecurityEventType.DMARC, message: `DMARC verification failed with error`, details: { error: error.message, emailId: email.getMessageId() }, success: false }); return result; } } /** * Apply DMARC policy to an email * @param email Email to apply policy to * @param dmarcResult DMARC verification result * @returns Whether the email should be accepted */ applyPolicy(email, dmarcResult) { // Apply action based on DMARC verification result switch (dmarcResult.action) { case 'reject': // Reject the email email.mightBeSpam = true; logger.log('warn', `Email rejected due to DMARC policy: ${dmarcResult.details}`, { emailId: email.getMessageId(), from: email.getFromEmail(), subject: email.subject }); return false; case 'quarantine': // Quarantine the email (mark as spam) email.mightBeSpam = true; // Add spam header if (!email.headers['X-Spam-Flag']) { email.headers['X-Spam-Flag'] = 'YES'; } // Add DMARC reason header email.headers['X-DMARC-Result'] = dmarcResult.details; logger.log('warn', `Email quarantined due to DMARC policy: ${dmarcResult.details}`, { emailId: email.getMessageId(), from: email.getFromEmail(), subject: email.subject }); return true; case 'pass': default: // Accept the email // Add DMARC result header for information email.headers['X-DMARC-Result'] = dmarcResult.details; return true; } } /** * End-to-end DMARC verification and policy application * This method should be called after SPF and DKIM verification * @param email Email to verify * @param spfResult SPF verification result * @param dkimResult DKIM verification result * @returns Whether the email should be accepted */ async verifyAndApply(email, spfResult, dkimResult) { // Verify DMARC const dmarcResult = await this.verify(email, spfResult, dkimResult); // Apply DMARC policy return this.applyPolicy(email, dmarcResult); } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5kbWFyY3ZlcmlmaWVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvbWFpbC9zZWN1cml0eS9jbGFzc2VzLmRtYXJjdmVyaWZpZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxrQkFBa0IsQ0FBQztBQUM1QyxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDekMsT0FBTyxFQUFFLGNBQWMsRUFBRSxnQkFBZ0IsRUFBRSxpQkFBaUIsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBSzlGOztHQUVHO0FBQ0gsTUFBTSxDQUFOLElBQVksV0FJWDtBQUpELFdBQVksV0FBVztJQUNyQiw0QkFBYSxDQUFBO0lBQ2Isd0NBQXlCLENBQUE7SUFDekIsZ0NBQWlCLENBQUE7QUFDbkIsQ0FBQyxFQUpXLFdBQVcsS0FBWCxXQUFXLFFBSXRCO0FBRUQ7O0dBRUc7QUFDSCxNQUFNLENBQU4sSUFBWSxjQUdYO0FBSEQsV0FBWSxjQUFjO0lBQ3hCLCtCQUFhLENBQUE7SUFDYiw4QkFBWSxDQUFBO0FBQ2QsQ0FBQyxFQUhXLGNBQWMsS0FBZCxjQUFjLFFBR3pCO0FBdUNEOztHQUVHO0FBQ0gsTUFBTSxPQUFPLGFBQWE7SUFDeEIsOENBQThDO0lBQ3RDLFVBQVUsQ0FBTztJQUV6QixZQUFZLFVBQWdCO1FBQzFCLElBQUksQ0FBQyxVQUFVLEdBQUcsVUFBVSxDQUFDO0lBQy9CLENBQUM7SUFFRDs7OztPQUlHO0lBQ0ksZ0JBQWdCLENBQUMsTUFBYztRQUNwQyxJQUFJLENBQUMsTUFBTSxDQUFDLFVBQVUsQ0FBQyxVQUFVLENBQUMsRUFBRSxDQUFDO1lBQ25DLE9BQU8sSUFBSSxDQUFDO1FBQ2QsQ0FBQztRQUVELElBQUksQ0FBQztZQUNILHdDQUF3QztZQUN4QyxNQUFNLFdBQVcsR0FBZ0I7Z0JBQy9CLE9BQU8sRUFBRSxRQUFRO2dCQUNqQixNQUFNLEVBQUUsV0FBVyxD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