use cap_std::fs::{Dir, OpenOptions}; use pam::constants::{PamFlag, PamResultCode}; use pam::module::{PamHandle, PamHooks}; use std::ffi::CStr; use std::fmt::{Display, Write as _}; use std::io::{ErrorKind, Write}; use std::path::Path; use std::process; const CG_MOUNT: &str = "/sys/fs/cgroup"; struct PAMUserCG; pam::pam_hooks!(PAMUserCG); fn create_and_open_dir + Copy>( d: &Dir, path: P, ) -> std::io::Result { if let Err(e) = d.create_dir(path) { if e.kind() != ErrorKind::AlreadyExists { return Err(e); } } d.open_dir(path) } struct SessionError; impl From for SessionError { fn from(_: std::io::Error) -> Self { SessionError } } trait MaxDisplayLength: Display { const MAX_DISPLAY_LENGTH: usize; } impl MaxDisplayLength for u32 { const MAX_DISPLAY_LENGTH: usize = u32::MAX.ilog10() as usize + 1; } fn safe_to_string(v: T) -> Result { let mut buf = String::new(); buf.try_reserve_exact(T::MAX_DISPLAY_LENGTH) .or(Err(SessionError))?; write!(buf, "{v}").unwrap(); Ok(buf) } fn open_session(h: &mut PamHandle) -> Result<(), SessionError> { let user = h.get_user(None).or(Err(SessionError))?; let user = users::get_user_by_name(&user).ok_or(SessionError)?; let uid = safe_to_string(user.uid())?; let d = Dir::open_ambient_dir(CG_MOUNT, cap_std::ambient_authority())?; let d = create_and_open_dir(&d, "user")?; let d = create_and_open_dir(&d, &uid)?; let d = create_and_open_dir(&d, "leaf")?; let pid = safe_to_string(process::id())?; let mut options = OpenOptions::new(); options.write(true); let mut procs = d.open_with("cgroup.procs", &options)?; procs.write_all(pid.as_bytes())?; Ok(()) } impl PamHooks for PAMUserCG { fn sm_open_session( h: &mut PamHandle, _args: Vec<&CStr>, _flags: PamFlag, ) -> PamResultCode { match open_session(h) { Ok(()) => PamResultCode::PAM_SUCCESS, Err(SessionError) => PamResultCode::PAM_SESSION_ERR, } } }