diff --git a/assets/config.toml b/assets/config.toml new file mode 100644 index 0000000..29bdbe8 --- /dev/null +++ b/assets/config.toml @@ -0,0 +1,7 @@ +# example file to show/test configuration + +[unix] +groups = [ "wheel", 0 ] + +# [server] +# listen = "127.0.0.1:8080" diff --git a/src/conf.rs b/src/conf.rs index 825f2db..30a31f3 100644 --- a/src/conf.rs +++ b/src/conf.rs @@ -1,40 +1,40 @@ +//! Configuration file structure. (TODO, docs) + use std::{ fmt::Debug, fs::File, io::{self, Read, Seek}, + net::{Ipv4Addr, SocketAddr, SocketAddrV4}, }; -use libc::gid_t; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize)] -#[serde(untagged)] -pub enum Either { - Left(T), - Right(U), -} - -impl Debug for Either { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Left(l) => l.fmt(f), - Self::Right(r) => r.fmt(f), - } - } -} - -pub type Group = Either; +use crate::serdes::Group; #[derive(Debug, Default, Serialize, Deserialize)] #[serde(default)] pub struct Unix { - groups: Vec, + pub groups: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(default)] +pub struct Server { + pub listen: SocketAddr, +} +impl Default for Server { + fn default() -> Self { + Self { + listen: SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 8080)), + } + } } #[derive(Debug, Default, Serialize, Deserialize)] #[serde(default)] pub struct Config { - unix: Unix, + pub unix: Unix, + pub server: Server, } #[derive(thiserror::Error, Debug)] diff --git a/src/main.rs b/src/main.rs index 1662ef0..a846e2e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ pub mod args; pub mod auth; pub mod conf; pub mod ext; +pub mod serdes; #[tokio::main] async fn main() -> anyhow::Result<()> { diff --git a/src/serdes/inner_helpers.rs b/src/serdes/inner_helpers.rs new file mode 100644 index 0000000..deadf49 --- /dev/null +++ b/src/serdes/inner_helpers.rs @@ -0,0 +1,103 @@ +use std::ffi::CString; + +use libc::gid_t; +use serde::{Deserialize, Deserializer, Serialize, de::Error}; + +/// Private module until its worth it to promote from size to a crate module. +mod __libc_wrappers { + use std::{ffi::CStr, mem::MaybeUninit}; + + use libc::c_char; + + #[derive(Debug, thiserror::Error)] + pub enum GetgrnamError { + #[error("no matching group record found")] + NoMatchingRecord, + /// ace variant for unexpected errors, error handling is shallow here + #[error("unexpected getgrnam_r error")] + Unexpected, + } + + pub fn get_group_by_name(cname: &CStr) -> Result { + use GetgrnamError as E; + + let name = cname.as_ptr(); + let mut grp = MaybeUninit::uninit(); + let mut buf = [c_char::from(0); 1024]; + let mut result: MaybeUninit<*mut libc::group> = MaybeUninit::uninit(); + let ret = unsafe { + libc::getgrnam_r( + name, + grp.as_mut_ptr(), + buf.as_mut_ptr(), + buf.len(), + result.as_mut_ptr(), + ) + }; + + // SAFETY: Pointers inside `grp` point may point to the buffer `buf` so they cannot be + // moved outside of this function. + + // > On success, getgrnam_r() [..] return zero, and set `*result` to `grp`. + // + // > If no matching group record was found, these functions return 0 and store NULL in + // `*result`. + // + // > In case of error, an error number is returned, and NULL stored in `*result`. + // + // SAFETY: Either way, `result` is initialized to a nullable pointer. + let result_ptr = unsafe { result.assume_init() }; + if ret == 0 && result_ptr == grp.as_mut_ptr() { + let grp_gid = unsafe { grp.assume_init() }.gr_gid; + Ok(grp_gid) + } else if ret == 0 && result_ptr.is_null() { + Err(E::NoMatchingRecord) + } else { + // ret should be != 0 and result NULL, but any other case is also unexpected and I'm + // not doing much FFI error handling rn + Err(E::Unexpected) + } + } +} + +#[derive(Serialize, Deserialize)] +#[serde(untagged)] +enum EitherTyp { + Gid(gid_t), + Groupname(String), +} + +/// Deserialize either a gid number or a groupname into a [`gid_t`] +#[allow(clippy::missing_errors_doc)] +pub fn deserialize_group<'de, D>(d: D) -> Result +where + D: Deserializer<'de>, +{ + let either = EitherTyp::deserialize(d)?; + + match either { + EitherTyp::Gid(gid) => Ok(gid), + EitherTyp::Groupname(groupname) => { + use __libc_wrappers::GetgrnamError as E; + use serde::de::Unexpected; + + let cname = CString::new(groupname).map_err(|nul_err| { + D::Error::invalid_value( + Unexpected::Bytes(&nul_err.into_vec()), + &"a string without null bytes", + ) + })?; + + let gid = __libc_wrappers::get_group_by_name(&cname).map_err(|err| match err { + E::NoMatchingRecord => D::Error::invalid_value( + Unexpected::Other("non-existent group"), + &"existent group", + ), + E::Unexpected => D::Error::custom("caught an unexpected getgrnam error"), + })?; + + Ok(gid) + } + } +} + diff --git a/src/serdes/mod.rs b/src/serdes/mod.rs new file mode 100644 index 0000000..8f0ecc1 --- /dev/null +++ b/src/serdes/mod.rs @@ -0,0 +1,16 @@ +use std::fmt::Debug; + +use libc::gid_t; +use serde::{Deserialize, Serialize}; + +pub mod inner_helpers; + +#[repr(transparent)] +#[derive(Serialize, Deserialize)] +pub struct Group(#[serde(deserialize_with = "inner_helpers::deserialize_group")] pub gid_t); + +impl Debug for Group { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Debug::fmt(&self.0, f) + } +}