// SPDX-FileCopyrightText: Matteo Settenvini // SPDX-License-Identifier: GPL-3.0-or-later #![allow(non_upper_case_globals)] #![allow(non_camel_case_types)] #![allow(non_snake_case)] #![allow(dead_code)] include!(concat!(env!("OUT_DIR"), "/bindings.rs")); mod dbus; use { anyhow::{anyhow, bail, ensure, Result}, libc::{freeaddrinfo, gai_strerror, getaddrinfo}, nix::sys::socket::{SockaddrLike as _, SockaddrStorage}, std::env, std::ffi::{CStr, CString}, std::net::{IpAddr, Ipv4Addr, Ipv6Addr}, std::path::PathBuf, std::process::Command, std::str::FromStr, }; pub use self::dbus::{DBusMockServerGuard, Restriction, Restrictions}; pub type Eai = EaiRetcode; #[static_init::dynamic] static GLOBAL_SETUP: () = { let cdylib_path = test_cdylib::build_current_project(); let file_name = cdylib_path.file_name().unwrap(); let mut versioned_file_name = file_name.to_owned(); versioned_file_name.push(".2"); // required for NSS 3 modules let dest = PathBuf::from(std::env::current_exe().unwrap()).with_file_name(versioned_file_name); let _ = std::fs::remove_file(&dest); std::fs::hard_link(&cdylib_path, &dest).expect(&format!( "Cannot hard link {} to {}", cdylib_path.display(), dest.display() )); }; /// Note that this should be called once per process; this implies that /// each test calling this function is in a separate process too. pub fn setup() -> Result<()> { let _ = env_logger::builder() .is_test(true) .filter_level(log::LevelFilter::max()) .try_init(); std::env::set_var("NSS_MALCONTENT_LOG", "trace"); // setup NSS to load our cdylib, now versioned let db = CString::new("hosts").unwrap(); let resolvers = CString::new("malcontent dns").unwrap(); let nss_config_status = unsafe { __nss_configure_lookup(db.as_ptr(), resolvers.as_ptr()) }; if nss_config_status != 0 { panic!( "Unable to configure NSS to load module: __nss_configure_lookup() returned {}", nss_config_status ); } Ok(()) } pub fn resolve_with_system(family: libc::c_int, host: &str) -> Result { let process = Command::new("getent") .arg(match family { libc::AF_INET => "ahostsv4", libc::AF_INET6 => "ahostsv6", _ => panic!("INET family must be either IPv4 or IPv6"), }) .arg(host) .output()?; ensure!( process.status.success(), "Failed to run getent to check host IP" ); let output = String::from_utf8(process.stdout)?; log::trace!("system getent reports: {}", &output); let addr_string = output .as_str() .split(' ') .next() .ok_or(anyhow!("Unparseable output from getent"))?; Ok(IpAddr::from_str(&addr_string)?) } pub fn resolve_with_module(family: libc::c_int, hostname: &str) -> Result> { let c_hostname = CString::new(hostname).unwrap(); unsafe { let mut addr = std::ptr::null_mut(); let hints = libc::addrinfo { ai_family: family, ai_flags: 0, ai_socktype: 0, ai_protocol: 0, ai_addrlen: 0, ai_addr: std::ptr::null_mut(), ai_canonname: std::ptr::null_mut(), ai_next: std::ptr::null_mut(), }; let getaddrinfo_status = getaddrinfo(c_hostname.as_ptr(), std::ptr::null(), &hints, &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 ips = convert_addrinfo(addr)?; freeaddrinfo(addr); Ok(ips) } } unsafe fn convert_addrinfo(addrs: *const libc::addrinfo) -> Result> { let mut ips = vec![]; let mut addr = addrs; while addr != std::ptr::null() { let addr_storage = SockaddrStorage::from_raw((*addr).ai_addr, Some((*addr).ai_addrlen)) .ok_or(anyhow!("Garbled addrinfo from getaddrinfo()"))?; if let Some(addr) = addr_storage.as_sockaddr_in() { ips.push(IpAddr::V4(Ipv4Addr::from(addr.ip()))); } else if let Some(addr) = addr_storage.as_sockaddr_in6() { ips.push(IpAddr::V6(Ipv6Addr::from(addr.ip()))); } else { bail!("addrinfo is not either an IPv4 or IPv6 address") } addr = (*addr).ai_next; } ips.sort(); ips.dedup(); Ok(ips) }