// SPDX-FileCopyrightText: 2022 Matteo Settenvini // SPDX-License-Identifier: GPL-3.0-or-later use { crate::nss_bindings::gaih_addrtuple, anyhow::{bail, Result}, libc::{c_char, hostent, AF_INET, AF_INET6}, nix::errno::Errno, once_cell::sync::Lazy, std::ffi::CString, std::mem::{align_of, discriminant, size_of}, trust_dns_proto::rr::resource::Record, trust_dns_proto::rr::RData, trust_dns_resolver::lookup::Lookup, }; static RUNTIME: Lazy> = Lazy::new(|| { // The runtime should remain single-threaded, some // programs depend on it (e.g. programs calling unshare()) let rt = tokio::runtime::Builder::new_current_thread() .enable_time() .enable_io() .build()?; Ok(rt) }); // TODO: error handling codes chosen a bit sloppily pub unsafe fn records_to_gaih_addr( lookup: Lookup, mut buf: &mut [u8], ) -> std::io::Result<*mut gaih_addrtuple> { const GAIH_ADDRTUPLE_SZ: usize = size_of::(); let mut ret = std::ptr::null_mut(); let mut prev_link: *mut *mut gaih_addrtuple = std::ptr::null_mut(); let mut name_dest; for record in lookup.record_iter() { // First add the name to the buffer (buf, name_dest) = write_record_name_to_buf(record, buf)?; // Then add the tuple with the address let offset = buf.as_ptr().align_offset(align_of::()); let l = GAIH_ADDRTUPLE_SZ; if buf.len() < offset + l { return Err(Errno::ERANGE.into()); } let tuple = &mut *(buf.as_mut_ptr().add(offset) as *mut gaih_addrtuple); tuple.next = std::ptr::null_mut(); tuple.name = name_dest; tuple.scopeid = 0; // how to set tuple.scopeid ???? set_if_valid(prev_link, &mut *tuple); // link from previous tuple to this tuple prev_link = &mut (*tuple).next; match record.data() { Some(RData::A(addr)) => { tuple.family = AF_INET; tuple.addr[0] = std::mem::transmute_copy(&addr.octets()); } Some(RData::AAAA(addr)) => { tuple.family = AF_INET6; tuple.addr = std::mem::transmute_copy(&addr.octets()); } Some(_) => return Err(Errno::EBADMSG.into()), None => return Err(Errno::ENODATA.into()), } if ret == std::ptr::null_mut() { ret = tuple; } buf = &mut buf[(offset + l)..]; } Ok(ret) } pub enum DnsResult { NxDomain, Found, } // TODO: refactor to be less ugly pub unsafe fn records_to_hostent( lookup: Lookup, host: &mut hostent, mut buf: &mut [u8], ) -> std::io::Result { // In C struct hostent: // // - for the type of queries we perform, we can assume h_aliases // is an empty array. // - hostent is limited to just one address type for all addresses // in the list. We pick the type of first result, and only // append addresses of the same type. let first_record = match lookup.record_iter().peekable().peek() { Some(record) => *record, None => return Ok(DnsResult::NxDomain), }; let first_rdata = match first_record.data() { Some(rdata) => rdata, None => return Err(Errno::ENODATA.into()), }; // First put the name in the buffer let name_dest; (buf, name_dest) = write_record_name_to_buf(first_record, buf)?; // Then the empty aliases array (just a null pointer) let offset = buf.as_ptr().align_offset(align_of::<*mut c_char>()); let l = size_of::<*mut c_char>(); if buf.len() < offset + l { return Err(Errno::ERANGE.into()); } let aliases_ptr = buf[offset..].as_mut_ptr().cast::<*mut c_char>(); aliases_ptr.write(std::ptr::null_mut()); buf = &mut buf[(offset + l)..]; // Then the address list let offset = buf.as_ptr().align_offset(align_of::<*mut c_char>()); buf = &mut buf[offset..]; let records = lookup .record_iter() .flat_map(Record::data) .filter(|r| discriminant(*r) == discriminant(first_rdata)); let mut addresses = vec![]; for record in records { let offset = buf.as_ptr().align_offset(align_of::<*mut c_char>()); buf = &mut buf[offset..]; addresses.push(buf.as_ptr()); let l = match record { RData::A(addr) => { let octets = addr.octets(); let l = octets.len(); if buf.len() < l { return Err(Errno::ERANGE.into()); } buf[..l].copy_from_slice(&octets); l } RData::AAAA(addr) => { let octets = addr.octets(); let l = octets.len(); if buf.len() < l { return Err(Errno::ERANGE.into()); } buf[..l].copy_from_slice(&octets); l } _ => unreachable!(), }; buf = &mut buf[l..]; } addresses.push(std::ptr::null_mut()); let offset = buf.as_ptr().align_offset(align_of::<*mut *mut c_char>()); let l = size_of::<*mut c_char>() * addresses.len(); if buf.len() < offset + l { return Err(Errno::ERANGE.into()); } let addr_list = buf[offset..].as_mut_ptr().cast::<*mut c_char>(); std::ptr::copy_nonoverlapping(addresses.as_ptr() as *const *mut c_char, addr_list, l); // Finally, populate the hostent structure host.h_name = name_dest; host.h_aliases = aliases_ptr; host.h_addr_list = addr_list; match first_rdata { RData::A(_) => { host.h_addrtype = AF_INET; host.h_length = 4; } RData::AAAA(_) => { host.h_addrtype = AF_INET6; host.h_length = 16; } _ => return Err(Errno::EBADMSG.into()), } Ok(DnsResult::Found) } unsafe fn write_record_name_to_buf<'a>( record: &Record, buf: &'a mut [u8], ) -> std::io::Result<(&'a mut [u8], *mut c_char)> { use std::io::{Error, ErrorKind}; let name = CString::new(record.name().to_utf8()) .map_err(|e| Error::new(ErrorKind::InvalidData, e.to_string()))?; let offset = buf.as_ptr().align_offset(align_of::()); let name_src = name.as_bytes_with_nul(); let l = name_src.len(); if buf.len() < offset + l { return Err(Errno::ERANGE.into()); } let name_dest = buf.as_mut_ptr().add(offset); std::ptr::copy_nonoverlapping(name_src.as_ptr(), name_dest, l); Ok((&mut buf[(offset + l)..], name_dest as *mut c_char)) } pub fn set_if_valid(ptr: *mut T, val: T) { if !ptr.is_null() { unsafe { *ptr = val }; } } pub fn block_on(f: F) -> Result where F: std::future::Future, { // TODO: use std::panic::catch_unwind() to be resilient // to OOM problems use std::ops::Deref; match RUNTIME.deref() { Ok(rt) => Ok(rt.block_on(async { f.await })), Err(e) => bail!("Unable to start tokio runtime: {}", e), } }