1use crate::rndc_types::{
19 AutoDnssecMode, CheckNamesMode, DnsClass, ForwardMode, ForwarderSpec, MasterfileFormat,
20 NotifyMode, PrimarySpec, ZoneConfig, ZoneType,
21};
22use nom::{
23 branch::alt,
24 bytes::complete::{tag, take_until, take_while1},
25 character::complete::{char, multispace0},
26 combinator::{map, opt, recognize},
27 multi::many0,
28 sequence::{delimited, preceded, terminated},
29 IResult, Parser,
30};
31use std::net::IpAddr;
32use thiserror::Error;
33
34#[derive(Debug, Error)]
36pub enum RndcParseError {
37 #[error("Parse error: {0}")]
38 ParseError(String),
39
40 #[error("Invalid zone type: {0}")]
41 InvalidZoneType(String),
42
43 #[error("Invalid DNS class: {0}")]
44 InvalidDnsClass(String),
45
46 #[error("Invalid IP address: {0}")]
47 InvalidIpAddress(String),
48
49 #[error("Missing required field: {0}")]
50 MissingField(String),
51
52 #[error("Incomplete input")]
53 Incomplete,
54}
55
56pub type ParseResult<T> = Result<T, RndcParseError>;
57
58fn ws<'a, F, O>(inner: F) -> impl Parser<&'a str, Output = O, Error = nom::error::Error<&'a str>>
62where
63 F: Parser<&'a str, Output = O, Error = nom::error::Error<&'a str>>,
64{
65 delimited(multispace0, inner, multispace0)
66}
67
68fn semicolon(input: &str) -> IResult<&str, char> {
70 ws(char(';')).parse(input)
71}
72
73pub(crate) fn quoted_string(input: &str) -> IResult<&str, String> {
75 let (input, content) = delimited(char('"'), take_until("\""), char('"')).parse(input)?;
76 Ok((input, content.to_string()))
77}
78
79fn identifier(input: &str) -> IResult<&str, &str> {
81 take_while1(|c: char| c.is_alphanumeric() || c == '_' || c == '-')(input)
82}
83
84pub(crate) fn ip_addr(input: &str) -> IResult<&str, IpAddr> {
87 let (input, addr_str) = recognize(take_while1(|c: char| {
89 c.is_ascii_hexdigit() || c == '.' || c == ':'
90 }))
91 .parse(input)?;
92
93 let addr = match addr_str.parse::<IpAddr>() {
95 Ok(addr) => addr,
96 Err(_) => {
97 return Err(nom::Err::Error(nom::error::Error::new(
98 input,
99 nom::error::ErrorKind::Verify,
100 )))
101 }
102 };
103
104 let (input, _) =
106 opt(preceded(char('/'), take_while1(|c: char| c.is_numeric()))).parse(input)?;
107
108 Ok((input, addr))
109}
110
111pub(crate) fn ip_with_port(input: &str) -> IResult<&str, PrimarySpec> {
113 let (input, addr) = ws(ip_addr).parse(input)?;
114 let (input, port) = opt(preceded(
115 ws(tag("port")),
116 map(take_while1(|c: char| c.is_numeric()), |s: &str| {
117 s.parse::<u16>().ok()
118 }),
119 ))
120 .parse(input)?;
121
122 Ok((
123 input,
124 PrimarySpec {
125 address: addr,
126 port: port.flatten(),
127 },
128 ))
129}
130
131fn ip_list(input: &str) -> IResult<&str, Vec<IpAddr>> {
133 delimited(
134 ws(char('{')),
135 many0(terminated(ws(ip_addr), semicolon)),
136 ws(char('}')),
137 )
138 .parse(input)
139}
140
141fn primary_list(input: &str) -> IResult<&str, Vec<PrimarySpec>> {
143 delimited(
144 ws(char('{')),
145 many0(terminated(ip_with_port, semicolon)),
146 ws(char('}')),
147 )
148 .parse(input)
149}
150
151#[derive(Debug)]
155#[allow(dead_code)]
156enum ZoneStatement {
157 Type(ZoneType),
159 File(String),
160
161 Primaries(Vec<PrimarySpec>),
163 AlsoNotify(Vec<IpAddr>),
164 Notify(NotifyMode),
165
166 AllowQuery(Vec<IpAddr>),
168 AllowTransfer(Vec<IpAddr>),
169 AllowUpdate(Vec<IpAddr>),
170 AllowUpdateRaw(String),
171 AllowUpdateForwarding(Vec<IpAddr>),
172 AllowNotify(Vec<IpAddr>),
173
174 MaxTransferTimeIn(u32),
176 MaxTransferTimeOut(u32),
177 MaxTransferIdleIn(u32),
178 MaxTransferIdleOut(u32),
179 TransferSource(IpAddr),
180 TransferSourceV6(IpAddr),
181 NotifySource(IpAddr),
182 NotifySourceV6(IpAddr),
183
184 UpdatePolicy(String),
186 Journal(String),
187 IxfrFromDifferences(bool),
188
189 InlineSigning(bool),
191 AutoDnssec(AutoDnssecMode),
192 KeyDirectory(String),
193 SigValidityInterval(u32),
194 DnskeySigValidity(u32),
195
196 Forward(ForwardMode),
198 Forwarders(Vec<ForwarderSpec>),
199
200 CheckNames(CheckNamesMode),
202 CheckMx(CheckNamesMode),
203 CheckIntegrity(bool),
204 MasterfileFormat(MasterfileFormat),
205 MaxZoneTtl(u32),
206
207 MaxRefreshTime(u32),
209 MinRefreshTime(u32),
210 MaxRetryTime(u32),
211 MinRetryTime(u32),
212
213 MultiMaster(bool),
215 RequestIxfr(bool),
216 RequestExpire(bool),
217
218 Unknown(String, String), }
221
222fn parse_type_statement(input: &str) -> IResult<&str, ZoneStatement> {
224 let (input, _) = ws(tag("type")).parse(input)?;
225 let (input, type_str) = ws(identifier).parse(input)?;
226 let (input, _) = semicolon(input)?;
227
228 let zone_type = ZoneType::parse(type_str).ok_or_else(|| {
229 nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Verify))
230 })?;
231
232 Ok((input, ZoneStatement::Type(zone_type)))
233}
234
235fn parse_file_statement(input: &str) -> IResult<&str, ZoneStatement> {
237 let (input, _) = ws(tag("file")).parse(input)?;
238 let (input, file) = ws(quoted_string).parse(input)?;
239 let (input, _) = semicolon(input)?;
240 Ok((input, ZoneStatement::File(file)))
241}
242
243fn parse_primaries_statement(input: &str) -> IResult<&str, ZoneStatement> {
246 let (input, _) = ws(alt((tag("primaries"), tag("masters")))).parse(input)?;
247 let (input, primaries) = primary_list(input)?;
248 let (input, _) = semicolon(input)?;
249 Ok((input, ZoneStatement::Primaries(primaries)))
250}
251
252fn parse_also_notify_statement(input: &str) -> IResult<&str, ZoneStatement> {
254 let (input, _) = ws(tag("also-notify")).parse(input)?;
255 let (input, addrs) = ip_list(input)?;
256 let (input, _) = semicolon(input)?;
257 Ok((input, ZoneStatement::AlsoNotify(addrs)))
258}
259
260fn parse_allow_transfer_statement(input: &str) -> IResult<&str, ZoneStatement> {
262 let (input, _) = ws(tag("allow-transfer")).parse(input)?;
263 let (input, addrs) = ip_list(input)?;
264 let (input, _) = semicolon(input)?;
265 Ok((input, ZoneStatement::AllowTransfer(addrs)))
266}
267
268fn parse_allow_update_statement(input: &str) -> IResult<&str, ZoneStatement> {
271 let (input, _) = ws(tag("allow-update")).parse(input)?;
272
273 let start_input = input;
275
276 let (input, _) = ws(char('{')).parse(input)?;
278
279 let mut addrs = Vec::new();
281 let mut has_key_ref = false;
282 let mut remaining = input;
283
284 loop {
285 let (input, _) = multispace0(remaining)?;
287
288 if let Ok((input, _)) = char::<_, nom::error::Error<&str>>('}')(input) {
290 remaining = input;
291 break;
292 }
293
294 if let Ok((input, _)) = ws(tag("key")).parse(input) {
296 has_key_ref = true;
297 let (input, _) = take_until(";")(input)?;
299 let (input, _) = char(';')(input)?;
300 remaining = input;
301 } else if let Ok((input, addr)) = ip_addr(input) {
302 addrs.push(addr);
304 let (input, _) = semicolon(input)?;
306 remaining = input;
307 } else {
308 let (input, _) = take_until(";")(input)?;
310 let (input, _) = char(';')(input)?;
311 remaining = input;
312 }
313 }
314
315 let (input, _) = semicolon(remaining)?;
316
317 if has_key_ref {
319 let raw_len = start_input.len() - input.len();
321 let raw_content = &start_input[..raw_len];
322 Ok((
323 input,
324 ZoneStatement::AllowUpdateRaw(raw_content.to_string()),
325 ))
326 } else {
327 Ok((input, ZoneStatement::AllowUpdate(addrs)))
328 }
329}
330
331fn parse_unknown_statement(input: &str) -> IResult<&str, ZoneStatement> {
334 let (input, option_name) = ws(identifier).parse(input)?;
336
337 let start_input = input;
339
340 let (input, _value) = alt((
342 delimited(ws(char('{')), take_until("}"), ws(char('}'))),
344 take_until(";"),
346 ))
347 .parse(input)?;
348
349 let value_len = start_input.len() - input.len();
351 let raw_value = start_input[..value_len].trim().to_string();
352
353 let (input, _) = semicolon(input)?;
354
355 Ok((
356 input,
357 ZoneStatement::Unknown(option_name.to_string(), raw_value),
358 ))
359}
360
361fn parse_zone_statement(input: &str) -> IResult<&str, ZoneStatement> {
363 alt((
364 parse_type_statement,
365 parse_file_statement,
366 parse_primaries_statement,
367 parse_also_notify_statement,
368 parse_allow_transfer_statement,
369 parse_allow_update_statement,
370 parse_unknown_statement,
372 ))
373 .parse(input)
374}
375
376fn parse_zone_config_internal(input: &str) -> IResult<&str, ZoneConfig> {
388 let (input, _) = ws(tag("zone")).parse(input)?;
390 let (input, zone_name) = ws(quoted_string).parse(input)?;
391
392 let (input, class) = opt(ws(alt((tag("IN"), tag("CH"), tag("HS"))))).parse(input)?;
394 let class = match class {
395 Some("IN") => DnsClass::IN,
396 Some("CH") => DnsClass::CH,
397 Some("HS") => DnsClass::HS,
398 _ => DnsClass::IN, };
400
401 let (input, statements) =
403 delimited(ws(char('{')), many0(parse_zone_statement), ws(tag("};"))).parse(input)?;
404
405 let mut config = ZoneConfig::new(zone_name, ZoneType::Primary); config.class = class;
408
409 for stmt in statements {
410 match stmt {
411 ZoneStatement::Type(t) => config.zone_type = t,
413 ZoneStatement::File(f) => config.file = Some(f),
414
415 ZoneStatement::Primaries(p) => config.primaries = Some(p),
417 ZoneStatement::AlsoNotify(a) => config.also_notify = Some(a),
418 ZoneStatement::Notify(n) => config.notify = Some(n),
419
420 ZoneStatement::AllowQuery(a) => config.allow_query = Some(a),
422 ZoneStatement::AllowTransfer(a) => config.allow_transfer = Some(a),
423 ZoneStatement::AllowUpdate(a) => config.allow_update = Some(a),
424 ZoneStatement::AllowUpdateRaw(raw) => config.allow_update_raw = Some(raw),
425 ZoneStatement::AllowUpdateForwarding(a) => config.allow_update_forwarding = Some(a),
426 ZoneStatement::AllowNotify(a) => config.allow_notify = Some(a),
427
428 ZoneStatement::MaxTransferTimeIn(v) => config.max_transfer_time_in = Some(v),
430 ZoneStatement::MaxTransferTimeOut(v) => config.max_transfer_time_out = Some(v),
431 ZoneStatement::MaxTransferIdleIn(v) => config.max_transfer_idle_in = Some(v),
432 ZoneStatement::MaxTransferIdleOut(v) => config.max_transfer_idle_out = Some(v),
433 ZoneStatement::TransferSource(ip) => config.transfer_source = Some(ip),
434 ZoneStatement::TransferSourceV6(ip) => config.transfer_source_v6 = Some(ip),
435 ZoneStatement::NotifySource(ip) => config.notify_source = Some(ip),
436 ZoneStatement::NotifySourceV6(ip) => config.notify_source_v6 = Some(ip),
437
438 ZoneStatement::UpdatePolicy(p) => config.update_policy = Some(p),
440 ZoneStatement::Journal(j) => config.journal = Some(j),
441 ZoneStatement::IxfrFromDifferences(v) => config.ixfr_from_differences = Some(v),
442
443 ZoneStatement::InlineSigning(v) => config.inline_signing = Some(v),
445 ZoneStatement::AutoDnssec(m) => config.auto_dnssec = Some(m),
446 ZoneStatement::KeyDirectory(d) => config.key_directory = Some(d),
447 ZoneStatement::SigValidityInterval(v) => config.sig_validity_interval = Some(v),
448 ZoneStatement::DnskeySigValidity(v) => config.dnskey_sig_validity = Some(v),
449
450 ZoneStatement::Forward(m) => config.forward = Some(m),
452 ZoneStatement::Forwarders(f) => config.forwarders = Some(f),
453
454 ZoneStatement::CheckNames(m) => config.check_names = Some(m),
456 ZoneStatement::CheckMx(m) => config.check_mx = Some(m),
457 ZoneStatement::CheckIntegrity(v) => config.check_integrity = Some(v),
458 ZoneStatement::MasterfileFormat(f) => config.masterfile_format = Some(f),
459 ZoneStatement::MaxZoneTtl(v) => config.max_zone_ttl = Some(v),
460
461 ZoneStatement::MaxRefreshTime(v) => config.max_refresh_time = Some(v),
463 ZoneStatement::MinRefreshTime(v) => config.min_refresh_time = Some(v),
464 ZoneStatement::MaxRetryTime(v) => config.max_retry_time = Some(v),
465 ZoneStatement::MinRetryTime(v) => config.min_retry_time = Some(v),
466
467 ZoneStatement::MultiMaster(v) => config.multi_master = Some(v),
469 ZoneStatement::RequestIxfr(v) => config.request_ixfr = Some(v),
470 ZoneStatement::RequestExpire(v) => config.request_expire = Some(v),
471
472 ZoneStatement::Unknown(key, value) => {
474 config.raw_options.insert(key, value);
475 }
476 }
477 }
478
479 Ok((input, config))
480}
481
482pub fn parse_showzone(input: &str) -> ParseResult<ZoneConfig> {
494 match parse_zone_config_internal(input.trim()) {
495 Ok((_, config)) => Ok(config),
496 Err(nom::Err::Error(e)) | Err(nom::Err::Failure(e)) => {
497 Err(RndcParseError::ParseError(format!("Parse failed: {:?}", e)))
498 }
499 Err(nom::Err::Incomplete(_)) => Err(RndcParseError::Incomplete),
500 }
501}