Skip to main content

dnsmasq_conf/
lib.rs

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                    // let pair =
101                    //     p.into_inner().map(|h| h.as_str().to_string()).collect::<Vec<String>>();
102                    // let name = pair[0].to_string();
103                    // let ip_address_or_interface = pair[1].to_string();
104                },
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}