1#![doc = include_str!("../README.md")]
2use core::fmt;
3use std::{
14 borrow::Cow,
15 fmt::{Display, Formatter},
16 net::{Ipv4Addr, Ipv6Addr},
17 str::FromStr,
18 time::Duration,
19};
20
21use hickory_client::proto::rr::dnssec::{KeyPair, Private};
22
23use providers::{
24 cloudflare::CloudflareProvider,
25 desec::DesecProvider,
26 digitalocean::DigitalOceanProvider,
27 ovh::{OvhProvider, OvhEndpoint},
28 rfc2136::{DnsAddress, Rfc2136Provider},
29};
30
31pub mod http;
32pub mod providers;
33pub mod tests;
34
35#[derive(Debug)]
36pub enum Error {
37 Protocol(String),
38 Parse(String),
39 Client(String),
40 Response(String),
41 Api(String),
42 Serialize(String),
43 Unauthorized,
44 NotFound,
45 BadRequest,
46}
47
48#[derive(Debug)]
50pub enum DnsRecordType {
51 A,
52 AAAA,
53 CNAME,
54 NS,
55 MX,
56 TXT,
57 SRV,
58}
59
60pub enum DnsRecord {
62 A {
63 content: Ipv4Addr,
64 },
65 AAAA {
66 content: Ipv6Addr,
67 },
68 CNAME {
69 content: String,
70 },
71 NS {
72 content: String,
73 },
74 MX {
75 content: String,
76 priority: u16,
77 },
78 TXT {
79 content: String,
80 },
81 SRV {
82 content: String,
83 priority: u16,
84 weight: u16,
85 port: u16,
86 },
87}
88
89pub enum TsigAlgorithm {
91 HmacMd5,
92 Gss,
93 HmacSha1,
94 HmacSha224,
95 HmacSha256,
96 HmacSha256_128,
97 HmacSha384,
98 HmacSha384_192,
99 HmacSha512,
100 HmacSha512_256,
101}
102
103pub enum Algorithm {
105 RSASHA256,
106 RSASHA512,
107 ECDSAP256SHA256,
108 ECDSAP384SHA384,
109 ED25519,
110}
111
112pub type Result<T> = std::result::Result<T, Error>;
113
114#[derive(Clone)]
115#[non_exhaustive]
116pub enum DnsUpdater {
117 Rfc2136(Rfc2136Provider),
118 Cloudflare(CloudflareProvider),
119 DigitalOcean(DigitalOceanProvider),
120 Desec(DesecProvider),
121 Ovh(OvhProvider),
122}
123
124pub trait IntoFqdn<'x> {
125 fn into_fqdn(self) -> Cow<'x, str>;
126 fn into_name(self) -> Cow<'x, str>;
127}
128
129impl DnsUpdater {
130 pub fn new_rfc2136_tsig(
132 addr: impl TryInto<DnsAddress>,
133 key_name: impl AsRef<str>,
134 key: impl Into<Vec<u8>>,
135 algorithm: TsigAlgorithm,
136 ) -> crate::Result<Self> {
137 Ok(DnsUpdater::Rfc2136(Rfc2136Provider::new_tsig(
138 addr,
139 key_name,
140 key,
141 algorithm.into(),
142 )?))
143 }
144
145 pub fn new_rfc2136_sig0(
147 addr: impl TryInto<DnsAddress>,
148 signer_name: impl AsRef<str>,
149 key: KeyPair<Private>,
150 public_key: impl Into<Vec<u8>>,
151 algorithm: Algorithm,
152 ) -> crate::Result<Self> {
153 Ok(DnsUpdater::Rfc2136(Rfc2136Provider::new_sig0(
154 addr,
155 signer_name,
156 key,
157 public_key,
158 algorithm.into(),
159 )?))
160 }
161
162 pub fn new_cloudflare(
164 secret: impl AsRef<str>,
165 email: Option<impl AsRef<str>>,
166 timeout: Option<Duration>,
167 ) -> crate::Result<Self> {
168 Ok(DnsUpdater::Cloudflare(CloudflareProvider::new(
169 secret, email, timeout,
170 )?))
171 }
172
173 pub fn new_digitalocean(
175 auth_token: impl AsRef<str>,
176 timeout: Option<Duration>,
177 ) -> crate::Result<Self> {
178 Ok(DnsUpdater::DigitalOcean(DigitalOceanProvider::new(
179 auth_token, timeout,
180 )))
181 }
182
183 pub fn new_desec(
185 auth_token: impl AsRef<str>,
186 timeout: Option<Duration>,
187 ) -> crate::Result<Self> {
188 Ok(DnsUpdater::Desec(DesecProvider::new(auth_token, timeout)))
189 }
190
191 pub fn new_ovh(
193 application_key: impl AsRef<str>,
194 application_secret: impl AsRef<str>,
195 consumer_key: impl AsRef<str>,
196 endpoint: OvhEndpoint,
197 timeout: Option<Duration>,
198 ) -> crate::Result<Self> {
199 Ok(DnsUpdater::Ovh(OvhProvider::new(
200 application_key,
201 application_secret,
202 consumer_key,
203 endpoint,
204 timeout,
205 )?))
206 }
207
208 pub async fn create(
210 &self,
211 name: impl IntoFqdn<'_>,
212 record: DnsRecord,
213 ttl: u32,
214 origin: impl IntoFqdn<'_>,
215 ) -> crate::Result<()> {
216 match self {
217 DnsUpdater::Rfc2136(provider) => provider.create(name, record, ttl, origin).await,
218 DnsUpdater::Cloudflare(provider) => provider.create(name, record, ttl, origin).await,
219 DnsUpdater::DigitalOcean(provider) => provider.create(name, record, ttl, origin).await,
220 DnsUpdater::Desec(provider) => provider.create(name, record, ttl, origin).await,
221 DnsUpdater::Ovh(provider) => provider.create(name, record, ttl, origin).await,
222 }
223 }
224
225 pub async fn update(
227 &self,
228 name: impl IntoFqdn<'_>,
229 record: DnsRecord,
230 ttl: u32,
231 origin: impl IntoFqdn<'_>,
232 ) -> crate::Result<()> {
233 match self {
234 DnsUpdater::Rfc2136(provider) => provider.update(name, record, ttl, origin).await,
235 DnsUpdater::Cloudflare(provider) => provider.update(name, record, ttl, origin).await,
236 DnsUpdater::DigitalOcean(provider) => provider.update(name, record, ttl, origin).await,
237 DnsUpdater::Desec(provider) => provider.update(name, record, ttl, origin).await,
238 DnsUpdater::Ovh(provider) => provider.update(name, record, ttl, origin).await,
239 }
240 }
241
242 pub async fn delete(
244 &self,
245 name: impl IntoFqdn<'_>,
246 origin: impl IntoFqdn<'_>,
247 record: DnsRecordType,
248 ) -> crate::Result<()> {
249 match self {
250 DnsUpdater::Rfc2136(provider) => provider.delete(name, origin).await,
251 DnsUpdater::Cloudflare(provider) => provider.delete(name, origin).await,
252 DnsUpdater::DigitalOcean(provider) => provider.delete(name, origin).await,
253 DnsUpdater::Desec(provider) => provider.delete(name, origin, record).await,
254 DnsUpdater::Ovh(provider) => provider.delete(name, origin, record).await,
255 }
256 }
257}
258
259impl<'x> IntoFqdn<'x> for &'x str {
260 fn into_fqdn(self) -> Cow<'x, str> {
261 if self.ends_with('.') {
262 Cow::Borrowed(self)
263 } else {
264 Cow::Owned(format!("{}.", self))
265 }
266 }
267
268 fn into_name(self) -> Cow<'x, str> {
269 if let Some(name) = self.strip_suffix('.') {
270 Cow::Borrowed(name)
271 } else {
272 Cow::Borrowed(self)
273 }
274 }
275}
276
277impl<'x> IntoFqdn<'x> for &'x String {
278 fn into_fqdn(self) -> Cow<'x, str> {
279 self.as_str().into_fqdn()
280 }
281
282 fn into_name(self) -> Cow<'x, str> {
283 self.as_str().into_name()
284 }
285}
286
287impl<'x> IntoFqdn<'x> for String {
288 fn into_fqdn(self) -> Cow<'x, str> {
289 if self.ends_with('.') {
290 Cow::Owned(self)
291 } else {
292 Cow::Owned(format!("{}.", self))
293 }
294 }
295
296 fn into_name(self) -> Cow<'x, str> {
297 if let Some(name) = self.strip_suffix('.') {
298 Cow::Owned(name.to_string())
299 } else {
300 Cow::Owned(self)
301 }
302 }
303}
304
305pub fn strip_origin_from_name(name: &str, origin: &str) -> String {
306 let name = name.trim_end_matches('.');
307 let origin = origin.trim_end_matches('.');
308
309 if name == origin {
310 return "@".to_string();
311 }
312
313 if name.ends_with(&format!(".{}", origin)) {
314 name[..name.len() - origin.len() - 1].to_string()
315 } else {
316 name.to_string()
317 }
318}
319
320impl FromStr for TsigAlgorithm {
321 type Err = ();
322
323 fn from_str(s: &str) -> std::prelude::v1::Result<Self, Self::Err> {
324 match s {
325 "hmac-md5" => Ok(TsigAlgorithm::HmacMd5),
326 "gss" => Ok(TsigAlgorithm::Gss),
327 "hmac-sha1" => Ok(TsigAlgorithm::HmacSha1),
328 "hmac-sha224" => Ok(TsigAlgorithm::HmacSha224),
329 "hmac-sha256" => Ok(TsigAlgorithm::HmacSha256),
330 "hmac-sha256-128" => Ok(TsigAlgorithm::HmacSha256_128),
331 "hmac-sha384" => Ok(TsigAlgorithm::HmacSha384),
332 "hmac-sha384-192" => Ok(TsigAlgorithm::HmacSha384_192),
333 "hmac-sha512" => Ok(TsigAlgorithm::HmacSha512),
334 "hmac-sha512-256" => Ok(TsigAlgorithm::HmacSha512_256),
335 _ => Err(()),
336 }
337 }
338}
339
340impl Display for Error {
341 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
342 match self {
343 Error::Protocol(e) => write!(f, "Protocol error: {}", e),
344 Error::Parse(e) => write!(f, "Parse error: {}", e),
345 Error::Client(e) => write!(f, "Client error: {}", e),
346 Error::Response(e) => write!(f, "Response error: {}", e),
347 Error::Api(e) => write!(f, "API error: {}", e),
348 Error::Serialize(e) => write!(f, "Serialize error: {}", e),
349 Error::Unauthorized => write!(f, "Unauthorized"),
350 Error::NotFound => write!(f, "Not found"),
351 Error::BadRequest => write!(f, "Bad request"),
352 }
353 }
354}
355
356impl Display for DnsRecordType {
357 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
358 write!(f, "{:?}", self)
359 }
360}