summaryrefslogtreecommitdiff
path: root/DomainDig/EmailSecurityService.swift
diff options
context:
space:
mode:
Diffstat (limited to 'DomainDig/EmailSecurityService.swift')
-rw-r--r--DomainDig/EmailSecurityService.swift61
1 files changed, 61 insertions, 0 deletions
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
+ }
+ }
+}