bindy/bind9/records/
txt.rs1use super::super::types::RndcKeyData;
7use super::{
8 build_authenticated_client, build_delete_rrset_record, build_record_fqdn, effective_record_ttl,
9 rrset_ttl_matches, should_update_record,
10};
11use anyhow::Result;
12use hickory_net::client::ClientHandle;
13use hickory_proto::op::ResponseCode;
14use hickory_proto::rr::{rdata, DNSClass, Name, RData, Record, RecordType};
15use std::str::FromStr;
16use tracing::info;
17
18fn compare_txt_rrset(existing_records: &[Record], texts: &[String], desired_ttl: u32) -> bool {
31 if existing_records.len() != 1 {
32 return false;
33 }
34 if !rrset_ttl_matches(existing_records, desired_ttl) {
35 return false;
36 }
37 let RData::TXT(existing_txt) = &existing_records[0].data else {
38 return false;
39 };
40 let existing_texts: Vec<String> = existing_txt
41 .txt_data
42 .iter()
43 .map(|bytes| String::from_utf8_lossy(bytes).to_string())
44 .collect();
45 existing_texts == texts
46}
47
48#[allow(clippy::too_many_arguments)]
57pub async fn add_txt_record(
58 zone_name: &str,
59 name: &str,
60 texts: &[String],
61 ttl: Option<i32>,
62 server: &str,
63 key_data: &RndcKeyData,
64) -> Result<()> {
65 let ttl_value = effective_record_ttl(ttl);
66 let should_update = should_update_record(
67 zone_name,
68 name,
69 RecordType::TXT,
70 "TXT",
71 server,
72 |existing_records| compare_txt_rrset(existing_records, texts, ttl_value),
73 )
74 .await?;
75
76 if !should_update {
77 return Ok(());
78 }
79
80 let zone = Name::from_str(zone_name)?;
81 let fqdn = build_record_fqdn(zone_name, name)?;
82
83 let mut record = Record::from_rdata(
84 fqdn.clone(),
85 ttl_value,
86 RData::TXT(rdata::TXT::new(texts.to_vec())),
87 );
88 record.dns_class = DNSClass::IN;
89
90 info!(
91 "Adding TXT record: {} -> {:?} (TTL: {})",
92 record.name, texts, ttl_value
93 );
94
95 let mut client = build_authenticated_client(server, key_data).await?;
96
97 let delete_record = build_delete_rrset_record(&fqdn, RecordType::TXT);
99 let _ = client.delete_rrset(delete_record, zone.clone()).await;
100
101 let response = client.append(record, zone, false).await?;
103
104 match response.metadata.response_code {
105 ResponseCode::NoError => {
106 info!("Successfully added TXT record: {}", name);
107 Ok(())
108 }
109 code => Err(anyhow::anyhow!(
110 "DNS update failed with response code: {code:?}"
111 )),
112 }
113}
114
115#[cfg(test)]
116#[path = "txt_tests.rs"]
117mod txt_tests;