Add TLS support to resolver, implement DBus ifaces
This commit is contained in:
parent
b5797b12f1
commit
c52195dd8b
12 changed files with 308 additions and 221 deletions
26
src/policy_checker/dbus.rs
Normal file
26
src/policy_checker/dbus.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
use {
|
||||
serde::{Deserialize, Serialize},
|
||||
std::net::IpAddr,
|
||||
zbus::{dbus_proxy, Result},
|
||||
};
|
||||
|
||||
#[dbus_proxy(
|
||||
default_service = "com.endlessm.ParentalControls",
|
||||
interface = "com.endlessm.ParentalControls.Dns",
|
||||
default_path = "/com/endlessm/ParentalControls/Dns",
|
||||
gen_blocking = false
|
||||
)]
|
||||
trait MalcontentDns {
|
||||
fn get_restrictions(&self, uid: u32) -> Result<Restrictions>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, zvariant::Type)]
|
||||
pub struct Restriction {
|
||||
pub ip: IpAddr,
|
||||
pub hostname: String,
|
||||
}
|
||||
|
||||
pub type Restrictions = Vec<Restriction>;
|
111
src/policy_checker/mod.rs
Normal file
111
src/policy_checker/mod.rs
Normal file
|
@ -0,0 +1,111 @@
|
|||
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
mod dbus;
|
||||
|
||||
use {
|
||||
self::dbus::*,
|
||||
anyhow::Result,
|
||||
nix::unistd::{getuid, Uid},
|
||||
once_cell::sync::Lazy,
|
||||
std::collections::HashMap,
|
||||
std::net::{SocketAddr, TcpStream},
|
||||
std::sync::{Arc, RwLock},
|
||||
trust_dns_proto::rr::domain::Name as DomainName,
|
||||
trust_dns_resolver::config as dns_config,
|
||||
trust_dns_resolver::TokioAsyncResolver,
|
||||
};
|
||||
|
||||
pub use self::dbus::{Restriction, Restrictions};
|
||||
|
||||
const DNS_UDP_PORT: u16 = 53;
|
||||
const DNS_TLS_PORT: u16 = 853;
|
||||
|
||||
pub static POLICY_CHECKER: Lazy<PolicyChecker> = Lazy::new(|| PolicyChecker::new());
|
||||
|
||||
// TODO: accept notifications about config changes
|
||||
pub struct PolicyChecker {
|
||||
resolvers: RwLock<HashMap<Uid, Option<Arc<TokioAsyncResolver>>>>,
|
||||
}
|
||||
|
||||
impl PolicyChecker {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
resolvers: RwLock::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn restrictions<'a>(&'a self, user: Uid) -> Result<Restrictions> {
|
||||
if user.is_root() {
|
||||
return Ok(vec![]);
|
||||
};
|
||||
|
||||
let connection = zbus::Connection::session().await?;
|
||||
let proxy = MalcontentDnsProxy::new(&connection).await?;
|
||||
Ok(proxy.get_restrictions(user.as_raw()).await?)
|
||||
}
|
||||
|
||||
pub async fn resolver(&self, user: Option<Uid>) -> Result<Option<Arc<TokioAsyncResolver>>> {
|
||||
let user = user.unwrap_or_else(|| getuid());
|
||||
|
||||
// Check if already cached
|
||||
{
|
||||
let ro_resolvers = self.resolvers.read().unwrap();
|
||||
if let Some(resolver) = ro_resolvers.get(&user) {
|
||||
return Ok(resolver.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Else, initialize and cache it
|
||||
{
|
||||
let mut rw_resolvers = self.resolvers.write().unwrap();
|
||||
if let Some(resolver) = rw_resolvers.get(&user) {
|
||||
return Ok(resolver.clone());
|
||||
}
|
||||
|
||||
// try first to prime resolver with DoT implementation,
|
||||
// fallback to unencrypted only if not available.
|
||||
let restrictions = self.restrictions(user).await?;
|
||||
let resolver = if !restrictions.is_empty() {
|
||||
let resolver = TokioAsyncResolver::tokio(
|
||||
resolver_config_for(restrictions),
|
||||
dns_config::ResolverOpts::default(),
|
||||
)?;
|
||||
Some(Arc::new(resolver))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
rw_resolvers.insert(user, resolver.clone());
|
||||
Ok(resolver)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resolver_config_for(restrictions: Vec<Restriction>) -> dns_config::ResolverConfig {
|
||||
use dns_config::NameServerConfigGroup as NsConfig;
|
||||
|
||||
let resolver_config_group =
|
||||
restrictions
|
||||
.into_iter()
|
||||
.fold(NsConfig::new(), |mut config, restr| {
|
||||
let new_config =
|
||||
if TcpStream::connect(SocketAddr::new(restr.ip, DNS_TLS_PORT)).is_ok() {
|
||||
NsConfig::from_ips_tls(&[restr.ip], DNS_TLS_PORT, restr.hostname, true)
|
||||
} else {
|
||||
NsConfig::from_ips_clear(&[restr.ip], DNS_UDP_PORT, true)
|
||||
};
|
||||
|
||||
config.merge(new_config);
|
||||
config
|
||||
});
|
||||
|
||||
let basename = gethostname::gethostname()
|
||||
.as_os_str()
|
||||
.to_str()
|
||||
.map(|hn| DomainName::from_labels(hn.split('.')).ok())
|
||||
.flatten()
|
||||
.map(|hn| hn.base_name());
|
||||
|
||||
dns_config::ResolverConfig::from_parts(basename, vec![], resolver_config_group)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue