DKIM / SPF / DMARC Builder & Checker — C++ source
Build the three email-authentication DNS records — SPF, DKIM, and DMARC — as ready-to-paste TXT values, or look up a domain's live records over DNS-over-HTTPS. Import existing records, catch publishing mistakes, share via URL. Entirely client-side.
This is the C++ implementation — the same logic the interactive tool runs, in a shareable, citable form.
// Email authentication (SPF / DKIM / DMARC) record parsing and lookup.
//
// Language: C++17 (standard library + OpenSSL for base64; DoH via injectable fetch)
// Ported from src/lib/dkim-spf-dmarc.ts (the canonical TypeScript
// implementation). display source — part of CosmoDev's polyglot tool pages.
//
// Two layers, mirroring the TS module:
// - Pure parsers (parseSPF / parseDKIM / parseDMARC) — deterministic, never
// throw, unit-testable without network.
// - Check functions (checkSPF / checkDKIM / checkDMARC) — one DNS-over-HTTPS
// fetch to Cloudflare's public resolver, then a pure parse. The transport
// is injectable (FetchFn) so the logic stays testable without a socket.
//
// Specs: SPF RFC 7208, DKIM RFC 6376, DMARC RFC 7489.
#include <algorithm>
#include <cctype>
#include <cstdint>
#include <functional>
#include <map>
#include <optional>
#include <stdexcept>
#include <string>
#include <vector>
namespace email_auth {
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
enum class CheckStatus { Pass, Warn, Fail };
enum class SpfQualifier { Pass, Fail, SoftFail, Neutral }; // + - ~ ?
enum class SpfKind { All, Include, A, Mx, Ip4, Ip6, Exists, Ptr };
struct SpfMechanism {
SpfQualifier qualifier = SpfQualifier::Pass;
SpfKind kind = SpfKind::All;
/** Domain, IP, or CIDR argument after the colon ("" when absent). */
std::string value;
bool hasValue = false;
};
struct SpfRecord {
bool valid = false;
std::string version;
std::vector<SpfMechanism> mechanisms;
bool hasRedirect = false;
std::string redirect;
std::string exp;
/** Mechanisms that cost a DNS query under RFC 7208 §4.6.4 (limit 10). */
int lookupCount = 0;
/** Number of v=spf1 records found (more than one is a hard error). */
int recordCount = 0;
std::vector<std::string> warnings;
};
struct DkimRecord {
bool valid = false;
std::string version;
std::string keyType = "rsa";
std::string publicKey; ///< base64, whitespace-stripped ("" until decoded)
/** Approximate modulus size in bits (RSA only; derived from DER byte length). */
int keyBits = -1;
bool hasKeyBits = false;
std::vector<std::string> hashes;
std::vector<std::string> services;
std::vector<std::string> flags;
std::vector<std::string> warnings;
};
enum class DmarcPolicy { None, Quarantine, Reject };
struct DmarcRecord {
bool valid = false;
std::optional<DmarcPolicy> policy;
std::optional<DmarcPolicy> subdomainPolicy;
std::vector<std::string> aggregateUris;
std::vector<std::string> forensicUris;
std::optional<int> percent;
std::optional<char> dkimAlignment; ///< 'r' | 's'
std::optional<char> spfAlignment; ///< 'r' | 's'
std::vector<std::string> warnings;
};
template <typename Record>
struct CheckResult {
CheckStatus status = CheckStatus::Fail;
bool found = false;
std::optional<Record> record;
std::string message;
};
using SpfCheck = CheckResult<SpfRecord>;
using DkimCheck = CheckResult<DkimRecord>;
using DmarcCheck = CheckResult<DmarcRecord>;
// ---------------------------------------------------------------------------
// Shared helpers
// ---------------------------------------------------------------------------
static std::string toLower(std::string s) {
std::transform(s.begin(), s.end(), s.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return s;
}
static std::string toUpper(std::string s) {
std::transform(s.begin(), s.end(), s.begin(),
[](unsigned char c) { return static_cast<char>(std::toupper(c)); });
return s;
}
static std::string trim(const std::string& s) {
size_t b = s.find_first_not_of(" \t\r\n");
if (b == std::string::npos) return "";
size_t e = s.find_last_not_of(" \t\r\n");
return s.substr(b, e - b + 1);
}
static bool startsWith(const std::string& s, const std::string& prefix) {
return s.size() >= prefix.size() && s.compare(0, prefix.size(), prefix) == 0;
}
static std::vector<std::string> splitOn(const std::string& s, char sep) {
std::vector<std::string> out;
std::string cur;
for (char c : s) {
if (c == sep) {
out.push_back(cur);
cur.clear();
} else {
cur += c;
}
}
out.push_back(cur);
return out;
}
static std::vector<std::string> splitOnSpaces(const std::string& s) {
std::vector<std::string> out;
std::string cur;
for (char c : s) {
if (std::isspace(static_cast<unsigned char>(c))) {
if (!cur.empty()) out.push_back(cur);
cur.clear();
} else {
cur += c;
}
}
if (!cur.empty()) out.push_back(cur);
return out;
}
static std::vector<std::string> filterNonEmpty(const std::vector<std::string>& in) {
std::vector<std::string> out;
for (const std::string& s : in) {
if (!s.empty()) out.push_back(s);
}
return out;
}
static std::vector<std::string> mapTrimmed(const std::vector<std::string>& in) {
std::vector<std::string> out;
out.reserve(in.size());
for (const std::string& s : in) out.push_back(trim(s));
return out;
}
static std::vector<std::string> splitLines(const std::string& body) {
std::vector<std::string> lines;
std::string cur;
for (char c : body) {
if (c == '\n') {
if (!cur.empty() && cur.back() == '\r') cur.pop_back();
lines.push_back(cur);
cur.clear();
} else {
cur += c;
}
}
lines.push_back(cur);
return lines;
}
/** Strip a leading scheme, userinfo, path, query, port, and trailing dot. */
std::string normalizeDomain(const std::string& raw) {
std::string s = trim(raw);
// scheme://
size_t schemeEnd = s.find("://");
if (schemeEnd != std::string::npos) {
const std::string scheme = toLower(s.substr(0, schemeEnd));
const bool isScheme = !scheme.empty() && std::isalpha(static_cast<unsigned char>(scheme[0])) &&
std::all_of(scheme.begin() + 1, scheme.end(), [](unsigned char c) {
return std::isalnum(c) != 0 || c == '+' || c == '.' || c == '-';
});
if (isScheme) s = s.substr(schemeEnd + 3);
}
if (startsWith(toLower(s), "mailto:")) s = s.substr(7); // mailto:user@domain
if (s.find('@') != std::string::npos) s = s.substr(s.rfind('@') + 1); // host of user@host
s = splitOn(s, '/')[0]; // drop path
s = splitOn(s, '?')[0]; // drop query
s = splitOn(s, ':')[0]; // drop port
while (!s.empty() && s.back() == '.') s.pop_back(); // trailing dot(s)
return toLower(s);
}
/** True when the string looks like a plausible multi-label domain (example.com):
* ^(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,}$ and length <= 253. */
bool isDomainLike(const std::string& domain) {
if (domain.empty() || domain.size() > 253) return false;
const std::vector<std::string> labels = splitOn(domain, '.');
if (labels.size() < 2) return false;
for (size_t i = 0; i < labels.size(); i++) {
const std::string& label = labels[i];
if (label.empty()) return false;
const bool ok = std::all_of(label.begin(), label.end(), [](unsigned char c) {
return std::isalnum(c) != 0 || c == '-';
});
if (!ok) return false;
if (label.front() == '-' || label.back() == '-') return false;
if (i == labels.size() - 1 && label.size() < 2) return false; // TLD needs 2+ chars
if (i == labels.size() - 1 &&
!std::all_of(label.begin(), label.end(),
[](unsigned char c) { return std::isalpha(c) != 0; })) {
return false; // the TLD is letters only per the TS regex
}
}
return true;
}
/** True when the selector is a safe single DNS label chain: ^[a-z0-9][a-z0-9._-]*$,
* 1-100 chars, no "..". */
bool isValidSelector(const std::string& selector) {
const std::string s = trim(selector);
if (s.empty() || s.size() > 100) return false;
if (!std::isalnum(static_cast<unsigned char>(s[0]))) return false;
for (char c : s) {
if (!(std::isalnum(static_cast<unsigned char>(c)) != 0 || c == '.' || c == '_' || c == '-')) {
return false;
}
}
return s.find("..") == std::string::npos;
}
// ---------------------------------------------------------------------------
// SPF — RFC 7208
// ---------------------------------------------------------------------------
static bool isLookupKind(SpfKind kind) {
return kind == SpfKind::Include || kind == SpfKind::A || kind == SpfKind::Mx ||
kind == SpfKind::Exists || kind == SpfKind::Ptr;
}
static SpfKind spfKindByName(const std::string& name) {
if (name == "all") return SpfKind::All;
if (name == "include") return SpfKind::Include;
if (name == "a") return SpfKind::A;
if (name == "mx") return SpfKind::Mx;
if (name == "ip4") return SpfKind::Ip4;
if (name == "ip6") return SpfKind::Ip6;
if (name == "exists") return SpfKind::Exists;
return SpfKind::Ptr; // "ptr"
}
static char qualifierChar(SpfQualifier q) {
switch (q) {
case SpfQualifier::Pass: return '+';
case SpfQualifier::Fail: return '-';
case SpfQualifier::SoftFail: return '~';
case SpfQualifier::Neutral: return '?';
}
return '+';
}
static std::string unquote(const std::string& t) {
if (t.size() >= 2 && t.front() == '"' && t.back() == '"') return t.substr(1, t.size() - 2);
return t;
}
/** Parse one or more (newline-joined) TXT record strings for SPF. Pure. */
SpfRecord parseSPF(const std::string& txt) {
std::vector<std::string> spfLines;
for (const std::string& line : splitOn(txt, '\n')) {
std::string l = trim(unquote(trim(line)));
if (l.empty()) continue;
const std::string lower = toLower(l);
if (lower == "v=spf1" || startsWith(lower, "v=spf1 ")) spfLines.push_back(l);
}
SpfRecord record;
if (spfLines.empty()) {
record.warnings.push_back("No v=spf1 record found in the supplied text.");
return record;
}
if (spfLines.size() > 1) {
record.warnings.push_back(std::to_string(spfLines.size()) +
" SPF records found — RFC 7208 allows exactly one. Receivers treat this as a permanent error and ignore SPF for the whole domain.");
}
const std::vector<std::string> terms = splitOnSpaces(spfLines[0]);
record.version = terms[0];
for (size_t i = 1; i < terms.size(); i++) {
const std::string& term = terms[i];
// Modifiers use '=': redirect= and exp=
const size_t eq = term.find('=');
if (eq != std::string::npos && eq > 0 &&
std::isalpha(static_cast<unsigned char>(term[0]))) {
bool nameIsModifierShape = true;
for (size_t c = 1; c < eq && nameIsModifierShape; c++) {
const char ch = term[c];
if (!(std::isalnum(static_cast<unsigned char>(ch)) != 0 || ch == '-')) nameIsModifierShape = false;
}
if (nameIsModifierShape) {
const std::string name = toLower(term.substr(0, eq));
if (name == "redirect") {
record.redirect = term.substr(eq + 1);
record.hasRedirect = true;
} else if (name == "exp") {
record.exp = term.substr(eq + 1);
} else {
record.warnings.push_back("Unknown modifier \"" + term + "\" ignored.");
}
continue;
}
}
// Mechanism: optional qualifier + name + optional ":value"
size_t pos = 0;
SpfQualifier qualifier = SpfQualifier::Pass;
if (term.size() > 1 && (term[0] == '+' || term[0] == '-' || term[0] == '~' || term[0] == '?')) {
qualifier = term[0] == '+' ? SpfQualifier::Pass
: term[0] == '-' ? SpfQualifier::Fail
: term[0] == '~' ? SpfQualifier::SoftFail
: SpfQualifier::Neutral;
pos = 1;
}
const size_t colon = term.find(':', pos);
const std::string name = toLower(term.substr(pos, colon == std::string::npos ? std::string::npos : colon - pos));
if (name.empty() ||
!std::all_of(name.begin(), name.end(), [](unsigned char c) { return std::isalnum(c) != 0; })) {
record.warnings.push_back("Unrecognized term \"" + term + "\" ignored.");
continue;
}
if (name != "all" && name != "include" && name != "a" && name != "mx" &&
name != "ip4" && name != "ip6" && name != "exists" && name != "ptr") {
record.warnings.push_back("Unknown mechanism \"" + term + "\" ignored.");
continue;
}
SpfMechanism mech;
mech.qualifier = qualifier;
mech.kind = spfKindByName(name);
if (colon != std::string::npos) {
mech.value = term.substr(colon + 1);
mech.hasValue = true;
}
if (!mech.hasValue && (mech.kind == SpfKind::Include || mech.kind == SpfKind::Exists)) {
record.warnings.push_back("Mechanism \"" + term + "\" is missing its required value.");
continue;
}
record.mechanisms.push_back(mech);
}
int lookups = 0;
for (const SpfMechanism& mech : record.mechanisms) {
if (isLookupKind(mech.kind)) lookups++;
}
if (record.hasRedirect) lookups++;
record.lookupCount = lookups;
const SpfMechanism* all = nullptr;
for (const SpfMechanism& mech : record.mechanisms) {
if (mech.kind == SpfKind::All) {
all = &mech;
break;
}
}
if (all == nullptr && !record.hasRedirect) {
record.warnings.push_back("No \"all\" mechanism and no \"redirect=\" — unmatched senders get a Neutral result, so anyone can still send mail that looks like this domain.");
}
if (all != nullptr && all->qualifier == SpfQualifier::Pass) {
record.warnings.push_back("\"+all\" explicitly allows every host on the internet to send mail as this domain — this defeats SPF entirely.");
} else if (all != nullptr && all->qualifier == SpfQualifier::Neutral) {
record.warnings.push_back("\"?all\" (Neutral) lets unmatched senders through with no protection. Prefer \"~all\" or \"-all\".");
}
for (const SpfMechanism& mech : record.mechanisms) {
if (mech.kind == SpfKind::Ptr) {
record.warnings.push_back("The \"ptr\" mechanism is deprecated (RFC 7208 §5.5) and should not be used.");
break;
}
}
if (record.hasRedirect && all != nullptr) {
record.warnings.push_back("A \"redirect=\" modifier is ignored when an \"all\" mechanism is present.");
}
if (record.lookupCount > 10) {
record.warnings.push_back(std::to_string(record.lookupCount) +
" DNS-lookup mechanisms — RFC 7208 §4.6.4 caps SPF at 10. Receivers that hit the cap return permerror and ignore the record.");
}
record.valid = true;
record.recordCount = static_cast<int>(spfLines.size());
return record;
}
// ---------------------------------------------------------------------------
// DKIM — RFC 6376
// ---------------------------------------------------------------------------
/** Length in bytes of a base64 string decoded leniently; -1 when invalid. */
static int lenientBase64Length(const std::string& b64Raw) {
std::string clean;
for (char c : b64Raw) {
if (std::isspace(static_cast<unsigned char>(c))) continue;
clean += c;
}
if (clean.empty()) return -1;
for (char c : clean) {
if (!(std::isalnum(static_cast<unsigned char>(c)) != 0 || c == '+' || c == '/')) return -1;
}
int padding = 0;
while (clean.size() > padding && clean[clean.size() - 1 - padding] == '=') padding++;
if (padding > 2) return -1;
const size_t data = clean.size() - padding;
if (data % 4 == 1) return -1;
return static_cast<int>(data / 4 * 3 + (data % 4 == 2 ? 1 : data % 4 == 3 ? 2 : 0));
}
/** Split "k=v; k2=v2" tag lists (shared by DKIM and DMARC). */
static std::map<std::string, std::string> parseTags(const std::string& txt,
std::vector<std::string>& warnings,
const char* malformedPrefix) {
std::map<std::string, std::string> tags;
for (const std::string& part : splitOn(txt, ';')) {
const std::string term = trim(unquote(trim(part)));
if (term.empty()) continue;
const size_t eq = term.find('=');
if (eq == std::string::npos || eq == 0) {
warnings.push_back(std::string(malformedPrefix) + " \"" + term + "\" ignored.");
continue;
}
tags[toLower(trim(term.substr(0, eq)))] = trim(term.substr(eq + 1));
}
return tags;
}
/** Parse a `<selector>._domainkey` TXT record. Pure. */
DkimRecord parseDKIM(const std::string& txt) {
DkimRecord record;
const std::map<std::string, std::string> tags = parseTags(txt, record.warnings, "Malformed tag");
const auto it = [&](const std::string& key) { return tags.find(key) != tags.end(); };
std::string version = it("v") ? tags.at("v") : "";
if (!version.empty() && toUpper(version) != "DKIM1") {
record.warnings.push_back("Unusual version tag v=" + version + " (expected DKIM1).");
}
record.keyType = it("k") ? tags.at("k") : "rsa";
const bool hasP = it("p");
const std::string p = hasP ? tags.at("p") : "";
const std::string h = it("h") ? tags.at("h") : "";
const std::string s = it("s") ? tags.at("s") : "";
const std::string t = it("t") ? tags.at("t") : "";
record.hashes = filterNonEmpty(mapTrimmed(splitOn(h, ':')));
record.services = filterNonEmpty(mapTrimmed(splitOn(s, ':')));
record.flags = filterNonEmpty(mapTrimmed(splitOn(t, ':')));
record.valid = true;
if (!version.empty()) record.version = toUpper(version);
if (!hasP) {
record.valid = false;
record.warnings.push_back("No p= tag — this record is not a usable DKIM key.");
return record;
}
if (p.empty()) {
record.warnings.push_back("p= is empty — the key is revoked. Receivers will treat mail signed with this selector as unsigned.");
return record;
}
const int decodedLen = lenientBase64Length(p);
if (decodedLen < 0) {
record.warnings.push_back("The p= value is not valid base64 — the key could not be read.");
return record;
}
std::string compact;
for (char c : p) {
if (!std::isspace(static_cast<unsigned char>(c))) compact += c;
}
record.publicKey = compact;
if (record.keyType == "rsa") {
// SubjectPublicKeyInfo DER ≈ modulus bits/8 + ~24 bytes of ASN.1 overhead.
// The estimate is close enough to classify 512/1024/2048/4096-bit keys.
const int bits = std::max(0, decodedLen - 24) * 8;
record.keyBits = bits;
record.hasKeyBits = true;
if (bits < 1024) {
record.warnings.push_back("Weak RSA key (~" + std::to_string(bits) + " bits). Keys under 1024 bits are considered breakable; RFC 8301 discourages short keys.");
} else if (bits < 2048) {
record.warnings.push_back("RSA key of ~" + std::to_string(bits) + " bits works today but is below the recommended 2048 bits (RFC 8301).");
}
}
if (std::find(record.flags.begin(), record.flags.end(), "y") != record.flags.end()) {
record.warnings.push_back("t=y — the key is in test mode: receivers must treat signatures as if unsigned.");
}
if (std::find(record.flags.begin(), record.flags.end(), "s") != record.flags.end()) {
record.warnings.push_back("t=s — strict domain matching: the key cannot be used for subdomain signatures (informational).");
}
return record;
}
// ---------------------------------------------------------------------------
// DMARC — RFC 7489
// ---------------------------------------------------------------------------
static std::optional<DmarcPolicy> dmarcPolicyByName(const std::string& name) {
if (name == "none") return DmarcPolicy::None;
if (name == "quarantine") return DmarcPolicy::Quarantine;
if (name == "reject") return DmarcPolicy::Reject;
return std::nullopt;
}
/** Parse a `_dmarc` TXT record. Pure. */
DmarcRecord parseDMARC(const std::string& txt) {
DmarcRecord record;
const std::map<std::string, std::string> tags = parseTags(txt, record.warnings, "Malformed tag");
const auto has = [&](const std::string& key) { return tags.find(key) != tags.end(); };
if (!has("v")) {
record.warnings.push_back("No v= tag — this is not a DMARC record.");
return record;
}
if (toUpper(tags.at("v")) != "DMARC1") {
record.warnings.push_back("Unknown version v=" + tags.at("v") + " (expected DMARC1).");
return record;
}
if (!has("p")) {
record.warnings.push_back("No p= policy tag — DMARC requires it.");
return record;
}
const std::string p = toLower(tags.at("p"));
const std::optional<DmarcPolicy> policy = dmarcPolicyByName(p);
if (!policy.has_value()) {
record.warnings.push_back("Invalid policy p=" + p + " (expected none, quarantine, or reject).");
return record;
}
record.policy = policy;
record.valid = true;
if (has("sp")) {
const std::string sp = toLower(tags.at("sp"));
const std::optional<DmarcPolicy> sub = dmarcPolicyByName(sp);
if (sub.has_value()) record.subdomainPolicy = sub;
else record.warnings.push_back("Invalid sp=" + sp + " ignored (expected none, quarantine, or reject).");
}
if (has("rua")) {
record.aggregateUris = filterNonEmpty(mapTrimmed(splitOn(tags.at("rua"), ',')));
}
if (has("ruf")) {
record.forensicUris = filterNonEmpty(mapTrimmed(splitOn(tags.at("ruf"), ',')));
}
if (has("pct")) {
const std::string pct = tags.at("pct");
bool numeric = !pct.empty() &&
std::all_of(pct.begin(), pct.end(),
[](unsigned char c) { return std::isdigit(c) != 0; });
const long long n = numeric ? std::stoll(pct) : -1;
if (numeric && n >= 0 && n <= 100) {
record.percent = static_cast<int>(n);
} else {
record.warnings.push_back("Invalid pct=" + pct + " ignored (must be 0-100).");
}
}
if (has("adkim")) {
const std::string adkim = toLower(tags.at("adkim"));
if (adkim == "r" || adkim == "s") record.dkimAlignment = adkim[0];
else record.warnings.push_back("Invalid adkim=" + adkim + " ignored (expected r or s).");
}
if (has("aspf")) {
const std::string aspf = toLower(tags.at("aspf"));
if (aspf == "r" || aspf == "s") record.spfAlignment = aspf[0];
else record.warnings.push_back("Invalid aspf=" + aspf + " ignored (expected r or s).");
}
// Policy guidance
if (record.policy == DmarcPolicy::None) {
record.warnings.push_back("p=none is monitor-only — no mail is quarantined or rejected, but you still need SPF/DKIM to pass for reports to be useful.");
}
if (record.aggregateUris.empty()) {
record.warnings.push_back("No rua= address — without aggregate reports you cannot see who is failing authentication. Add rua=mailto:reports@example.com.");
} else if (!record.forensicUris.empty()) {
record.warnings.push_back("ruf= (forensic reports) is supported by few receivers and may leak message content to the report address (informational).");
}
if (record.percent.has_value() && *record.percent < 100 && record.policy != DmarcPolicy::None) {
record.warnings.push_back("pct=" + std::to_string(*record.percent) + " applies the policy to only " +
std::to_string(*record.percent) + "% of mail — the other " +
std::to_string(100 - *record.percent) + "% is unaffected.");
}
return record;
}
// ---------------------------------------------------------------------------
// DNS-over-HTTPS lookup
// ---------------------------------------------------------------------------
constexpr const char* DOH_ENDPOINT = "https://cloudflare-dns.com/dns-query";
/** Minimal response shape the injected fetch must provide (the TS `Response`). */
struct FetchResponse {
bool ok = false;
int status = 0;
std::string body;
};
/// Injectable transport: URL in, response out. Throw to signal a network error.
using FetchFn = std::function<FetchResponse(const std::string& url)>;
/** Query TXT records for a name via Cloudflare's DoH JSON API. Returns unquoted
* strings. The injected transport answers with one TXT `data` field per line
* (the JSON envelope's type-16 answers); surrounding quotes are stripped and
* the `" "` separator between multi-chunk TXT strings is joined, matching the
* TS `.replace(/^"(.*)"$/, '$1').replace(/" "/g, '')`. */
std::vector<std::string> queryTxt(const FetchFn& fetchFn, const std::string& name) {
const std::string url = std::string(DOH_ENDPOINT) + "?name=" + name + "&type=TXT";
const FetchResponse res = fetchFn(url); // throws on transport error
if (!res.ok) {
throw std::runtime_error("DNS resolver responded with HTTP " + std::to_string(res.status) + ".");
}
std::vector<std::string> out;
for (const std::string& raw : splitLines(res.body)) {
if (trim(raw).empty()) continue;
std::string data = trim(raw);
std::string unquoted = unquote(data); // strip the outer "..." if present
std::string joined;
std::string chunkSep = "\" \""; // merge multi-chunk "a" "b" -> ab
size_t pos = 0;
while (pos < unquoted.size()) {
const size_t hit = unquoted.find(chunkSep, pos);
if (hit == std::string::npos) {
joined += unquoted.substr(pos);
break;
}
joined += unquoted.substr(pos, hit - pos);
pos = hit + chunkSep.size();
}
out.push_back(joined);
}
return out;
}
static CheckStatus statusForWarnings(const std::vector<std::string>& warnings) {
return warnings.empty() ? CheckStatus::Pass : CheckStatus::Warn;
}
/** Look up and evaluate a domain's SPF record. */
SpfCheck checkSPF(const std::string& domain, const FetchFn& fetchFn) {
const std::string host = normalizeDomain(domain);
if (!isDomainLike(host)) {
return SpfCheck{CheckStatus::Fail, false, std::nullopt, "Enter a valid domain, e.g. example.com."};
}
try {
const std::vector<std::string> txts = queryTxt(fetchFn, host);
std::string joined;
for (size_t i = 0; i < txts.size(); i++) {
if (i > 0) joined += '\n';
joined += txts[i];
}
const SpfRecord record = parseSPF(joined);
if (!record.valid) {
return SpfCheck{CheckStatus::Fail, false, std::nullopt,
"No SPF record found for " + host + ". Receivers cannot verify which servers may send mail for it."};
}
const CheckStatus status = statusForWarnings(record.warnings);
std::string message = status == CheckStatus::Pass ? "SPF record found and looks healthy." : "";
return SpfCheck{status, true, record, message};
} catch (const std::exception& err) {
return SpfCheck{CheckStatus::Fail, false, std::nullopt, err.what()};
} catch (...) {
return SpfCheck{CheckStatus::Fail, false, std::nullopt, "DNS lookup failed."};
}
}
/** Look up and evaluate a domain's DKIM public key for one selector. */
DkimCheck checkDKIM(const std::string& domain, const std::string& selector, const FetchFn& fetchFn) {
const std::string host = normalizeDomain(domain);
if (!isDomainLike(host)) {
return DkimCheck{CheckStatus::Fail, false, std::nullopt, "Enter a valid domain, e.g. example.com."};
}
const std::string sel = toLower(trim(selector));
if (!isValidSelector(sel)) {
return DkimCheck{CheckStatus::Fail, false, std::nullopt,
"Enter a valid selector (letters, digits, dots, hyphens, underscores)."};
}
try {
const std::vector<std::string> txts = queryTxt(fetchFn, sel + "._domainkey." + host);
std::string joined;
for (size_t i = 0; i < txts.size(); i++) {
if (i > 0) joined += '\n';
joined += txts[i];
}
const DkimRecord record = parseDKIM(joined);
if (!record.valid) {
return DkimCheck{CheckStatus::Fail, false, std::nullopt,
"No DKIM record found at " + sel + "._domainkey." + host + ". Try another selector — only one is checked per lookup."};
}
bool revoked = false;
for (const std::string& w : record.warnings) {
if (w.find("revoked") != std::string::npos) revoked = true;
}
const CheckStatus status = revoked ? CheckStatus::Fail : statusForWarnings(record.warnings);
return DkimCheck{status, true, record, ""};
} catch (const std::exception& err) {
return DkimCheck{CheckStatus::Fail, false, std::nullopt, err.what()};
} catch (...) {
return DkimCheck{CheckStatus::Fail, false, std::nullopt, "DNS lookup failed."};
}
}
/** Look up and evaluate a domain's DMARC policy. */
DmarcCheck checkDMARC(const std::string& domain, const FetchFn& fetchFn) {
const std::string host = normalizeDomain(domain);
if (!isDomainLike(host)) {
return DmarcCheck{CheckStatus::Fail, false, std::nullopt, "Enter a valid domain, e.g. example.com."};
}
try {
const std::vector<std::string> txts = queryTxt(fetchFn, "_dmarc." + host);
std::string joined;
for (size_t i = 0; i < txts.size(); i++) {
if (i > 0) joined += '\n';
joined += txts[i];
}
const DmarcRecord record = parseDMARC(joined);
if (!record.valid) {
return DmarcCheck{CheckStatus::Fail, false, std::nullopt,
"No DMARC record found at _dmarc." + host + ". Receivers have no policy to apply when SPF or DKIM fails."};
}
return DmarcCheck{statusForWarnings(record.warnings), true, record, ""};
} catch (const std::exception& err) {
return DmarcCheck{CheckStatus::Fail, false, std::nullopt, err.what()};
} catch (...) {
return DmarcCheck{CheckStatus::Fail, false, std::nullopt, "DNS lookup failed."};
}
}
} // namespace email_auth
Also available in 9 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →