// 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")); include!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/constants.rs")); use { anyhow::{anyhow, bail, ensure, Result}, futures_util::TryStreamExt, libc::{freeaddrinfo, gai_strerror, getaddrinfo}, nix::sys::socket::{SockaddrLike as _, SockaddrStorage}, nix::unistd::Uid, std::collections::HashMap, std::env, std::ffi::{CStr, CString}, std::net::{IpAddr, Ipv4Addr, Ipv6Addr}, std::path::PathBuf, std::process::Command, std::str::FromStr, std::sync::Once, tokio::task, tokio::task::JoinHandle, }; pub type Eai = EaiRetcode; static SETUP: Once = Once::new(); pub fn setup() -> Result<()> { let out_dir = PathBuf::from(env!("OUT_DIR")); let nss_config_status = unsafe { SETUP.call_once(|| { 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(); }); let db = CString::new("hosts").unwrap(); let resolvers = CString::new("malcontent dns").unwrap(); __nss_configure_lookup(db.as_ptr(), resolvers.as_ptr()) }; ensure!( nss_config_status == 0, "Unable to configure NSS to load module: __nss_configure_lookup() returned {}", nss_config_status ); Ok(()) } pub fn system_resolve(host: &str) -> Result { 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 { 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)) } } pub type Restrictions = Vec; pub fn mock_dbus(responses: HashMap>) -> JoinHandle> { async fn dbus_loop(mut responses: HashMap>) -> Result<()> { use zbus::MessageType::*; let mut responses_size: usize = responses.values().map(|v| v.len()).sum(); for r in responses.values_mut() { r.reverse(); // we pop responses from the back, so... } let connection = zbus::ConnectionBuilder::session()?.build().await?; let mut stream = zbus::MessageStream::from(&connection); while responses_size > 0 { let msg = stream .try_next() .await? .ok_or(anyhow!("Unparseable DBus message"))?; if msg.header()?.message_type()? == MethodCall && msg .interface() .ok_or(anyhow!("Invoked method has no interface"))? == DBUS_INTERFACE && msg .member() .ok_or(anyhow!("Invoked method has no member"))? == DBUS_GET_RESTRICTIONS_METHOD { let user_id: u32 = msg.body()?; match responses.get_mut(&Uid::from(user_id)) { Some(answers) => match answers.pop() { Some(a) => { responses_size = responses_size - 1; let ips: Vec = a.into_iter().map(|ip| ip.to_string()).collect(); connection.reply(&msg, &ips).await } None => { connection .reply_error( &msg, "MockExhausted", &format!("DBus mock is saturated for user with id {}", user_id), ) .await } }, None => { connection .reply_error( &msg, "MockExhausted", &format!( "No mocked invocations available for user with id {}", user_id ), ) .await } }?; } } Ok(()) } task::spawn(async { dbus_loop(responses).await }) }