resolved/
fs.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
use std::io;
use std::path::{Path, PathBuf};
use tokio::fs::{read_dir, read_to_string};

use dns_types::hosts::types::Hosts;
use dns_types::zones::types::{Zone, Zones};

/// Load the hosts and zones from the configuration, generating the
/// `Zones` parameter for the resolver.
pub async fn load_zone_configuration(
    hosts_files: &[PathBuf],
    hosts_dirs: &[PathBuf],
    zone_files: &[PathBuf],
    zone_dirs: &[PathBuf],
) -> Option<Zones> {
    let mut is_error = false;
    let mut hosts_file_paths = Vec::from(hosts_files);
    let mut zone_file_paths = Vec::from(zone_files);

    for path in zone_dirs {
        match get_files_from_dir(path).await {
            Ok(mut paths) => zone_file_paths.append(&mut paths),
            Err(error) => {
                tracing::warn!(?path, ?error, "could not read zone directory");
                is_error = true;
            }
        }
    }
    for path in hosts_dirs {
        match get_files_from_dir(path).await {
            Ok(mut paths) => hosts_file_paths.append(&mut paths),
            Err(error) => {
                tracing::warn!(?path, ?error, "could not read hosts directory");
                is_error = true;
            }
        }
    }

    let mut combined_zones = Zones::new();
    for path in &zone_file_paths {
        match zone_from_file(Path::new(path)).await {
            Ok(Ok(zone)) => combined_zones.insert_merge(zone),
            Ok(Err(error)) => {
                tracing::warn!(?path, ?error, "could not parse zone file");
                is_error = true;
            }
            Err(error) => {
                tracing::warn!(?path, ?error, "could not read zone file");
                is_error = true;
            }
        }
    }

    let mut combined_hosts = Hosts::default();
    for path in &hosts_file_paths {
        match hosts_from_file(Path::new(path)).await {
            Ok(Ok(hosts)) => combined_hosts.merge(hosts),
            Ok(Err(error)) => {
                tracing::warn!(?path, ?error, "could not parse hosts file");
                is_error = true;
            }
            Err(error) => {
                tracing::warn!(?path, ?error, "could not read hosts file");
                is_error = true;
            }
        }
    }

    if is_error {
        None
    } else {
        combined_zones.insert_merge(combined_hosts.into());
        Some(combined_zones)
    }
}

/// Read a hosts file, for example /etc/hosts.
async fn hosts_from_file<P: AsRef<Path>>(
    path: P,
) -> io::Result<Result<Hosts, dns_types::hosts::deserialise::Error>> {
    let data = read_to_string(path).await?;
    Ok(Hosts::deserialise(&data))
}

/// Read a zone file.
///
/// If it has a SOA record, it is an authoritative zone: it may
/// only have *one* SOA record, and all RRs must be subdomains of
/// the SOA domain.
///
/// If it does not have a SOA record, it is a non-authoritative
/// zone, and the root domain will be used for its apex.
async fn zone_from_file<P: AsRef<Path>>(
    path: P,
) -> io::Result<Result<Zone, dns_types::zones::deserialise::Error>> {
    let data = read_to_string(path).await?;
    Ok(Zone::deserialise(&data))
}

/// Get files from a directory, sorted.
async fn get_files_from_dir(dir: &Path) -> io::Result<Vec<PathBuf>> {
    let mut out = Vec::new();

    let mut reader = read_dir(dir).await?;
    while let Some(entry) = reader.next_entry().await? {
        let path = entry.path();
        if !path.is_dir() {
            out.push(path);
        }
    }

    out.sort();
    Ok(out)
}