use crate::checks::caps::{CAP_CHOWN, CAP_FOWNER, CAP_FSETID, CAP_SYS_ADMIN};
use crate::state::acl::{AclEntry, AclPerms, AclTag, PosixAcl};
use crate::state::path::PathComponent;
use crate::state::Probe;
use crate::state::SystemState;
use super::super::{Fix, FixAction};
pub fn apply_fix(fix: &Fix, state: &mut SystemState) {
match &fix.action {
FixAction::Chmod { path, mode_change } => apply_chmod(state, path, mode_change),
FixAction::SetAcl { path, entry } => apply_setacl(state, path, entry),
FixAction::Chattr { path, flags } => apply_chattr(state, path, flags),
FixAction::Remount {
mountpoint,
options,
} => apply_remount(state, mountpoint, options),
FixAction::Chown { path, owner, group } => apply_chown(state, path, *owner, *group),
FixAction::GrantCap { capability, .. } => apply_grant_cap(state, capability),
}
}
fn apply_chmod(state: &mut SystemState, path: &std::path::Path, mode_change: &str) {
let Some(comp) = find_component_mut(state, path) else {
return;
};
let Probe::Known(ref mut stat) = comp.stat else {
return;
};
let Some((class, bits)) = parse_mode_change(mode_change) else {
return;
};
apply_mode_bits(&mut stat.mode, class, bits);
}
fn apply_setacl(state: &mut SystemState, path: &std::path::Path, entry_str: &str) {
let Some(comp) = find_component_mut(state, path) else {
return;
};
let Some(acl_entry) = parse_acl_entry(entry_str) else {
return;
};
match &mut comp.acl {
Probe::Known(acl) => merge_acl_entry(acl, acl_entry),
Probe::Unknown | Probe::Inaccessible => {
comp.acl = Probe::Known(PosixAcl(vec![acl_entry]));
}
}
}
fn apply_chattr(state: &mut SystemState, path: &std::path::Path, flags: &str) {
let Some(comp) = find_component_mut(state, path) else {
return;
};
let Probe::Known(ref mut fs_flags) = comp.flags else {
return;
};
match flags {
"-i" => fs_flags.immutable = false,
"-a" => fs_flags.append_only = false,
_ => {}
}
}
fn apply_remount(state: &mut SystemState, mountpoint: &std::path::Path, options: &str) {
let entry = state
.mounts
.0
.iter_mut()
.find(|e| e.mountpoint == mountpoint);
let Some(entry) = entry else { return };
match options {
"rw" => entry.options.read_only = false,
"exec" => entry.options.noexec = false,
_ => {}
}
}
fn apply_grant_cap(state: &mut SystemState, capability: &str) {
let bit: u64 = match capability {
"cap_fowner" => CAP_FOWNER,
"cap_chown" => CAP_CHOWN,
"cap_sys_admin" => CAP_SYS_ADMIN,
"cap_fsetid" => CAP_FSETID,
_ => return,
};
state.subject.capabilities = match state.subject.capabilities {
Probe::Known(existing) => Probe::Known(existing | bit),
Probe::Unknown | Probe::Inaccessible => Probe::Known(bit),
};
}
fn apply_chown(
state: &mut SystemState,
path: &std::path::Path,
owner: Option<u32>,
group: Option<u32>,
) {
let Some(comp) = find_component_mut(state, path) else {
return;
};
let Probe::Known(ref mut stat) = comp.stat else {
return;
};
if let Some(uid) = owner {
stat.uid = uid;
stat.mode &= !0o4000; }
if let Some(gid) = group {
stat.gid = gid;
if stat.mode & 0o010 != 0 {
stat.mode &= !0o2000; }
}
}
fn find_component_mut<'a>(
state: &'a mut SystemState,
path: &std::path::Path,
) -> Option<&'a mut PathComponent> {
state.walk.iter_mut().find(|c| c.path == path)
}
fn parse_mode_change(s: &str) -> Option<(char, &str)> {
let mut chars = s.chars();
let class = chars.next()?;
let plus = chars.next()?;
if plus != '+' {
return None;
}
let rest = &s[2..];
if rest.is_empty() {
return None;
}
Some((class, rest))
}
fn apply_mode_bits(mode: &mut u32, class: char, bits: &str) {
let shift = match class {
'u' => 6,
'g' => 3,
'o' => 0,
_ => return,
};
for ch in bits.chars() {
let bit = match ch {
'r' => 4,
'w' => 2,
'x' => 1,
_ => continue,
};
*mode |= bit << shift;
}
}
fn parse_acl_entry(s: &str) -> Option<AclEntry> {
let parts: Vec<&str> = s.splitn(3, ':').collect();
if parts.len() != 3 {
return None;
}
let tag = match parts[0] {
"u" => AclTag::User,
"g" => AclTag::Group,
_ => return None,
};
let qualifier = parts[1].parse::<u32>().ok();
let perms = parse_acl_perms(parts[2]);
Some(AclEntry {
tag,
qualifier,
perms,
})
}
fn parse_acl_perms(s: &str) -> AclPerms {
AclPerms {
read: s.contains('r'),
write: s.contains('w'),
execute: s.contains('x'),
}
}
fn merge_acl_entry(acl: &mut PosixAcl, new_entry: AclEntry) {
let existing = acl
.0
.iter_mut()
.find(|e| e.tag == new_entry.tag && e.qualifier == new_entry.qualifier);
if let Some(entry) = existing {
entry.perms = new_entry.perms;
} else {
acl.0.push(new_entry);
}
}