1pub(crate) mod errors;
2use std::collections::BTreeMap;
3use std::net::Ipv4Addr;
4use std::path::MAIN_SEPARATOR_STR;
5use std::str::FromStr;
6
7pub use errors::{Error, Result};
8use hickory_proto::rr::domain::Name;
9use iocore::Path;
10use pest::iterators::Pair;
11use pest::Parser;
12use pest_derive::Parser;
13#[derive(Parser, Debug, Clone)]
14#[grammar = "dnsmasq-conf/grammar.pest"]
15pub struct ConfFormat;
16impl ConfFormat {
17 pub fn parse_into_config_attributes(string: &str) -> Result<Vec<ConfigAttribute>> {
18 let mut config_pairs = ConfFormat::parse(Rule::config, string).map_err(|e| {
19 let fancy_e = e.renamed_rules(|rule| match *rule {
20 Rule::EOI => "end of input".to_string(),
21 Rule::int => "an integer".to_string(),
22 Rule::boolean => "a boolean".to_string(),
23 Rule::address => "a host dns name and optional ipv4 address".to_string(),
24 Rule::key_value => "a key/value pair".to_string(),
25 Rule::log_facility => "path to log file".to_string(),
26 Rule::conf_file => "path to a conf file entry".to_string(),
27 Rule::server => "server ip address".to_string(),
28 Rule::attribute => "attribute".to_string(),
29 Rule::ipv4_int => "ipv4_int".to_string(),
30 Rule::value => "value".to_string(),
31 Rule::ip_address => "ip_address".to_string(),
32 Rule::dns_name | Rule::dns_name_glob => "dns_name".to_string(),
33 Rule::port_limit => "port-limit".to_string(),
34 Rule::min_port => "min-port".to_string(),
35 Rule::max_port => "max-port".to_string(),
36 Rule::auth_server => "auth-server".to_string(),
37 Rule::edns_packet_max => "edns-packet-max".to_string(),
38 Rule::listen_address => "listen-address".to_string(),
39 Rule::except_interface => "except_interface".to_string(),
40 Rule::eq => "=".to_string(),
41 Rule::double_quoted_string => "double-quoted string".to_string(),
42 Rule::single_quoted_string => "single-quoted string".to_string(),
43 Rule::forbidden_ebcdic_character => "forbidden EBCDIC character".to_string(),
44 Rule::WHITESPACE => "whitespace".to_string(),
45 _ => format!("{:#?}", *rule).replace("_", " "),
46 });
47 eprintln!("{}", fancy_e.to_string());
48
49 return Error::ParseError(fancy_e.to_string());
50 })?;
51 let mut attributes = Vec::new();
52 for p in config_pairs.next().unwrap().into_inner() {
53 match p.as_rule() {
54 Rule::boolean => attributes.push(ConfigAttribute::Boolean(p.as_str().to_string())),
55 Rule::conf_file => {
56 let conf_file = p
57 .into_inner()
58 .next()
59 .expect("conf_file MUST be a compound atomic")
60 .as_str();
61
62 attributes.push(ConfigAttribute::ConfPath(conf_file.to_string()));
63 },
64 Rule::address => {
65 let pair =
66 p.into_inner().map(|h| h.as_str().to_string()).collect::<Vec<String>>();
67 let name = pair[0].to_string();
68 if pair.len() == 2 {
69 let ip_address = pair[1].to_string();
70 let name = Name::from_str(name.as_str())?;
71 let ip_address = Ipv4Addr::from_str(ip_address.as_str())?;
72 attributes.push(ConfigAttribute::AddressWithIpv4(name, ip_address));
73 } else {
74 let name = Name::from_str(name.as_str())?;
75 attributes.push(ConfigAttribute::AddressName(name));
76 }
77 },
78 Rule::listen_address => {
79 let ip_address = Ipv4Addr::from_str(p.into_inner().as_str())?;
80 attributes.push(ConfigAttribute::ListenAddress(ip_address));
81 },
82 Rule::auth_server => {
83 let mut pair = p.into_inner();
84 let name = pair
85 .next()
86 .expect("auth_server MUST be a compound atomic")
87 .as_str()
88 .to_string();
89 let name = Name::from_str(name.as_str())?;
90 let ip_address_or_interface =
91 pair.next().expect("auth_server MUST be a compound atomic");
92 attributes.push(match Ipv4Addr::from_str(ip_address_or_interface.as_str()) {
93 Ok(ip_address) => ConfigAttribute::AuthServerIpv4(name, ip_address),
94 Err(_) => ConfigAttribute::AuthServerInterface(
95 name,
96 ip_address_or_interface.as_str().to_string(),
97 ),
98 });
99
100 },
105 Rule::log_facility => {
106 let log_facility = p
107 .into_inner()
108 .next()
109 .expect("log_facility MUST be a compound atomic")
110 .as_str();
111
112 attributes.push(ConfigAttribute::LogFacility(log_facility.to_string()));
113 },
114 Rule::server => {
115 let ip_address = Ipv4Addr::from_str(p.into_inner().as_str())?;
116 attributes.push(ConfigAttribute::Server(ip_address));
117 },
118 Rule::port => {
119 let port = u16::from_str(p.into_inner().as_str())?;
120 attributes.push(ConfigAttribute::Port(port));
121 },
122 Rule::min_port => {
123 let port = u16::from_str(p.into_inner().as_str())?;
124 attributes.push(ConfigAttribute::MinPort(port));
125 },
126 Rule::max_port => {
127 let port = u16::from_str(p.into_inner().as_str())?;
128 attributes.push(ConfigAttribute::MaxPort(port));
129 },
130 Rule::port_limit => {
131 let port = u64::from_str(p.into_inner().as_str())?;
132 attributes.push(ConfigAttribute::PortLimit(port));
133 },
134 Rule::cache_size => {
135 let port = u64::from_str(p.into_inner().as_str())?;
136 attributes.push(ConfigAttribute::CacheSize(port));
137 },
138 Rule::edns_packet_max => {
139 let value = u64::from_str(
140 p.into_inner()
141 .next()
142 .expect("edns_packet_max MUST be a compound atomic")
143 .as_str(),
144 )?;
145 attributes.push(ConfigAttribute::EdnsPacketMax(value));
146 },
147 Rule::except_interface => {
148 let value = p.into_inner().as_str().to_string();
149 attributes.push(ConfigAttribute::ExceptInterface(value));
150 },
151 Rule::key_value => {
152 let mut pair = p.into_inner();
153 let key = pair.next().unwrap().as_str().to_string();
154 let value = pair.next().expect("key_value MUST be a compound atomic");
155 let value_pair =
156 value.into_inner().next().expect("value MUST be a compound atomic");
157 let value = Value::from_pair(value_pair)?;
158 attributes.push(ConfigAttribute::KeyValue(key, value));
159 },
160 Rule::EOI
161 | Rule::config
162 | Rule::key
163 | Rule::attribute
164 | Rule::ip_address
165 | Rule::ipv4_int
166 | Rule::interface
167 | Rule::value
168 | Rule::dns_name
169 | Rule::dns_name_glob
170 | Rule::eq
171 | Rule::delimiter
172 | Rule::WHITESPACE
173 | Rule::int
174 | Rule::double_quoted_string
175 | Rule::single_quoted_string
176 | Rule::forbidden_ebcdic_character
177 | Rule::string
178 | Rule::path => {},
179 }
180 }
181 Ok(attributes)
182 }
183}
184
185#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
186pub enum Listener {
187 Ipv4Addr(Ipv4Addr),
188 Interface(String),
189}
190
191#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
192pub enum Value {
193 Ipv4Addr(Ipv4Addr),
194 UnsignedInteger(u64),
195 Path(String),
196 String(String),
197}
198impl Value {
199 pub fn from_pair(p: Pair<Rule>) -> Result<Value> {
200 match p.as_rule() {
201 Rule::ip_address => Ok(Value::Ipv4Addr(Ipv4Addr::from_str(p.as_str())?)),
202 Rule::path => Ok(Value::Path(p.as_str().to_string())),
203 Rule::int => Ok(Value::UnsignedInteger(u64::from_str(p.as_str())?)),
204 Rule::string => Ok(Value::String(p.as_str().to_string())),
205 rule => {
206 let (line, column) = p.line_col();
207 Err(Error::ParseError(format!(
208 "unexpected {:#?} value {:#?} (line {}, column {})",
209 rule,
210 p.as_str(),
211 line,
212 column
213 )))
214 },
215 }
216 }
217
218 pub fn path_from_heuristic(value: &str) -> Result<Value> {
219 let path = Path::raw(value);
220 if path.try_canonicalize().exists() {
221 Ok(Value::Path(value.to_string()))
222 } else if path.extension().is_some() {
223 Ok(Value::Path(value.to_string()))
224 } else if value.contains(MAIN_SEPARATOR_STR) {
225 Ok(Value::Path(value.to_string()))
226 } else {
227 Err(Error::HeuristicError(format!("{:#?} does not appear to be a path because it does not exist, and contains neither the separator {:#?} nor a file extension", value, MAIN_SEPARATOR_STR)))
228 }
229 }
230}
231
232#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
233pub enum ConfigAttribute {
234 Boolean(String),
235 ConfPath(String),
236 LogFacility(String),
237 KeyValue(String, Value),
238 Server(Ipv4Addr),
239 CacheSize(u64),
240 AddressWithIpv4(Name, Ipv4Addr),
241 AddressName(Name),
242 Port(u16),
243 MinPort(u16),
244 MaxPort(u16),
245 PortLimit(u64),
246 ExceptInterface(String),
247 EdnsPacketMax(u64),
248 AuthServerInterface(Name, String),
249 AuthServerIpv4(Name, Ipv4Addr),
250 ListenAddress(Ipv4Addr),
251 #[default]
252 None,
253}
254impl std::fmt::Debug for ConfigAttribute {
255 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
256 write!(
257 f,
258 "{}",
259 match self {
260 ConfigAttribute::ConfPath(path) => {
261 format!("ConfPath({:#?})", path.to_string())
262 },
263 ConfigAttribute::LogFacility(path) => {
264 format!("LogFacility({:#?})", path)
265 },
266 ConfigAttribute::Boolean(string) => {
267 format!("Boolean({})", string)
268 },
269 ConfigAttribute::KeyValue(key, value) => {
270 format!("KeyValue({}={:#?})", key, value)
271 },
272 ConfigAttribute::Server(ipv4addr) => {
273 format!("Server({:#?})", ipv4addr)
274 },
275 ConfigAttribute::AddressWithIpv4(name, ipv4addr) => {
276 format!("AddressWithIpv4({:#?})", (name, ipv4addr))
277 },
278 ConfigAttribute::AddressName(name) => {
279 format!("AddressName({:#?})", name)
280 },
281 ConfigAttribute::Port(val_u16) => {
282 format!("Port({:#?})", val_u16)
283 },
284 ConfigAttribute::MinPort(val_u16) => {
285 format!("MinPort({:#?})", val_u16)
286 },
287 ConfigAttribute::MaxPort(val_u16) => {
288 format!("MaxPort({:#?})", val_u16)
289 },
290 ConfigAttribute::PortLimit(val_u64) => {
291 format!("PortLimit({:#?})", val_u64)
292 },
293 ConfigAttribute::ExceptInterface(string) => {
294 format!("ExceptInterface({:#?})", string)
295 },
296 ConfigAttribute::EdnsPacketMax(val_u64) => {
297 format!("EdnsPacketMax({:#?})", val_u64)
298 },
299 ConfigAttribute::AuthServerInterface(name, string) => {
300 format!("AuthServerInterface({:#?})", (name, string))
301 },
302 ConfigAttribute::AuthServerIpv4(name, ipv4addr) => {
303 format!("AuthServerIpv4({:#?})", (name, ipv4addr))
304 },
305 ConfigAttribute::ListenAddress(ipv4addr) => {
306 format!("ListenAddress({:#?})", ipv4addr.to_string())
307 },
308 ConfigAttribute::CacheSize(val_u64) => {
309 format!("CacheSize({:#?})", val_u64)
310 },
311 ConfigAttribute::None => {
312 format!("None")
313 },
314 }
315 )
316 }
317}
318
319#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
320pub struct Config {
321 pub path: Path,
322 pub conf_file: Vec<String>,
323 pub server: Vec<Ipv4Addr>,
324 pub strict_order: bool,
325 pub domain_needed: bool,
326 pub expand_hosts: bool,
327 pub log_queries: bool,
328 pub log_debug: bool,
329 pub bogus_priv: bool,
330 pub log_facility: Option<String>,
331 pub address: Vec<(Name, Option<Ipv4Addr>)>,
332 pub key_value: BTreeMap<String, Value>,
333 pub port: Option<u16>,
334 pub min_port: Option<u16>,
335 pub max_port: Option<u16>,
336 pub edns_packet_max: Option<u64>,
337 pub port_limit: Option<u64>,
338 pub cache_size: Option<u64>,
339 pub except_interface: Option<String>,
340 pub listen_address: Option<Ipv4Addr>,
341 pub auth_server: Option<(Name, Listener)>,
342}
343impl Config {
344 pub fn new(path: &Path) -> Config {
345 Config {
346 path: valid_path(path).unwrap().relative_to_cwd().tildify(),
347 conf_file: Vec::default(),
348 server: Vec::default(),
349 strict_order: bool::default(),
350 domain_needed: bool::default(),
351 expand_hosts: bool::default(),
352 log_queries: bool::default(),
353 log_debug: bool::default(),
354 bogus_priv: bool::default(),
355 log_facility: Option::default(),
356 address: Vec::default(),
357 key_value: BTreeMap::default(),
358 port: Option::default(),
359 min_port: Option::default(),
360 max_port: Option::default(),
361 edns_packet_max: Option::default(),
362 port_limit: Option::default(),
363 cache_size: Option::default(),
364 except_interface: Option::default(),
365 listen_address: Option::default(),
366 auth_server: Option::default(),
367 }
368 }
369
370 pub fn from_attributes(attributes: Vec<ConfigAttribute>, path: &Path) -> Result<Config> {
371 let mut config = Config::new(path);
372 config.update_from_attributes(attributes)?;
373 Ok(config)
374 }
375
376 pub fn update_from_attributes(&mut self, attributes: Vec<ConfigAttribute>) -> Result<()> {
377 for attr in attributes {
378 match attr {
379 ConfigAttribute::ConfPath(path) => self.conf_file.push(path.to_string()),
380 ConfigAttribute::LogFacility(path) => {
381 self.log_facility = Some(path);
382 },
383 ConfigAttribute::Boolean(string) => {
384 self.set_true(string)?;
385 },
386 ConfigAttribute::KeyValue(key, value) => {
387 self.key_value.insert(key.to_string(), value.clone());
388 },
389 ConfigAttribute::Server(ipv4addr) => {
390 self.server.push(ipv4addr.clone());
391 },
392 ConfigAttribute::AddressWithIpv4(name, ipv4addr) => {
393 self.address.push((name.clone(), Some(ipv4addr.clone())));
394 },
395 ConfigAttribute::AddressName(name) => {
396 self.address.push((name.clone(), None));
397 },
398 ConfigAttribute::Port(val_u16) => {
399 self.port = Some(val_u16);
400 },
401 ConfigAttribute::MinPort(val_u16) => {
402 self.min_port = Some(val_u16);
403 },
404 ConfigAttribute::MaxPort(val_u16) => {
405 self.max_port = Some(val_u16);
406 },
407 ConfigAttribute::PortLimit(val_u64) => {
408 self.port_limit = Some(val_u64);
409 },
410 ConfigAttribute::CacheSize(val_u64) => {
411 self.cache_size = Some(val_u64);
412 },
413 ConfigAttribute::ExceptInterface(string) => {
414 self.except_interface = Some(string.to_string());
415 },
416 ConfigAttribute::EdnsPacketMax(val_u64) => {
417 self.edns_packet_max = Some(val_u64);
418 },
419 ConfigAttribute::AuthServerInterface(name, string) => {
420 self.auth_server = Some((name, Listener::Interface(string)));
421 },
422 ConfigAttribute::AuthServerIpv4(name, ipv4addr) => {
423 self.auth_server = Some((name, Listener::Ipv4Addr(ipv4addr)));
424 },
425 ConfigAttribute::ListenAddress(ipv4addr) => {
426 self.listen_address = Some(ipv4addr.clone());
427 },
428 _ => {},
429 }
430 }
431 Ok(())
432 }
433
434 pub fn from_path<'i>(path: &Path) -> Result<Config> {
435 let string = path.read()?;
436 let attributes = ConfFormat::parse_into_config_attributes(string.as_str())?;
437 Ok(Config::from_attributes(attributes, path)?)
438 }
439
440 pub fn set_true<T: std::fmt::Display + std::fmt::Debug>(&mut self, flag: T) -> Result<()> {
441 match flag.to_string().replace("-", "_").as_str() {
442 "strict_order" => {
443 self.strict_order = true;
444 },
445 "domain_needed" => {
446 self.domain_needed = true;
447 },
448 "expand_hosts" => {
449 self.expand_hosts = true;
450 },
451 "log_queries" => {
452 self.log_queries = true;
453 },
454 "log_debug" => {
455 self.log_debug = true;
456 },
457 "bogus_priv" => {
458 self.bogus_priv = true;
459 },
460 _ => return Err(Error::ConfigError(format!("unexpected boolean flag: {:#?}", flag))),
461 }
462 Ok(())
463 }
464
465 pub fn path(&self) -> Path {
466 self.path.clone()
467 }
468
469 pub fn parent_path(&self) -> Path {
470 self.path()
471 .parent()
472 .expect(format!("{:#?} to have a parent folder", self.path().to_string()).as_str())
473 }
474
475 pub fn is_unified(&self) -> bool {
476 self.conf_file.is_empty()
477 }
478
479 pub fn unify(&self) -> Result<Config> {
480 let mut unified = self.clone();
481 let main_conf_parent_path = get_parent_path(&self.path())?;
482 let last_path = self.path();
483 let last_parent_path = main_conf_parent_path.clone();
484
485 while unified.conf_file.len() > 0 {
486 let conf_path = unified.conf_file.remove(0).to_string();
487 let path = {
488 let mut potential_parents =
489 vec![last_parent_path.clone(), main_conf_parent_path.clone(), Path::cwd()];
490 let lookup_paths = potential_parents
491 .iter()
492 .map(|h| h.relative_to_cwd().tildify().to_string())
493 .collect::<Vec<String>>();
494 loop {
495 if potential_parents.is_empty() {
496 return Err(Error::ConfigError(format!(
497 "error in config file {:#?}: could not find {:#?} in: {}",
498 last_path.to_string(),
499 conf_path,
500 lookup_paths.join(", ")
501 )));
502 }
503 let parent = potential_parents.remove(0);
504 let possible_path = valid_path(parent.join(conf_path.as_str()));
505 if possible_path.is_ok() {
506 break possible_path?;
507 }
508 }
509 };
510 let sub_config_attributes = path.read()?;
511 let attributes =
512 ConfFormat::parse_into_config_attributes(sub_config_attributes.as_str())?;
513 unified.update_from_attributes(attributes)?;
514 }
515 Ok(unified)
516 }
517}
518
519pub fn valid_path(path: impl std::fmt::Display) -> Result<Path> {
520 let path_str = path.to_string();
521 let path = Path::raw(path_str.as_str()).try_canonicalize();
522 if !path.exists() {
523 Err(Error::IOError(format!("{:#?} does not exist", path_str.as_str())))
524 } else if !path.is_file() {
525 Err(Error::IOError(format!(
526 "{:#?} is not a file ({})",
527 path_str.as_str(),
528 path.kind()
529 )))
530 } else {
531 Ok(path)
532 }
533}
534
535pub fn get_parent_path(path: &Path) -> Result<Path> {
536 Ok(path.parent().ok_or_else(|| {
537 Error::IOError(format!("{:#?} does not have a parent folder", path.to_string()))
538 })?)
539}