summaryrefslogtreecommitdiff
path: root/src/auth.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/auth.rs')
-rw-r--r--src/auth.rs264
1 files changed, 264 insertions, 0 deletions
diff --git a/src/auth.rs b/src/auth.rs
new file mode 100644
index 0000000..99a8216
--- /dev/null
+++ b/src/auth.rs
@@ -0,0 +1,264 @@
+use nix::unistd::{User, Group, Uid, Gid, self};
+use crate::persist;
+
+
+pub struct Config {
+ pub permit: bool,
+ pub persist: bool,
+ pub nopass: bool,
+ pub user_uid: Option<Uid>,
+ pub user_gid: Option<Gid>,
+ pub privlaged_uid: Uid,
+}
+
+
+/// Returns a option containing the config ad the specific pile path.
+/// This function will print out config syntax errors to the standard
+/// output if it failes to parse certain lines.
+/// #### Arguments
+/// * `path` - The path to the config file
+/// #### Returns
+/// * `None` - If the config failed to load
+/// * `Some(Vec<Config>)` - If the config was sucessfully parsed.
+pub fn load_config_file(path: &str) -> Option<Vec<Config>> {
+ let file = match std::fs::read_to_string(path) {
+ Err(e) => {
+ eprintln!("{}: {}", &path, e);
+ return None
+ },
+ Ok(data) => data
+ };
+
+ let mut configs = vec![];
+
+ for (line_num, line) in file.split("\n").enumerate() {
+
+ let args: Vec<&str> = line.split(" ").collect();
+ let len = args.len();
+
+ if line.starts_with("#") || line.trim() == "" {
+ continue;
+ }
+
+ if len < 2 {
+ config_error(line_num, "Not enough arguments");
+ continue;
+ }
+
+ let permit = match args[0] {
+ "permit" => true,
+ "deny" => false,
+ _ => {
+ config_error(line_num, "The first argument must be `permit` or `deny");
+ continue;
+ }
+ };
+
+ let (user_name, privlaged_name, name_index) = match args.iter().position(|&a| a == "as") {
+ Some(index) => {
+ if index != len - 2 {
+ config_error(line_num, "Target user not specified or to many arguments after `as`");
+ continue;
+ }
+ (args[index-1].to_string(), args[index+1].to_string(), index-1)
+ },
+ None => (args[len-1].to_string(), "root".to_string(), len-1)
+ };
+
+ let persist = args[1..name_index].contains(&"persist");
+
+ let nopass = args[1..name_index].contains(&"nopass");
+
+
+ for &check in args[1..name_index].iter() {
+ match check {
+ "persist" => continue,
+ "nopass" => continue,
+ _ => {
+ config_error(line_num, &format!("Unexpected token `{}`", check))
+ }
+ }
+ }
+
+
+ let (user_uid, user_gid) =
+ if user_name.starts_with(":") {
+ match get_gid_from_name(&user_name[1..]) {
+ Some(gid) => (None, Some(gid)),
+ None => {
+ config_error(line_num, &format!("Group `{}` does not exist", &user_name[1..]));
+ continue;
+ }
+ }
+ } else {
+ match get_uid_from_name(&user_name) {
+ Some(uid) => (Some(uid), None),
+ None => {
+ config_error(line_num, &format!("User `{}` does not exist", user_name));
+ continue;
+ }
+ }
+ };
+
+ let privlaged_uid = match get_uid_from_name(&privlaged_name) {
+ Some(uid) => uid,
+ None => {
+ config_error(line_num, &format!("User `{}` does not exist", privlaged_name));
+ continue;
+ }
+ };
+
+ configs.push(Config {
+ permit,
+ persist,
+ nopass,
+ user_uid,
+ user_gid,
+ privlaged_uid
+ });
+ }
+ Some(configs)
+}
+
+
+/// Print a crab config error to the standard output
+fn config_error(line_num: usize, message: &str) {
+ eprintln!("Error in config at line {}: {}", line_num, message);
+}
+
+
+/// Returns a Uid from a Users name
+/// #### Arguments
+/// * `name` - The name of the user
+/// #### Returns
+/// * `None` - If the user doesn't exist
+/// * `Some(Gid)` - If the user exists
+fn get_uid_from_name(name: &str) -> Option<Uid> {
+ return match User::from_name(name) {
+ Ok(result) => match result {
+ Some(data) => Some(data.uid),
+ None => None
+ },
+ Err(_) => None
+ }
+}
+
+
+/// Returns a Uesrs name from a Uid
+/// #### Arguments
+/// * `uid` - The uid of the user
+/// #### Returns
+/// * `None` - If the user doesn't exist
+/// * `Some(Gid)` - If the user exists
+fn get_name_from_uid(uid: Uid) -> Option<String> {
+ return match User::from_uid(uid) {
+ Ok(result) => match result {
+ Some(data) => Some(data.name),
+ None => None
+ },
+ Err(_) => None
+ }
+}
+
+
+/// Returns a Gid from a Groups name
+/// #### Arguments
+/// * `name` - The name of the group
+/// #### Returns
+/// * `None` - If the group doesn't exist
+/// * `Some(Gid)` - If the group exists
+fn get_gid_from_name(name: &str) -> Option<Gid> {
+ return match Group::from_name(name) {
+ Ok(result) => match result {
+ Some(data) => Some(data.gid),
+ None => None
+ },
+ Err(_) => None
+ }
+}
+
+
+/// Returns a vector of group names of the groups the current effective uid is in
+/// #### Returns
+/// A vector of strings of the groups the user is in. If the vector is empty,
+/// either the function coudn't retrieve the users groups, or the user is not in
+/// any groups.
+fn get_groups() -> Vec<Gid> {
+ let groups = match unistd::getgroups() {
+ Ok(data) => data,
+ Err(_) => return vec![]
+ };
+ groups
+}
+
+
+/// Returns if the user is authorized given a sepcific config and user name.
+/// #### Arguments
+/// * `config` - A config struct contaning the authorization settings for crab
+/// * `uid` - The uid to check is authorized
+/// #### Returns
+/// * `None` - If the uid is not authorized
+/// * `Some(Config)` - If the uid is authorized, returns the specific index of the associated config
+pub fn authorize(configs: &Vec<Config>, uid: Uid) -> Option<usize> {
+ let groups = get_groups();
+ for (config_index, config) in configs.iter().enumerate() {
+ if config.user_gid.is_some() {
+ if groups.contains(&config.user_gid.unwrap()) {
+ if config.permit {
+ return Some(config_index)
+ } else {
+ return None
+ }
+ }
+ } else if config.user_uid.is_some() {
+ if config.user_uid.unwrap() == uid {
+ if config.permit {
+ return Some(config_index)
+ } else {
+ return None
+ }
+ }
+ }
+ }
+ None
+}
+
+
+/// Authenticates an authorized user for the current session.
+/// If the user is already persisted, it will attempt to
+/// read the persist file, and then skip authentication if
+/// the is still persisted.
+/// #### Arguments
+/// * `config` - The config associated with the user to authenticate with
+/// * `force_pass` - Force a password prompt even if persistance is set to true
+/// * `uid` - The user uid that is authenticating
+/// #### Returns
+/// * `true` - If the user authenticated sucessfully
+/// * `false` - If the user failed to authenticate
+pub fn authenticate(config: &Config, force_pass: bool, uid: Uid) -> bool {
+ let name = match get_name_from_uid(uid) {
+ Some(data) => data,
+ None => return false
+ };
+ if config.nopass || ( !force_pass && config.persist && persist::get_persist(&name) ) {
+ return true;
+ }
+ let input = match rpassword::prompt_password(format!("crab ({}) password: ", &name)) {
+ Ok(data) => data,
+ Err(_) => return false
+ };
+ let mut auth = match pam::Authenticator::with_password("crab") {
+ Ok(data) => data,
+ Err(_) => return false
+ };
+ auth.get_handler().set_credentials(&name, input);
+ if !auth.authenticate().is_ok() || !auth.open_session().is_ok() {
+ return false;
+ }
+ if !force_pass && config.persist {
+ persist::set_persist(&name);
+ } else if force_pass {
+ persist::remove_persist(&name);
+ }
+ return true;
+}