use super::nfs::fattr3;
use super::rpc::auth_unix;
pub const S_IRUSR: u32 = 0o400;
pub const S_IWUSR: u32 = 0o200;
pub const S_IXUSR: u32 = 0o100;
pub const S_IRGRP: u32 = 0o040;
pub const S_IWGRP: u32 = 0o020;
pub const S_IXGRP: u32 = 0o010;
pub const S_IROTH: u32 = 0o004;
pub const S_IWOTH: u32 = 0o002;
pub const S_IXOTH: u32 = 0o001;
pub const S_ISVTX: u32 = 0o1000;
pub const ACCESS3_READ: u32 = 0x0001;
pub const ACCESS3_LOOKUP: u32 = 0x0002;
pub const ACCESS3_MODIFY: u32 = 0x0004;
pub const ACCESS3_EXTEND: u32 = 0x0008;
pub const ACCESS3_DELETE: u32 = 0x0010;
pub const ACCESS3_EXECUTE: u32 = 0x0020;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Permission {
Read,
Write,
Execute,
}
pub fn check_permission(auth: &auth_unix, attr: &fattr3, perm: Permission) -> bool {
let mode = attr.mode;
let file_uid = attr.uid;
let file_gid = attr.gid;
if auth.uid == 0 {
return true;
}
let (read_bit, write_bit, exec_bit) = if auth.uid == file_uid {
(S_IRUSR, S_IWUSR, S_IXUSR)
} else if is_in_group(auth, file_gid) {
(S_IRGRP, S_IWGRP, S_IXGRP)
} else {
(S_IROTH, S_IWOTH, S_IXOTH)
};
match perm {
Permission::Read => (mode & read_bit) != 0,
Permission::Write => (mode & write_bit) != 0,
Permission::Execute => (mode & exec_bit) != 0,
}
}
fn is_in_group(auth: &auth_unix, gid: u32) -> bool {
if auth.gid == gid {
return true;
}
auth.gids.contains(&gid)
}
pub fn can_read(auth: &auth_unix, attr: &fattr3) -> bool {
check_permission(auth, attr, Permission::Read)
}
pub fn can_write(auth: &auth_unix, attr: &fattr3) -> bool {
check_permission(auth, attr, Permission::Write)
}
pub fn can_execute(auth: &auth_unix, attr: &fattr3) -> bool {
check_permission(auth, attr, Permission::Execute)
}
pub fn compute_access(auth: &auth_unix, attr: &fattr3, requested: u32) -> u32 {
let mut result = 0u32;
let is_dir = matches!(attr.ftype, super::nfs::ftype3::NF3DIR);
if (requested & ACCESS3_READ) != 0 && can_read(auth, attr) {
result |= ACCESS3_READ;
}
if (requested & ACCESS3_LOOKUP) != 0 && is_dir && can_execute(auth, attr) {
result |= ACCESS3_LOOKUP;
}
if (requested & ACCESS3_MODIFY) != 0 && can_write(auth, attr) {
result |= ACCESS3_MODIFY;
}
if (requested & ACCESS3_EXTEND) != 0 && can_write(auth, attr) {
result |= ACCESS3_EXTEND;
}
if (requested & ACCESS3_DELETE) != 0 {
if is_dir && can_write(auth, attr) {
result |= ACCESS3_DELETE;
}
}
if (requested & ACCESS3_EXECUTE) != 0 && !is_dir && can_execute(auth, attr) {
result |= ACCESS3_EXECUTE;
}
result
}
pub fn can_modify_directory(auth: &auth_unix, dir_attr: &fattr3) -> bool {
can_write(auth, dir_attr) && can_execute(auth, dir_attr)
}
pub fn can_delete_entry(auth: &auth_unix, dir_attr: &fattr3, entry_attr: &fattr3) -> bool {
if !can_modify_directory(auth, dir_attr) {
return false;
}
if (dir_attr.mode & S_ISVTX) == 0 {
return true;
}
auth.uid == 0 || auth.uid == dir_attr.uid || auth.uid == entry_attr.uid
}
pub fn is_owner(auth: &auth_unix, attr: &fattr3) -> bool {
auth.uid == 0 || auth.uid == attr.uid
}
#[cfg(test)]
mod tests {
use super::*;
use crate::nfsserve::nfs::{ftype3, nfstime3, specdata3};
fn make_auth(uid: u32, gid: u32, gids: Vec<u32>) -> auth_unix {
auth_unix {
stamp: 0,
machinename: Vec::new(),
uid,
gid,
gids,
}
}
fn make_attr(mode: u32, uid: u32, gid: u32, ftype: ftype3) -> fattr3 {
fattr3 {
ftype,
mode,
nlink: 1,
uid,
gid,
size: 0,
used: 0,
rdev: specdata3::default(),
fsid: 0,
fileid: 1,
atime: nfstime3::default(),
mtime: nfstime3::default(),
ctime: nfstime3::default(),
}
}
#[test]
fn test_root_always_allowed() {
let auth = make_auth(0, 0, vec![]);
let attr = make_attr(0o000, 1000, 1000, ftype3::NF3REG);
assert!(can_read(&auth, &attr));
assert!(can_write(&auth, &attr));
assert!(can_execute(&auth, &attr));
}
#[test]
fn test_owner_permissions() {
let auth = make_auth(1000, 1000, vec![]);
let attr = make_attr(0o400, 1000, 2000, ftype3::NF3REG);
assert!(can_read(&auth, &attr));
assert!(!can_write(&auth, &attr));
assert!(!can_execute(&auth, &attr));
let attr = make_attr(0o200, 1000, 2000, ftype3::NF3REG);
assert!(!can_read(&auth, &attr));
assert!(can_write(&auth, &attr));
assert!(!can_execute(&auth, &attr));
let attr = make_attr(0o100, 1000, 2000, ftype3::NF3REG);
assert!(!can_read(&auth, &attr));
assert!(!can_write(&auth, &attr));
assert!(can_execute(&auth, &attr));
}
#[test]
fn test_group_permissions() {
let auth = make_auth(1000, 2000, vec![]);
let attr = make_attr(0o040, 3000, 2000, ftype3::NF3REG);
assert!(can_read(&auth, &attr));
assert!(!can_write(&auth, &attr));
let attr = make_attr(0o020, 3000, 2000, ftype3::NF3REG);
assert!(!can_read(&auth, &attr));
assert!(can_write(&auth, &attr));
}
#[test]
fn test_auxiliary_group() {
let auth = make_auth(1000, 1000, vec![2000, 3000]);
let attr = make_attr(0o040, 9999, 2000, ftype3::NF3REG);
assert!(can_read(&auth, &attr));
let attr = make_attr(0o040, 9999, 3000, ftype3::NF3REG);
assert!(can_read(&auth, &attr));
}
#[test]
fn test_other_permissions() {
let auth = make_auth(1000, 1000, vec![]);
let attr = make_attr(0o004, 2000, 2000, ftype3::NF3REG);
assert!(can_read(&auth, &attr));
assert!(!can_write(&auth, &attr));
let attr = make_attr(0o002, 2000, 2000, ftype3::NF3REG);
assert!(!can_read(&auth, &attr));
assert!(can_write(&auth, &attr));
}
#[test]
fn test_access_computation() {
let auth = make_auth(1000, 1000, vec![]);
let attr = make_attr(0o700, 1000, 1000, ftype3::NF3REG);
let access = compute_access(&auth, &attr, 0x3f);
assert!((access & ACCESS3_READ) != 0);
assert!((access & ACCESS3_MODIFY) != 0);
assert!((access & ACCESS3_EXTEND) != 0);
assert!((access & ACCESS3_EXECUTE) != 0);
assert!((access & ACCESS3_LOOKUP) == 0);
let attr = make_attr(0o700, 1000, 1000, ftype3::NF3DIR);
let access = compute_access(&auth, &attr, 0x3f);
assert!((access & ACCESS3_READ) != 0);
assert!((access & ACCESS3_LOOKUP) != 0);
assert!((access & ACCESS3_MODIFY) != 0);
assert!((access & ACCESS3_EXTEND) != 0);
assert!((access & ACCESS3_DELETE) != 0);
assert!((access & ACCESS3_EXECUTE) == 0);
}
}