pub mod embedded;
pub mod user;
pub mod version;
use crate::policy;
use nono::{NonoError, Result};
use std::path::{Path, PathBuf};
pub fn validated_home() -> Result<String> {
let home = std::env::var("HOME").map_err(|_| NonoError::EnvVarValidation {
var: "HOME".to_string(),
reason: "not set".to_string(),
})?;
if !Path::new(&home).is_absolute() {
return Err(NonoError::EnvVarValidation {
var: "HOME".to_string(),
reason: format!("must be an absolute path, got: {}", home),
});
}
Ok(home)
}
pub fn validated_tmpdir() -> Result<String> {
match std::env::var("TMPDIR") {
Ok(tmpdir) => {
if !Path::new(&tmpdir).is_absolute() {
return Err(NonoError::EnvVarValidation {
var: "TMPDIR".to_string(),
reason: format!("must be an absolute path, got: {}", tmpdir),
});
}
Ok(tmpdir)
}
Err(_) => Ok("/tmp".to_string()),
}
}
#[allow(dead_code)]
pub fn user_config_dir() -> Option<PathBuf> {
dirs::config_dir().map(|p| p.join("nono"))
}
pub fn user_state_dir() -> Option<PathBuf> {
crate::state_paths::user_state_dir().ok()
}
pub fn check_blocked_command(
cmd: impl AsRef<std::ffi::OsStr>,
allowed_commands: &[String],
extra_blocked: &[String],
) -> Result<Option<String>> {
use std::ffi::OsStr;
use std::path::Path;
let cmd = cmd.as_ref();
let binary_os = Path::new(cmd).file_name().unwrap_or(cmd);
if allowed_commands.iter().any(|a| OsStr::new(a) == binary_os) {
return Ok(None);
}
if extra_blocked.iter().any(|b| OsStr::new(b) == binary_os) {
return Ok(Some(binary_os.to_string_lossy().into_owned()));
}
Ok(None)
}
pub fn check_sensitive_path(path_str: &str) -> Result<Option<policy::SensitivePathRule>> {
let home = validated_home()?;
let expanded = if path_str.starts_with("~/") {
path_str.replacen("~", &home, 1)
} else if path_str == "~" {
home.clone()
} else {
path_str.to_string()
};
let expanded_path = Path::new(&expanded);
let loaded_policy = policy::load_embedded_policy()?;
let sensitive = policy::get_sensitive_paths(&loaded_policy)?;
for rule in sensitive {
let sensitive_path = Path::new(&rule.expanded_path);
if expanded_path == sensitive_path || expanded_path.starts_with(sensitive_path) {
return Ok(Some(rule));
}
}
Ok(None)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_check_blocked_command_basic() {
assert!(
check_blocked_command("echo", &[], &[])
.expect("should not fail")
.is_none()
);
assert!(
check_blocked_command("ls", &[], &[])
.expect("should not fail")
.is_none()
);
}
#[test]
fn test_check_blocked_command_with_path() {
let blocked = vec!["rm".to_string(), "dd".to_string()];
assert!(
check_blocked_command("/bin/rm", &[], &blocked)
.expect("should not fail")
.is_some()
);
assert!(
check_blocked_command("/usr/bin/dd", &[], &blocked)
.expect("should not fail")
.is_some()
);
}
#[test]
fn test_check_blocked_command_with_override() {
let allowed = vec!["rm".to_string()];
let blocked = vec!["rm".to_string(), "dd".to_string()];
assert!(
check_blocked_command("rm", &allowed, &blocked)
.expect("should not fail")
.is_none()
);
assert!(
check_blocked_command("dd", &allowed, &blocked)
.expect("should not fail")
.is_some()
);
}
#[test]
fn test_check_blocked_command_extra_blocked() {
let extra = vec!["custom-dangerous".to_string()];
assert!(
check_blocked_command("custom-dangerous", &[], &extra)
.expect("should not fail")
.is_some()
);
assert!(
check_blocked_command("rm", &[], &extra)
.expect("should not fail")
.is_none()
);
}
#[test]
fn test_check_blocked_command_only_uses_resolved_policy() {
assert!(
check_blocked_command("rm", &[], &[])
.expect("should not fail")
.is_none()
);
}
#[test]
fn test_check_sensitive_path() {
assert!(
check_sensitive_path("~/.ssh")
.expect("should not fail")
.is_some()
);
assert!(
check_sensitive_path("~/.aws")
.expect("should not fail")
.is_some()
);
assert!(
check_sensitive_path("~/.bashrc")
.expect("should not fail")
.is_some()
);
assert!(
check_sensitive_path("/tmp")
.expect("should not fail")
.is_none()
);
assert!(
check_sensitive_path("~/Documents")
.expect("should not fail")
.is_none()
);
}
#[test]
fn test_check_sensitive_path_component_wise() {
let home = validated_home().expect("HOME must be set");
let evil_path = format!("{}/.sshevil", home);
assert!(
check_sensitive_path(&evil_path)
.expect("should not fail")
.is_none()
);
assert!(
check_sensitive_path("~/.ssh/id_rsa")
.expect("should not fail")
.is_some()
);
}
}