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 dnssec_policy: Option<String>,
315 pub inline_signing: Option<bool>,
316 pub auto_dnssec: Option<AutoDnssecMode>,
317 pub key_directory: Option<String>,
318 pub sig_validity_interval: Option<u32>,
319 pub dnskey_sig_validity: Option<u32>,
320
321 pub forward: Option<ForwardMode>,
323 pub forwarders: Option<Vec<ForwarderSpec>>,
324
325 pub check_names: Option<CheckNamesMode>,
327 pub check_mx: Option<CheckNamesMode>,
328 pub check_integrity: Option<bool>,
329 pub masterfile_format: Option<MasterfileFormat>,
330 pub max_zone_ttl: Option<u32>,
331
332 pub max_refresh_time: Option<u32>,
334 pub min_refresh_time: Option<u32>,
335 pub max_retry_time: Option<u32>,
336 pub min_retry_time: Option<u32>,
337
338 pub multi_master: Option<bool>,
340 pub request_ixfr: Option<bool>,
341 pub request_expire: Option<bool>,
342
343 pub raw_options: HashMap<String, String>,
348}
349
350impl ZoneConfig {
351 pub fn new(zone_name: String, zone_type: ZoneType) -> Self {
353 Self {
354 zone_name,
355 class: DnsClass::IN,
356 zone_type,
357 file: None,
358 primaries: None,
359 also_notify: None,
360 notify: None,
361 allow_query: None,
362 allow_transfer: None,
363 allow_update: None,
364 allow_update_raw: None,
365 allow_update_forwarding: None,
366 allow_notify: None,
367 max_transfer_time_in: None,
368 max_transfer_time_out: None,
369 max_transfer_idle_in: None,
370 max_transfer_idle_out: None,
371 transfer_source: None,
372 transfer_source_v6: None,
373 notify_source: None,
374 notify_source_v6: None,
375 update_policy: None,
376 journal: None,
377 ixfr_from_differences: None,
378 dnssec_policy: None,
379 inline_signing: None,
380 auto_dnssec: None,
381 key_directory: None,
382 sig_validity_interval: None,
383 dnskey_sig_validity: None,
384 forward: None,
385 forwarders: None,
386 check_names: None,
387 check_mx: None,
388 check_integrity: None,
389 masterfile_format: None,
390 max_zone_ttl: None,
391 max_refresh_time: None,
392 min_refresh_time: None,
393 max_retry_time: None,
394 min_retry_time: None,
395 multi_master: None,
396 request_ixfr: None,
397 request_expire: None,
398 raw_options: HashMap::new(),
399 }
400 }
401
402 pub fn to_rndc_block(&self) -> String {
407 let mut parts = Vec::new();
408
409 parts.push(format!("type {}", self.zone_type.as_str()));
411
412 if let Some(ref file) = self.file {
414 parts.push(format!(r#"file "{}""#, file));
415 }
416
417 if let Some(ref primaries) = self.primaries {
419 if !primaries.is_empty() {
420 let primary_list = primaries
421 .iter()
422 .map(|p| {
423 if let Some(port) = p.port {
424 format!("{} port {}", p.address, port)
425 } else {
426 p.address.to_string()
427 }
428 })
429 .collect::<Vec<_>>()
430 .join("; ");
431 parts.push(format!("primaries {{ {}; }}", primary_list));
432 }
433 }
434
435 if let Some(ref also_notify) = self.also_notify {
437 if !also_notify.is_empty() {
438 let notify_list = also_notify
439 .iter()
440 .map(|ip| ip.to_string())
441 .collect::<Vec<_>>()
442 .join("; ");
443 parts.push(format!("also-notify {{ {}; }}", notify_list));
444 }
445 }
446
447 if let Some(notify) = self.notify {
449 parts.push(format!("notify {}", notify.as_str()));
450 }
451
452 if let Some(ref allow_query) = self.allow_query {
454 if !allow_query.is_empty() {
455 let query_list = allow_query
456 .iter()
457 .map(|ip| ip.to_string())
458 .collect::<Vec<_>>()
459 .join("; ");
460 parts.push(format!("allow-query {{ {}; }}", query_list));
461 }
462 }
463
464 if let Some(ref allow_transfer) = self.allow_transfer {
466 if !allow_transfer.is_empty() {
467 let transfer_list = allow_transfer
468 .iter()
469 .map(|ip| ip.to_string())
470 .collect::<Vec<_>>()
471 .join("; ");
472 parts.push(format!("allow-transfer {{ {}; }}", transfer_list));
473 }
474 }
475
476 if let Some(ref raw) = self.allow_update_raw {
478 let raw_trimmed = raw.trim_end().trim_end_matches(';').trim();
479 parts.push(format!("allow-update {}", raw_trimmed));
480 } else if let Some(ref allow_update) = self.allow_update {
481 if !allow_update.is_empty() {
482 let update_list = allow_update
483 .iter()
484 .map(|ip| ip.to_string())
485 .collect::<Vec<_>>()
486 .join("; ");
487 parts.push(format!("allow-update {{ {}; }}", update_list));
488 }
489 }
490
491 if let Some(ref allow_update_forwarding) = self.allow_update_forwarding {
493 if !allow_update_forwarding.is_empty() {
494 let list = allow_update_forwarding
495 .iter()
496 .map(|ip| ip.to_string())
497 .collect::<Vec<_>>()
498 .join("; ");
499 parts.push(format!("allow-update-forwarding {{ {}; }}", list));
500 }
501 }
502
503 if let Some(ref allow_notify) = self.allow_notify {
505 if !allow_notify.is_empty() {
506 let list = allow_notify
507 .iter()
508 .map(|ip| ip.to_string())
509 .collect::<Vec<_>>()
510 .join("; ");
511 parts.push(format!("allow-notify {{ {}; }}", list));
512 }
513 }
514
515 if let Some(val) = self.max_transfer_time_in {
517 parts.push(format!("max-transfer-time-in {}", val));
518 }
519 if let Some(val) = self.max_transfer_time_out {
520 parts.push(format!("max-transfer-time-out {}", val));
521 }
522 if let Some(val) = self.max_transfer_idle_in {
523 parts.push(format!("max-transfer-idle-in {}", val));
524 }
525 if let Some(val) = self.max_transfer_idle_out {
526 parts.push(format!("max-transfer-idle-out {}", val));
527 }
528
529 if let Some(ip) = self.transfer_source {
531 parts.push(format!("transfer-source {}", ip));
532 }
533 if let Some(ip) = self.transfer_source_v6 {
534 parts.push(format!("transfer-source-v6 {}", ip));
535 }
536 if let Some(ip) = self.notify_source {
537 parts.push(format!("notify-source {}", ip));
538 }
539 if let Some(ip) = self.notify_source_v6 {
540 parts.push(format!("notify-source-v6 {}", ip));
541 }
542
543 if let Some(ref policy) = self.update_policy {
545 let policy_trimmed = policy.trim_end().trim_end_matches(';').trim();
546 parts.push(format!("update-policy {}", policy_trimmed));
547 }
548 if let Some(ref journal) = self.journal {
549 parts.push(format!(r#"journal "{}""#, journal));
550 }
551 if let Some(val) = self.ixfr_from_differences {
552 parts.push(format!(
553 "ixfr-from-differences {}",
554 if val { "yes" } else { "no" }
555 ));
556 }
557
558 if let Some(ref policy) = self.dnssec_policy {
560 parts.push(format!(r#"dnssec-policy "{}""#, policy));
561 }
562 if let Some(val) = self.inline_signing {
563 parts.push(format!("inline-signing {}", if val { "yes" } else { "no" }));
564 }
565 if let Some(mode) = self.auto_dnssec {
566 parts.push(format!("auto-dnssec {}", mode.as_str()));
567 }
568 if let Some(ref dir) = self.key_directory {
569 parts.push(format!(r#"key-directory "{}""#, dir));
570 }
571 if let Some(val) = self.sig_validity_interval {
572 parts.push(format!("sig-validity-interval {}", val));
573 }
574 if let Some(val) = self.dnskey_sig_validity {
575 parts.push(format!("dnskey-sig-validity {}", val));
576 }
577
578 if let Some(mode) = self.forward {
580 parts.push(format!("forward {}", mode.as_str()));
581 }
582 if let Some(ref forwarders) = self.forwarders {
583 if !forwarders.is_empty() {
584 let forwarder_list = forwarders
585 .iter()
586 .map(|f| {
587 if let Some(ref tls) = f.tls_config {
588 format!("{} tls {}", f.address, tls)
589 } else if let Some(port) = f.port {
590 format!("{} port {}", f.address, port)
591 } else {
592 f.address.to_string()
593 }
594 })
595 .collect::<Vec<_>>()
596 .join("; ");
597 parts.push(format!("forwarders {{ {}; }}", forwarder_list));
598 }
599 }
600
601 if let Some(mode) = self.check_names {
603 parts.push(format!("check-names {}", mode.as_str()));
604 }
605 if let Some(mode) = self.check_mx {
606 parts.push(format!("check-mx {}", mode.as_str()));
607 }
608 if let Some(val) = self.check_integrity {
609 parts.push(format!(
610 "check-integrity {}",
611 if val { "yes" } else { "no" }
612 ));
613 }
614 if let Some(format) = self.masterfile_format {
615 parts.push(format!("masterfile-format {}", format.as_str()));
616 }
617 if let Some(val) = self.max_zone_ttl {
618 parts.push(format!("max-zone-ttl {}", val));
619 }
620
621 if let Some(val) = self.max_refresh_time {
623 parts.push(format!("max-refresh-time {}", val));
624 }
625 if let Some(val) = self.min_refresh_time {
626 parts.push(format!("min-refresh-time {}", val));
627 }
628 if let Some(val) = self.max_retry_time {
629 parts.push(format!("max-retry-time {}", val));
630 }
631 if let Some(val) = self.min_retry_time {
632 parts.push(format!("min-retry-time {}", val));
633 }
634
635 if let Some(val) = self.multi_master {
637 parts.push(format!("multi-master {}", if val { "yes" } else { "no" }));
638 }
639 if let Some(val) = self.request_ixfr {
640 parts.push(format!("request-ixfr {}", if val { "yes" } else { "no" }));
641 }
642 if let Some(val) = self.request_expire {
643 parts.push(format!("request-expire {}", if val { "yes" } else { "no" }));
644 }
645
646 for (key, value) in &self.raw_options {
648 let value_trimmed = value.trim_end().trim_end_matches(';').trim();
649 parts.push(format!("{} {}", key, value_trimmed));
650 }
651
652 format!("{{ {}; }};", parts.join("; "))
653 }
654}