1use std::collections::HashMap;
10use std::net::IpAddr;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
14pub enum DnsClass {
15 #[default]
16 IN, CH, HS, }
20
21impl DnsClass {
22 pub fn as_str(&self) -> &'static str {
23 match self {
24 DnsClass::IN => "IN",
25 DnsClass::CH => "CH",
26 DnsClass::HS => "HS",
27 }
28 }
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum ZoneType {
34 Primary,
35 Secondary,
36 Stub,
37 Forward,
38 Hint,
39 Mirror,
40 Delegation,
41 Redirect,
42}
43
44impl ZoneType {
45 pub fn as_str(&self) -> &'static str {
46 match self {
47 ZoneType::Primary => "primary",
48 ZoneType::Secondary => "secondary",
49 ZoneType::Stub => "stub",
50 ZoneType::Forward => "forward",
51 ZoneType::Hint => "hint",
52 ZoneType::Mirror => "mirror",
53 ZoneType::Delegation => "delegation-only",
54 ZoneType::Redirect => "redirect",
55 }
56 }
57
58 pub fn parse(s: &str) -> Option<Self> {
60 match s {
61 "primary" | "master" => Some(ZoneType::Primary),
62 "secondary" | "slave" => Some(ZoneType::Secondary),
63 "stub" => Some(ZoneType::Stub),
64 "forward" => Some(ZoneType::Forward),
65 "hint" => Some(ZoneType::Hint),
66 "mirror" => Some(ZoneType::Mirror),
67 "delegation-only" => Some(ZoneType::Delegation),
68 "redirect" => Some(ZoneType::Redirect),
69 _ => None,
70 }
71 }
72}
73
74#[derive(Debug, Clone, PartialEq, Eq)]
76pub struct PrimarySpec {
77 pub address: IpAddr,
78 pub port: Option<u16>,
79}
80
81impl PrimarySpec {
82 pub fn new(address: IpAddr) -> Self {
83 Self {
84 address,
85 port: None,
86 }
87 }
88
89 pub fn with_port(address: IpAddr, port: u16) -> Self {
90 Self {
91 address,
92 port: Some(port),
93 }
94 }
95}
96
97#[derive(Debug, Clone, PartialEq, Eq)]
99pub struct ForwarderSpec {
100 pub address: IpAddr,
101 pub port: Option<u16>,
102 pub tls_config: Option<String>,
103}
104
105impl ForwarderSpec {
106 pub fn new(address: IpAddr) -> Self {
107 Self {
108 address,
109 port: None,
110 tls_config: None,
111 }
112 }
113
114 pub fn with_port(address: IpAddr, port: u16) -> Self {
115 Self {
116 address,
117 port: Some(port),
118 tls_config: None,
119 }
120 }
121
122 pub fn with_tls(address: IpAddr, tls_config: String) -> Self {
123 Self {
124 address,
125 port: None,
126 tls_config: Some(tls_config),
127 }
128 }
129}
130
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
133pub enum NotifyMode {
134 Yes,
135 No,
136 Explicit,
137 MasterOnly,
138 PrimaryOnly,
139}
140
141impl NotifyMode {
142 pub fn as_str(&self) -> &'static str {
143 match self {
144 NotifyMode::Yes => "yes",
145 NotifyMode::No => "no",
146 NotifyMode::Explicit => "explicit",
147 NotifyMode::MasterOnly => "master-only",
148 NotifyMode::PrimaryOnly => "primary-only",
149 }
150 }
151
152 pub fn parse(s: &str) -> Option<Self> {
153 match s {
154 "yes" => Some(NotifyMode::Yes),
155 "no" => Some(NotifyMode::No),
156 "explicit" => Some(NotifyMode::Explicit),
157 "master-only" => Some(NotifyMode::MasterOnly),
158 "primary-only" => Some(NotifyMode::PrimaryOnly),
159 _ => None,
160 }
161 }
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq)]
166pub enum ForwardMode {
167 Only,
168 First,
169}
170
171impl ForwardMode {
172 pub fn as_str(&self) -> &'static str {
173 match self {
174 ForwardMode::Only => "only",
175 ForwardMode::First => "first",
176 }
177 }
178
179 pub fn parse(s: &str) -> Option<Self> {
180 match s {
181 "only" => Some(ForwardMode::Only),
182 "first" => Some(ForwardMode::First),
183 _ => None,
184 }
185 }
186}
187
188#[derive(Debug, Clone, Copy, PartialEq, Eq)]
190pub enum AutoDnssecMode {
191 Off,
192 Maintain,
193 Create,
194}
195
196impl AutoDnssecMode {
197 pub fn as_str(&self) -> &'static str {
198 match self {
199 AutoDnssecMode::Off => "off",
200 AutoDnssecMode::Maintain => "maintain",
201 AutoDnssecMode::Create => "create",
202 }
203 }
204
205 pub fn parse(s: &str) -> Option<Self> {
206 match s {
207 "off" => Some(AutoDnssecMode::Off),
208 "maintain" => Some(AutoDnssecMode::Maintain),
209 "create" => Some(AutoDnssecMode::Create),
210 _ => None,
211 }
212 }
213}
214
215#[derive(Debug, Clone, Copy, PartialEq, Eq)]
217pub enum CheckNamesMode {
218 Fail,
219 Warn,
220 Ignore,
221}
222
223impl CheckNamesMode {
224 pub fn as_str(&self) -> &'static str {
225 match self {
226 CheckNamesMode::Fail => "fail",
227 CheckNamesMode::Warn => "warn",
228 CheckNamesMode::Ignore => "ignore",
229 }
230 }
231
232 pub fn parse(s: &str) -> Option<Self> {
233 match s {
234 "fail" => Some(CheckNamesMode::Fail),
235 "warn" => Some(CheckNamesMode::Warn),
236 "ignore" => Some(CheckNamesMode::Ignore),
237 _ => None,
238 }
239 }
240}
241
242#[derive(Debug, Clone, Copy, PartialEq, Eq)]
244pub enum MasterfileFormat {
245 Text,
246 Raw,
247 Map,
248}
249
250impl MasterfileFormat {
251 pub fn as_str(&self) -> &'static str {
252 match self {
253 MasterfileFormat::Text => "text",
254 MasterfileFormat::Raw => "raw",
255 MasterfileFormat::Map => "map",
256 }
257 }
258
259 pub fn parse(s: &str) -> Option<Self> {
260 match s {
261 "text" => Some(MasterfileFormat::Text),
262 "raw" => Some(MasterfileFormat::Raw),
263 "map" => Some(MasterfileFormat::Map),
264 _ => None,
265 }
266 }
267}
268
269#[derive(Debug, Clone, PartialEq)]
271pub struct ZoneConfig {
272 pub zone_name: String,
274 pub class: DnsClass,
275 pub zone_type: ZoneType,
276 pub file: Option<String>,
277
278 pub primaries: Option<Vec<PrimarySpec>>,
280 pub also_notify: Option<Vec<IpAddr>>,
281 pub notify: Option<NotifyMode>,
282
283 pub allow_query: Option<Vec<IpAddr>>,
285 pub allow_transfer: Option<Vec<IpAddr>>,
286 pub allow_update: Option<Vec<IpAddr>>,
287 pub allow_update_raw: Option<String>,
290 pub allow_update_forwarding: Option<Vec<IpAddr>>,
291 pub allow_notify: Option<Vec<IpAddr>>,
292
293 pub max_transfer_time_in: Option<u32>,
295 pub max_transfer_time_out: Option<u32>,
296 pub max_transfer_idle_in: Option<u32>,
297 pub max_transfer_idle_out: Option<u32>,
298 pub transfer_source: Option<IpAddr>,
299 pub transfer_source_v6: Option<IpAddr>,
300 pub notify_source: Option<IpAddr>,
301 pub notify_source_v6: Option<IpAddr>,
302
303 pub update_policy: Option<String>,
306 pub journal: Option<String>,
307 pub ixfr_from_differences: Option<bool>,
308
309 pub inline_signing: Option<bool>,
311 pub auto_dnssec: Option<AutoDnssecMode>,
312 pub key_directory: Option<String>,
313 pub sig_validity_interval: Option<u32>,
314 pub dnskey_sig_validity: Option<u32>,
315
316 pub forward: Option<ForwardMode>,
318 pub forwarders: Option<Vec<ForwarderSpec>>,
319
320 pub check_names: Option<CheckNamesMode>,
322 pub check_mx: Option<CheckNamesMode>,
323 pub check_integrity: Option<bool>,
324 pub masterfile_format: Option<MasterfileFormat>,
325 pub max_zone_ttl: Option<u32>,
326
327 pub max_refresh_time: Option<u32>,
329 pub min_refresh_time: Option<u32>,
330 pub max_retry_time: Option<u32>,
331 pub min_retry_time: Option<u32>,
332
333 pub multi_master: Option<bool>,
335 pub request_ixfr: Option<bool>,
336 pub request_expire: Option<bool>,
337
338 pub raw_options: HashMap<String, String>,
343}
344
345impl ZoneConfig {
346 pub fn new(zone_name: String, zone_type: ZoneType) -> Self {
348 Self {
349 zone_name,
350 class: DnsClass::IN,
351 zone_type,
352 file: None,
353 primaries: None,
354 also_notify: None,
355 notify: None,
356 allow_query: None,
357 allow_transfer: None,
358 allow_update: None,
359 allow_update_raw: None,
360 allow_update_forwarding: None,
361 allow_notify: None,
362 max_transfer_time_in: None,
363 max_transfer_time_out: None,
364 max_transfer_idle_in: None,
365 max_transfer_idle_out: None,
366 transfer_source: None,
367 transfer_source_v6: None,
368 notify_source: None,
369 notify_source_v6: None,
370 update_policy: None,
371 journal: None,
372 ixfr_from_differences: None,
373 inline_signing: None,
374 auto_dnssec: None,
375 key_directory: None,
376 sig_validity_interval: None,
377 dnskey_sig_validity: None,
378 forward: None,
379 forwarders: None,
380 check_names: None,
381 check_mx: None,
382 check_integrity: None,
383 masterfile_format: None,
384 max_zone_ttl: None,
385 max_refresh_time: None,
386 min_refresh_time: None,
387 max_retry_time: None,
388 min_retry_time: None,
389 multi_master: None,
390 request_ixfr: None,
391 request_expire: None,
392 raw_options: HashMap::new(),
393 }
394 }
395
396 pub fn to_rndc_block(&self) -> String {
401 let mut parts = Vec::new();
402
403 parts.push(format!("type {}", self.zone_type.as_str()));
405
406 if let Some(ref file) = self.file {
408 parts.push(format!(r#"file "{}""#, file));
409 }
410
411 if let Some(ref primaries) = self.primaries {
413 if !primaries.is_empty() {
414 let primary_list = primaries
415 .iter()
416 .map(|p| {
417 if let Some(port) = p.port {
418 format!("{} port {}", p.address, port)
419 } else {
420 p.address.to_string()
421 }
422 })
423 .collect::<Vec<_>>()
424 .join("; ");
425 parts.push(format!("primaries {{ {}; }}", primary_list));
426 }
427 }
428
429 if let Some(ref also_notify) = self.also_notify {
431 if !also_notify.is_empty() {
432 let notify_list = also_notify
433 .iter()
434 .map(|ip| ip.to_string())
435 .collect::<Vec<_>>()
436 .join("; ");
437 parts.push(format!("also-notify {{ {}; }}", notify_list));
438 }
439 }
440
441 if let Some(notify) = self.notify {
443 parts.push(format!("notify {}", notify.as_str()));
444 }
445
446 if let Some(ref allow_query) = self.allow_query {
448 if !allow_query.is_empty() {
449 let query_list = allow_query
450 .iter()
451 .map(|ip| ip.to_string())
452 .collect::<Vec<_>>()
453 .join("; ");
454 parts.push(format!("allow-query {{ {}; }}", query_list));
455 }
456 }
457
458 if let Some(ref allow_transfer) = self.allow_transfer {
460 if !allow_transfer.is_empty() {
461 let transfer_list = allow_transfer
462 .iter()
463 .map(|ip| ip.to_string())
464 .collect::<Vec<_>>()
465 .join("; ");
466 parts.push(format!("allow-transfer {{ {}; }}", transfer_list));
467 }
468 }
469
470 if let Some(ref raw) = self.allow_update_raw {
472 let raw_trimmed = raw.trim_end().trim_end_matches(';').trim();
473 parts.push(format!("allow-update {}", raw_trimmed));
474 } else if let Some(ref allow_update) = self.allow_update {
475 if !allow_update.is_empty() {
476 let update_list = allow_update
477 .iter()
478 .map(|ip| ip.to_string())
479 .collect::<Vec<_>>()
480 .join("; ");
481 parts.push(format!("allow-update {{ {}; }}", update_list));
482 }
483 }
484
485 if let Some(ref allow_update_forwarding) = self.allow_update_forwarding {
487 if !allow_update_forwarding.is_empty() {
488 let list = allow_update_forwarding
489 .iter()
490 .map(|ip| ip.to_string())
491 .collect::<Vec<_>>()
492 .join("; ");
493 parts.push(format!("allow-update-forwarding {{ {}; }}", list));
494 }
495 }
496
497 if let Some(ref allow_notify) = self.allow_notify {
499 if !allow_notify.is_empty() {
500 let list = allow_notify
501 .iter()
502 .map(|ip| ip.to_string())
503 .collect::<Vec<_>>()
504 .join("; ");
505 parts.push(format!("allow-notify {{ {}; }}", list));
506 }
507 }
508
509 if let Some(val) = self.max_transfer_time_in {
511 parts.push(format!("max-transfer-time-in {}", val));
512 }
513 if let Some(val) = self.max_transfer_time_out {
514 parts.push(format!("max-transfer-time-out {}", val));
515 }
516 if let Some(val) = self.max_transfer_idle_in {
517 parts.push(format!("max-transfer-idle-in {}", val));
518 }
519 if let Some(val) = self.max_transfer_idle_out {
520 parts.push(format!("max-transfer-idle-out {}", val));
521 }
522
523 if let Some(ip) = self.transfer_source {
525 parts.push(format!("transfer-source {}", ip));
526 }
527 if let Some(ip) = self.transfer_source_v6 {
528 parts.push(format!("transfer-source-v6 {}", ip));
529 }
530 if let Some(ip) = self.notify_source {
531 parts.push(format!("notify-source {}", ip));
532 }
533 if let Some(ip) = self.notify_source_v6 {
534 parts.push(format!("notify-source-v6 {}", ip));
535 }
536
537 if let Some(ref policy) = self.update_policy {
539 let policy_trimmed = policy.trim_end().trim_end_matches(';').trim();
540 parts.push(format!("update-policy {}", policy_trimmed));
541 }
542 if let Some(ref journal) = self.journal {
543 parts.push(format!(r#"journal "{}""#, journal));
544 }
545 if let Some(val) = self.ixfr_from_differences {
546 parts.push(format!(
547 "ixfr-from-differences {}",
548 if val { "yes" } else { "no" }
549 ));
550 }
551
552 if let Some(val) = self.inline_signing {
554 parts.push(format!("inline-signing {}", if val { "yes" } else { "no" }));
555 }
556 if let Some(mode) = self.auto_dnssec {
557 parts.push(format!("auto-dnssec {}", mode.as_str()));
558 }
559 if let Some(ref dir) = self.key_directory {
560 parts.push(format!(r#"key-directory "{}""#, dir));
561 }
562 if let Some(val) = self.sig_validity_interval {
563 parts.push(format!("sig-validity-interval {}", val));
564 }
565 if let Some(val) = self.dnskey_sig_validity {
566 parts.push(format!("dnskey-sig-validity {}", val));
567 }
568
569 if let Some(mode) = self.forward {
571 parts.push(format!("forward {}", mode.as_str()));
572 }
573 if let Some(ref forwarders) = self.forwarders {
574 if !forwarders.is_empty() {
575 let forwarder_list = forwarders
576 .iter()
577 .map(|f| {
578 if let Some(ref tls) = f.tls_config {
579 format!("{} tls {}", f.address, tls)
580 } else if let Some(port) = f.port {
581 format!("{} port {}", f.address, port)
582 } else {
583 f.address.to_string()
584 }
585 })
586 .collect::<Vec<_>>()
587 .join("; ");
588 parts.push(format!("forwarders {{ {}; }}", forwarder_list));
589 }
590 }
591
592 if let Some(mode) = self.check_names {
594 parts.push(format!("check-names {}", mode.as_str()));
595 }
596 if let Some(mode) = self.check_mx {
597 parts.push(format!("check-mx {}", mode.as_str()));
598 }
599 if let Some(val) = self.check_integrity {
600 parts.push(format!(
601 "check-integrity {}",
602 if val { "yes" } else { "no" }
603 ));
604 }
605 if let Some(format) = self.masterfile_format {
606 parts.push(format!("masterfile-format {}", format.as_str()));
607 }
608 if let Some(val) = self.max_zone_ttl {
609 parts.push(format!("max-zone-ttl {}", val));
610 }
611
612 if let Some(val) = self.max_refresh_time {
614 parts.push(format!("max-refresh-time {}", val));
615 }
616 if let Some(val) = self.min_refresh_time {
617 parts.push(format!("min-refresh-time {}", val));
618 }
619 if let Some(val) = self.max_retry_time {
620 parts.push(format!("max-retry-time {}", val));
621 }
622 if let Some(val) = self.min_retry_time {
623 parts.push(format!("min-retry-time {}", val));
624 }
625
626 if let Some(val) = self.multi_master {
628 parts.push(format!("multi-master {}", if val { "yes" } else { "no" }));
629 }
630 if let Some(val) = self.request_ixfr {
631 parts.push(format!("request-ixfr {}", if val { "yes" } else { "no" }));
632 }
633 if let Some(val) = self.request_expire {
634 parts.push(format!("request-expire {}", if val { "yes" } else { "no" }));
635 }
636
637 for (key, value) in &self.raw_options {
639 let value_trimmed = value.trim_end().trim_end_matches(';').trim();
640 parts.push(format!("{} {}", key, value_trimmed));
641 }
642
643 format!("{{ {}; }};", parts.join("; "))
644 }
645}