dns_update/providers/
rfc2136.rs

1/*
2 * Copyright Stalwart Labs LLC See the COPYING
3 * file at the top-level directory of this distribution.
4 *
5 * Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 * https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 * <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
8 * option. This file may not be copied, modified, or distributed
9 * except according to those terms.
10 */
11
12use std::net::{AddrParseError, SocketAddr};
13use std::sync::Arc;
14
15use hickory_client::client::{AsyncClient, ClientConnection, ClientHandle, Signer};
16use hickory_client::error::ClientError;
17use hickory_client::op::ResponseCode;
18use hickory_client::proto::error::ProtoError;
19use hickory_client::proto::rr::dnssec::tsig::TSigner;
20use hickory_client::proto::rr::dnssec::{Algorithm, KeyPair, Private, SigSigner};
21use hickory_client::rr::rdata::key::KEY;
22use hickory_client::rr::rdata::tsig::TsigAlgorithm;
23use hickory_client::rr::rdata::{A, AAAA, CNAME, MX, NS, SRV, TXT};
24use hickory_client::rr::{DNSClass, Name, RData, Record, RecordType};
25use hickory_client::tcp::TcpClientConnection;
26use hickory_client::udp::UdpClientConnection;
27
28use crate::{DnsRecord, Error, IntoFqdn};
29
30#[derive(Clone)]
31pub struct Rfc2136Provider {
32    addr: DnsAddress,
33    signer: Arc<Signer>,
34}
35
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub enum DnsAddress {
38    Tcp(SocketAddr),
39    Udp(SocketAddr),
40}
41
42impl Rfc2136Provider {
43    pub(crate) fn new_tsig(
44        addr: impl TryInto<DnsAddress>,
45        key_name: impl AsRef<str>,
46        key: impl Into<Vec<u8>>,
47        algorithm: TsigAlgorithm,
48    ) -> crate::Result<Self> {
49        Ok(Rfc2136Provider {
50            addr: addr
51                .try_into()
52                .map_err(|_| Error::Parse("Invalid address".to_string()))?,
53            signer: Arc::new(Signer::from(TSigner::new(
54                key.into(),
55                algorithm,
56                Name::from_ascii(key_name.as_ref())?,
57                60,
58            )?)),
59        })
60    }
61
62    pub(crate) fn new_sig0(
63        addr: impl TryInto<DnsAddress>,
64        signer_name: impl AsRef<str>,
65        key: KeyPair<Private>,
66        public_key: impl Into<Vec<u8>>,
67        algorithm: Algorithm,
68    ) -> crate::Result<Self> {
69        let sig0key = KEY::new(
70            Default::default(),
71            Default::default(),
72            Default::default(),
73            Default::default(),
74            algorithm,
75            public_key.into(),
76        );
77
78        let signer = SigSigner::sig0(sig0key, key, Name::from_str_relaxed(signer_name.as_ref())?);
79
80        Ok(Rfc2136Provider {
81            addr: addr
82                .try_into()
83                .map_err(|_| Error::Parse("Invalid address".to_string()))?,
84            signer: Arc::new(Signer::from(signer)),
85        })
86    }
87
88    async fn connect(&self) -> crate::Result<AsyncClient> {
89        match &self.addr {
90            DnsAddress::Udp(addr) => {
91                let conn = UdpClientConnection::new(*addr)?.new_stream(Some(self.signer.clone()));
92                let (client, bg) = AsyncClient::connect(conn).await?;
93                tokio::spawn(bg);
94                Ok(client)
95            }
96            DnsAddress::Tcp(addr) => {
97                let conn = TcpClientConnection::new(*addr)?.new_stream(Some(self.signer.clone()));
98                let (client, bg) = AsyncClient::connect(conn).await?;
99                tokio::spawn(bg);
100                Ok(client)
101            }
102        }
103    }
104
105    pub(crate) async fn create(
106        &self,
107        name: impl IntoFqdn<'_>,
108        record: DnsRecord,
109        ttl: u32,
110        origin: impl IntoFqdn<'_>,
111    ) -> crate::Result<()> {
112        let (rr_type, rdata) = convert_record(record)?;
113        let mut record = Record::with(
114            Name::from_str_relaxed(name.into_name().as_ref())?,
115            rr_type,
116            ttl,
117        );
118        record.set_data(Some(rdata));
119
120        let mut client = self.connect().await?;
121        let result = client
122            .create(record, Name::from_str_relaxed(origin.into_fqdn().as_ref())?)
123            .await?;
124        if result.response_code() == ResponseCode::NoError {
125            Ok(())
126        } else {
127            Err(crate::Error::Response(result.response_code().to_string()))
128        }
129    }
130
131    pub(crate) async fn update(
132        &self,
133        name: impl IntoFqdn<'_>,
134        record: DnsRecord,
135        ttl: u32,
136        origin: impl IntoFqdn<'_>,
137    ) -> crate::Result<()> {
138        let (rr_type, rdata) = convert_record(record)?;
139        let mut record = Record::with(
140            Name::from_str_relaxed(name.into_name().as_ref())?,
141            rr_type,
142            ttl,
143        );
144        record.set_data(Some(rdata));
145
146        let mut client = self.connect().await?;
147        let result = client
148            .append(
149                record,
150                Name::from_str_relaxed(origin.into_fqdn().as_ref())?,
151                false,
152            )
153            .await?;
154        if result.response_code() == ResponseCode::NoError {
155            Ok(())
156        } else {
157            Err(crate::Error::Response(result.response_code().to_string()))
158        }
159    }
160
161    pub(crate) async fn delete(
162        &self,
163        name: impl IntoFqdn<'_>,
164        origin: impl IntoFqdn<'_>,
165    ) -> crate::Result<()> {
166        let mut client = self.connect().await?;
167        let result = client
168            .delete_all(
169                Name::from_str_relaxed(name.into_name().as_ref())?,
170                Name::from_str_relaxed(origin.into_fqdn().as_ref())?,
171                DNSClass::IN,
172            )
173            .await?;
174        if result.response_code() == ResponseCode::NoError {
175            Ok(())
176        } else {
177            Err(crate::Error::Response(result.response_code().to_string()))
178        }
179    }
180}
181
182fn convert_record(record: DnsRecord) -> crate::Result<(RecordType, RData)> {
183    Ok(match record {
184        DnsRecord::A { content } => (RecordType::A, RData::A(A::from(content))),
185        DnsRecord::AAAA { content } => (RecordType::AAAA, RData::AAAA(AAAA::from(content))),
186        DnsRecord::CNAME { content } => (
187            RecordType::CNAME,
188            RData::CNAME(CNAME(Name::from_str_relaxed(content)?)),
189        ),
190        DnsRecord::NS { content } => (
191            RecordType::NS,
192            RData::NS(NS(Name::from_str_relaxed(content)?)),
193        ),
194        DnsRecord::MX { content, priority } => (
195            RecordType::MX,
196            RData::MX(MX::new(priority, Name::from_str_relaxed(content)?)),
197        ),
198        DnsRecord::TXT { content } => (RecordType::TXT, RData::TXT(TXT::new(vec![content]))),
199        DnsRecord::SRV {
200            content,
201            priority,
202            weight,
203            port,
204        } => (
205            RecordType::SRV,
206            RData::SRV(SRV::new(
207                priority,
208                weight,
209                port,
210                Name::from_str_relaxed(content)?,
211            )),
212        ),
213    })
214}
215
216impl TryFrom<&str> for DnsAddress {
217    type Error = ();
218
219    fn try_from(url: &str) -> Result<Self, Self::Error> {
220        let (host, is_tcp) = if let Some(host) = url.strip_prefix("udp://") {
221            (host, false)
222        } else if let Some(host) = url.strip_prefix("tcp://") {
223            (host, true)
224        } else {
225            (url, false)
226        };
227        let (host, port) = if let Some(host) = host.strip_prefix('[') {
228            let (host, maybe_port) = host.rsplit_once(']').ok_or(())?;
229
230            (
231                host,
232                maybe_port
233                    .rsplit_once(':')
234                    .map(|(_, port)| port)
235                    .unwrap_or("53"),
236            )
237        } else if let Some((host, port)) = host.rsplit_once(':') {
238            (host, port)
239        } else {
240            (host, "53")
241        };
242
243        let addr = SocketAddr::new(host.parse().map_err(|_| ())?, port.parse().map_err(|_| ())?);
244
245        if is_tcp {
246            Ok(DnsAddress::Tcp(addr))
247        } else {
248            Ok(DnsAddress::Udp(addr))
249        }
250    }
251}
252
253impl TryFrom<&String> for DnsAddress {
254    type Error = ();
255
256    fn try_from(url: &String) -> Result<Self, Self::Error> {
257        DnsAddress::try_from(url.as_str())
258    }
259}
260
261impl TryFrom<String> for DnsAddress {
262    type Error = ();
263
264    fn try_from(url: String) -> Result<Self, Self::Error> {
265        DnsAddress::try_from(url.as_str())
266    }
267}
268
269impl From<crate::TsigAlgorithm> for TsigAlgorithm {
270    fn from(alg: crate::TsigAlgorithm) -> Self {
271        match alg {
272            crate::TsigAlgorithm::HmacMd5 => TsigAlgorithm::HmacMd5,
273            crate::TsigAlgorithm::Gss => TsigAlgorithm::Gss,
274            crate::TsigAlgorithm::HmacSha1 => TsigAlgorithm::HmacSha1,
275            crate::TsigAlgorithm::HmacSha224 => TsigAlgorithm::HmacSha224,
276            crate::TsigAlgorithm::HmacSha256 => TsigAlgorithm::HmacSha256,
277            crate::TsigAlgorithm::HmacSha256_128 => TsigAlgorithm::HmacSha256_128,
278            crate::TsigAlgorithm::HmacSha384 => TsigAlgorithm::HmacSha384,
279            crate::TsigAlgorithm::HmacSha384_192 => TsigAlgorithm::HmacSha384_192,
280            crate::TsigAlgorithm::HmacSha512 => TsigAlgorithm::HmacSha512,
281            crate::TsigAlgorithm::HmacSha512_256 => TsigAlgorithm::HmacSha512_256,
282        }
283    }
284}
285
286impl From<crate::Algorithm> for Algorithm {
287    fn from(alg: crate::Algorithm) -> Self {
288        match alg {
289            crate::Algorithm::RSASHA256 => Algorithm::RSASHA256,
290            crate::Algorithm::RSASHA512 => Algorithm::RSASHA512,
291            crate::Algorithm::ECDSAP256SHA256 => Algorithm::ECDSAP256SHA256,
292            crate::Algorithm::ECDSAP384SHA384 => Algorithm::ECDSAP384SHA384,
293            crate::Algorithm::ED25519 => Algorithm::ED25519,
294        }
295    }
296}
297
298impl From<ProtoError> for Error {
299    fn from(e: ProtoError) -> Self {
300        Error::Protocol(e.to_string())
301    }
302}
303
304impl From<AddrParseError> for Error {
305    fn from(e: AddrParseError) -> Self {
306        Error::Parse(e.to_string())
307    }
308}
309
310impl From<ClientError> for Error {
311    fn from(e: ClientError) -> Self {
312        Error::Client(e.to_string())
313    }
314}