1use std::fmt::{Display, Formatter};
2use std::net::{IpAddr, Ipv6Addr};
3use std::{fmt, io};
4
5use dns_lookup::{getaddrinfo, LookupErrorKind};
6use log::*;
7use thiserror::Error;
8
9#[cfg(dbus)]
10use crate::dbus::lookup_dbus;
11#[cfg(dbus)]
12use crate::DnsCheckError::{NoDBus, NoResolved};
13
14#[cfg(all(feature = "dbus", target_os = "linux"))]
15mod dbus;
16
17#[derive(Debug, Copy, Clone)]
18pub enum Query<'a> {
19 Address(IpAddr),
20 Domain(&'a str),
21}
22
23impl Display for Query<'_> {
24 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
25 match self {
26 Query::Address(addr) => {
27 write!(f, "{}", addr)
28 }
29 Query::Domain(domain) => {
30 write!(f, "{}", domain)
31 }
32 }
33 }
34}
35
36#[derive(Error, Debug)]
37pub enum DnsCheckError {
38 #[error("DBus reported {0}: {1}")]
39 DBus(String, String),
40 #[error("DBus support is missing")]
41 NoDBus,
42 #[error("systemd-resolved not found: {0}")]
43 NoResolved(#[source] anyhow::Error),
44 #[error("NXDOMAIN {0}")]
45 NxDomain(String),
46 #[error("Something went wrong: {0}")]
47 Unknown(#[from] anyhow::Error),
48}
49
50impl From<io::Error> for DnsCheckError {
51 fn from(e: io::Error) -> Self {
52 DnsCheckError::Unknown(e.into())
53 }
54}
55
56pub struct DnsListMembership {
57 pub name: String,
58 pub list: String,
59 pub found: bool,
60}
61
62pub fn lookup(source: &str, query: &Query) -> Result<DnsListMembership, DnsCheckError> {
63 #[cfg(dbus)]
64 match lookup_dbus(source, query) {
65 Ok(r) => return Ok(r),
66 Err(NoDBus) => {
67 warn!("DBus not compiled in, falling back to internal resolution")
68 }
69 Err(NoResolved(e)) => {
70 warn!("DBus resolution failed: {:?}", e)
71 }
72 Err(e) => return Err(e),
73 };
74
75 lookup_dns(source, query)
76}
77
78fn lookup_dns(source: &str, query: &Query) -> Result<DnsListMembership, DnsCheckError> {
79 let queryhost = match query {
80 Query::Domain(d) => format!("{}.", d),
81 Query::Address(ip) => format_ip(ip),
82 };
83
84 let hostname = format!("{}{}.", queryhost, source);
85
86 let mut addrinfo = match getaddrinfo(Some(&hostname), None, None) {
87 Ok(a) => Ok(a),
88 Err(e) => match e.kind() {
89 LookupErrorKind::NoName => {
90 return Ok(DnsListMembership {
91 name: query.to_string(),
92 list: format!("{:?}", source),
93 found: false,
94 })
95 }
96 _ => Err(DnsCheckError::Unknown(io::Error::from(e).into())),
97 },
98 }?;
99
100 Ok(DnsListMembership {
101 name: source.to_string(),
102 list: format!("{}", query),
103 found: addrinfo.next().is_some(),
104 })
105}
106
107fn format_ip(ip: &IpAddr) -> String {
108 match ip {
109 IpAddr::V4(v4) => format!(
110 "{}.{}.{}.{}.",
111 v4.octets()[3],
112 v4.octets()[2],
113 v4.octets()[1],
114 v4.octets()[0]
115 ),
116 IpAddr::V6(v6) => format_v6(v6),
117 }
118}
119
120fn format_v6(ip: &Ipv6Addr) -> String {
121 ip.octets()
122 .iter()
123 .flat_map(|o| vec![o >> 4, o & 0xF])
124 .map(|d| format!("{:x}", d))
125 .fold("".to_owned(), |a: String, d: String| format!("{}.{}", d, a))
126}
127
128pub fn count_lists(
129 queries: &[Query],
130 sources: &[&str],
131) -> Result<Vec<DnsListMembership>, DnsCheckError> {
132 queries
133 .iter()
134 .flat_map(|query| sources.iter().map(move |&source| lookup(source, query)))
135 .collect()
136}