bindy/bind9/records/
mx.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
18const DEFAULT_MX_PREFERENCE: u16 = 10;
20
21fn compare_mx_rrset(
35 existing_records: &[Record],
36 preference: u16,
37 mail_server: &str,
38 desired_ttl: u32,
39) -> bool {
40 if existing_records.len() != 1 {
41 return false;
42 }
43 if !rrset_ttl_matches(existing_records, desired_ttl) {
44 return false;
45 }
46 let RData::MX(existing_mx) = &existing_records[0].data else {
47 return false;
48 };
49 existing_mx.preference == preference && existing_mx.exchange.to_string() == mail_server
50}
51
52#[allow(clippy::too_many_arguments)]
62pub async fn add_mx_record(
63 zone_name: &str,
64 name: &str,
65 priority: i32,
66 mail_server: &str,
67 ttl: Option<i32>,
68 server: &str,
69 key_data: &RndcKeyData,
70) -> Result<()> {
71 let priority_u16 = u16::try_from(priority).unwrap_or(DEFAULT_MX_PREFERENCE);
72 let ttl_value = effective_record_ttl(ttl);
73 let should_update = should_update_record(
74 zone_name,
75 name,
76 RecordType::MX,
77 "MX",
78 server,
79 |existing_records| compare_mx_rrset(existing_records, priority_u16, mail_server, ttl_value),
80 )
81 .await?;
82
83 if !should_update {
84 return Ok(());
85 }
86
87 let zone = Name::from_str(zone_name)?;
88 let fqdn = build_record_fqdn(zone_name, name)?;
89 let mx_name = Name::from_str(mail_server)?;
90
91 let mut record = Record::from_rdata(
92 fqdn.clone(),
93 ttl_value,
94 RData::MX(rdata::MX::new(priority_u16, mx_name)),
95 );
96 record.dns_class = DNSClass::IN;
97
98 info!(
99 "Adding MX record: {} -> {} (priority: {}, TTL: {})",
100 fqdn, mail_server, priority_u16, ttl_value
101 );
102
103 let mut client = build_authenticated_client(server, key_data).await?;
104
105 let delete_record = build_delete_rrset_record(&fqdn, RecordType::MX);
107 let _ = client.delete_rrset(delete_record, zone.clone()).await;
108
109 let response = client.append(record, zone, false).await?;
111
112 match response.metadata.response_code {
113 ResponseCode::NoError => {
114 info!("Successfully added MX record: {} -> {}", name, mail_server);
115 Ok(())
116 }
117 code => Err(anyhow::anyhow!(
118 "DNS update failed with response code: {code:?}"
119 )),
120 }
121}
122
123#[cfg(test)]
124#[path = "mx_tests.rs"]
125mod mx_tests;