kanidm_lib_file_permissions/
unix.rsuse std::fs::Metadata;
#[cfg(target_os = "freebsd")]
use std::os::freebsd::fs::MetadataExt;
#[cfg(target_os = "linux")]
use std::os::linux::fs::MetadataExt;
#[cfg(target_os = "macos")]
use std::os::macos::fs::MetadataExt;
#[cfg(target_os = "illumos")]
use std::os::illumos::fs::MetadataExt;
use kanidm_utils_users::{get_current_gid, get_current_uid};
use std::fmt;
use std::path::{Path, PathBuf};
pub fn readonly(meta: &Metadata) -> bool {
let cuid = get_current_uid();
let cgid = get_current_gid();
let f_gid = meta.st_gid();
let f_uid = meta.st_uid();
let f_mode = meta.st_mode();
!(
cuid == f_uid ||
(cgid == f_gid && (f_mode & 0o0020) != 0) ||
((f_mode & 0o0002) != 0)
)
}
#[derive(Debug)]
pub enum PathStatus {
Dir {
f_gid: u32,
f_uid: u32,
f_mode: u32,
access: bool,
},
Link {
f_gid: u32,
f_uid: u32,
f_mode: u32,
access: bool,
},
File {
f_gid: u32,
f_uid: u32,
f_mode: u32,
access: bool,
},
Error(std::io::Error),
}
#[derive(Debug)]
pub struct Diagnosis {
cuid: u32,
cgid: u32,
path: PathBuf,
abs_path: Result<PathBuf, std::io::Error>,
ancestors: Vec<(PathBuf, PathStatus)>,
}
impl fmt::Display for Diagnosis {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "-- diagnosis for path: {}", self.path.to_string_lossy())?;
let indent = match &self.abs_path {
Ok(abs) => {
let abs_str = abs.to_string_lossy();
writeln!(f, "canonicalised to: {}", abs_str)?;
abs_str.len() + 1
}
Err(_) => {
writeln!(f, "unable to canonicalise path")?;
self.path.to_string_lossy().len() + 1
}
};
writeln!(f, "running as: {}:{}", self.cuid, self.cgid)?;
writeln!(f, "path permissions\n")?;
for (anc, status) in &self.ancestors {
match &status {
PathStatus::Dir {
f_gid,
f_uid,
f_mode,
access,
} => {
writeln!(
f,
" {:indent$}: DIR access: {} owner: {} group: {} mode: {}",
anc.to_string_lossy(),
access,
f_uid,
f_gid,
mode_to_string(*f_mode)
)?;
}
PathStatus::Link {
f_gid,
f_uid,
f_mode,
access,
} => {
writeln!(
f,
" {:indent$}: LINK access: {} owner: {} group: {} mode: {}",
anc.to_string_lossy(),
access,
f_uid,
f_gid,
mode_to_string(*f_mode)
)?;
}
PathStatus::File {
f_gid,
f_uid,
f_mode,
access,
} => {
writeln!(
f,
" {:indent$}: FILE access: {} owner: {} group: {} mode: {}",
anc.to_string_lossy(),
access,
f_uid,
f_gid,
mode_to_string(*f_mode)
)?;
}
PathStatus::Error(err) => {
writeln!(f, " {:indent$}: ERROR: {:?}", anc.to_string_lossy(), err)?;
}
}
}
writeln!(
f,
"\n note that accessibility does not account for ACL's or MAC"
)?;
writeln!(f, "-- end diagnosis")
}
}
pub fn diagnose_path(path: &Path) -> Diagnosis {
let cuid = get_current_uid();
let cgid = get_current_gid();
let path: PathBuf = path.into();
let abs_path = path.canonicalize();
let mut all_ancestors: Vec<_> = match &abs_path {
Ok(ap) => ap.ancestors().collect(),
Err(_) => path.ancestors().collect(),
};
let mut ancestors = Vec::with_capacity(all_ancestors.len());
while let Some(anc) = all_ancestors.pop() {
let status = match anc.metadata() {
Ok(meta) => {
let f_gid = meta.st_gid();
let f_uid = meta.st_uid();
let f_mode = meta.st_mode();
if meta.is_dir() {
let access = x_accessible(cuid, cgid, f_uid, f_gid, f_mode);
PathStatus::Dir {
f_gid,
f_uid,
f_mode,
access,
}
} else if meta.is_symlink() {
let access = x_accessible(cuid, cgid, f_uid, f_gid, f_mode);
PathStatus::Link {
f_gid,
f_uid,
f_mode,
access,
}
} else {
let access = accessible(cuid, cgid, f_uid, f_gid, f_mode);
PathStatus::File {
f_gid,
f_uid,
f_mode,
access,
}
}
}
Err(e) => PathStatus::Error(e),
};
ancestors.push((anc.into(), status))
}
Diagnosis {
cuid,
cgid,
path,
abs_path,
ancestors,
}
}
fn x_accessible(cuid: u32, cgid: u32, f_uid: u32, f_gid: u32, f_mode: u32) -> bool {
(cuid == f_uid && f_mode & 0o500 == 0o500)
|| (cgid == f_gid && f_mode & 0o050 == 0o050)
|| f_mode & 0o005 == 0o005
}
fn accessible(cuid: u32, cgid: u32, f_uid: u32, f_gid: u32, f_mode: u32) -> bool {
(cuid == f_uid && f_mode & 0o400 == 0o400)
|| (cgid == f_gid && f_mode & 0o040 == 0o040)
|| f_mode & 0o004 == 0o004
}
fn mode_to_string(mode: u32) -> String {
let mut mode_str = String::with_capacity(9);
if mode & 0o400 != 0 {
mode_str.push('r')
} else {
mode_str.push('-')
}
if mode & 0o200 != 0 {
mode_str.push('w')
} else {
mode_str.push('-')
}
if mode & 0o100 != 0 {
mode_str.push('x')
} else {
mode_str.push('-')
}
if mode & 0o040 != 0 {
mode_str.push('r')
} else {
mode_str.push('-')
}
if mode & 0o020 != 0 {
mode_str.push('w')
} else {
mode_str.push('-')
}
if mode & 0o010 != 0 {
mode_str.push('x')
} else {
mode_str.push('-')
}
if mode & 0o004 != 0 {
mode_str.push('r')
} else {
mode_str.push('-')
}
if mode & 0o002 != 0 {
mode_str.push('w')
} else {
mode_str.push('-')
}
if mode & 0o001 != 0 {
mode_str.push('x')
} else {
mode_str.push('-')
}
mode_str
}
#[test]
fn test_readonly() {
let meta = std::fs::metadata("Cargo.toml").expect("Can't find Cargo.toml");
println!("meta={:?} -> readonly={:?}", meta, readonly(&meta));
assert!(!readonly(&meta));
}