#![warn( clippy::all, clippy::restriction, clippy::pedantic, clippy::nursery, clippy::cargo )] #![allow(clippy::non_ascii_literal)] use log::warn; use std::collections::HashMap; use std::fs::File; use std::io::{BufReader, Read}; use std::path::PathBuf; pub struct UserDBLocal { source_files: Files, pub users: HashMap, pub groups: Vec, } pub struct Files { passwd: Option, shadow: Option, group: Option, } impl Default for Files { /// use the default Linux `/etc/` paths fn default() -> Self { Self { passwd: Some(PathBuf::from("/etc/passwd")), shadow: Some(PathBuf::from("/etc/shadow")), group: Some(PathBuf::from("/etc/group")), } } } impl UserDBLocal { /// Import the database from strings #[must_use] pub fn import_from_strings( passwd_content: &str, shadow_content: &str, group_content: &str, ) -> Self { let shadow_entries: Vec = string_to(&shadow_content); let mut users = user_vec_to_hashmap(string_to(&passwd_content)); let groups = string_to(&group_content); shadow_to_users(&mut users, shadow_entries); let res = Self { source_files: Files { passwd: None, group: None, shadow: None, }, users, groups, }; res } /// Import the database from a [`Files`] struct #[must_use] pub fn load_files(files: Files) -> Self { let my_passwd_lines = file_to_string(files.passwd.as_ref()); let my_group_lines = file_to_string(files.group.as_ref()); let my_shadow_lines = file_to_string(files.shadow.as_ref()); let mut users = user_vec_to_hashmap(string_to(&my_passwd_lines)); let passwds: Vec = string_to(&my_shadow_lines); shadow_to_users(&mut users, passwds); Self { source_files: files, users, groups: string_to(&my_group_lines), } } } use crate::api::UserDBRead; impl UserDBRead for UserDBLocal { fn get_all_users(&self) -> Vec<&crate::User> { self.users.iter().map(|(_, x)| x).collect() } fn get_user_by_name(&self, name: &str) -> Option<&crate::User> { self.users.get(name) } fn get_user_by_id(&self, uid: u32) -> Option<&crate::User> { // could probably be more efficient - on the other hand its no problem to loop a thousand users. for (_, user) in self.users.iter() { if user.get_uid() == uid { return Some(&user); } } None } fn get_all_groups(&self) -> Vec<&crate::Group> { self.groups.iter().collect() } fn get_group_by_name(&self, name: &str) -> Option<&crate::Group> { for group in self.groups.iter() { if group.get_groupname() == name { return Some(group); } } None } fn get_group_by_id(&self, id: u32) -> Option<&crate::Group> { for group in self.groups.iter() { if group.get_gid() == id { return Some(group); } } None } } use crate::api::UserDBValidation; impl UserDBValidation for UserDBLocal { fn is_uid_valid_and_free(&self, uid: u32) -> bool { warn!("No valid check, only free check"); let free = self.users.iter().all(|(_, u)| u.get_uid() != uid); free } fn is_username_valid_and_free(&self, name: &str) -> bool { let valid = crate::user::passwd_fields::is_username_valid(name); let free = self.get_user_by_name(name).is_none(); valid && free } fn is_gid_valid_and_free(&self, gid: u32) -> bool { warn!("No valid check, only free check"); self.groups.iter().all(|x| x.get_gid() != gid) } fn is_groupname_valid_and_free(&self, name: &str) -> bool { let valid = crate::group::is_groupname_valid(name); let free = self.groups.iter().all(|x| x.get_groupname() != name); valid && free } } /// Parse a file to a string fn file_to_string(path: Option<&PathBuf>) -> String { let file = File::open(path.expect("Path cannot be None".into())) .expect("Failed to read the file. Most of the time root permissions are needed".into()); let mut reader = BufReader::new(file); let mut lines = String::new(); reader.read_to_string(&mut lines).unwrap(); lines } /// Merge the Shadow passwords into the users fn shadow_to_users( users: &mut HashMap, shadow: Vec, ) -> &mut HashMap { for pass in shadow { let user = users .get_mut(pass.get_username()) .expect(&format!("the user {} does not exist", pass.get_username())); user.password = crate::Password::Shadow(pass); } users } /// Convert a `Vec` to a `HashMap` where the username is used as key fn user_vec_to_hashmap(users: Vec) -> HashMap { users .into_iter() .map(|x| (x.get_username().to_owned(), x)) .collect() } /// Try to parse a String into some Object /// /// # Errors /// if the parsing failed a [`UserLibError::Message`] is returned containing a more detailed error message. pub trait NewFromString { fn new_from_string(line: String) -> Result where Self: Sized; } /// A generic function that parses a string line by line and creates the appropriate `Vec` requested by the type system. fn string_to(source: &str) -> Vec where T: NewFromString, { source .lines() .filter_map(|line| { if line.len() > 5 { //println!("{}", line); Some(T::new_from_string(line.to_owned()).expect("failed to read lines")) } else { None } }) .collect() } #[test] fn test_creator_user_db_local() { let data = UserDBLocal::import_from_strings("test:x:1001:1001:full Name,004,000342,001-2312,myemail@test.com:/home/test:/bin/test", "test:!!$6$/RotIe4VZzzAun4W$7YUONvru1rDnllN5TvrnOMsWUD5wSDUPAD6t6/Xwsr/0QOuWF3HcfAhypRkGa8G1B9qqWV5kZSnCb8GKMN9N61:18260:0:99999:7:::", "teste:x:1002:test,test"); assert_eq!(data.users.get("test").unwrap().get_username(), "test") } #[test] fn test_parsing_local_database() { // Parse the worldreadable user database ignore the shadow database as this would require root privileges. let my_passwd_lines = file_to_string(Some(&PathBuf::from("/etc/passwd"))); let my_group_lines = file_to_string(Some(&PathBuf::from("/etc/group"))); let data = UserDBLocal::import_from_strings(&my_passwd_lines, "", &my_group_lines); assert_eq!(data.groups.get(0).unwrap().get_groupname(), "root"); } #[test] fn test_user_db_read_implementation() { let pass = file_to_string(Some(&PathBuf::from("/etc/passwd"))); let group = file_to_string(Some(&PathBuf::from("/etc/group"))); let data = UserDBLocal::import_from_strings(&pass, "", &group); // Usually there are more than 10 users assert!(data.get_all_users().len() > 10); assert!(data.get_user_by_name("root").is_some()); assert_eq!(data.get_user_by_name("root").unwrap().get_uid(), 0); assert_eq!(data.get_user_by_id(0).unwrap().get_username(), "root"); assert!(data.get_all_groups().len() > 10); assert!(data.get_group_by_name("root").is_some()); assert_eq!(data.get_group_by_name("root").unwrap().get_gid(), 0); assert_eq!(data.get_group_by_id(0).unwrap().get_groupname(), "root"); assert!(data.get_user_by_name("norealnameforsure").is_none()); assert!(data.get_group_by_name("norealgroupforsure").is_none()); }