196 lines
6.0 KiB
Rust
196 lines
6.0 KiB
Rust
use crate::{app::App, user_info::*};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::{
|
|
collections::{BTreeMap, BTreeSet},
|
|
fs::File,
|
|
io::Read,
|
|
path::PathBuf,
|
|
};
|
|
use users::Group;
|
|
|
|
const ALL: &'static str = "ALL";
|
|
const DEFAULT_RUDOERS: &'static str = "/etc/rudoers.toml";
|
|
|
|
fn default_password_required() -> bool {
|
|
true
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
pub struct HostPerms {
|
|
#[serde(default = "default_password_required")]
|
|
password_required: bool,
|
|
aliases: BTreeSet<String>,
|
|
#[serde(default)]
|
|
commands: BTreeSet<String>,
|
|
}
|
|
|
|
#[derive(Deserialize, Serialize)]
|
|
#[serde(untagged)]
|
|
pub enum Entry {
|
|
CommandPaths(BTreeSet<String>),
|
|
Hosts(BTreeMap<String, HostPerms>),
|
|
}
|
|
|
|
#[derive(Default, Deserialize, Serialize)]
|
|
pub struct Rudoers {
|
|
users: BTreeMap<String, Entry>,
|
|
#[serde(default)]
|
|
groups: BTreeMap<String, Entry>,
|
|
}
|
|
|
|
fn parse_rudoers() -> Result<Rudoers, String> {
|
|
let mut rudoers = match File::open(DEFAULT_RUDOERS) {
|
|
Ok(file) => file,
|
|
Err(e) => {
|
|
return Err(format!["Cannot open {DEFAULT_RUDOERS}: {e}"]);
|
|
}
|
|
};
|
|
let mut contents = String::new();
|
|
match rudoers.read_to_string(&mut contents) {
|
|
Ok(_) => (),
|
|
Err(e) => {
|
|
return Err(format!["Could not read {DEFAULT_RUDOERS}: {e}"]);
|
|
}
|
|
};
|
|
match toml::from_str(&contents) {
|
|
Ok(rudoers) => Ok(rudoers),
|
|
Err(e) => {
|
|
return Err(format!["Failed to parse {DEFAULT_RUDOERS}: {e}"]);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn check_entry(
|
|
entry: &Entry,
|
|
hostname: &str,
|
|
alias: &str,
|
|
command_path: &str,
|
|
) -> Result<bool, String> {
|
|
let permission_denied = String::from("Error: Permission denied.");
|
|
let operation_not_permitted = String::from("Error: Operation not permitted.");
|
|
match entry {
|
|
Entry::CommandPaths(cmds) => {
|
|
if cmds.contains(command_path) || cmds.contains(ALL) {
|
|
Ok(true)
|
|
} else {
|
|
Err(permission_denied)
|
|
}
|
|
}
|
|
Entry::Hosts(hosts) => {
|
|
if let Some(ref host) = hosts.get(hostname).or_else(|| hosts.get(ALL)) {
|
|
if host.aliases.contains(alias) || host.aliases.contains(ALL) {
|
|
if host.commands.contains(command_path) || host.commands.contains(ALL) {
|
|
Ok(host.password_required)
|
|
} else {
|
|
Err(permission_denied)
|
|
}
|
|
} else {
|
|
Err(if alias == "root" {
|
|
permission_denied
|
|
} else {
|
|
operation_not_permitted
|
|
})
|
|
}
|
|
} else {
|
|
Err(permission_denied)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn check_rudoers(args: &App) -> Result<bool, String> {
|
|
let command_path: PathBuf = if let Some(first) = args.cmd.first() {
|
|
get_command_path(first)?
|
|
} else {
|
|
return Err(String::from("Error: No command specified."));
|
|
};
|
|
let alias: String = args.user.clone().unwrap_or("root".into());
|
|
let hostname: String = get_hostname()?;
|
|
let username: String = get_username()?;
|
|
let groups: Vec<Group> = get_groups();
|
|
let rudoers = parse_rudoers()?;
|
|
Ok(if let Some(entry) = rudoers.users.get(&username) {
|
|
check_entry(&entry, &hostname, &alias, &command_path.to_string_lossy())?
|
|
} else {
|
|
let mut result = Err(String::from("Error: User is not in the rudoers file."));
|
|
for group in groups {
|
|
if let Some(entry) = rudoers.groups.get(group.name().to_str().unwrap()) {
|
|
let output =
|
|
check_entry(&entry, &hostname, &alias, &command_path.to_string_lossy());
|
|
if output.is_ok() {
|
|
result = output;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
result?
|
|
})
|
|
}
|
|
|
|
pub fn make_example_rudoers_file() -> String {
|
|
let mut rudoers = Rudoers::default();
|
|
rudoers.users.insert(
|
|
"alice".to_string(),
|
|
Entry::Hosts({
|
|
let mut hosts = BTreeMap::new();
|
|
hosts.insert(
|
|
"northpole".to_string(),
|
|
HostPerms {
|
|
password_required: false,
|
|
aliases: {
|
|
let mut set = BTreeSet::new();
|
|
set.insert("root".to_string());
|
|
set
|
|
},
|
|
commands: {
|
|
let mut commands = BTreeSet::new();
|
|
commands.insert("/bin/cat".into());
|
|
commands.insert("/bin/grep".into());
|
|
commands.insert("/bin/xargs".into());
|
|
commands
|
|
},
|
|
},
|
|
);
|
|
hosts
|
|
}),
|
|
);
|
|
rudoers.users.insert(
|
|
"bob".to_string(),
|
|
Entry::Hosts({
|
|
let mut hosts = BTreeMap::new();
|
|
hosts.insert(
|
|
"northpole".to_string(),
|
|
HostPerms {
|
|
password_required: false,
|
|
aliases: {
|
|
let mut set = BTreeSet::new();
|
|
set.insert("root".to_string());
|
|
set
|
|
},
|
|
commands: {
|
|
let mut commands = BTreeSet::new();
|
|
commands.insert("/bin/cat".into());
|
|
commands.insert("/bin/grep".into());
|
|
commands.insert("/bin/xargs".into());
|
|
commands
|
|
},
|
|
},
|
|
);
|
|
hosts
|
|
}),
|
|
);
|
|
let rudoers = toml::to_string(&rudoers).unwrap();
|
|
let mut text = String::new();
|
|
for (n, s) in rudoers.lines().enumerate() {
|
|
if n > 0 {
|
|
text.push('\n');
|
|
}
|
|
if s.starts_with(|c: char| !c.is_whitespace()) {
|
|
text.push_str(&format!["# {s}"]);
|
|
} else {
|
|
text.push_str(&s);
|
|
}
|
|
}
|
|
text
|
|
}
|