1use crate::rndc_conf_types::{KeyBlock, OptionsBlock, RndcConfFile, ServerAddress, ServerBlock};
30use nom::{
31 branch::alt,
32 bytes::complete::{tag, take_until, take_while, take_while1},
33 character::complete::{char, digit1, multispace0, multispace1},
34 combinator::{map, recognize, value},
35 multi::{many0, separated_list0},
36 sequence::{delimited, preceded},
37 IResult, Parser,
38};
39use std::collections::HashSet;
40use std::net::IpAddr;
41use std::path::{Path, PathBuf};
42use thiserror::Error;
43
44#[derive(Debug, Error)]
46pub enum RndcConfParseError {
47 #[error("Parse error: {0}")]
48 ParseError(String),
49
50 #[error("Invalid server address: {0}")]
51 InvalidServerAddress(String),
52
53 #[error("Invalid IP address: {0}")]
54 InvalidIpAddress(String),
55
56 #[error("Missing required field: {0}")]
57 MissingField(String),
58
59 #[error("Circular include detected: {0}")]
60 CircularInclude(String),
61
62 #[error("File not found: {0}")]
63 FileNotFound(String),
64
65 #[error("IO error: {0}")]
66 IoError(#[from] std::io::Error),
67
68 #[error("Incomplete input")]
69 Incomplete,
70}
71
72pub type ParseResult<T> = Result<T, RndcConfParseError>;
73
74fn line_comment(input: &str) -> IResult<&str, ()> {
78 let (input, _) = tag("//")(input)?;
79 let (input, _) = take_while(|c| c != '\n')(input)?;
80 Ok((input, ()))
81}
82
83fn hash_comment(input: &str) -> IResult<&str, ()> {
85 let (input, _) = char('#')(input)?;
86 let (input, _) = take_while(|c| c != '\n')(input)?;
87 Ok((input, ()))
88}
89
90fn block_comment(input: &str) -> IResult<&str, ()> {
92 value((), (tag("/*"), take_until("*/"), tag("*/"))).parse(input)
93}
94
95fn comment(input: &str) -> IResult<&str, ()> {
97 alt((line_comment, hash_comment, block_comment)).parse(input)
98}
99
100fn ws<'a, F, O>(inner: F) -> impl Parser<&'a str, Output = O, Error = nom::error::Error<&'a str>>
102where
103 F: Parser<&'a str, Output = O, Error = nom::error::Error<&'a str>>,
104{
105 delimited(
106 many0(alt((value((), multispace1), comment))),
107 inner,
108 many0(alt((value((), multispace1), comment))),
109 )
110}
111
112fn semicolon(input: &str) -> IResult<&str, char> {
114 ws(char(';')).parse(input)
115}
116
117fn escaped_char(input: &str) -> IResult<&str, char> {
121 preceded(
122 char('\\'),
123 alt((
124 value('"', char('"')),
125 value('\\', char('\\')),
126 value('\n', char('n')),
127 value('\r', char('r')),
128 value('\t', char('t')),
129 )),
130 )
131 .parse(input)
132}
133
134fn quoted_string(input: &str) -> IResult<&str, String> {
136 delimited(
137 char('"'),
138 map(
139 many0(alt((
140 map(escaped_char, |c| c.to_string()),
141 map(take_while1(|c| c != '"' && c != '\\'), |s: &str| {
142 s.to_string()
143 }),
144 ))),
145 |parts| parts.join(""),
146 ),
147 char('"'),
148 )
149 .parse(input)
150}
151
152fn identifier(input: &str) -> IResult<&str, &str> {
155 take_while1(|c: char| c.is_alphanumeric() || c == '_' || c == '-' || c == '.' || c == ':')(
156 input,
157 )
158}
159
160fn ipv4_addr(input: &str) -> IResult<&str, IpAddr> {
164 let (input, addr_str) = recognize((
165 digit1,
166 char('.'),
167 digit1,
168 char('.'),
169 digit1,
170 char('.'),
171 digit1,
172 ))
173 .parse(input)?;
174
175 let addr = match addr_str.parse::<IpAddr>() {
176 Ok(addr) => addr,
177 Err(_) => {
178 return Err(nom::Err::Error(nom::error::Error::new(
179 input,
180 nom::error::ErrorKind::Verify,
181 )))
182 }
183 };
184
185 Ok((input, addr))
186}
187
188fn ipv6_addr(input: &str) -> IResult<&str, IpAddr> {
190 let (input, addr_str) =
191 recognize(take_while1(|c: char| c.is_ascii_hexdigit() || c == ':')).parse(input)?;
192
193 if !addr_str.contains("::") && addr_str.matches(':').count() < 2 {
195 return Err(nom::Err::Error(nom::error::Error::new(
196 input,
197 nom::error::ErrorKind::Verify,
198 )));
199 }
200
201 let addr = match addr_str.parse::<IpAddr>() {
202 Ok(addr) => addr,
203 Err(_) => {
204 return Err(nom::Err::Error(nom::error::Error::new(
205 input,
206 nom::error::ErrorKind::Verify,
207 )))
208 }
209 };
210
211 Ok((input, addr))
212}
213
214fn ip_addr(input: &str) -> IResult<&str, IpAddr> {
216 alt((ipv6_addr, ipv4_addr)).parse(input)
217}
218
219fn port_number(input: &str) -> IResult<&str, u16> {
221 map(digit1, |s: &str| s.parse::<u16>().unwrap_or(953)).parse(input)
222}
223
224fn server_address(input: &str) -> IResult<&str, ServerAddress> {
226 alt((
228 map(ip_addr, ServerAddress::IpAddr),
229 map(identifier, |s: &str| ServerAddress::Hostname(s.to_string())),
230 ))
231 .parse(input)
232}
233
234#[derive(Debug)]
238enum KeyField {
239 Algorithm(String),
240 Secret(String),
241}
242
243fn parse_algorithm_field(input: &str) -> IResult<&str, KeyField> {
245 let (input, _) = ws(tag("algorithm")).parse(input)?;
246 let (input, algo) = ws(identifier).parse(input)?;
247 let (input, _) = semicolon(input)?;
248 Ok((input, KeyField::Algorithm(algo.to_string())))
249}
250
251fn parse_secret_field(input: &str) -> IResult<&str, KeyField> {
253 let (input, _) = ws(tag("secret")).parse(input)?;
254 let (input, secret) = ws(quoted_string).parse(input)?;
255 let (input, _) = semicolon(input)?;
256 Ok((input, KeyField::Secret(secret)))
257}
258
259fn parse_key_field(input: &str) -> IResult<&str, KeyField> {
261 alt((parse_algorithm_field, parse_secret_field)).parse(input)
262}
263
264fn parse_key_block(input: &str) -> IResult<&str, (String, KeyBlock)> {
266 let (input, _) = ws(tag("key")).parse(input)?;
267 let (input, name) = ws(quoted_string).parse(input)?;
268 let (input, fields) =
269 delimited(ws(char('{')), many0(parse_key_field), ws(tag("};"))).parse(input)?;
270
271 let mut algorithm = None;
272 let mut secret = None;
273
274 for field in fields {
275 match field {
276 KeyField::Algorithm(a) => algorithm = Some(a),
277 KeyField::Secret(s) => secret = Some(s),
278 }
279 }
280
281 let key_block = KeyBlock {
282 name: name.clone(),
283 algorithm: algorithm.unwrap_or_else(|| "hmac-sha256".to_string()),
284 secret: secret.unwrap_or_default(),
285 };
286
287 Ok((input, (name, key_block)))
288}
289
290#[derive(Debug)]
294enum ServerField {
295 Key(String),
296 Port(u16),
297 Addresses(Vec<IpAddr>),
298}
299
300fn parse_server_key_field(input: &str) -> IResult<&str, ServerField> {
302 let (input, _) = ws(tag("key")).parse(input)?;
303 let (input, key) = ws(quoted_string).parse(input)?;
304 let (input, _) = semicolon(input)?;
305 Ok((input, ServerField::Key(key)))
306}
307
308fn parse_server_port_field(input: &str) -> IResult<&str, ServerField> {
310 let (input, _) = ws(tag("port")).parse(input)?;
311 let (input, port) = ws(port_number).parse(input)?;
312 let (input, _) = semicolon(input)?;
313 Ok((input, ServerField::Port(port)))
314}
315
316fn parse_server_addresses_field(input: &str) -> IResult<&str, ServerField> {
318 let (input, _) = ws(tag("addresses")).parse(input)?;
319 let (input, addrs) = delimited(
320 ws(char('{')),
321 separated_list0(semicolon, ws(ip_addr)),
322 ws(tag("};")),
323 )
324 .parse(input)?;
325 Ok((input, ServerField::Addresses(addrs)))
326}
327
328fn parse_server_field(input: &str) -> IResult<&str, ServerField> {
330 alt((
331 parse_server_key_field,
332 parse_server_port_field,
333 parse_server_addresses_field,
334 ))
335 .parse(input)
336}
337
338fn parse_server_block(input: &str) -> IResult<&str, (String, ServerBlock)> {
340 let (input, _) = ws(tag("server")).parse(input)?;
341 let (input, addr) = ws(server_address).parse(input)?;
342 let (input, fields) =
343 delimited(ws(char('{')), many0(parse_server_field), ws(tag("};"))).parse(input)?;
344
345 let mut server = ServerBlock::new(addr.clone());
346
347 for field in fields {
348 match field {
349 ServerField::Key(k) => server.key = Some(k),
350 ServerField::Port(p) => server.port = Some(p),
351 ServerField::Addresses(a) => server.addresses = Some(a),
352 }
353 }
354
355 Ok((input, (addr.to_string(), server)))
356}
357
358#[derive(Debug)]
362#[allow(clippy::enum_variant_names)]
363enum OptionField {
364 DefaultServer(String),
365 DefaultKey(String),
366 DefaultPort(u16),
367}
368
369fn parse_default_server_field(input: &str) -> IResult<&str, OptionField> {
371 let (input, _) = ws(tag("default-server")).parse(input)?;
372 let (input, server) = ws(identifier).parse(input)?;
373 let (input, _) = semicolon(input)?;
374 Ok((input, OptionField::DefaultServer(server.to_string())))
375}
376
377fn parse_default_key_field(input: &str) -> IResult<&str, OptionField> {
379 let (input, _) = ws(tag("default-key")).parse(input)?;
380 let (input, key) = ws(quoted_string).parse(input)?;
381 let (input, _) = semicolon(input)?;
382 Ok((input, OptionField::DefaultKey(key)))
383}
384
385fn parse_default_port_field(input: &str) -> IResult<&str, OptionField> {
387 let (input, _) = ws(tag("default-port")).parse(input)?;
388 let (input, port) = ws(port_number).parse(input)?;
389 let (input, _) = semicolon(input)?;
390 Ok((input, OptionField::DefaultPort(port)))
391}
392
393fn parse_option_field(input: &str) -> IResult<&str, OptionField> {
395 alt((
396 parse_default_server_field,
397 parse_default_key_field,
398 parse_default_port_field,
399 ))
400 .parse(input)
401}
402
403fn parse_options_block(input: &str) -> IResult<&str, OptionsBlock> {
405 let (input, _) = ws(tag("options")).parse(input)?;
406 let (input, fields) =
407 delimited(ws(char('{')), many0(parse_option_field), ws(tag("};"))).parse(input)?;
408
409 let mut options = OptionsBlock::new();
410
411 for field in fields {
412 match field {
413 OptionField::DefaultServer(s) => options.default_server = Some(s),
414 OptionField::DefaultKey(k) => options.default_key = Some(k),
415 OptionField::DefaultPort(p) => options.default_port = Some(p),
416 }
417 }
418
419 Ok((input, options))
420}
421
422fn parse_include_stmt(input: &str) -> IResult<&str, PathBuf> {
426 let (input, _) = ws(tag("include")).parse(input)?;
427 let (input, path) = ws(quoted_string).parse(input)?;
428 let (input, _) = semicolon(input)?;
429 Ok((input, PathBuf::from(path)))
430}
431
432#[derive(Debug)]
436enum Statement {
437 Include(PathBuf),
438 Key(String, KeyBlock),
439 Server(String, ServerBlock),
440 Options(OptionsBlock),
441}
442
443fn parse_statement(input: &str) -> IResult<&str, Statement> {
445 alt((
446 map(parse_include_stmt, Statement::Include),
447 map(parse_key_block, |(name, key)| Statement::Key(name, key)),
448 map(parse_server_block, |(addr, srv)| {
449 Statement::Server(addr, srv)
450 }),
451 map(parse_options_block, Statement::Options),
452 ))
453 .parse(input)
454}
455
456fn parse_rndc_conf_internal(input: &str) -> IResult<&str, RndcConfFile> {
460 let (input, statements) = many0(ws(parse_statement)).parse(input)?;
461 let (input, _) = multispace0(input)?;
462
463 let mut conf = RndcConfFile::new();
464
465 for stmt in statements {
466 match stmt {
467 Statement::Include(path) => conf.includes.push(path),
468 Statement::Key(name, key) => {
469 conf.keys.insert(name, key);
470 }
471 Statement::Server(addr, server) => {
472 conf.servers.insert(addr, server);
473 }
474 Statement::Options(opts) => {
475 if opts.default_server.is_some() {
477 conf.options.default_server = opts.default_server;
478 }
479 if opts.default_key.is_some() {
480 conf.options.default_key = opts.default_key;
481 }
482 if opts.default_port.is_some() {
483 conf.options.default_port = opts.default_port;
484 }
485 }
486 }
487 }
488
489 Ok((input, conf))
490}
491
492pub fn parse_rndc_conf_str(input: &str) -> ParseResult<RndcConfFile> {
510 match parse_rndc_conf_internal(input) {
511 Ok((_, conf)) => Ok(conf),
512 Err(nom::Err::Error(e)) | Err(nom::Err::Failure(e)) => {
513 Err(RndcConfParseError::ParseError(format!("{:?}", e)))
514 }
515 Err(nom::Err::Incomplete(_)) => Err(RndcConfParseError::Incomplete),
516 }
517}
518
519pub fn parse_rndc_conf_file(path: &Path) -> ParseResult<RndcConfFile> {
532 let mut visited = HashSet::new();
533 parse_rndc_conf_file_recursive(path, &mut visited)
534}
535
536fn parse_rndc_conf_file_recursive(
538 path: &Path,
539 visited: &mut HashSet<PathBuf>,
540) -> ParseResult<RndcConfFile> {
541 let canonical_path = path
543 .canonicalize()
544 .map_err(|_| RndcConfParseError::FileNotFound(path.display().to_string()))?;
545
546 if visited.contains(&canonical_path) {
547 return Err(RndcConfParseError::CircularInclude(
548 canonical_path.display().to_string(),
549 ));
550 }
551
552 visited.insert(canonical_path.clone());
553
554 let content = std::fs::read_to_string(path)?;
556 let mut conf = parse_rndc_conf_str(&content)?;
557
558 let includes = conf.includes.clone();
560 conf.includes.clear();
561
562 for include_path in includes {
563 let resolved_path = if include_path.is_absolute() {
565 include_path
566 } else {
567 path.parent()
568 .unwrap_or_else(|| Path::new("."))
569 .join(include_path)
570 };
571
572 let included_conf = parse_rndc_conf_file_recursive(&resolved_path, visited)?;
574
575 for (name, key) in included_conf.keys {
577 conf.keys.entry(name).or_insert(key);
578 }
579
580 for (addr, server) in included_conf.servers {
581 conf.servers.entry(addr).or_insert(server);
582 }
583
584 if conf.options.default_server.is_none() {
586 conf.options.default_server = included_conf.options.default_server;
587 }
588 if conf.options.default_key.is_none() {
589 conf.options.default_key = included_conf.options.default_key;
590 }
591 if conf.options.default_port.is_none() {
592 conf.options.default_port = included_conf.options.default_port;
593 }
594
595 conf.includes.push(resolved_path);
596 }
597
598 Ok(conf)
599}
600
601#[cfg(test)]
602#[path = "rndc_conf_parser_tests.rs"]
603mod tests;