From e9fbbd91ceed81d91dbbf78249b87f08efac3938 Mon Sep 17 00:00:00 2001 From: Dietrich Date: Tue, 24 Nov 2020 19:20:48 +0100 Subject: [PATCH] Enum to discriminate primary and reg. memberships --- README.md | 2 +- src/bin/read_all.rs | 19 +++++++++++ src/group/mod.rs | 52 +++++++++++++++++++++--------- src/user/mod.rs | 6 ++-- src/userlib/mod.rs | 78 +++++++++++++++++++++++++++++++-------------- 5 files changed, 114 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 2f18c30..7f7ad73 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ What is working so far: * Parsing: * `/etc/passwd` * `/etc/shadow` (root permission needed) - * `/etc/group` (not yet really assigned to the users) + * `/etc/group` * Modifying: * delete a user * [x] passwd diff --git a/src/bin/read_all.rs b/src/bin/read_all.rs index ea45fca..ba52724 100644 --- a/src/bin/read_all.rs +++ b/src/bin/read_all.rs @@ -1,4 +1,5 @@ extern crate umanux; +use umanux::api::GroupRead; use umanux::api::UserDBRead; fn main() { @@ -13,5 +14,23 @@ fn main() { for u in db.get_all_users() { println!("{}", u); + println!( + "Groups: {:?}", + u.get_groups() + .iter() + .map(|group| { + ( + format!("{:?}", group.0), + group.1.borrow().get_groupname().unwrap().to_owned(), + ) + }) + .collect::>() + ); + } + + for group in db.get_all_groups() { + let gp = group.borrow(); + println!("{}", gp); + println!("{:?}", gp.get_member_names()) } } diff --git a/src/group/mod.rs b/src/group/mod.rs index 2fec6c4..fc0a1a1 100644 --- a/src/group/mod.rs +++ b/src/group/mod.rs @@ -4,16 +4,22 @@ use crate::userlib::NewFromString; use log::warn; use crate::UserLibError; -use std::convert::TryFrom; use std::fmt::{self, Debug, Display}; +use std::{cell::RefCell, convert::TryFrom}; use std::{cmp::Eq, rc::Rc}; #[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum Membership { +pub enum MembershipKind { Primary, Member, } +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Membership { + kind: MembershipKind, + username: crate::Username, +} + #[derive(Debug, PartialEq, Eq)] pub struct Groupname { groupname: String, @@ -44,7 +50,7 @@ pub(crate) fn is_groupname_valid(name: &str) -> bool { crate::user::passwd_fields::is_username_valid(name) } -pub type Group = Rc; +pub type Group = Rc>; /// A record(line) in the user database `/etc/shadow` found in most linux systems. #[derive(Debug, PartialEq, Eq)] pub struct Inner { @@ -53,7 +59,7 @@ pub struct Inner { groupname: Groupname, /* Username. */ pub(crate) password: crate::Password, /* Usually not used (disabled with x) */ gid: crate::Gid, /* Group ID. */ - members: Vec, /* Real name. */ + members: Vec, /* Real name. */ } impl Inner { @@ -65,6 +71,15 @@ impl Inner { .collect::>() .join("\n") } + + pub(super) fn append_user(&mut self, username: &str) { + self.members.push(Membership { + kind: MembershipKind::Primary, + username: crate::Username { + username: username.to_owned(), + }, + }) + } } use crate::api::GroupRead; @@ -77,7 +92,7 @@ impl GroupRead for Inner { fn get_member_names(&self) -> Option> { let mut r: Vec<&str> = Vec::new(); for u in &self.members { - r.push(&u.username); + r.push(&u.username.username); } Some(r) } @@ -101,14 +116,18 @@ impl Display for Inner { self.gid, self.members .iter() - .map(|mem| format!("{}", mem)) + .filter_map(|mem| if mem.kind == MembershipKind::Member { + Some(format!("{}", mem.username)) + } else { + None + }) .collect::>() .join(",") ) } } -impl NewFromString for Rc { +impl NewFromString for Group { /// Parse a line formatted like one in `/etc/group` and construct a matching [`Group`] instance /// /// # Example @@ -119,7 +138,7 @@ impl NewFromString for Rc { /// "teste:x:1002:test,teste".to_owned(), /// 0, /// ).unwrap(); - /// assert_eq!(grp.get_groupname().unwrap(), "teste"); + /// assert_eq!(grp.borrow().get_groupname().unwrap(), "teste"); /// ``` /// /// # Errors @@ -127,14 +146,14 @@ impl NewFromString for Rc { fn new_from_string(line: String, position: u32) -> Result { let elements: Vec = line.split(':').map(ToString::to_string).collect(); if elements.len() == 4 { - Ok(Self::new(Inner { + Ok(Self::new(RefCell::new(Inner { pos: position, source: line, groupname: Groupname::try_from(elements.get(0).unwrap().to_string())?, password: crate::Password::Disabled, gid: crate::Gid::try_from(elements.get(2).unwrap().to_string())?, members: parse_members_list(elements.get(3).unwrap()), - })) + }))) } else { Err(format!( "Failed to parse: not enough elements ({}): {:?}", @@ -146,7 +165,7 @@ impl NewFromString for Rc { } } -fn parse_members_list(source: &str) -> Vec { +fn parse_members_list(source: &str) -> Vec { let mut res = vec![]; for mem in source.split(',').filter_map(|x| { if x.is_empty() { @@ -155,7 +174,10 @@ fn parse_members_list(source: &str) -> Vec { Some(x.to_string()) } }) { - res.push(crate::Username::try_from(mem).expect("failed to parse username")); + res.push(Membership { + kind: MembershipKind::Member, + username: crate::Username::try_from(mem).expect("failed to parse username"), + }); } res } @@ -164,18 +186,18 @@ fn parse_members_list(source: &str) -> Vec { fn test_parse_and_back_identity() { let line = "teste:x:1002:test,teste"; let line2: Group = Group::new_from_string(line.to_owned(), 0).unwrap(); - assert_eq!(format!("{}", line2), line); + assert_eq!(format!("{}", line2.borrow()), line); } #[test] fn test_groupname() { let line = "teste:x:1002:test,teste"; let line2 = Group::new_from_string(line.to_owned(), 0).unwrap(); - assert_eq!(line2.get_groupname().unwrap(), "teste"); + assert_eq!(line2.borrow().get_groupname().unwrap(), "teste"); } #[test] fn test_root_group() { let line = "root:x:0:"; let line2 = Group::new_from_string(line.to_owned(), 0).unwrap(); - assert_eq!(line2.get_groupname().unwrap(), "root"); + assert_eq!(line2.borrow().get_groupname().unwrap(), "root"); } diff --git a/src/user/mod.rs b/src/user/mod.rs index 2d490f1..abfc1c1 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -21,7 +21,7 @@ pub struct User { gecos: crate::Gecos, /* Real name. */ home_dir: crate::HomeDir, /* Home directory. */ shell_path: crate::ShellPath, /* Shell program. */ - groups: Vec<(crate::group::Membership, crate::Group)>, + groups: Vec<(crate::group::MembershipKind, crate::Group)>, } impl User { @@ -70,7 +70,7 @@ impl User { pub fn add_group( &mut self, - group_type: crate::group::Membership, + group_type: crate::group::MembershipKind, group: crate::Group, ) -> &mut Self { self.groups.push((group_type, group)); @@ -78,7 +78,7 @@ impl User { } #[must_use] - pub const fn get_groups(&self) -> &Vec<(crate::group::Membership, crate::Group)> { + pub const fn get_groups(&self) -> &Vec<(crate::group::MembershipKind, crate::Group)> { &self.groups } } diff --git a/src/userlib/mod.rs b/src/userlib/mod.rs index 10e6a61..a53a7fe 100644 --- a/src/userlib/mod.rs +++ b/src/userlib/mod.rs @@ -34,9 +34,9 @@ impl UserDBLocal { ) -> 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); + let mut groups = string_to(group_content); shadow_to_users(&mut users, shadow_entries); - groups_to_users(&mut users, &groups); + groups_to_users(&mut users, &mut groups); Self { source_files: files::Files { passwd: None, @@ -65,9 +65,9 @@ impl UserDBLocal { let mut users = user_vec_to_hashmap(string_to(&my_passwd_lines)); let passwds: Vec = string_to(&my_shadow_lines); - let groups: Vec = string_to(&my_group_lines); + let mut groups: Vec = string_to(&my_group_lines); shadow_to_users(&mut users, passwds); - groups_to_users(&mut users, &groups); + groups_to_users(&mut users, &mut groups); Ok(Self { source_files: files, users, @@ -119,7 +119,7 @@ impl UserDBLocal { group_file_content: &str, locked_g: &mut files::LockedFileGuard, ) -> Result<(), UserLibError> { - let modified_g = group.remove_in(group_file_content); + let modified_g = group.borrow().remove_in(group_file_content); let replace_result = locked_g.replace_contents(modified_g); match replace_result { Ok(_) => Ok(()), @@ -147,7 +147,7 @@ impl UserDBLocal { fn get_group_pos_by_id(&self, id: u32) -> Option<(&crate::Group, usize)> { for (i, group) in self.groups.iter().enumerate() { - if group.get_gid()? == id { + if group.borrow().get_gid()? == id { return Some((group, i)); } } @@ -197,6 +197,7 @@ impl UserDBWrite for UserDBLocal { let group = self.get_group_pos_by_id(user.get_gid()); if let Some((group, id)) = group { if group + .borrow() .get_member_names() .expect("groups have to have members") .len() @@ -207,7 +208,7 @@ impl UserDBWrite for UserDBLocal { } else { warn!( "The primary group {} was not empty and is thus not removed.", - group.get_groupname().unwrap() + group.borrow().get_groupname().unwrap() ); } } else { @@ -293,7 +294,7 @@ impl UserDBRead for UserDBLocal { fn get_group_by_name(&self, name: &str) -> Option<&crate::Group> { for group in &self.groups { - if group.get_groupname()? == name { + if group.borrow().get_groupname()? == name { return Some(group); } } @@ -302,7 +303,7 @@ impl UserDBRead for UserDBLocal { fn get_group_by_id(&self, id: u32) -> Option<&crate::Group> { for group in &self.groups { - if group.get_gid()? == id { + if group.borrow().get_gid()? == id { return Some(group); } } @@ -326,7 +327,9 @@ impl UserDBValidation for UserDBLocal { 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().unwrap() != gid) + self.groups + .iter() + .all(|x| x.borrow().get_gid().unwrap() != gid) } fn is_groupname_valid_and_free(&self, name: &str) -> bool { @@ -334,7 +337,7 @@ impl UserDBValidation for UserDBLocal { let free = self .groups .iter() - .all(|x| x.get_groupname().unwrap() != name); + .all(|x| x.borrow().get_groupname().unwrap() != name); valid && free } } @@ -350,28 +353,39 @@ fn file_to_string(file: &File) -> Result { } } -fn groups_to_users<'a>(users: &'a mut UserList, groups: &'a [crate::Group]) -> &'a mut UserList { - for group in groups { - match group.get_member_names() { +fn groups_to_users<'a>( + users: &'a mut UserList, + groups: &'a mut [crate::Group], +) -> &'a mut UserList { + // Populate the regular groups + + for group in groups.iter() { + match group.borrow().get_member_names() { Some(usernames) => { for username in usernames { if let Some(user) = users.get_mut(username) { - user.add_group(crate::group::Membership::Member, group.clone()); + user.add_group(crate::group::MembershipKind::Member, group.clone()); } } } None => continue, } } + + // Populate the primary membership for user in users.values_mut() { let gid = user.get_gid(); let grouplist: Vec<&crate::Group> = groups .iter() - .filter(|g| g.get_gid().unwrap() == gid) + .filter(|g| g.borrow().get_gid().unwrap() == gid) .collect(); if grouplist.len() == 1 { let group = *grouplist.first().unwrap(); - user.add_group(crate::group::Membership::Primary, group.clone()); + group.borrow_mut().append_user( + user.get_username() + .expect("Users without username are not supported"), + ); + user.add_group(crate::group::MembershipKind::Primary, group.clone()); } else { error!( "Somehow the group with gid {} was found {} times", @@ -457,10 +471,10 @@ fn test_creator_user_db_local() { let (member_group1, group1) = user.get_groups().first().unwrap(); let (member_group2, group2) = user.get_groups().get(1).unwrap(); - assert_eq!(*member_group1, crate::group::Membership::Member); - assert_eq!(group1.get_groupname(), Some("another")); - assert_eq!(*member_group2, crate::group::Membership::Primary); - assert_eq!(group2.get_groupname(), Some("teste")); + assert_eq!(*member_group1, crate::group::MembershipKind::Member); + assert_eq!(group1.borrow().get_groupname(), Some("another")); + assert_eq!(*member_group2, crate::group::MembershipKind::Primary); + assert_eq!(group2.borrow().get_groupname(), Some("teste")); } } @@ -473,7 +487,15 @@ fn test_parsing_local_database() { let my_passwd_lines = file_to_string(&pwdfile).unwrap(); let my_group_lines = file_to_string(&grpfile).unwrap(); let data = UserDBLocal::import_from_strings(&my_passwd_lines, "", &my_group_lines); - assert_eq!(data.groups.get(0).unwrap().get_groupname().unwrap(), "root"); + assert_eq!( + data.groups + .get(0) + .unwrap() + .borrow() + .get_groupname() + .unwrap(), + "root" + ); } #[test] @@ -495,11 +517,19 @@ fn test_user_db_read_implementation() { 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().unwrap(), + data.get_group_by_name("root") + .unwrap() + .borrow() + .get_gid() + .unwrap(), 0 ); assert_eq!( - data.get_group_by_id(0).unwrap().get_groupname().unwrap(), + data.get_group_by_id(0) + .unwrap() + .borrow() + .get_groupname() + .unwrap(), "root" ); assert!(data.get_user_by_name("norealnameforsure").is_none());