krz/domain-dig
an ios app for DNS & SSL analysis
clone: git clone https://gitbay.org/krz/domain-dig.git
v1.4.0: DomainDig/EmailSecurityService.swift · raw
1import Foundation
2
3struct EmailSecurityService {
4 /// Analyze email security records. SPF is parsed from existing TXT records;
5 /// DMARC and DKIM require additional DoH queries.
6 static func analyze(domain: String, txtRecords: [DNSRecord]) async -> EmailSecurityResult {
7 // SPF: extract from existing TXT records
8 let spfRecord = txtRecords.first(where: { $0.value.lowercased().hasPrefix("v=spf1") })
9 let spf = EmailSecurityRecord(found: spfRecord != nil, value: spfRecord?.value)
10
11 // DMARC and DKIM queries in parallel
12 async let dmarcResult = queryTXT(subdomain: "_dmarc.\(domain)")
13 async let dkimResult = queryDKIM(domain: domain)
14
15 let dmarcValue = await dmarcResult
16 let dkimValue = await dkimResult
17
18 let dmarc = EmailSecurityRecord(
19 found: dmarcValue != nil,
20 value: dmarcValue
21 )
22 let dkim = EmailSecurityRecord(
23 found: dkimValue != nil,
24 value: dkimValue
25 )
26
27 return EmailSecurityResult(spf: spf, dmarc: dmarc, dkim: dkim)
28 }
29
30 /// Query a TXT record for the given subdomain via DoH.
31 private static func queryTXT(subdomain: String) async -> String? {
32 do {
33 let records = try await DNSLookupService.lookup(domain: subdomain, recordType: .TXT)
34 return records.first?.value
35 } catch {
36 return nil
37 }
38 }
39
40 /// Try common DKIM selectors and return the first found.
41 private static func queryDKIM(domain: String) async -> String? {
42 let selectors = ["default", "google", "mail"]
43 return await withTaskGroup(of: (Int, String?).self, returning: String?.self) { group in
44 for (index, selector) in selectors.enumerated() {
45 group.addTask {
46 let value = await queryTXT(subdomain: "\(selector)._domainkey.\(domain)")
47 return (index, value)
48 }
49 }
50
51 var results: [(Int, String?)] = []
52 for await result in group {
53 results.append(result)
54 }
55 // Return the first (by selector order) that has a value
56 return results
57 .sorted { $0.0 < $1.0 }
58 .first(where: { $0.1 != nil })?.1
59 }
60 }
61}