use log::warn;
use rasn::types::ObjectIdentifier;
use toml;
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Perm {
pub group_name: Vec<u8>,
pub security_level: u8,
pub rules: Vec<Rule>,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Rule {
pub read: bool,
pub write: bool,
pub context: Option<Vec<u8>>,
pub include: Vec<Vec<u32>>,
pub exclude: Vec<Vec<u32>>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct ParsePermError;
impl Perm {
pub fn check(&self, flags: u8, set: bool, oid: &ObjectIdentifier, context: &[u8]) -> bool {
let sec_level = 1 + (flags & 1) + (flags & 2);
if sec_level < self.security_level {
return false;
}
for rule in &self.rules {
if rule.context.is_some() && context != rule.context.as_ref().unwrap() {
continue;
}
if set && !rule.write {
continue;
}
if !set && !rule.read {
continue;
}
let mut excluded = false;
for exc in &rule.exclude {
excluded = excluded || oid.starts_with(exc.as_slice());
}
if excluded {
continue;
}
let mut included = false;
for inc in &rule.include {
included = included || oid.starts_with(inc.as_slice());
}
if included {
return true;
}
} false
}
}
pub fn load_from_str(toml_text: &str) -> Vec<Perm> {
let mut perms = Vec::new();
let data = toml_text.parse::<toml::Table>().unwrap(); let groups = data.get("groups").unwrap().as_array().unwrap(); for group in groups {
let group_name = group
.get("name")
.unwrap() .as_str()
.unwrap() .as_bytes()
.to_vec();
let level = group.get("level").unwrap().as_str().unwrap(); let trules = group.get("rules").unwrap().as_array().unwrap(); let mut rules = vec![];
for rule in trules {
let read = rule.get("read").unwrap().as_bool().unwrap(); let write = rule.get("write").unwrap().as_bool().unwrap(); let con_op = rule.get("context");
let context = con_op.map(|cstr| cstr.as_str().unwrap().as_bytes().to_vec());
let tinclude = rule.get("include").unwrap().as_array().unwrap(); let texclude = rule.get("exclude").unwrap().as_array().unwrap(); let mut include = vec![];
let mut exclude = vec![];
for arc in tinclude {
let dots: Vec<u32> = arc
.as_str()
.unwrap() .split(".")
.map(|s| {
let u: u32 = s.parse().unwrap(); u
})
.collect();
include.push(dots);
}
for arc in texclude {
let dots: Vec<u32> = arc
.as_str()
.unwrap() .split(".")
.map(|s| {
let u: u32 = s.parse().unwrap(); u
})
.collect();
exclude.push(dots);
}
let prule = Rule {
read,
write,
context,
include,
exclude,
};
rules.push(prule);
}
let security_level = match level {
"noAuthNoPriv" => 1,
"authNoPriv" => 2,
"authPriv" => 3,
_ => {
warn!("Unrecognized security level name {level}, denying all access");
255
}
};
let perm = Perm {
group_name,
security_level,
rules,
};
perms.push(perm);
}
perms
}
pub struct FlagPerm<'a> {
pub perm: &'a Perm,
flags: u8,
context: &'a [u8],
}
impl<'a> FlagPerm<'a> {
pub fn new(flags: u8, context: &'a [u8], perm: &'a Perm) -> Self {
FlagPerm {
perm,
flags,
context,
}
}
pub fn check(&self, set: bool, oid: &ObjectIdentifier) -> bool {
self.perm.check(self.flags, set, oid, self.context)
}
}
#[cfg(test)]
mod tests {
use super::*;
use rasn::types::ObjectIdentifier;
const ARC_IN: [u32; 2] = [1, 1];
const ARC_OUT: [u32; 2] = [2, 1];
fn perms() -> Vec<Perm> {
let rules = vec![
Rule {
read: false,
write: false,
context: None,
include: vec![vec![1u32]],
exclude: vec![],
},
Rule {
read: true,
write: true,
context: None,
include: vec![vec![1u32]],
exclude: vec![vec![1u32, 3u32]],
},
];
vec![Perm {
rules,
security_level: 2u8, group_name: "test".as_bytes().to_vec(),
}]
}
#[test]
fn test_check() {
let o_in = ObjectIdentifier::new(&ARC_IN).unwrap(); let o_out = ObjectIdentifier::new(&ARC_OUT).unwrap(); let p = &perms()[0];
let context = [];
assert!(p.check(2, true, &o_in, &context));
assert!(p.check(2, false, &o_in, &context));
assert!(!p.check(0, false, &o_in, &context));
assert!(!p.check(2, false, &o_out, &context));
}
#[test]
fn test_flag_check() {
let o_in = ObjectIdentifier::new(&ARC_IN).unwrap(); let o_out = ObjectIdentifier::new(&ARC_OUT).unwrap(); let p = &perms()[0];
let f = FlagPerm {
perm: p,
context: &[],
flags: 2u8,
};
assert!(f.check(true, &o_in));
assert!(f.check(false, &o_in));
assert!(!f.check(false, &o_out));
}
#[test]
fn test_load_from_str() {
let o_in = ObjectIdentifier::new(&ARC_IN).unwrap(); let txt = "
[[groups]]
name = \"admin\"
level = \"authPriv\"
rules = [ {read = true, write = true, include=[ \"1.1\" ], exclude = [ \"1.3.6.1.6.3.1.25\"]} ]
";
let perms = load_from_str(&txt);
assert_eq!(perms.len(), 1);
let p = &perms[0];
let f = FlagPerm {
perm: p,
context: &[6],
flags: 2u8,
};
assert!(f.check(false, &o_in))
}
#[test]
fn test_load_from_bad_str() {
let o_in = ObjectIdentifier::new(&ARC_IN).unwrap(); let txt = "
[[groups]]
name = \"admin\"
level = \"sillyLevel\"
rules = [ {read = true, write = true, include=[ \"1.1\" ], exclude = [ \"1.3.6.1.6.3.1.25\"]} ]
";
let perms = load_from_str(&txt);
assert_eq!(perms.len(), 1);
let p = &perms[0];
let f = FlagPerm {
perm: p,
context: &[6],
flags: 2u8,
};
assert!(!f.check(false, &o_in))
}
#[test]
fn test_wrong_context() {
let o_in = ObjectIdentifier::new(&ARC_IN).unwrap(); let txt = "
[[groups]]
name = \"admin\"
level = \"authPriv\"
rules = [ {read = true, write = true, context=\"a\", include=[ \"1.1\" ], exclude = [ \"1.3.6.1.6.3.1.25\"]} ]
";
let perms = load_from_str(&txt);
assert_eq!(perms.len(), 1);
let p = &perms[0];
let f = FlagPerm {
perm: p,
context: &[6],
flags: 2u8,
};
assert!(!f.check(false, &o_in))
}
}