feat(cfg): turn groupnames into gids at parse time

This commit is contained in:
2026-03-10 14:36:44 +01:00
parent 054c3ff1f8
commit fb62111c7f
5 changed files with 147 additions and 20 deletions

7
assets/config.toml Normal file
View File

@@ -0,0 +1,7 @@
# example file to show/test configuration
[unix]
groups = [ "wheel", 0 ]
# [server]
# listen = "127.0.0.1:8080"

View File

@@ -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<T, U> {
Left(T),
Right(U),
}
impl<T: Debug, U: Debug> Debug for Either<T, U> {
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<String, gid_t>;
use crate::serdes::Group;
#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct Unix {
groups: Vec<Group>,
pub groups: Vec<Group>,
}
#[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)]

View File

@@ -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<()> {

103
src/serdes/inner_helpers.rs Normal file
View File

@@ -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<libc::gid_t, GetgrnamError> {
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<gid_t, D::Error>
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)
}
}
}

16
src/serdes/mod.rs Normal file
View File

@@ -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)
}
}