From 376e7b55b4a0c4f54faa0c2828dea4fd17dff288 Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Wed, 18 Mar 2026 11:59:38 -0500 Subject: realigning commits --- DomainDig/EmailSecurityService.swift | 61 ++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 DomainDig/EmailSecurityService.swift (limited to 'DomainDig/EmailSecurityService.swift') diff --git a/DomainDig/EmailSecurityService.swift b/DomainDig/EmailSecurityService.swift new file mode 100644 index 0000000..8922993 --- /dev/null +++ b/DomainDig/EmailSecurityService.swift @@ -0,0 +1,61 @@ +import Foundation + +struct EmailSecurityService { + /// Analyze email security records. SPF is parsed from existing TXT records; + /// DMARC and DKIM require additional DoH queries. + static func analyze(domain: String, txtRecords: [DNSRecord]) async -> EmailSecurityResult { + // SPF: extract from existing TXT records + let spfRecord = txtRecords.first(where: { $0.value.lowercased().hasPrefix("v=spf1") }) + let spf = EmailSecurityRecord(found: spfRecord != nil, value: spfRecord?.value) + + // DMARC and DKIM queries in parallel + async let dmarcResult = queryTXT(subdomain: "_dmarc.\(domain)") + async let dkimResult = queryDKIM(domain: domain) + + let dmarcValue = await dmarcResult + let dkimValue = await dkimResult + + let dmarc = EmailSecurityRecord( + found: dmarcValue != nil, + value: dmarcValue + ) + let dkim = EmailSecurityRecord( + found: dkimValue != nil, + value: dkimValue + ) + + return EmailSecurityResult(spf: spf, dmarc: dmarc, dkim: dkim) + } + + /// Query a TXT record for the given subdomain via DoH. + private static func queryTXT(subdomain: String) async -> String? { + do { + let records = try await DNSLookupService.lookup(domain: subdomain, recordType: .TXT) + return records.first?.value + } catch { + return nil + } + } + + /// Try common DKIM selectors and return the first found. + private static func queryDKIM(domain: String) async -> String? { + let selectors = ["default", "google", "mail"] + return await withTaskGroup(of: (Int, String?).self, returning: String?.self) { group in + for (index, selector) in selectors.enumerated() { + group.addTask { + let value = await queryTXT(subdomain: "\(selector)._domainkey.\(domain)") + return (index, value) + } + } + + var results: [(Int, String?)] = [] + for await result in group { + results.append(result) + } + // Return the first (by selector order) that has a value + return results + .sorted { $0.0 < $1.0 } + .first(where: { $0.1 != nil })?.1 + } + } +} -- cgit v1.2.3