Start implementing and testing gethostbyname4_r
This commit is contained in:
parent
9ce503e052
commit
37ef5d4e65
|
@ -15,6 +15,7 @@ FetchContent_Declare(
|
|||
GIT_TAG v0.2.1
|
||||
)
|
||||
|
||||
set(Rust_TOOLCHAIN nightly)
|
||||
FetchContent_MakeAvailable(Corrosion)
|
||||
corrosion_import_crate(MANIFEST_PATH Cargo.toml)
|
||||
|
||||
|
|
|
@ -29,4 +29,4 @@ version = "0.2"
|
|||
|
||||
[dependencies.nix]
|
||||
version = "0.25"
|
||||
features = []
|
||||
features = ["socket", "user", "sched"]
|
||||
|
|
6
build.rs
6
build.rs
|
@ -18,10 +18,14 @@ fn main() {
|
|||
println!("cargo:rustc-link-search={}", &out_dir);
|
||||
|
||||
let bindings = bindgen::Builder::default()
|
||||
.header("wrapper.h")
|
||||
.header("wrapper.hpp")
|
||||
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
|
||||
.newtype_enum("nss_status")
|
||||
.allowlist_type("nss_status")
|
||||
.newtype_enum("HErrno")
|
||||
.allowlist_type("HErrno")
|
||||
.newtype_enum("EaiRetcode")
|
||||
.allowlist_type("EaiRetcode")
|
||||
.allowlist_type("gaih_addrtuple")
|
||||
.allowlist_function("__nss_configure_lookup")
|
||||
.generate()
|
||||
|
|
|
@ -4,5 +4,8 @@
|
|||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![feature(once_cell)]
|
||||
|
||||
mod nss_api;
|
||||
pub mod nss_api;
|
||||
mod policy_checker;
|
||||
mod utils;
|
||||
|
|
|
@ -5,35 +5,57 @@
|
|||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||
|
||||
use {
|
||||
::std::os::raw::{c_char, c_int, c_void},
|
||||
crate::policy_checker::{MalcontentPolicyChecker, PolicyChecker as _},
|
||||
crate::utils::set_if_valid,
|
||||
libc::{hostent, size_t, socklen_t, AF_INET},
|
||||
nix::errno,
|
||||
nix::errno::Errno,
|
||||
std::ffi::CStr,
|
||||
std::os::raw::{c_char, c_int, c_void},
|
||||
std::ptr,
|
||||
};
|
||||
|
||||
// See https://support.mozilla.org/en-US/kb/configuring-networks-disable-dns-over-https
|
||||
const CANARY_HOSTNAME: &[u8] = b"use-application-dns.net\0";
|
||||
|
||||
// -------------- by host ---------------
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn _nss_malcontent_gethostbyname4_r(
|
||||
_name: *const c_char,
|
||||
pub unsafe extern "C" fn _nss_malcontent_gethostbyname4_r(
|
||||
name: *const c_char,
|
||||
_pat: *mut *mut gaih_addrtuple,
|
||||
_buffer: *mut c_char,
|
||||
_buflen: size_t,
|
||||
_errnop: *mut c_int,
|
||||
_h_errnop: *mut c_int,
|
||||
errnop: *mut Errno,
|
||||
h_errnop: *mut HErrno,
|
||||
_ttlp: *mut i32,
|
||||
) -> nss_status {
|
||||
todo!()
|
||||
let name = CStr::from_ptr(name);
|
||||
let policy_checker = MalcontentPolicyChecker::new(); // TODO: make LazySync
|
||||
match policy_checker.restrictions(None) {
|
||||
Ok(None) => nss_status::NSS_STATUS_NOTFOUND,
|
||||
Ok(Some(_addrs)) => {
|
||||
if name == CStr::from_bytes_with_nul_unchecked(CANARY_HOSTNAME) {
|
||||
set_if_valid(errnop, errno::from_i32(0));
|
||||
set_if_valid(h_errnop, HErrno::HostNotFound);
|
||||
return nss_status::NSS_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
nss_status::NSS_STATUS_UNAVAIL
|
||||
}
|
||||
Err(_err) => nss_status::NSS_STATUS_UNAVAIL,
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn _nss_malcontent_gethostbyname3_r(
|
||||
pub unsafe extern "C" fn _nss_malcontent_gethostbyname3_r(
|
||||
_name: *const c_char,
|
||||
_af: c_int,
|
||||
_host: *mut hostent,
|
||||
_buffer: *mut c_char,
|
||||
_buflen: size_t,
|
||||
_errnop: *mut c_int,
|
||||
_h_errnop: *mut c_int,
|
||||
_errnop: *mut Errno,
|
||||
_h_errnop: *mut HErrno,
|
||||
_ttlp: *mut i32,
|
||||
_canonp: *mut *mut char,
|
||||
) -> nss_status {
|
||||
|
@ -41,14 +63,14 @@ pub extern "C" fn _nss_malcontent_gethostbyname3_r(
|
|||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn _nss_malcontent_gethostbyname2_r(
|
||||
pub unsafe extern "C" fn _nss_malcontent_gethostbyname2_r(
|
||||
name: *const c_char,
|
||||
af: c_int,
|
||||
host: *mut hostent,
|
||||
buffer: *mut c_char,
|
||||
buflen: size_t,
|
||||
errnop: *mut c_int,
|
||||
h_errnop: *mut c_int,
|
||||
errnop: *mut Errno,
|
||||
h_errnop: *mut HErrno,
|
||||
) -> nss_status {
|
||||
_nss_malcontent_gethostbyname3_r(
|
||||
name,
|
||||
|
@ -64,13 +86,13 @@ pub extern "C" fn _nss_malcontent_gethostbyname2_r(
|
|||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn _nss_malcontent_gethostbyname_r(
|
||||
pub unsafe extern "C" fn _nss_malcontent_gethostbyname_r(
|
||||
name: *const c_char,
|
||||
host: *mut hostent,
|
||||
buffer: *mut c_char,
|
||||
buflen: size_t,
|
||||
errnop: *mut c_int,
|
||||
h_errnop: *mut c_int,
|
||||
errnop: *mut Errno,
|
||||
h_errnop: *mut HErrno,
|
||||
) -> nss_status {
|
||||
_nss_malcontent_gethostbyname3_r(
|
||||
name,
|
||||
|
@ -88,30 +110,30 @@ pub extern "C" fn _nss_malcontent_gethostbyname_r(
|
|||
// ----------------- by addr -----------------
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn _nss_malcontent_gethostbyaddr2_r(
|
||||
pub unsafe extern "C" fn _nss_malcontent_gethostbyaddr2_r(
|
||||
_addr: *const c_void,
|
||||
_len: socklen_t,
|
||||
_af: c_int,
|
||||
_host: *mut hostent,
|
||||
_buffer: *mut c_char,
|
||||
_buflen: size_t,
|
||||
_errnop: *mut c_int,
|
||||
_h_errnop: *mut c_int,
|
||||
_errnop: *mut Errno,
|
||||
_h_errnop: *mut HErrno,
|
||||
_ttlp: *mut i32,
|
||||
) -> nss_status {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn _nss_malcontent_gethostbyaddr_r(
|
||||
pub unsafe extern "C" fn _nss_malcontent_gethostbyaddr_r(
|
||||
addr: *const c_void,
|
||||
len: socklen_t,
|
||||
af: c_int,
|
||||
host: *mut hostent,
|
||||
buffer: *mut c_char,
|
||||
buflen: size_t,
|
||||
errnop: *mut c_int,
|
||||
h_errnop: *mut c_int,
|
||||
errnop: *mut Errno,
|
||||
h_errnop: *mut HErrno,
|
||||
) -> nss_status {
|
||||
_nss_malcontent_gethostbyaddr2_r(
|
||||
addr,
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
use {
|
||||
anyhow::Result,
|
||||
nix::unistd::{getuid, Uid},
|
||||
std::net::{IpAddr, Ipv4Addr, Ipv6Addr},
|
||||
std::sync::LazyLock,
|
||||
};
|
||||
|
||||
type Restrictions<'a> = &'a [IpAddr];
|
||||
|
||||
pub trait PolicyChecker {
|
||||
fn restrictions<'a>(&'a self, user: Option<Uid>) -> Result<Option<Restrictions<'a>>>;
|
||||
}
|
||||
|
||||
static CLOUDFLARE_PARENTALCONTROL_ADDRS: LazyLock<Vec<IpAddr>> = LazyLock::new(|| {
|
||||
vec![
|
||||
IpAddr::V4(Ipv4Addr::new(1, 1, 1, 3)),
|
||||
IpAddr::V4(Ipv4Addr::new(1, 0, 0, 3)),
|
||||
IpAddr::V6(Ipv6Addr::new(2606, 4700, 4700, 0, 0, 0, 0, 1113)),
|
||||
IpAddr::V6(Ipv6Addr::new(2606, 4700, 4700, 0, 0, 0, 0, 1003)),
|
||||
]
|
||||
});
|
||||
|
||||
pub struct MalcontentPolicyChecker;
|
||||
|
||||
impl MalcontentPolicyChecker {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl PolicyChecker for MalcontentPolicyChecker {
|
||||
fn restrictions<'a>(&'a self, user: Option<Uid>) -> Result<Option<Restrictions<'a>>> {
|
||||
let uid = user.unwrap_or_else(|| getuid());
|
||||
|
||||
if uid.is_root() {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
// TODO: for now, hardcoded DNS records, later go
|
||||
// through D-Bus to malcontent and ask about user.
|
||||
|
||||
Ok(Some(CLOUDFLARE_PARENTALCONTROL_ADDRS.as_slice()))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
pub fn set_if_valid<T>(ptr: *mut T, val: T) {
|
||||
if !ptr.is_null() {
|
||||
unsafe { *ptr = val };
|
||||
}
|
||||
}
|
|
@ -6,15 +6,23 @@
|
|||
#![allow(non_snake_case)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||
|
||||
use {
|
||||
anyhow::{ensure, Result},
|
||||
anyhow::{anyhow, bail, ensure, Result},
|
||||
libc::{freeaddrinfo, gai_strerror, getaddrinfo},
|
||||
nix::sys::socket::{SockaddrLike as _, SockaddrStorage},
|
||||
std::env,
|
||||
std::ffi::CString,
|
||||
std::ffi::{CStr, CString},
|
||||
std::net::{IpAddr, Ipv4Addr, Ipv6Addr},
|
||||
std::path::PathBuf,
|
||||
std::process::Command,
|
||||
std::sync::Once,
|
||||
};
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||
pub type Eai = EaiRetcode;
|
||||
|
||||
static SETUP: Once = Once::new();
|
||||
|
||||
|
@ -31,7 +39,7 @@ pub fn setup() -> Result<()> {
|
|||
});
|
||||
|
||||
let db = CString::new("hosts").unwrap();
|
||||
let resolvers = CString::new("malcontent [UNAVAIL=return] dns").unwrap();
|
||||
let resolvers = CString::new("malcontent dns").unwrap();
|
||||
__nss_configure_lookup(db.as_ptr(), resolvers.as_ptr())
|
||||
};
|
||||
|
||||
|
@ -43,3 +51,58 @@ pub fn setup() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn system_resolve(host: &str) -> Result<IpAddr> {
|
||||
let process = Command::new("getent").arg("hosts").arg(host).output()?;
|
||||
ensure!(
|
||||
process.status.success(),
|
||||
"Failed to run getent to check host IP"
|
||||
);
|
||||
let output = String::from_utf8(process.stdout)?;
|
||||
let addr_string = output
|
||||
.as_str()
|
||||
.split(' ')
|
||||
.next()
|
||||
.ok_or(anyhow!("Unparseable output from getent"))?;
|
||||
Ok(IpAddr::from_str(&addr_string)?)
|
||||
}
|
||||
|
||||
pub fn convert_addrinfo(sa: &SockaddrStorage) -> Result<IpAddr> {
|
||||
if let Some(addr) = sa.as_sockaddr_in() {
|
||||
Ok(IpAddr::V4(Ipv4Addr::from(addr.ip())))
|
||||
} else if let Some(addr) = sa.as_sockaddr_in6() {
|
||||
Ok(IpAddr::V6(Ipv6Addr::from(addr.ip())))
|
||||
} else {
|
||||
bail!("addrinfo is not either an IPv4 or IPv6 address")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_system_and_us(hostname: &str) -> Result<(IpAddr, IpAddr)> {
|
||||
setup()?;
|
||||
let ip_from_system = system_resolve(hostname)?;
|
||||
|
||||
let c_hostname = CString::new(hostname).unwrap();
|
||||
unsafe {
|
||||
let mut addr = std::ptr::null_mut();
|
||||
let getaddrinfo_status = getaddrinfo(
|
||||
c_hostname.as_ptr(),
|
||||
std::ptr::null(),
|
||||
std::ptr::null(),
|
||||
&mut addr,
|
||||
);
|
||||
|
||||
let error = CStr::from_ptr(gai_strerror(getaddrinfo_status));
|
||||
assert_eq!(
|
||||
getaddrinfo_status,
|
||||
Eai::Success.0,
|
||||
"Should have gotten hostname for {}, instead got {}",
|
||||
hostname,
|
||||
error.to_str().unwrap()
|
||||
);
|
||||
let addr_storage = SockaddrStorage::from_raw((*addr).ai_addr, Some((*addr).ai_addrlen))
|
||||
.ok_or(anyhow!("Garbled addrinfo from getaddrinfo()"))?;
|
||||
let ip_from_us = convert_addrinfo(&addr_storage)?;
|
||||
freeaddrinfo(addr);
|
||||
Ok((ip_from_system, ip_from_us))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,32 +4,88 @@
|
|||
mod common;
|
||||
|
||||
use {
|
||||
anyhow::{bail, Result},
|
||||
crate::common::Eai,
|
||||
anyhow::Result,
|
||||
libc::{freeaddrinfo, gai_strerror, getaddrinfo},
|
||||
std::net::{IpAddr, Ipv4Addr},
|
||||
};
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "not yet implemented")]
|
||||
fn nss_module_is_loaded() {
|
||||
common::setup().unwrap();
|
||||
fn nss_module_is_loaded() -> Result<()> {
|
||||
common::setup()?;
|
||||
|
||||
let hostname = std::ffi::CString::new("gnome.org").unwrap();
|
||||
unsafe {
|
||||
let mut addr = std::ptr::null_mut();
|
||||
match getaddrinfo(
|
||||
let getaddrinfo_status = getaddrinfo(
|
||||
hostname.as_ptr(),
|
||||
std::ptr::null(),
|
||||
std::ptr::null(),
|
||||
&mut addr,
|
||||
) {
|
||||
0 => freeaddrinfo(addr),
|
||||
status => {
|
||||
let error = std::ffi::CStr::from_ptr(gai_strerror(status));
|
||||
panic!(
|
||||
"Unable to resolve hostname, getaddrinfo returned {}",
|
||||
);
|
||||
|
||||
let error = std::ffi::CStr::from_ptr(gai_strerror(getaddrinfo_status));
|
||||
assert_eq!(
|
||||
getaddrinfo_status,
|
||||
0,
|
||||
"Unable to resolve hostname, getaddrinfo failed: {}",
|
||||
error.to_str().unwrap()
|
||||
)
|
||||
}
|
||||
}
|
||||
);
|
||||
freeaddrinfo(addr);
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn application_dns_is_nxdomain() -> Result<()> {
|
||||
common::setup()?;
|
||||
|
||||
let hostname = std::ffi::CString::new("use-application-dns.net").unwrap();
|
||||
unsafe {
|
||||
let mut addr = std::ptr::null_mut();
|
||||
let getaddrinfo_status = getaddrinfo(
|
||||
hostname.as_ptr(),
|
||||
std::ptr::null(),
|
||||
std::ptr::null(),
|
||||
&mut addr,
|
||||
);
|
||||
|
||||
let error = std::ffi::CStr::from_ptr(gai_strerror(getaddrinfo_status));
|
||||
assert_eq!(
|
||||
getaddrinfo_status,
|
||||
Eai::NoName.0,
|
||||
"Should have gotten no hostname (NXDOMAIN), instead got {}",
|
||||
error.to_str().unwrap()
|
||||
);
|
||||
freeaddrinfo(addr);
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
fn wikipedia_is_unrestricted() -> Result<()> {
|
||||
let (system_addr, our_addr) = common::resolve_system_and_us("wikipedia.org")?;
|
||||
assert_eq!(system_addr, our_addr);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn adultsite_is_restricted() -> Result<()> {
|
||||
let (system_addr, our_addr) = common::resolve_system_and_us("pornhub.com")?;
|
||||
assert_ne!(system_addr, our_addr);
|
||||
assert_eq!(our_addr, IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn root_user_bypasses_restrictions() -> Result<()> {
|
||||
// TODO fake root
|
||||
|
||||
let (system_addr, our_addr) = common::resolve_system_and_us("pornhub.com")?;
|
||||
assert_eq!(system_addr, our_addr);
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include <nss.h>
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include <nss.h>
|
||||
#include <netdb.h>
|
||||
|
||||
// Work around enums in netdb.h defined as macros instead :-p
|
||||
|
||||
enum class HErrno {
|
||||
Success = 0,
|
||||
HostNotFound = HOST_NOT_FOUND,
|
||||
TryAgain = TRY_AGAIN,
|
||||
NoRecovery = NO_RECOVERY,
|
||||
NoData = NO_DATA,
|
||||
};
|
||||
|
||||
enum class EaiRetcode {
|
||||
Success = 0,
|
||||
BadFlags = EAI_BADFLAGS,
|
||||
NoName = EAI_NONAME,
|
||||
Fail = EAI_FAIL,
|
||||
Family = EAI_FAMILY,
|
||||
SockType = EAI_SOCKTYPE,
|
||||
Service = EAI_SERVICE,
|
||||
Memory = EAI_MEMORY,
|
||||
System = EAI_SYSTEM,
|
||||
Overflow = EAI_OVERFLOW,
|
||||
#ifdef __USE_GNU
|
||||
NoData = EAI_NODATA,
|
||||
AddrFamily = EAI_ADDRFAMILY,
|
||||
InProgress = EAI_INPROGRESS,
|
||||
Canceled = EAI_CANCELED,
|
||||
NotCanceled = EAI_NOTCANCELED,
|
||||
AllDone = EAI_ALLDONE,
|
||||
Interrupted = EAI_INTR,
|
||||
IdnEncode = EAI_IDN_ENCODE,
|
||||
#endif /* __USE_GNU */
|
||||
};
|
Loading…
Reference in New Issue