// SPDX-FileCopyrightText: 2022 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, std::sync::Once, }; pub use self::dbus::{mock_dbus, Restriction, Restrictions}; // Adapted from rusty_forkfork (which inherits it from rusty_fork) // to allow a custom pre-fork function #[macro_export] macro_rules! fork_test { (#![rusty_fork(timeout_ms = $timeout:expr)] $( $(#[$meta:meta])* fn $test_name:ident() $(-> $ret:ty)? $body:block )*) => { $( $(#[$meta])* fn $test_name() { // Eagerly convert everything to function pointers so that all // tests use the same instantiation of `fork`. fn body_fn() $(-> $ret)? $body let body: fn () $(-> $ret)? = body_fn; fn supervise_fn(child: &mut rusty_forkfork::ChildWrapper, _file: &mut ::std::fs::File) { rusty_forkfork::fork_test::supervise_child(child, $timeout) } let supervise: fn (&mut rusty_forkfork::ChildWrapper, &mut ::std::fs::File) = supervise_fn; rusty_forkfork::fork( rusty_forkfork::rusty_fork_test_name!($test_name), rusty_forkfork::rusty_fork_id!(), $crate::common::prefork_setup, supervise, body).expect("forking test failed"); } )* }; ($( $(#[$meta:meta])* fn $test_name:ident() $(-> $ret:ty)? $body:block )*) => { fork_test! { #![rusty_fork(timeout_ms = 0)] $($(#[$meta])* fn $test_name() $(-> $ret)? $body)* } }; } pub fn prefork_setup(_child: &mut Command) { let out_dir = PathBuf::from(env!("OUT_DIR")); PREFORK.call_once(|| { std::env::set_var("LD_LIBRARY_PATH", &out_dir); let library_path = test_cdylib::build_current_project(); let mut library_filename = library_path.file_name().unwrap().to_owned(); library_filename.push(".2"); // required for NSS modules let dest = out_dir.join(library_filename); std::fs::copy(library_path, &dest).unwrap(); }); } pub type Eai = EaiRetcode; static PREFORK: Once = Once::new(); static SETUP: Once = Once::new(); pub fn setup() -> Result<()> { SETUP.call_once(|| { env_logger::init(); let nss_config_status = unsafe { let db = CString::new("hosts").unwrap(); let resolvers = CString::new("malcontent dns").unwrap(); __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)?; 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> { assert!( SETUP.is_completed(), "Forgot to call common::setup() at beginning of test?" ); 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) }