diff --git a/CHANGELOG.md b/CHANGELOG.md index c94ffc1a..e43b2cf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ If you are upgrading from v0.16.x, replace the binary (or run `docker pull`). If - Sharing: Includes resource that themselves carry a direct ACL grant and are leaves. - Tasks cannot be deleted in OSS builds. - Directory: Per-domain external directory resolution fails. +- DNS updater: Keep external `TXT` records when updating RRSet. ## [0.16.6] - 2026-05-20 diff --git a/Cargo.lock b/Cargo.lock index 930a610f..ac9260b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,9 +44,9 @@ dependencies = [ [[package]] name = "aes" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66bd29a732b644c0431c6140f370d097879203d79b80c94a6747ba0872adaef8" +checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138" dependencies = [ "cipher 0.5.2", "cpubits", @@ -340,7 +340,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16e2cdb6d5ed835199484bb92bb8b3edd526effe995c61732580439c1a67e2e9" dependencies = [ "base64 0.22.1", - "http 1.4.0", + "http 1.4.1", "log", "rustls 0.23.40", "serde", @@ -351,9 +351,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-creds" @@ -774,9 +774,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytecheck" @@ -843,9 +843,9 @@ dependencies = [ [[package]] name = "calcard" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34e4d12044ea273eef981542ab87562104f9edb434d28317277d77e8019d7609" +checksum = "c6d4f7ea9ab08310fba58c0010e3c203aaa2b6464f1d4886d36855d99b6b6036" dependencies = [ "ahash", "chrono", @@ -1142,7 +1142,7 @@ dependencies = [ "rcgen", "regex", "registry", - "reqwest 0.13.3", + "reqwest 0.13.4", "ring", "rkyv", "rsa", @@ -1173,9 +1173,9 @@ dependencies = [ [[package]] name = "compact_str" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" +checksum = "9dfdd1c2274d9aa354115b09dc9a901d6c5576818cdf70d14cae2bdb47df00ab" dependencies = [ "castaway", "cfg-if", @@ -1876,7 +1876,7 @@ dependencies = [ "pwhash", "regex", "registry", - "reqwest 0.13.3", + "reqwest 0.13.4", "rkyv", "rustls 0.23.40", "rustls-pki-types", @@ -1917,9 +1917,9 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", @@ -1946,7 +1946,7 @@ dependencies = [ "hickory-net", "hickory-proto", "quick-xml 0.39.4", - "reqwest 0.13.3", + "reqwest 0.13.4", "rustls 0.23.40", "serde", "serde_json", @@ -2063,9 +2063,9 @@ dependencies = [ [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "elliptic-curve" @@ -2359,7 +2359,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3757d8523a60f4d3516a018e767cd3bea0829a43045d290ace16dcc92ac4f4cd" dependencies = [ "bytes", - "http 1.4.0", + "http 1.4.1", "httparse", "memchr", "mime", @@ -2721,7 +2721,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.4.0", + "http 1.4.1", "indexmap 2.14.0", "slab", "tokio", @@ -2829,7 +2829,7 @@ dependencies = [ "futures-util", "h2", "hickory-proto", - "http 1.4.0", + "http 1.4.1", "idna", "ipnet", "jni 0.22.4", @@ -3013,9 +3013,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" dependencies = [ "bytes", "itoa", @@ -3028,7 +3028,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.4.0", + "http 1.4.1", ] [[package]] @@ -3039,7 +3039,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.4.0", + "http 1.4.1", "http-body", "pin-project-lite", ] @@ -3110,16 +3110,16 @@ dependencies = [ [[package]] name = "hyper" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +checksum = "eb92f162bf56536459fc83c79b974bb12837acfed43d6bc370a7916d0ae15ecc" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", "h2", - "http 1.4.0", + "http 1.4.1", "http-body", "httparse", "httpdate", @@ -3136,7 +3136,7 @@ version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ - "http 1.4.0", + "http 1.4.1", "hyper", "hyper-util", "rustls 0.23.40", @@ -3169,7 +3169,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.4.0", + "http 1.4.1", "http-body", "hyper", "ipnet", @@ -3608,7 +3608,7 @@ dependencies = [ "pkcs8", "rand 0.9.4", "registry", - "reqwest 0.13.3", + "reqwest 0.13.4", "rkyv", "rsa", "serde", @@ -3642,7 +3642,7 @@ dependencies = [ "futures-util", "maybe-async", "parking_lot", - "reqwest 0.13.3", + "reqwest 0.13.4", "rustls 0.23.40", "rustls-pki-types", "serde", @@ -3769,9 +3769,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.98" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" dependencies = [ "cfg-if", "futures-util", @@ -3990,9 +3990,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" dependencies = [ "libc", ] @@ -4064,9 +4064,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" [[package]] name = "lru" @@ -4122,11 +4122,11 @@ dependencies = [ [[package]] name = "lzma-rust2" -version = "0.16.2" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47bb1e988e6fb779cf720ad431242d3f03167c1b3f2b1aae7f1a94b2495b36ae" +checksum = "5e9ceaec84b54518262de7cf06b8b43e83c808349960f1610b21b0bfc9640f20" dependencies = [ - "sha2 0.10.9", + "sha2 0.11.0", ] [[package]] @@ -4265,9 +4265,9 @@ checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0" [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "memory-stats" @@ -4811,9 +4811,9 @@ source = "git+https://github.com/stalwartlabs/opentelemetry-rust#274b4d324794280 dependencies = [ "async-trait", "bytes", - "http 1.4.0", + "http 1.4.1", "opentelemetry", - "reqwest 0.13.3", + "reqwest 0.13.4", ] [[package]] @@ -4821,13 +4821,13 @@ name = "opentelemetry-otlp" version = "0.31.0" source = "git+https://github.com/stalwartlabs/opentelemetry-rust#274b4d324794280ce6f4def095a3428197a9e6e3" dependencies = [ - "http 1.4.0", + "http 1.4.1", "opentelemetry", "opentelemetry-http", "opentelemetry-proto", "opentelemetry_sdk", "prost", - "reqwest 0.13.3", + "reqwest 0.13.4", "thiserror 2.0.18", "tokio", "tonic", @@ -5475,9 +5475,9 @@ dependencies = [ [[package]] name = "psl" -version = "2.1.210" +version = "2.1.211" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27fbd4d9b2eb0b6e0cbd654bce70243f460cbc8dc92df6747e3fb911ce1cd4a8" +checksum = "fa14bac28d3b158b9651f643b2c6ad02da1624df7f06f0380d6f22358a4979c0" dependencies = [ "psl-types", ] @@ -6068,7 +6068,7 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http 1.4.0", + "http 1.4.1", "http-body", "http-body-util", "hyper", @@ -6101,16 +6101,16 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ "base64 0.22.1", "bytes", "futures-core", "futures-util", "h2", - "http 1.4.0", + "http 1.4.1", "http-body", "http-body-util", "hyper", @@ -6337,7 +6337,7 @@ dependencies = [ "futures-util", "hex", "hmac 0.12.1", - "http 1.4.0", + "http 1.4.1", "log", "maybe-async", "md5", @@ -6834,9 +6834,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "indexmap 2.14.0", "itoa", @@ -6983,7 +6983,7 @@ dependencies = [ "memory-stats", "p256", "registry", - "reqwest 0.13.3", + "reqwest 0.13.4", "rsa", "serde", "serde_json", @@ -7242,7 +7242,7 @@ dependencies = [ "rayon", "regex", "registry", - "reqwest 0.13.3", + "reqwest 0.13.4", "rkyv", "rustls 0.23.40", "rustls-pemfile", @@ -7330,7 +7330,7 @@ dependencies = [ "nlp", "psl", "registry", - "reqwest 0.13.3", + "reqwest 0.13.4", "rkyv", "serde", "sha1 0.11.0", @@ -7373,9 +7373,9 @@ dependencies = [ [[package]] name = "sqlite-wasm-rs" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdd578e94101503d97e2b286bbf8db2135035ca24b2ce4cbf3f9e2fb2bbf1eee" +checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75" dependencies = [ "cc", "js-sys", @@ -7496,7 +7496,7 @@ dependencies = [ "redis", "regex", "registry", - "reqwest 0.13.3", + "reqwest 0.13.4", "rkyv", "roaring", "rocksdb", @@ -7707,7 +7707,7 @@ dependencies = [ "quick-xml 0.39.4", "rayon", "registry", - "reqwest 0.13.3", + "reqwest 0.13.4", "rkyv", "rustls 0.23.40", "rustls-pemfile", @@ -7984,7 +7984,7 @@ dependencies = [ "bytes", "futures-core", "futures-sink", - "http 1.4.0", + "http 1.4.1", "httparse", "rand 0.8.6", "rustls-pki-types", @@ -8005,9 +8005,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.11+spec-1.1.0" +version = "0.25.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" dependencies = [ "indexmap 2.14.0", "toml_datetime", @@ -8033,7 +8033,7 @@ dependencies = [ "async-trait", "base64 0.22.1", "bytes", - "http 1.4.0", + "http 1.4.1", "http-body", "http-body-util", "hyper", @@ -8119,7 +8119,7 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http 1.4.0", + "http 1.4.1", "http-body", "http-body-util", "pin-project-lite", @@ -8230,7 +8230,7 @@ dependencies = [ "mail-auth", "mail-parser", "parking_lot", - "reqwest 0.13.3", + "reqwest 0.13.4", "rkyv", "rtrb", "serde", @@ -8258,7 +8258,7 @@ checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" dependencies = [ "bytes", "data-encoding", - "http 1.4.0", + "http 1.4.1", "httparse", "log", "rand 0.9.4", @@ -8502,7 +8502,7 @@ dependencies = [ "rand 0.9.4", "rcgen", "regex", - "reqwest 0.13.3", + "reqwest 0.13.4", "rkyv", "rustls 0.23.40", "rustls-pemfile", @@ -8660,9 +8660,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" dependencies = [ "cfg-if", "once_cell", @@ -8673,9 +8673,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.71" +version = "0.4.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" +checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" dependencies = [ "js-sys", "wasm-bindgen", @@ -8683,9 +8683,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -8693,9 +8693,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" dependencies = [ "bumpalo", "proc-macro2", @@ -8706,9 +8706,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" dependencies = [ "unicode-ident", ] @@ -8775,9 +8775,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.98" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" +checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" dependencies = [ "js-sys", "wasm-bindgen", @@ -9936,18 +9936,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "bce33a6288fa3f072a8c2c7d0f2fdbb90e28298f0135c1f99b96c3db2efcc60b" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "8fd425244944f4ab65ccff928e7323354c5a018c75838362fdce749dfad2ee1e" dependencies = [ "proc-macro2", "quote", @@ -10034,7 +10034,7 @@ version = "8.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b" dependencies = [ - "aes 0.9.0", + "aes 0.9.1", "bzip2", "constant_time_eq 0.4.2", "crc32fast", diff --git a/crates/common/src/network/dns/update.rs b/crates/common/src/network/dns/update.rs index 89f796e2..79cd6743 100644 --- a/crates/common/src/network/dns/update.rs +++ b/crates/common/src/network/dns/update.rs @@ -1185,6 +1185,15 @@ impl DnsUpdater { Ok(()) } + pub async fn list_rrset( + &self, + origin: &str, + name: &str, + record_type: DnsRecordType, + ) -> dns_update::Result> { + self.updater.list_rrset(name, record_type, origin).await + } + pub async fn remove_from_rrset( &self, origin: &str, diff --git a/crates/services/src/task_manager/dns.rs b/crates/services/src/task_manager/dns.rs index 242b2d2f..49d3b13e 100644 --- a/crates/services/src/task_manager/dns.rs +++ b/crates/services/src/task_manager/dns.rs @@ -6,12 +6,13 @@ use crate::task_manager::TaskResult; use common::Server; -use dns_update::{DnsRecord, DnsRecordType}; +use dns_update::{DnsRecord, DnsRecordType, Error as DnsUpdateError}; use registry::schema::structs::{ DnsManagement, Domain, Task, TaskDnsManagement, TaskDomainManagement, TaskStatus, }; use std::fmt::Write; use store::ahash::AHashMap; +use trc::DnsEvent; pub(crate) trait DnsManagementTask: Sync + Send { fn dns_management(&self, task: &TaskDnsManagement) -> impl Future + Send; @@ -71,7 +72,39 @@ async fn dns_management(server: &Server, task: &TaskDnsManagement) -> trc::Resul } let mut errors = String::new(); - for ((name, record_type), recs) in by_owner { + for ((name, record_type), mut recs) in by_owner { + if matches!(record_type, DnsRecordType::TXT) && !is_owned_txt_name(&name) { + match dns_updater.list_rrset(origin, &name, record_type).await { + Ok(existing) => { + for existing_rec in existing { + if !recs.iter().any(|new| same_txt_family(new, &existing_rec)) { + recs.push(existing_rec); + } + } + } + Err(DnsUpdateError::Unsupported(reason)) => { + trc::event!( + Dns(DnsEvent::RecordLookupFailed), + Hostname = name.clone(), + Details = origin.to_string(), + Type = record_type.as_str(), + Reason = format!( + "DNS provider cannot list RRSet, unrelated records at this name may be overwritten: {reason}" + ), + ); + } + Err(err) => { + trc::event!( + Dns(DnsEvent::RecordLookupFailed), + Hostname = name.clone(), + Details = origin.to_string(), + Type = record_type.as_str(), + Reason = format!("DNS provider failed to list RRSet: {err}"), + ); + } + } + } + if let Err(err) = dns_updater .set_rrset(origin, &name, record_type, recs) .await @@ -104,3 +137,28 @@ async fn dns_management(server: &Server, task: &TaskDnsManagement) -> trc::Resul Ok(TaskResult::permanent(errors)) } } + +fn same_txt_family(a: &DnsRecord, b: &DnsRecord) -> bool { + match (a, b) { + (DnsRecord::TXT(va), DnsRecord::TXT(vb)) => match (txt_family(va), txt_family(vb)) { + (Some(fa), Some(fb)) => fa.eq_ignore_ascii_case(fb), + _ => false, + }, + _ => false, + } +} + +fn txt_family(value: &str) -> Option<&str> { + let rest = value.trim_start().strip_prefix("v=")?; + let end = rest.find([';', ' ']).unwrap_or(rest.len()); + Some(&rest[..end]) +} + +fn is_owned_txt_name(name: &str) -> bool { + name.contains("_dmarc.") + || name.contains("_smtp._tls.") + || name.contains("_mta-sts.") + || name.contains("_ua-auto-config.") + || name.contains("_validation-persist.") + || name.contains("._domainkey.") +}