From 310575a8e92f9f8864d54afcc7aa1756fac4199b Mon Sep 17 00:00:00 2001 From: Dietrich Date: Mon, 5 Oct 2020 08:42:26 +0200 Subject: [PATCH 1/6] temporary commit --- src/lib.rs | 5 +++++ src/passwd.rs | 3 +++ src/shadow.rs | 3 ++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 3be952e..b8cd37d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,12 @@ extern crate lazy_static; extern crate log; +pub mod api; +pub mod group; pub mod passwd; pub mod shadow; +pub mod userlib; pub mod userlib_error; +pub use group::Group; pub use passwd::{Gecos, Gid, HomeDir, Passwd, Password, ShellPath, Uid, Username}; +pub use shadow::Shadow; diff --git a/src/passwd.rs b/src/passwd.rs index d55e2da..716810d 100644 --- a/src/passwd.rs +++ b/src/passwd.rs @@ -57,6 +57,7 @@ impl<'a> TryFrom<&'a str> for Username<'a> { pub enum Password<'a> { Encrypted(EncryptedPassword<'a>), Shadow(crate::shadow::Shadow<'a>), + Disabled, } impl Display for Password<'_> { @@ -64,6 +65,7 @@ impl Display for Password<'_> { match self { Password::Encrypted(EncryptedPassword { password }) => write!(f, "{}", password,), Password::Shadow(_) => write!(f, "x"), + Password::Disabled => write!(f, "x"), } } } @@ -380,6 +382,7 @@ impl<'a> Passwd<'a> { match self.password { Password::Encrypted(EncryptedPassword { password }) => password, Password::Shadow(crate::shadow::Shadow { ref password, .. }) => password.password, + Password::Disabled => "x", } } #[must_use] diff --git a/src/shadow.rs b/src/shadow.rs index 329666b..202dc67 100644 --- a/src/shadow.rs +++ b/src/shadow.rs @@ -122,6 +122,7 @@ impl<'a> Shadow<'a> { } const SECONDS_PER_DAY: i64 = 86400; + fn date_since_epoch(days_since_epoch: &str) -> Option { if days_since_epoch.is_empty() { None @@ -141,7 +142,7 @@ fn duration_for_days(days_source: &str) -> Option { } #[test] -fn test_since_epoch() { +fn test_parse_and_back_identity() { println!("Test"); let line = "test:!!$6$/RotIe4VZzzAun4W$7YUONvru1rDnllN5TvrnOMsWUD5wSDUPAD6t6/Xwsr/0QOuWF3HcfAhypRkGa8G1B9qqWV5kZSnCb8GKMN9N61:18260:0:99999:7:::"; let line2 = Shadow::new_from_string(line).unwrap(); -- 2.46.1 From bceb479c789672b4553c6443d76e10baf8f27d80 Mon Sep 17 00:00:00 2001 From: Dietrich Date: Mon, 5 Oct 2020 08:52:21 +0200 Subject: [PATCH 2/6] test2 --- src/api.rs | 22 ++++++++ src/group.rs | 143 +++++++++++++++++++++++++++++++++++++++++++++++++ src/userlib.rs | 57 ++++++++++++++++++++ 3 files changed, 222 insertions(+) create mode 100644 src/api.rs create mode 100644 src/group.rs create mode 100644 src/userlib.rs diff --git a/src/api.rs b/src/api.rs new file mode 100644 index 0000000..3ba0b68 --- /dev/null +++ b/src/api.rs @@ -0,0 +1,22 @@ +trait UserDBRead { + fn get_all_users(&self) -> Vec; + fn get_user_by_name(&self, name: &str) -> Option; + fn get_user_by_id(&self, uid: u64) -> Option; + fn get_all_groups(&self) -> Vec; + fn get_group_by_name(&self) -> Option; + fn get_group_by_id(&self) -> Option; +} + +trait UserDBValidation { + fn is_uid_valid_and_free(&self) -> bool; + fn is_username_valid_and_free(&self) -> bool; + fn is_gid_valid_and_free(&self) -> bool; + fn is_groupname_valid_and_free(&self) -> bool; +} + +trait UserDBWrite { + fn set_user(&self) -> Option; + fn new_user(&self) -> Option; + fn set_group(&self) -> Option; + fn new_group(&self) -> Option; +} diff --git a/src/group.rs b/src/group.rs new file mode 100644 index 0000000..e3e5ffb --- /dev/null +++ b/src/group.rs @@ -0,0 +1,143 @@ +#![warn( + clippy::all, +/* clippy::restriction,*/ + clippy::pedantic, + clippy::nursery, + clippy::cargo +)] +#![allow(clippy::non_ascii_literal)] + +use log::warn; +use regex::Regex; + +use crate::passwd; +use crate::userlib_error::UserLibError; +use std::cmp::Eq; +use std::convert::TryFrom; +use std::fmt::{self, Debug, Display}; + +#[derive(Debug, PartialEq, Eq)] +pub struct Groupname<'a> { + groupname: &'a str, +} + +impl Display for Groupname<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.groupname,) + } +} + +impl<'a> TryFrom<&'a str> for Groupname<'a> { + type Error = UserLibError; + fn try_from(source: &'a str) -> std::result::Result { + lazy_static! { + static ref USERVALIDATION: Regex = + Regex::new("^[a-z_]([a-z0-9_\\-]{0,31}|[a-z0-9_\\-]{0,30}\\$)$").unwrap(); + } + if USERVALIDATION.is_match(source) { + Ok(Self { groupname: source }) + } else if source == "Debian-exim" { + warn!("username {} is not a valid username. This might cause problems. (It is default in Debian and Ubuntu)", source); + Ok(Self { groupname: source }) + } else { + Err(UserLibError::Message(format!( + "Invalid username {}", + source + ))) + } + } +} + +/// A record(line) in the user database `/etc/shadow` found in most linux systems. +#[derive(Debug, PartialEq, Eq)] +pub struct Group<'a> { + groupname: Groupname<'a>, /* Username. */ + pub(crate) password: passwd::Password<'a>, /* Usually not used (disabled with x) */ + gid: passwd::Gid, /* Group ID. */ + members: Vec>, /* Real name. */ +} + +impl<'a> Group<'a> { + #[must_use] + pub const fn get_groupname(&self) -> &'a str { + self.groupname.groupname + } + #[must_use] + pub const fn get_members(&self) -> &Vec> { + &self.members + } +} + +impl<'a> Display for Group<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!( + f, + "{}:{}:{}:{}", + self.groupname, + self.password, + self.gid, + self.members + .iter() + .map(|mem| format!("{}", mem)) + .collect::>() + .join(",") + ) + } +} + +impl<'a> Group<'a> { + /// Parse a line formatted like one in `/etc/shadow` and construct a matching `Shadow` instance + /// + /// # Example + /// ``` + /// /*let shad = adduser::shadow::Shadow::new_from_string( + /// "test:!!$6$/RotIe4VZzzAun4W$7YUONvru1rDnllN5TvrnOMsWUD5wSDUPAD6t6/Xwsr/0QOuWF3HcfAhypRkGa8G1B9qqWV5kZSnCb8GKMN9N61:18260:0:99999:7:::" + /// ).unwrap(); + /// assert_eq!(shad.get_username(), "test");*/ + /// ``` + /// + /// # Errors + /// When parsing fails this function returns a `UserLibError::Message` containing some information as to why the function failed. + pub fn new_from_string(line: &'a str) -> Result { + println!("{}", &line); + let elements: Vec<&str> = line.split(':').collect(); + if elements.len() == 4 { + Ok(Group { + groupname: Groupname::try_from(*elements.get(0).unwrap())?, + password: passwd::Password::Disabled, + gid: passwd::Gid::try_from(*elements.get(2).unwrap())?, + members: parse_members_list(*elements.get(3).unwrap()), + }) + } else { + Err(UserLibError::Message(format!( + "Failed to parse: not enough elements ({}): {:?}", + elements.len(), + elements + ))) + } + } +} + +fn parse_members_list<'a>(source: &'a str) -> Vec> { + let mut res = vec![]; + for mem in source.split(',') { + res.push(passwd::Username::try_from(mem).expect("failed to parse username")); + } + res +} + +#[test] +fn test_parse_and_back_identity() { + println!("Test"); + let line = "teste:x:1002:test,teste"; + let line2 = Group::new_from_string(line).unwrap(); + assert_eq!(format!("{}", line2), line); +} + +#[test] +fn test_groupname() { + println!("Test"); + let line = "teste:x:1002:test,teste"; + let line2 = Group::new_from_string(line).unwrap(); + assert_eq!(line2.get_groupname(), "teste"); +} diff --git a/src/userlib.rs b/src/userlib.rs new file mode 100644 index 0000000..a861749 --- /dev/null +++ b/src/userlib.rs @@ -0,0 +1,57 @@ +#![warn( + clippy::all, +/* clippy::restriction,*/ + clippy::pedantic, + clippy::nursery, + clippy::cargo +)] +#![allow(clippy::non_ascii_literal)] + +use log::warn; +use regex::Regex; + +use crate::userlib_error::UserLibError; +use std::cmp::Eq; +use std::convert::TryFrom; +use std::fmt::{self, Display}; +use std::io::{BufRead, Read}; + +pub struct UserDBLocal<'a> { + pub(crate) passwd_entries: Vec>, + pub(crate) shadow_entries: Vec>, + pub(crate) group_entries: Vec>, +} + +impl<'a> UserDBLocal<'a> { + #[must_use] + pub fn import_from_strings( + passwd_content: &'a str, + shadow_content: &'a str, + group_content: &'a str, + ) -> Self { + let res = UserDBLocal { + passwd_entries: passwd_content + .lines() + .map(|line| crate::Passwd::new_from_string(line).expect("failed to read lines")) + .collect(), + group_entries: group_content + .lines() + .map(|line| crate::Group::new_from_string(line).expect("Parsing failed")) + .collect(), + shadow_entries: shadow_content + .lines() + .map(|line| crate::Shadow::new_from_string(line).expect("Parsing failed")) + .collect(), + }; + res + } +} + +#[test] +fn test_creator_user_db_local() { + let data = UserDBLocal::import_from_strings("testuser: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,teste"); + assert_eq!( + data.passwd_entries.get(0).unwrap().get_username(), + "testuser" + ) +} -- 2.46.1 From 8fa154bdd895ac0d1d7833675037b0c34e433256 Mon Sep 17 00:00:00 2001 From: Dietrich Date: Tue, 6 Oct 2020 13:05:26 +0200 Subject: [PATCH 3/6] restructuring beginn --- src/api.rs | 13 +++++++ src/bin/{main.rs => read_all.rs} | 4 +-- src/{group.rs => group/mod.rs} | 31 ++++++++-------- src/lib.rs | 9 ++--- src/user/mod.rs | 14 ++++++++ src/{passwd.rs => user/passwd_fields.rs} | 7 ++-- src/{shadow.rs => user/shadow_fields.rs} | 23 ++++++------ src/userlib.rs | 45 +++++++++++++++++++++--- 8 files changed, 108 insertions(+), 38 deletions(-) rename src/bin/{main.rs => read_all.rs} (94%) rename src/{group.rs => group/mod.rs} (80%) create mode 100644 src/user/mod.rs rename src/{passwd.rs => user/passwd_fields.rs} (99%) rename src/{shadow.rs => user/shadow_fields.rs} (83%) diff --git a/src/api.rs b/src/api.rs index 3ba0b68..318b9db 100644 --- a/src/api.rs +++ b/src/api.rs @@ -20,3 +20,16 @@ trait UserDBWrite { fn set_group(&self) -> Option; fn new_group(&self) -> Option; } + +trait UserRead { + fn get_username(&self) -> Option; + fn get_uid(&self) -> Option; + fn get_gid(&self) -> Option; + // … +} + +trait UserWrite {} + +trait GroupRead {} + +trait GroupWrite {} diff --git a/src/bin/main.rs b/src/bin/read_all.rs similarity index 94% rename from src/bin/main.rs rename to src/bin/read_all.rs index 59e9a66..fbe0a17 100644 --- a/src/bin/main.rs +++ b/src/bin/read_all.rs @@ -1,7 +1,7 @@ extern crate adduser; -use adduser::passwd::Passwd; -use adduser::shadow::Shadow; +use adduser::Passwd; +use adduser::Shadow; use std::fs::File; use std::io::{prelude::*, BufReader}; diff --git a/src/group.rs b/src/group/mod.rs similarity index 80% rename from src/group.rs rename to src/group/mod.rs index e3e5ffb..351b7ec 100644 --- a/src/group.rs +++ b/src/group/mod.rs @@ -10,7 +10,6 @@ use log::warn; use regex::Regex; -use crate::passwd; use crate::userlib_error::UserLibError; use std::cmp::Eq; use std::convert::TryFrom; @@ -41,7 +40,7 @@ impl<'a> TryFrom<&'a str> for Groupname<'a> { Ok(Self { groupname: source }) } else { Err(UserLibError::Message(format!( - "Invalid username {}", + "Invalid groupname -{}-", source ))) } @@ -51,10 +50,10 @@ impl<'a> TryFrom<&'a str> for Groupname<'a> { /// A record(line) in the user database `/etc/shadow` found in most linux systems. #[derive(Debug, PartialEq, Eq)] pub struct Group<'a> { - groupname: Groupname<'a>, /* Username. */ - pub(crate) password: passwd::Password<'a>, /* Usually not used (disabled with x) */ - gid: passwd::Gid, /* Group ID. */ - members: Vec>, /* Real name. */ + groupname: Groupname<'a>, /* Username. */ + pub(crate) password: crate::Password<'a>, /* Usually not used (disabled with x) */ + gid: crate::Gid, /* Group ID. */ + members: Vec>, /* Real name. */ } impl<'a> Group<'a> { @@ -63,7 +62,7 @@ impl<'a> Group<'a> { self.groupname.groupname } #[must_use] - pub const fn get_members(&self) -> &Vec> { + pub const fn get_members(&self) -> &Vec> { &self.members } } @@ -104,8 +103,8 @@ impl<'a> Group<'a> { if elements.len() == 4 { Ok(Group { groupname: Groupname::try_from(*elements.get(0).unwrap())?, - password: passwd::Password::Disabled, - gid: passwd::Gid::try_from(*elements.get(2).unwrap())?, + password: crate::Password::Disabled, + gid: crate::Gid::try_from(*elements.get(2).unwrap())?, members: parse_members_list(*elements.get(3).unwrap()), }) } else { @@ -118,17 +117,16 @@ impl<'a> Group<'a> { } } -fn parse_members_list<'a>(source: &'a str) -> Vec> { +fn parse_members_list<'a>(source: &'a str) -> Vec> { let mut res = vec![]; - for mem in source.split(',') { - res.push(passwd::Username::try_from(mem).expect("failed to parse username")); + for mem in source.split(',').filter(|x| !x.is_empty()) { + res.push(crate::Username::try_from(mem).expect("failed to parse username")); } res } #[test] fn test_parse_and_back_identity() { - println!("Test"); let line = "teste:x:1002:test,teste"; let line2 = Group::new_from_string(line).unwrap(); assert_eq!(format!("{}", line2), line); @@ -136,8 +134,13 @@ fn test_parse_and_back_identity() { #[test] fn test_groupname() { - println!("Test"); let line = "teste:x:1002:test,teste"; let line2 = Group::new_from_string(line).unwrap(); assert_eq!(line2.get_groupname(), "teste"); } +#[test] +fn test_root_group() { + let line = "root:x:0:"; + let line2 = Group::new_from_string(line).unwrap(); + assert_eq!(line2.get_groupname(), "root"); +} diff --git a/src/lib.rs b/src/lib.rs index b8cd37d..c8e947d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,10 +5,11 @@ extern crate log; pub mod api; pub mod group; -pub mod passwd; -pub mod shadow; +pub mod user; pub mod userlib; pub mod userlib_error; pub use group::Group; -pub use passwd::{Gecos, Gid, HomeDir, Passwd, Password, ShellPath, Uid, Username}; -pub use shadow::Shadow; +pub use user::passwd_fields::{ + EncryptedPassword, Gecos, Gid, HomeDir, Passwd, Password, ShellPath, Uid, Username, +}; +pub use user::shadow_fields::Shadow; diff --git a/src/user/mod.rs b/src/user/mod.rs new file mode 100644 index 0000000..53046cb --- /dev/null +++ b/src/user/mod.rs @@ -0,0 +1,14 @@ +pub mod passwd_fields; +pub mod shadow_fields; +/// A record(line) in the user database `/etc/passwd` found in most linux systems. +#[derive(Debug, PartialEq, Eq)] +pub struct User<'a> { + source: &'a str, + username: crate::Username<'a>, /* Username. */ + password: crate::Password<'a>, /* Hashed passphrase, if shadow database not in use (see shadow.h). */ + uid: crate::Uid, /* User ID. */ + gid: crate::Gid, /* Group ID. */ + gecos: crate::Gecos<'a>, /* Real name. */ + home_dir: crate::HomeDir<'a>, /* Home directory. */ + shell_path: crate::ShellPath<'a>, /* Shell program. */ +} diff --git a/src/passwd.rs b/src/user/passwd_fields.rs similarity index 99% rename from src/passwd.rs rename to src/user/passwd_fields.rs index 716810d..80b251a 100644 --- a/src/passwd.rs +++ b/src/user/passwd_fields.rs @@ -56,7 +56,7 @@ impl<'a> TryFrom<&'a str> for Username<'a> { #[derive(Debug, PartialEq, Eq)] pub enum Password<'a> { Encrypted(EncryptedPassword<'a>), - Shadow(crate::shadow::Shadow<'a>), + Shadow(crate::Shadow<'a>), Disabled, } @@ -333,6 +333,7 @@ impl<'a> TryFrom<&'a str> for ShellPath<'a> { /// A record(line) in the user database `/etc/passwd` found in most linux systems. #[derive(Debug, PartialEq, Eq)] pub struct Passwd<'a> { + source: &'a str, username: Username<'a>, /* Username. */ password: Password<'a>, /* Hashed passphrase, if shadow database not in use (see shadow.h). */ uid: Uid, /* User ID. */ @@ -359,6 +360,7 @@ impl<'a> Passwd<'a> { let elements: Vec<&str> = line.split(':').collect(); if elements.len() == 7 { Ok(Passwd { + source: line, username: Username::try_from(*elements.get(0).unwrap())?, password: Password::Encrypted(EncryptedPassword::try_from( *elements.get(1).unwrap(), @@ -381,7 +383,7 @@ impl<'a> Passwd<'a> { pub const fn get_password(&self) -> &'a str { match self.password { Password::Encrypted(EncryptedPassword { password }) => password, - Password::Shadow(crate::shadow::Shadow { ref password, .. }) => password.password, + Password::Shadow(crate::Shadow { ref password, .. }) => password.password, Password::Disabled => "x", } } @@ -410,6 +412,7 @@ impl<'a> Passwd<'a> { impl Default for Passwd<'_> { fn default() -> Self { Passwd { + source: "", username: Username { username: "defaultuser", }, diff --git a/src/shadow.rs b/src/user/shadow_fields.rs similarity index 83% rename from src/shadow.rs rename to src/user/shadow_fields.rs index 202dc67..aa7c0e4 100644 --- a/src/shadow.rs +++ b/src/user/shadow_fields.rs @@ -9,7 +9,6 @@ use log::warn; -use crate::passwd; use crate::userlib_error::UserLibError; use std::cmp::Eq; use std::convert::TryFrom; @@ -18,15 +17,15 @@ use std::fmt::{self, Debug, Display}; /// A record(line) in the user database `/etc/shadow` found in most linux systems. #[derive(Debug, PartialEq, Eq)] pub struct Shadow<'a> { - username: passwd::Username<'a>, /* Username. */ - pub(crate) password: passwd::EncryptedPassword<'a>, /* Hashed passphrase */ - last_change: Option, /* User ID. */ - earliest_change: Option, /* Group ID. */ - latest_change: Option, /* Real name. */ - warn_period: Option, /* Home directory. */ - deactivated: Option, /* Shell program. */ - deactivated_since: Option, /* Shell program. */ - extensions: Option, /* Shell program. */ + username: crate::Username<'a>, /* Username. */ + pub(crate) password: crate::EncryptedPassword<'a>, /* Hashed passphrase */ + last_change: Option, /* User ID. */ + earliest_change: Option, /* Group ID. */ + latest_change: Option, /* Real name. */ + warn_period: Option, /* Home directory. */ + deactivated: Option, /* Shell program. */ + deactivated_since: Option, /* Shell program. */ + extensions: Option, /* Shell program. */ } impl<'a> Shadow<'a> { @@ -97,8 +96,8 @@ impl<'a> Shadow<'a> { if elements.len() == 9 { let extra = elements.get(8).unwrap(); Ok(Shadow { - username: passwd::Username::try_from(*elements.get(0).unwrap())?, - password: passwd::EncryptedPassword::try_from(*elements.get(1).unwrap())?, + username: crate::Username::try_from(*elements.get(0).unwrap())?, + password: crate::EncryptedPassword::try_from(*elements.get(1).unwrap())?, last_change: date_since_epoch(elements.get(2).unwrap()), earliest_change: date_since_epoch(elements.get(3).unwrap()), latest_change: date_since_epoch(elements.get(4).unwrap()), diff --git a/src/userlib.rs b/src/userlib.rs index a861749..d12dbd6 100644 --- a/src/userlib.rs +++ b/src/userlib.rs @@ -14,7 +14,8 @@ use crate::userlib_error::UserLibError; use std::cmp::Eq; use std::convert::TryFrom; use std::fmt::{self, Display}; -use std::io::{BufRead, Read}; +use std::fs::File; +use std::io::{BufRead, BufReader, Read}; pub struct UserDBLocal<'a> { pub(crate) passwd_entries: Vec>, @@ -32,15 +33,34 @@ impl<'a> UserDBLocal<'a> { let res = UserDBLocal { passwd_entries: passwd_content .lines() - .map(|line| crate::Passwd::new_from_string(line).expect("failed to read lines")) + .filter_map(|line| { + if line.len() > 5 { + println!("{}", line); + Some(crate::Passwd::new_from_string(line).expect("failed to read lines")) + } else { + None + } + }) .collect(), group_entries: group_content .lines() - .map(|line| crate::Group::new_from_string(line).expect("Parsing failed")) + .filter_map(|line| { + if line.len() > 5 { + Some(crate::Group::new_from_string(line).expect("Parsing failed")) + } else { + None + } + }) .collect(), shadow_entries: shadow_content .lines() - .map(|line| crate::Shadow::new_from_string(line).expect("Parsing failed")) + .filter_map(|line| { + if line.len() > 5 { + Some(crate::Shadow::new_from_string(line).expect("Parsing failed")) + } else { + None + } + }) .collect(), }; res @@ -55,3 +75,20 @@ fn test_creator_user_db_local() { "testuser" ) } + +#[test] +fn test_parsing_local_database() { + let passwd_file = File::open("/etc/passwd").unwrap(); + let mut passwd_reader = BufReader::new(passwd_file); + let mut my_passwd_lines = "".to_string(); + passwd_reader.read_to_string(&mut my_passwd_lines).unwrap(); + let group_file = File::open("/etc/group").unwrap(); + let mut group_reader = BufReader::new(group_file); + let mut my_group_lines = "".to_string(); + group_reader.read_to_string(&mut my_group_lines).unwrap(); + let data = UserDBLocal::import_from_strings(&my_passwd_lines, "", &my_group_lines); + assert_eq!( + format!("{}", data.group_entries.get(0).unwrap().get_groupname()), + "teste" + ); +} -- 2.46.1 From 5cff7bf64b68a48f5ac39054d0c25e6e7bdfa965 Mon Sep 17 00:00:00 2001 From: Dietrich Date: Tue, 6 Oct 2020 21:43:12 +0200 Subject: [PATCH 4/6] complete the move --- src/api.rs | 16 +- src/bin/read_all.rs | 8 +- src/lib.rs | 5 +- src/user/gecos_fields.rs | 223 ++++++++++++++++++++ src/user/mod.rs | 178 ++++++++++++++++ src/user/passwd_fields.rs | 419 +------------------------------------- src/user/shadow_fields.rs | 2 +- src/userlib.rs | 23 +-- 8 files changed, 432 insertions(+), 442 deletions(-) create mode 100644 src/user/gecos_fields.rs diff --git a/src/api.rs b/src/api.rs index 318b9db..8c15df4 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,7 +1,7 @@ trait UserDBRead { - fn get_all_users(&self) -> Vec; - fn get_user_by_name(&self, name: &str) -> Option; - fn get_user_by_id(&self, uid: u64) -> Option; + fn get_all_users(&self) -> Vec; + fn get_user_by_name(&self, name: &str) -> Option; + fn get_user_by_id(&self, uid: u64) -> Option; fn get_all_groups(&self) -> Vec; fn get_group_by_name(&self) -> Option; fn get_group_by_id(&self) -> Option; @@ -15,16 +15,16 @@ trait UserDBValidation { } trait UserDBWrite { - fn set_user(&self) -> Option; - fn new_user(&self) -> Option; + fn set_user(&self) -> Option; + fn new_user(&self) -> Option; fn set_group(&self) -> Option; fn new_group(&self) -> Option; } trait UserRead { - fn get_username(&self) -> Option; - fn get_uid(&self) -> Option; - fn get_gid(&self) -> Option; + fn get_username(&self) -> Option; + fn get_uid(&self) -> Option; + fn get_gid(&self) -> Option; // … } diff --git a/src/bin/read_all.rs b/src/bin/read_all.rs index fbe0a17..27056b1 100644 --- a/src/bin/read_all.rs +++ b/src/bin/read_all.rs @@ -1,7 +1,7 @@ extern crate adduser; -use adduser::Passwd; use adduser::Shadow; +use adduser::User; use std::fs::File; use std::io::{prelude::*, BufReader}; @@ -18,15 +18,15 @@ fn main() { for line in reader.lines() { let line = line.unwrap(); println!("{}", line); - println!("{}", Passwd::new_from_string(&line).unwrap()); + println!("{}", User::new_from_string(&line).unwrap()); } let line = "test:!!$6$/RotIe4VZzzAun4W$7YUONvru1rDnllN5TvrnOMsWUD5wSDUPAD6t6/Xwsr/0QOuWF3HcfAhypRkGa8G1B9qqWV5kZSnCb8GKMN9N61:18260:0:99999:7:::"; assert_eq!(format!("{}", Shadow::new_from_string(line).unwrap()), line); - // let pwd = Passwd::default(); + // let pwd = User::default(); // let pwd2 = - // Passwd::new_from_string("howdy:notencrypted:1001:1001:not done:/home/test:/bin/bash"); + // User::new_from_string("howdy:notencrypted:1001:1001:not done:/home/test:/bin/bash"); // println!("Test struct: {}", pwd); // assert_eq!(pwd, pwd2.unwrap()) diff --git a/src/lib.rs b/src/lib.rs index c8e947d..d19baf2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,10 @@ pub mod user; pub mod userlib; pub mod userlib_error; pub use group::Group; +pub use user::gecos_fields::Gecos; pub use user::passwd_fields::{ - EncryptedPassword, Gecos, Gid, HomeDir, Passwd, Password, ShellPath, Uid, Username, + EncryptedPassword, Gid, HomeDir, Password, ShellPath, Uid, Username, }; pub use user::shadow_fields::Shadow; +pub use user::User; +pub use userlib_error::UserLibError; diff --git a/src/user/gecos_fields.rs b/src/user/gecos_fields.rs new file mode 100644 index 0000000..d6fc0fa --- /dev/null +++ b/src/user/gecos_fields.rs @@ -0,0 +1,223 @@ +use crate::UserLibError; +use std::cmp::Eq; +use std::convert::TryFrom; +use std::fmt::{self, Display}; +/// The gecos field of a user. +/// +/// In the `/etc/passwd` file this field is a `,` sepparated list of items. +/// The first 4 values are more or less standardised to be full name, room, phone at work and phone at home. After that there can be some extra fields often containing the emailadress and even additional information. +/// +/// This enum represents the first 4 values by name and adds the other values to a list of strings [`Gecos::Detail`]. If only one field is found and no `,` at all this value is used as a human readable comment [`Gecos::Simple`]. +#[derive(Debug, PartialEq, Eq)] +pub enum Gecos<'a> { + Detail { + full_name: &'a str, + room: &'a str, + phone_work: &'a str, + phone_home: &'a str, + other: Option>, + }, + Simple { + comment: &'a str, + }, +} + +impl<'a> Gecos<'a> { + #[must_use] + pub const fn get_comment(&'a self) -> Option<&'a str> { + match *self { + Gecos::Simple { comment, .. } => Some(comment), + Gecos::Detail { .. } => None, + } + } + #[must_use] + pub const fn get_full_name(&'a self) -> Option<&'a str> { + match *self { + Gecos::Simple { .. } => None, + Gecos::Detail { full_name, .. } => { + if full_name.is_empty() { + None + } else { + Some(full_name) + } + } + } + } + #[must_use] + pub const fn get_room(&'a self) -> Option<&'a str> { + match *self { + Gecos::Simple { .. } => None, + Gecos::Detail { room, .. } => { + if room.is_empty() { + None + } else { + Some(room) + } + } + } + } + #[must_use] + pub const fn get_phone_work(&'a self) -> Option<&'a str> { + match *self { + Gecos::Simple { .. } => None, + Gecos::Detail { phone_work, .. } => { + if phone_work.is_empty() { + None + } else { + Some(phone_work) + } + } + } + } + #[must_use] + pub const fn get_phone_home(&'a self) -> Option<&'a str> { + match *self { + Gecos::Simple { .. } => None, + Gecos::Detail { phone_home, .. } => { + if phone_home.is_empty() { + None + } else { + Some(phone_home) + } + } + } + } + #[must_use] + pub const fn get_other(&'a self) -> Option<&Vec<&'a str>> { + match self { + Gecos::Simple { .. } => None, + Gecos::Detail { other, .. } => match other { + None => None, + Some(comments) => Some(comments), + }, + } + } +} + +impl Display for Gecos<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self { + Gecos::Simple { comment } => write!(f, "{}", comment), + Gecos::Detail { + full_name, + room, + phone_work, + phone_home, + other, + } => write!( + f, + "{},{},{},{}{}", + full_name, + room, + phone_work, + phone_home, + match other { + None => "".to_string(), + Some(cont) => format!(",{}", cont.join(",")), + } + ), + } + } +} + +impl<'a> TryFrom<&'a str> for Gecos<'a> { + type Error = UserLibError; + fn try_from(source: &'a str) -> std::result::Result { + let vals: Vec<&str> = source.split(',').collect(); + if vals.len() > 3 { + Ok(Gecos::Detail { + full_name: vals[0], + room: vals[1], + phone_work: vals[2], + phone_home: vals[3], + other: if vals.len() == 4 { + None + } else { + Some(vals[4..].to_vec()) + }, + }) + } else if vals.len() == 1 { + Ok(Gecos::Simple { + comment: vals.get(0).unwrap(), + }) + } else { + panic!(format!("Could not parse this string: {}", source)) + } + } +} + +#[test] +fn test_parse_gecos() { + // test if the Gecos field can be parsed and the resulting struct is populated correctly. + let gcdetail = "Full Name,504,11345342,ä1-2312,myemail@test.com"; + let gcsimple = "A böring comment →"; + let gc_no_other: &str = "systemd Network Management,,,"; + let res_detail = crate::Gecos::try_from(gcdetail).unwrap(); + let res_simple = crate::Gecos::try_from(gcsimple).unwrap(); + let res_no_other = crate::Gecos::try_from(gc_no_other).unwrap(); + match res_simple { + crate::Gecos::Simple { comment } => assert_eq!(comment, "A böring comment →"), + _ => unreachable!(), + } + match res_detail { + crate::Gecos::Detail { + full_name, + room, + phone_work, + phone_home, + other, + } => { + assert_eq!(full_name, "Full Name"); + assert_eq!(room, "504"); + assert_eq!(phone_work, "11345342"); + assert_eq!(phone_home, "ä1-2312"); + assert_eq!(other.unwrap()[0], "myemail@test.com"); + } + _ => unreachable!(), + } + match res_no_other { + crate::Gecos::Detail { + full_name, + room, + phone_work, + phone_home, + other, + } => { + assert_eq!(full_name, "systemd Network Management"); + assert_eq!(room, ""); + assert_eq!(phone_work, ""); + assert_eq!(phone_home, ""); + assert_eq!(other, None); + } + _ => unreachable!(), + } +} + +#[test] +fn test_gecos_getters() { + // test if the Gecos field can be parsed and the resulting struct is populated correctly. + let gcdetail = "Full Name,504,11345342,ä1-2312,myemail@test.com"; + let gcsimple = "A böring comment →"; + let gc_no_other: &str = "systemd Network Management,,,"; + let res_detail = crate::Gecos::try_from(gcdetail).unwrap(); + let res_simple = crate::Gecos::try_from(gcsimple).unwrap(); + let res_no_other = crate::Gecos::try_from(gc_no_other).unwrap(); + assert_eq!(res_simple.get_comment(), Some("A böring comment →")); + + assert_eq!(res_detail.get_comment(), None); + println!("{:?}", res_detail); + assert_eq!(res_detail.get_full_name(), Some("Full Name")); + assert_eq!(res_detail.get_room(), Some("504")); + assert_eq!(res_detail.get_phone_work(), Some("11345342")); + assert_eq!(res_detail.get_phone_home(), Some("ä1-2312")); + assert_eq!(res_detail.get_other(), Some(&vec!["myemail@test.com"])); + + assert_eq!( + res_no_other.get_full_name(), + Some("systemd Network Management") + ); + assert_eq!(res_no_other.get_room(), None); + assert_eq!(res_no_other.get_phone_work(), None); + assert_eq!(res_no_other.get_phone_home(), None); + assert_eq!(res_no_other.get_other(), None); +} diff --git a/src/user/mod.rs b/src/user/mod.rs index 53046cb..a91bbbb 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -1,5 +1,10 @@ +pub mod gecos_fields; pub mod passwd_fields; pub mod shadow_fields; + +use std::convert::TryFrom; +use std::fmt::{self, Display}; + /// A record(line) in the user database `/etc/passwd` found in most linux systems. #[derive(Debug, PartialEq, Eq)] pub struct User<'a> { @@ -12,3 +17,176 @@ pub struct User<'a> { home_dir: crate::HomeDir<'a>, /* Home directory. */ shell_path: crate::ShellPath<'a>, /* Shell program. */ } + +impl<'a> User<'a> { + /// Parse a line formatted like one in `/etc/passwd` and construct a matching [`adduser::User`] instance + /// + /// # Example + /// ``` + /// let pwd = adduser::User::new_from_string( + /// "testuser:testpassword:1001:1001:full Name,,,,:/home/test:/bin/test" + /// ).unwrap(); + /// assert_eq!(pwd.get_username(), "testuser"); + /// ``` + /// + /// # Errors + /// When parsing fails this function returns a `UserLibError::Message` containing some information as to why the function failed. + pub fn new_from_string(line: &'a str) -> Result { + let elements: Vec<&str> = line.split(':').collect(); + if elements.len() == 7 { + Ok(Self { + source: line, + username: crate::Username::try_from(*elements.get(0).unwrap())?, + password: crate::Password::Encrypted(crate::EncryptedPassword::try_from( + *elements.get(1).unwrap(), + )?), + uid: crate::Uid::try_from(*elements.get(2).unwrap())?, + gid: crate::Gid::try_from(*elements.get(3).unwrap())?, + gecos: crate::Gecos::try_from(*elements.get(4).unwrap())?, + home_dir: crate::HomeDir::try_from(*elements.get(5).unwrap())?, + shell_path: crate::ShellPath::try_from(*elements.get(6).unwrap())?, + }) + } else { + Err("Failed to parse: not enough elements".into()) + } + } + #[must_use] + pub const fn get_username(&self) -> &'a str { + self.username.username + } + #[must_use] + pub const fn get_password(&self) -> &'a str { + match self.password { + crate::Password::Encrypted(crate::EncryptedPassword { password }) => password, + crate::Password::Shadow(crate::Shadow { ref password, .. }) => password.password, + crate::Password::Disabled => "x", + } + } + #[must_use] + pub const fn get_uid(&self) -> u32 { + self.uid.uid + } + #[must_use] + pub const fn get_gid(&self) -> u32 { + self.gid.gid + } + #[must_use] + pub const fn get_comment(&self) -> &crate::Gecos { + &self.gecos + } + #[must_use] + pub const fn get_home_dir(&self) -> &'a str { + self.home_dir.dir + } + #[must_use] + pub const fn get_shell_path(&self) -> &'a str { + self.shell_path.shell + } +} + +impl Default for User<'_> { + fn default() -> Self { + Self { + source: "", + username: crate::Username { + username: "defaultuser", + }, + password: crate::Password::Encrypted(crate::EncryptedPassword { + password: "notencrypted", + }), + uid: crate::Uid { uid: 1001 }, + gid: crate::Gid { gid: 1001 }, + gecos: crate::Gecos::Simple { + comment: "gecos default comment", + }, + home_dir: crate::HomeDir { + dir: "/home/default", + }, + shell_path: crate::ShellPath { shell: "/bin/bash" }, + } + } +} + +impl Display for User<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}:{}:{}:{}:{}:{}:{}", + self.username, + self.password, + self.uid, + self.gid, + self.gecos, + self.home_dir, + self.shell_path + ) + } +} + +#[test] +fn test_default_user() { + // Check if a user can be created. + let pwd = User::default(); + assert_eq!(pwd.username.username, "defaultuser"); + assert_eq!(pwd.home_dir.dir, "/home/default"); + assert_eq!(pwd.uid.uid, 1001); +} + +#[test] +fn test_new_from_string() { + // Test if a single line can be parsed and if the resulting struct is populated correctly. + let fail = User::new_from_string("").err().unwrap(); + assert_eq!( + fail, + crate::UserLibError::Message("Failed to parse: not enough elements".into()) + ); + let pwd = + User::new_from_string("testuser:testpassword:1001:1001:testcomment:/home/test:/bin/test") + .unwrap(); + let pwd2 = + User::new_from_string("testuser:testpassword:1001:1001:full Name,004,000342,001-2312,myemail@test.com:/home/test:/bin/test") + .unwrap(); + assert_eq!(pwd.username.username, "testuser"); + assert_eq!(pwd.home_dir.dir, "/home/test"); + assert_eq!(pwd.uid.uid, 1001); + match pwd.gecos { + crate::Gecos::Simple { comment } => assert_eq!(comment, "testcomment"), + _ => unreachable!(), + } + match pwd2.gecos { + crate::Gecos::Detail { + full_name, + room, + phone_work, + phone_home, + other, + } => { + assert_eq!(full_name, "full Name"); + assert_eq!(room, "004"); + assert_eq!(phone_work, "000342"); + assert_eq!(phone_home, "001-2312"); + assert_eq!(other.unwrap()[0], "myemail@test.com"); + } + _ => unreachable!(), + } +} + +#[test] +fn test_parse_passwd() { + // Test wether the passwd file can be parsed and recreated without throwing an exception + use std::fs::File; + use std::io::{prelude::*, BufReader}; + let file = File::open("/etc/passwd").unwrap(); + let reader = BufReader::new(file); + + for line in reader.lines() { + let lineorig: String = line.unwrap(); + let linecopy = lineorig.clone(); + let pass_struc = User::new_from_string(&linecopy).unwrap(); + assert_eq!( + // ignoring the numbers of `,` since the implementation does not (yet) reproduce a missing comment field. + lineorig, + format!("{}", pass_struc) + ); + } +} diff --git a/src/user/passwd_fields.rs b/src/user/passwd_fields.rs index 80b251a..69f2ee4 100644 --- a/src/user/passwd_fields.rs +++ b/src/user/passwd_fields.rs @@ -23,7 +23,7 @@ use std::fmt::{self, Display}; #[derive(Debug, PartialEq, Eq)] pub struct Username<'a> { /// The username value - pub(crate) username: &'a str, + pub(in crate::user) username: &'a str, } impl Display for Username<'_> { @@ -55,7 +55,7 @@ impl<'a> TryFrom<&'a str> for Username<'a> { #[derive(Debug, PartialEq, Eq)] pub enum Password<'a> { - Encrypted(EncryptedPassword<'a>), + Encrypted(crate::EncryptedPassword<'a>), Shadow(crate::Shadow<'a>), Disabled, } @@ -72,7 +72,7 @@ impl Display for Password<'_> { #[derive(Debug, PartialEq, Eq)] pub struct EncryptedPassword<'a> { - pub(crate) password: &'a str, + pub(in crate::user) password: &'a str, } impl Display for EncryptedPassword<'_> { @@ -95,7 +95,7 @@ impl<'a> TryFrom<&'a str> for EncryptedPassword<'a> { #[derive(Debug, PartialEq, Eq)] pub struct Uid { - uid: u32, + pub(in crate::user) uid: u32, } impl Display for Uid { @@ -123,7 +123,7 @@ impl Uid { #[derive(Debug, PartialEq, Eq)] pub struct Gid { - gid: u32, + pub(in crate::user) gid: u32, } impl Display for Gid { @@ -149,153 +149,10 @@ impl Gid { } } -/// The gecos field of a user. -/// -/// In the `/etc/passwd` file this field is a `,` sepparated list of items. -/// The first 4 values are more or less standardised to be full name, room, phone at work and phone at home. After that there can be some extra fields often containing the emailadress and even additional information. -/// -/// This enum represents the first 4 values by name and adds the other values to a list of strings [`Gecos::Detail`]. If only one field is found and no `,` at all this value is used as a human readable comment [`Gecos::Simple`]. -#[derive(Debug, PartialEq, Eq)] -pub enum Gecos<'a> { - Detail { - full_name: &'a str, - room: &'a str, - phone_work: &'a str, - phone_home: &'a str, - other: Option>, - }, - Simple { - comment: &'a str, - }, -} - -impl<'a> Gecos<'a> { - #[must_use] - pub const fn get_comment(&'a self) -> Option<&'a str> { - match *self { - Gecos::Simple { comment, .. } => Some(comment), - Gecos::Detail { .. } => None, - } - } - #[must_use] - pub const fn get_full_name(&'a self) -> Option<&'a str> { - match *self { - Gecos::Simple { .. } => None, - Gecos::Detail { full_name, .. } => { - if full_name.is_empty() { - None - } else { - Some(full_name) - } - } - } - } - #[must_use] - pub const fn get_room(&'a self) -> Option<&'a str> { - match *self { - Gecos::Simple { .. } => None, - Gecos::Detail { room, .. } => { - if room.is_empty() { - None - } else { - Some(room) - } - } - } - } - #[must_use] - pub const fn get_phone_work(&'a self) -> Option<&'a str> { - match *self { - Gecos::Simple { .. } => None, - Gecos::Detail { phone_work, .. } => { - if phone_work.is_empty() { - None - } else { - Some(phone_work) - } - } - } - } - #[must_use] - pub const fn get_phone_home(&'a self) -> Option<&'a str> { - match *self { - Gecos::Simple { .. } => None, - Gecos::Detail { phone_home, .. } => { - if phone_home.is_empty() { - None - } else { - Some(phone_home) - } - } - } - } - #[must_use] - pub const fn get_other(&'a self) -> Option<&Vec<&'a str>> { - match self { - Gecos::Simple { .. } => None, - Gecos::Detail { other, .. } => match other { - None => None, - Some(comments) => Some(comments), - }, - } - } -} - -impl Display for Gecos<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self { - Gecos::Simple { comment } => write!(f, "{}", comment), - Gecos::Detail { - full_name, - room, - phone_work, - phone_home, - other, - } => write!( - f, - "{},{},{},{}{}", - full_name, - room, - phone_work, - phone_home, - match other { - None => "".to_string(), - Some(cont) => format!(",{}", cont.join(",")), - } - ), - } - } -} - -impl<'a> TryFrom<&'a str> for Gecos<'a> { - type Error = UserLibError; - fn try_from(source: &'a str) -> std::result::Result { - let vals: Vec<&str> = source.split(',').collect(); - if vals.len() > 3 { - Ok(Gecos::Detail { - full_name: vals[0], - room: vals[1], - phone_work: vals[2], - phone_home: vals[3], - other: if vals.len() == 4 { - None - } else { - Some(vals[4..].to_vec()) - }, - }) - } else if vals.len() == 1 { - Ok(Gecos::Simple { - comment: vals.get(0).unwrap(), - }) - } else { - panic!(format!("Could not parse this string: {}", source)) - } - } -} /// The home directory of a user #[derive(Debug, PartialEq, Eq)] pub struct HomeDir<'a> { - dir: &'a str, + pub(in crate::user) dir: &'a str, } impl Display for HomeDir<'_> { @@ -314,7 +171,7 @@ impl<'a> TryFrom<&'a str> for HomeDir<'a> { /// The path to the Shell binary #[derive(Debug, PartialEq, Eq)] pub struct ShellPath<'a> { - shell: &'a str, + pub(in crate::user) shell: &'a str, } impl Display for ShellPath<'_> { @@ -330,124 +187,6 @@ impl<'a> TryFrom<&'a str> for ShellPath<'a> { } } -/// A record(line) in the user database `/etc/passwd` found in most linux systems. -#[derive(Debug, PartialEq, Eq)] -pub struct Passwd<'a> { - source: &'a str, - username: Username<'a>, /* Username. */ - password: Password<'a>, /* Hashed passphrase, if shadow database not in use (see shadow.h). */ - uid: Uid, /* User ID. */ - gid: Gid, /* Group ID. */ - gecos: Gecos<'a>, /* Real name. */ - home_dir: HomeDir<'a>, /* Home directory. */ - shell_path: ShellPath<'a>, /* Shell program. */ -} - -impl<'a> Passwd<'a> { - /// Parse a line formatted like one in `/etc/passwd` and construct a matching `Passwd` instance - /// - /// # Example - /// ``` - /// let pwd = adduser::passwd::Passwd::new_from_string( - /// "testuser:testpassword:1001:1001:full Name,,,,:/home/test:/bin/test" - /// ).unwrap(); - /// assert_eq!(pwd.get_username(), "testuser"); - /// ``` - /// - /// # Errors - /// When parsing fails this function returns a `UserLibError::Message` containing some information as to why the function failed. - pub fn new_from_string(line: &'a str) -> Result { - let elements: Vec<&str> = line.split(':').collect(); - if elements.len() == 7 { - Ok(Passwd { - source: line, - username: Username::try_from(*elements.get(0).unwrap())?, - password: Password::Encrypted(EncryptedPassword::try_from( - *elements.get(1).unwrap(), - )?), - uid: Uid::try_from(*elements.get(2).unwrap())?, - gid: Gid::try_from(*elements.get(3).unwrap())?, - gecos: Gecos::try_from(*elements.get(4).unwrap())?, - home_dir: HomeDir::try_from(*elements.get(5).unwrap())?, - shell_path: ShellPath::try_from(*elements.get(6).unwrap())?, - }) - } else { - Err("Failed to parse: not enough elements".into()) - } - } - #[must_use] - pub const fn get_username(&self) -> &'a str { - self.username.username - } - #[must_use] - pub const fn get_password(&self) -> &'a str { - match self.password { - Password::Encrypted(EncryptedPassword { password }) => password, - Password::Shadow(crate::Shadow { ref password, .. }) => password.password, - Password::Disabled => "x", - } - } - #[must_use] - pub const fn get_uid(&self) -> u32 { - self.uid.uid - } - #[must_use] - pub const fn get_gid(&self) -> u32 { - self.gid.gid - } - #[must_use] - pub const fn get_comment(&self) -> &Gecos { - &self.gecos - } - #[must_use] - pub const fn get_home_dir(&self) -> &'a str { - self.home_dir.dir - } - #[must_use] - pub const fn get_shell_path(&self) -> &'a str { - self.shell_path.shell - } -} - -impl Default for Passwd<'_> { - fn default() -> Self { - Passwd { - source: "", - username: Username { - username: "defaultuser", - }, - password: Password::Encrypted(EncryptedPassword { - password: "notencrypted", - }), - uid: Uid { uid: 1001 }, - gid: Gid { gid: 1001 }, - gecos: Gecos::Simple { - comment: "gecos default comment", - }, - home_dir: HomeDir { - dir: "/home/default", - }, - shell_path: ShellPath { shell: "/bin/bash" }, - } - } -} - -impl Display for Passwd<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}:{}:{}:{}:{}:{}:{}", - self.username, - self.password, - self.uid, - self.gid, - self.gecos, - self.home_dir, - self.shell_path - ) - } -} - // Tests ---------------------------------------------------------------------- #[test] @@ -488,15 +227,6 @@ fn test_username_validation() { assert_eq!(normal.unwrap().username, "enya$"); } -#[test] -fn test_default_user() { - // Check if a user can be created. - let pwd = Passwd::default(); - assert_eq!(pwd.username.username, "defaultuser"); - assert_eq!(pwd.home_dir.dir, "/home/default"); - assert_eq!(pwd.uid.uid, 1001); -} - #[test] fn test_guid_system_user() { // Check uids of system users. @@ -506,138 +236,3 @@ fn test_guid_system_user() { assert_eq!(Gid::try_from(val.0).unwrap().is_system_gid(), val.1); } } - -#[test] -fn test_parse_gecos() { - // test if the Gecos field can be parsed and the resulting struct is populated correctly. - let gcdetail = "Full Name,504,11345342,ä1-2312,myemail@test.com"; - let gcsimple = "A böring comment →"; - let gc_no_other: &str = "systemd Network Management,,,"; - let res_detail = Gecos::try_from(gcdetail).unwrap(); - let res_simple = Gecos::try_from(gcsimple).unwrap(); - let res_no_other = Gecos::try_from(gc_no_other).unwrap(); - match res_simple { - Gecos::Simple { comment } => assert_eq!(comment, "A böring comment →"), - _ => unreachable!(), - } - match res_detail { - Gecos::Detail { - full_name, - room, - phone_work, - phone_home, - other, - } => { - assert_eq!(full_name, "Full Name"); - assert_eq!(room, "504"); - assert_eq!(phone_work, "11345342"); - assert_eq!(phone_home, "ä1-2312"); - assert_eq!(other.unwrap()[0], "myemail@test.com"); - } - _ => unreachable!(), - } - match res_no_other { - Gecos::Detail { - full_name, - room, - phone_work, - phone_home, - other, - } => { - assert_eq!(full_name, "systemd Network Management"); - assert_eq!(room, ""); - assert_eq!(phone_work, ""); - assert_eq!(phone_home, ""); - assert_eq!(other, None); - } - _ => unreachable!(), - } -} - -#[test] -fn test_gecos_getters() { - // test if the Gecos field can be parsed and the resulting struct is populated correctly. - let gcdetail = "Full Name,504,11345342,ä1-2312,myemail@test.com"; - let gcsimple = "A böring comment →"; - let gc_no_other: &str = "systemd Network Management,,,"; - let res_detail = Gecos::try_from(gcdetail).unwrap(); - let res_simple = Gecos::try_from(gcsimple).unwrap(); - let res_no_other = Gecos::try_from(gc_no_other).unwrap(); - assert_eq!(res_simple.get_comment(), Some("A böring comment →")); - - assert_eq!(res_detail.get_comment(), None); - println!("{:?}", res_detail); - assert_eq!(res_detail.get_full_name(), Some("Full Name")); - assert_eq!(res_detail.get_room(), Some("504")); - assert_eq!(res_detail.get_phone_work(), Some("11345342")); - assert_eq!(res_detail.get_phone_home(), Some("ä1-2312")); - assert_eq!(res_detail.get_other(), Some(&vec!["myemail@test.com"])); - - assert_eq!( - res_no_other.get_full_name(), - Some("systemd Network Management") - ); - assert_eq!(res_no_other.get_room(), None); - assert_eq!(res_no_other.get_phone_work(), None); - assert_eq!(res_no_other.get_phone_home(), None); - assert_eq!(res_no_other.get_other(), None); -} - -#[test] -fn test_new_from_string() { - // Test if a single line can be parsed and if the resulting struct is populated correctly. - let fail = Passwd::new_from_string("").err().unwrap(); - assert_eq!( - fail, - UserLibError::Message("Failed to parse: not enough elements".into()) - ); - let pwd = - Passwd::new_from_string("testuser:testpassword:1001:1001:testcomment:/home/test:/bin/test") - .unwrap(); - let pwd2 = - Passwd::new_from_string("testuser:testpassword:1001:1001:full Name,004,000342,001-2312,myemail@test.com:/home/test:/bin/test") - .unwrap(); - assert_eq!(pwd.username.username, "testuser"); - assert_eq!(pwd.home_dir.dir, "/home/test"); - assert_eq!(pwd.uid.uid, 1001); - match pwd.gecos { - Gecos::Simple { comment } => assert_eq!(comment, "testcomment"), - _ => unreachable!(), - } - match pwd2.gecos { - Gecos::Detail { - full_name, - room, - phone_work, - phone_home, - other, - } => { - assert_eq!(full_name, "full Name"); - assert_eq!(room, "004"); - assert_eq!(phone_work, "000342"); - assert_eq!(phone_home, "001-2312"); - assert_eq!(other.unwrap()[0], "myemail@test.com"); - } - _ => unreachable!(), - } -} - -#[test] -fn test_parse_passwd() { - // Test wether the passwd file can be parsed and recreated without throwing an exception - use std::fs::File; - use std::io::{prelude::*, BufReader}; - let file = File::open("/etc/passwd").unwrap(); - let reader = BufReader::new(file); - - for line in reader.lines() { - let lineorig: String = line.unwrap(); - let linecopy = lineorig.clone(); - let pass_struc = Passwd::new_from_string(&linecopy).unwrap(); - assert_eq!( - // ignoring the numbers of `,` since the implementation does not (yet) reproduce a missing comment field. - lineorig, - format!("{}", pass_struc) - ); - } -} diff --git a/src/user/shadow_fields.rs b/src/user/shadow_fields.rs index aa7c0e4..12943e1 100644 --- a/src/user/shadow_fields.rs +++ b/src/user/shadow_fields.rs @@ -82,7 +82,7 @@ impl<'a> Shadow<'a> { /// /// # Example /// ``` - /// let shad = adduser::shadow::Shadow::new_from_string( + /// let shad = adduser::Shadow::new_from_string( /// "test:!!$6$/RotIe4VZzzAun4W$7YUONvru1rDnllN5TvrnOMsWUD5wSDUPAD6t6/Xwsr/0QOuWF3HcfAhypRkGa8G1B9qqWV5kZSnCb8GKMN9N61:18260:0:99999:7:::" /// ).unwrap(); /// assert_eq!(shad.get_username(), "test"); diff --git a/src/userlib.rs b/src/userlib.rs index d12dbd6..18340b8 100644 --- a/src/userlib.rs +++ b/src/userlib.rs @@ -8,19 +8,11 @@ #![allow(clippy::non_ascii_literal)] use log::warn; -use regex::Regex; - -use crate::userlib_error::UserLibError; -use std::cmp::Eq; -use std::convert::TryFrom; -use std::fmt::{self, Display}; -use std::fs::File; -use std::io::{BufRead, BufReader, Read}; pub struct UserDBLocal<'a> { - pub(crate) passwd_entries: Vec>, - pub(crate) shadow_entries: Vec>, - pub(crate) group_entries: Vec>, + pub passwd_entries: Vec>, + pub shadow_entries: Vec>, + pub group_entries: Vec>, } impl<'a> UserDBLocal<'a> { @@ -36,7 +28,7 @@ impl<'a> UserDBLocal<'a> { .filter_map(|line| { if line.len() > 5 { println!("{}", line); - Some(crate::Passwd::new_from_string(line).expect("failed to read lines")) + Some(crate::User::new_from_string(line).expect("failed to read lines")) } else { None } @@ -78,6 +70,8 @@ fn test_creator_user_db_local() { #[test] fn test_parsing_local_database() { + use std::fs::File; + use std::io::{BufReader, Read}; let passwd_file = File::open("/etc/passwd").unwrap(); let mut passwd_reader = BufReader::new(passwd_file); let mut my_passwd_lines = "".to_string(); @@ -87,8 +81,5 @@ fn test_parsing_local_database() { let mut my_group_lines = "".to_string(); group_reader.read_to_string(&mut my_group_lines).unwrap(); let data = UserDBLocal::import_from_strings(&my_passwd_lines, "", &my_group_lines); - assert_eq!( - format!("{}", data.group_entries.get(0).unwrap().get_groupname()), - "teste" - ); + assert_eq!(data.group_entries.get(0).unwrap().get_groupname(), "root"); } -- 2.46.1 From a645ebac75d304759a2264690861932ea50018e5 Mon Sep 17 00:00:00 2001 From: Dietrich Date: Wed, 7 Oct 2020 09:30:59 +0200 Subject: [PATCH 5/6] lifetime error --- src/bin/read_all.rs | 1 - src/group/mod.rs | 5 +-- src/user/mod.rs | 11 ++++-- src/user/shadow_fields.rs | 5 +-- src/userlib.rs | 72 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 87 insertions(+), 7 deletions(-) diff --git a/src/bin/read_all.rs b/src/bin/read_all.rs index 27056b1..7cc05ca 100644 --- a/src/bin/read_all.rs +++ b/src/bin/read_all.rs @@ -17,7 +17,6 @@ fn main() { for line in reader.lines() { let line = line.unwrap(); - println!("{}", line); println!("{}", User::new_from_string(&line).unwrap()); } diff --git a/src/group/mod.rs b/src/group/mod.rs index 351b7ec..963c0bf 100644 --- a/src/group/mod.rs +++ b/src/group/mod.rs @@ -7,6 +7,7 @@ )] #![allow(clippy::non_ascii_literal)] +use crate::userlib::NewFromString; use log::warn; use regex::Regex; @@ -84,7 +85,7 @@ impl<'a> Display for Group<'a> { } } -impl<'a> Group<'a> { +impl<'a> NewFromString<'a> for Group<'a> { /// Parse a line formatted like one in `/etc/shadow` and construct a matching `Shadow` instance /// /// # Example @@ -97,7 +98,7 @@ impl<'a> Group<'a> { /// /// # Errors /// When parsing fails this function returns a `UserLibError::Message` containing some information as to why the function failed. - pub fn new_from_string(line: &'a str) -> Result { + fn new_from_string(line: &'a str) -> Result { println!("{}", &line); let elements: Vec<&str> = line.split(':').collect(); if elements.len() == 4 { diff --git a/src/user/mod.rs b/src/user/mod.rs index a91bbbb..cde6916 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -2,6 +2,7 @@ pub mod gecos_fields; pub mod passwd_fields; pub mod shadow_fields; +use crate::userlib::NewFromString; use std::convert::TryFrom; use std::fmt::{self, Display}; @@ -18,7 +19,7 @@ pub struct User<'a> { shell_path: crate::ShellPath<'a>, /* Shell program. */ } -impl<'a> User<'a> { +impl<'a> NewFromString<'a> for User<'a> { /// Parse a line formatted like one in `/etc/passwd` and construct a matching [`adduser::User`] instance /// /// # Example @@ -31,7 +32,10 @@ impl<'a> User<'a> { /// /// # Errors /// When parsing fails this function returns a `UserLibError::Message` containing some information as to why the function failed. - pub fn new_from_string(line: &'a str) -> Result { + fn new_from_string(line: &'a str) -> Result + where + Self: Sized, + { let elements: Vec<&str> = line.split(':').collect(); if elements.len() == 7 { Ok(Self { @@ -50,6 +54,9 @@ impl<'a> User<'a> { Err("Failed to parse: not enough elements".into()) } } +} + +impl<'a> User<'a> { #[must_use] pub const fn get_username(&self) -> &'a str { self.username.username diff --git a/src/user/shadow_fields.rs b/src/user/shadow_fields.rs index 12943e1..ec53357 100644 --- a/src/user/shadow_fields.rs +++ b/src/user/shadow_fields.rs @@ -7,6 +7,7 @@ )] #![allow(clippy::non_ascii_literal)] +use crate::userlib::NewFromString; use log::warn; use crate::userlib_error::UserLibError; @@ -77,7 +78,7 @@ fn show_option_duration(input: Option) -> String { } } -impl<'a> Shadow<'a> { +impl<'a> NewFromString<'a> for Shadow<'a> { /// Parse a line formatted like one in `/etc/shadow` and construct a matching `Shadow` instance /// /// # Example @@ -90,7 +91,7 @@ impl<'a> Shadow<'a> { /// /// # Errors /// When parsing fails this function returns a `UserLibError::Message` containing some information as to why the function failed. - pub fn new_from_string(line: &'a str) -> Result { + fn new_from_string(line: &'a str) -> Result { println!("{}", &line); let elements: Vec<&str> = line.split(':').collect(); if elements.len() == 9 { diff --git a/src/userlib.rs b/src/userlib.rs index 18340b8..a15b06d 100644 --- a/src/userlib.rs +++ b/src/userlib.rs @@ -8,13 +8,33 @@ #![allow(clippy::non_ascii_literal)] use log::warn; +use std::fs::File; +use std::io::{BufReader, Read}; +use std::path::PathBuf; pub struct UserDBLocal<'a> { + source_files: Files, pub passwd_entries: Vec>, pub shadow_entries: Vec>, pub group_entries: Vec>, } +pub struct Files { + passwd: Option, + shadow: Option, + group: Option, +} + +impl Default for Files { + fn default() -> Self { + Self { + passwd: Some(PathBuf::from("/etc/passwd")), + shadow: Some(PathBuf::from("/etc/shadow")), + group: Some(PathBuf::from("/etc/group")), + } + } +} + impl<'a> UserDBLocal<'a> { #[must_use] pub fn import_from_strings( @@ -23,6 +43,11 @@ impl<'a> UserDBLocal<'a> { group_content: &'a str, ) -> Self { let res = UserDBLocal { + source_files: Files { + passwd: None, + group: None, + shadow: None, + }, passwd_entries: passwd_content .lines() .filter_map(|line| { @@ -57,6 +82,53 @@ impl<'a> UserDBLocal<'a> { }; res } + + pub fn load_files(files: Files) -> Self { + let passwd_file = + File::open(files.group.expect("passwd file path cannot be None")).unwrap(); + let mut passwd_reader = BufReader::new(passwd_file); + let mut my_passwd_lines = String::new(); + passwd_reader.read_to_string(&mut my_passwd_lines).unwrap(); + let group_file = File::open(files.group.expect("group file path cannot be None")).unwrap(); + let mut group_reader = BufReader::new(group_file); + let mut my_group_lines = String::new(); + group_reader.read_to_string(&mut my_group_lines).unwrap(); + let shadow_file = File::open(files.shadow.expect("shadow file path cannot be None")) + .expect("Failed to read the shadow file. Most of the time root permissions are needed"); + let mut shadow_reader = BufReader::new(group_file); + let mut my_shadow_lines = String::new(); + shadow_reader.read_to_string(&mut my_shadow_lines).unwrap(); + + Self { + source_files: files, + passwd_entries: string_to(&my_passwd_lines), + group_entries: string_to(&my_group_lines), + shadow_entries: string_to(&my_shadow_lines), + } + } +} + +pub trait NewFromString<'a> { + fn new_from_string(line: &'a str) -> Result + where + Self: Sized; +} + +fn string_to<'a, T>(source: &'a str) -> Vec +where + T: NewFromString<'a>, +{ + source + .lines() + .filter_map(|line| { + if line.len() > 5 { + println!("{}", line); + Some(T::new_from_string(line).expect("failed to read lines")) + } else { + None + } + }) + .collect() } #[test] -- 2.46.1 From 6f59cf17ba401060a8c5690d17c4a6e73ccec65e Mon Sep 17 00:00:00 2001 From: dietrich Date: Wed, 7 Oct 2020 17:05:49 +0200 Subject: [PATCH 6/6] Compiles agin. --- src/bin/read_all.rs | 10 ++-- src/group/mod.rs | 58 ++++++++++++----------- src/lib.rs | 1 + src/user/gecos_fields.rs | 83 +++++++++++++++++---------------- src/user/mod.rs | 97 ++++++++++++++++++++------------------- src/user/passwd_fields.rs | 92 +++++++++++++++++++------------------ src/user/shadow_fields.rs | 47 +++++++++---------- src/userlib.rs | 68 +++++++++++++++++---------- 8 files changed, 248 insertions(+), 208 deletions(-) diff --git a/src/bin/read_all.rs b/src/bin/read_all.rs index 7cc05ca..51fc998 100644 --- a/src/bin/read_all.rs +++ b/src/bin/read_all.rs @@ -1,5 +1,6 @@ extern crate adduser; +use adduser::NewFromString; use adduser::Shadow; use adduser::User; use std::fs::File; @@ -17,11 +18,14 @@ fn main() { for line in reader.lines() { let line = line.unwrap(); - println!("{}", User::new_from_string(&line).unwrap()); + println!("{}", User::new_from_string(line).unwrap()); } - let line = "test:!!$6$/RotIe4VZzzAun4W$7YUONvru1rDnllN5TvrnOMsWUD5wSDUPAD6t6/Xwsr/0QOuWF3HcfAhypRkGa8G1B9qqWV5kZSnCb8GKMN9N61:18260:0:99999:7:::"; - assert_eq!(format!("{}", Shadow::new_from_string(line).unwrap()), line); + let line = "test:!!$6$/RotIe4VZzzAun4W$7YUONvru1rDnllN5TvrnOMsWUD5wSDUPAD6t6/Xwsr/0QOuWF3HcfAhypRkGa8G1B9qqWV5kZSnCb8GKMN9N61:18260:0:99999:7:::".to_string(); + assert_eq!( + format!("{}", Shadow::new_from_string(line.clone()).unwrap()), + line + ); // let pwd = User::default(); // let pwd2 = diff --git a/src/group/mod.rs b/src/group/mod.rs index 963c0bf..7aaae21 100644 --- a/src/group/mod.rs +++ b/src/group/mod.rs @@ -17,24 +17,24 @@ use std::convert::TryFrom; use std::fmt::{self, Debug, Display}; #[derive(Debug, PartialEq, Eq)] -pub struct Groupname<'a> { - groupname: &'a str, +pub struct Groupname { + groupname: String, } -impl Display for Groupname<'_> { +impl Display for Groupname { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.groupname,) } } -impl<'a> TryFrom<&'a str> for Groupname<'a> { +impl TryFrom for Groupname { type Error = UserLibError; - fn try_from(source: &'a str) -> std::result::Result { + fn try_from(source: String) -> std::result::Result { lazy_static! { static ref USERVALIDATION: Regex = Regex::new("^[a-z_]([a-z0-9_\\-]{0,31}|[a-z0-9_\\-]{0,30}\\$)$").unwrap(); } - if USERVALIDATION.is_match(source) { + if USERVALIDATION.is_match(&source) { Ok(Self { groupname: source }) } else if source == "Debian-exim" { warn!("username {} is not a valid username. This might cause problems. (It is default in Debian and Ubuntu)", source); @@ -50,25 +50,25 @@ impl<'a> TryFrom<&'a str> for Groupname<'a> { /// A record(line) in the user database `/etc/shadow` found in most linux systems. #[derive(Debug, PartialEq, Eq)] -pub struct Group<'a> { - groupname: Groupname<'a>, /* Username. */ - pub(crate) password: crate::Password<'a>, /* Usually not used (disabled with x) */ - gid: crate::Gid, /* Group ID. */ - members: Vec>, /* Real name. */ +pub struct Group { + groupname: Groupname, /* Username. */ + pub(crate) password: crate::Password, /* Usually not used (disabled with x) */ + gid: crate::Gid, /* Group ID. */ + members: Vec, /* Real name. */ } -impl<'a> Group<'a> { +impl Group { #[must_use] - pub const fn get_groupname(&self) -> &'a str { - self.groupname.groupname + pub fn get_groupname(&self) -> &str { + &self.groupname.groupname } #[must_use] - pub const fn get_members(&self) -> &Vec> { + pub const fn get_members(&self) -> &Vec { &self.members } } -impl<'a> Display for Group<'a> { +impl Display for Group { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!( f, @@ -85,7 +85,7 @@ impl<'a> Display for Group<'a> { } } -impl<'a> NewFromString<'a> for Group<'a> { +impl NewFromString for Group { /// Parse a line formatted like one in `/etc/shadow` and construct a matching `Shadow` instance /// /// # Example @@ -98,15 +98,15 @@ impl<'a> NewFromString<'a> for Group<'a> { /// /// # Errors /// When parsing fails this function returns a `UserLibError::Message` containing some information as to why the function failed. - fn new_from_string(line: &'a str) -> Result { + fn new_from_string(line: String) -> Result { println!("{}", &line); - let elements: Vec<&str> = line.split(':').collect(); + let elements: Vec = line.split(':').map(ToString::to_string).collect(); if elements.len() == 4 { Ok(Group { - groupname: Groupname::try_from(*elements.get(0).unwrap())?, + groupname: Groupname::try_from(elements.get(0).unwrap().to_string())?, password: crate::Password::Disabled, - gid: crate::Gid::try_from(*elements.get(2).unwrap())?, - members: parse_members_list(*elements.get(3).unwrap()), + gid: crate::Gid::try_from(elements.get(2).unwrap().to_string())?, + members: parse_members_list(elements.get(3).unwrap().to_string()), }) } else { Err(UserLibError::Message(format!( @@ -118,9 +118,13 @@ impl<'a> NewFromString<'a> for Group<'a> { } } -fn parse_members_list<'a>(source: &'a str) -> Vec> { +fn parse_members_list(source: String) -> Vec { let mut res = vec![]; - for mem in source.split(',').filter(|x| !x.is_empty()) { + for mem in source + .split(',') + .filter(|x| !x.is_empty()) + .map(ToString::to_string) + { res.push(crate::Username::try_from(mem).expect("failed to parse username")); } res @@ -129,19 +133,19 @@ fn parse_members_list<'a>(source: &'a str) -> Vec> { #[test] fn test_parse_and_back_identity() { let line = "teste:x:1002:test,teste"; - let line2 = Group::new_from_string(line).unwrap(); + let line2 = Group::new_from_string(line.to_owned()).unwrap(); assert_eq!(format!("{}", line2), line); } #[test] fn test_groupname() { let line = "teste:x:1002:test,teste"; - let line2 = Group::new_from_string(line).unwrap(); + let line2 = Group::new_from_string(line.to_owned()).unwrap(); assert_eq!(line2.get_groupname(), "teste"); } #[test] fn test_root_group() { let line = "root:x:0:"; - let line2 = Group::new_from_string(line).unwrap(); + let line2 = Group::new_from_string(line.to_owned()).unwrap(); assert_eq!(line2.get_groupname(), "root"); } diff --git a/src/lib.rs b/src/lib.rs index d19baf2..a451ea8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,4 +15,5 @@ pub use user::passwd_fields::{ }; pub use user::shadow_fields::Shadow; pub use user::User; +pub use userlib::NewFromString; pub use userlib_error::UserLibError; diff --git a/src/user/gecos_fields.rs b/src/user/gecos_fields.rs index d6fc0fa..af2ee16 100644 --- a/src/user/gecos_fields.rs +++ b/src/user/gecos_fields.rs @@ -9,81 +9,81 @@ use std::fmt::{self, Display}; /// /// This enum represents the first 4 values by name and adds the other values to a list of strings [`Gecos::Detail`]. If only one field is found and no `,` at all this value is used as a human readable comment [`Gecos::Simple`]. #[derive(Debug, PartialEq, Eq)] -pub enum Gecos<'a> { +pub enum Gecos { Detail { - full_name: &'a str, - room: &'a str, - phone_work: &'a str, - phone_home: &'a str, - other: Option>, + full_name: String, + room: String, + phone_work: String, + phone_home: String, + other: Option>, }, Simple { - comment: &'a str, + comment: String, }, } -impl<'a> Gecos<'a> { +impl<'a> Gecos { #[must_use] - pub const fn get_comment(&'a self) -> Option<&'a str> { - match *self { - Gecos::Simple { comment, .. } => Some(comment), + pub fn get_comment(&'a self) -> Option<&'a str> { + match &self { + Gecos::Simple { comment, .. } => Some(&comment), Gecos::Detail { .. } => None, } } #[must_use] - pub const fn get_full_name(&'a self) -> Option<&'a str> { - match *self { + pub fn get_full_name(&self) -> Option<&str> { + match &self { Gecos::Simple { .. } => None, Gecos::Detail { full_name, .. } => { if full_name.is_empty() { None } else { - Some(full_name) + Some(&full_name) } } } } #[must_use] - pub const fn get_room(&'a self) -> Option<&'a str> { - match *self { + pub fn get_room(&self) -> Option<&str> { + match &self { Gecos::Simple { .. } => None, Gecos::Detail { room, .. } => { if room.is_empty() { None } else { - Some(room) + Some(&room) } } } } #[must_use] - pub const fn get_phone_work(&'a self) -> Option<&'a str> { - match *self { + pub fn get_phone_work(&self) -> Option<&str> { + match &self { Gecos::Simple { .. } => None, Gecos::Detail { phone_work, .. } => { if phone_work.is_empty() { None } else { - Some(phone_work) + Some(&phone_work) } } } } #[must_use] - pub const fn get_phone_home(&'a self) -> Option<&'a str> { - match *self { + pub fn get_phone_home(&'_ self) -> Option<&'_ str> { + match &self { Gecos::Simple { .. } => None, Gecos::Detail { phone_home, .. } => { if phone_home.is_empty() { None } else { - Some(phone_home) + Some(&phone_home) } } } } #[must_use] - pub const fn get_other(&'a self) -> Option<&Vec<&'a str>> { + pub const fn get_other(&self) -> Option<&Vec> { match self { Gecos::Simple { .. } => None, Gecos::Detail { other, .. } => match other { @@ -94,7 +94,7 @@ impl<'a> Gecos<'a> { } } -impl Display for Gecos<'_> { +impl Display for Gecos { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self { Gecos::Simple { comment } => write!(f, "{}", comment), @@ -120,16 +120,16 @@ impl Display for Gecos<'_> { } } -impl<'a> TryFrom<&'a str> for Gecos<'a> { +impl TryFrom for Gecos { type Error = UserLibError; - fn try_from(source: &'a str) -> std::result::Result { - let vals: Vec<&str> = source.split(',').collect(); + fn try_from(source: String) -> std::result::Result { + let vals: Vec = source.split(',').map(ToString::to_string).collect(); if vals.len() > 3 { Ok(Gecos::Detail { - full_name: vals[0], - room: vals[1], - phone_work: vals[2], - phone_home: vals[3], + full_name: vals[0].clone(), + room: vals[1].clone(), + phone_work: vals[2].clone(), + phone_home: vals[3].clone(), other: if vals.len() == 4 { None } else { @@ -138,7 +138,7 @@ impl<'a> TryFrom<&'a str> for Gecos<'a> { }) } else if vals.len() == 1 { Ok(Gecos::Simple { - comment: vals.get(0).unwrap(), + comment: vals.get(0).unwrap().into(), }) } else { panic!(format!("Could not parse this string: {}", source)) @@ -149,9 +149,9 @@ impl<'a> TryFrom<&'a str> for Gecos<'a> { #[test] fn test_parse_gecos() { // test if the Gecos field can be parsed and the resulting struct is populated correctly. - let gcdetail = "Full Name,504,11345342,ä1-2312,myemail@test.com"; - let gcsimple = "A böring comment →"; - let gc_no_other: &str = "systemd Network Management,,,"; + let gcdetail = "Full Name,504,11345342,ä1-2312,myemail@test.com".to_string(); + let gcsimple = "A böring comment →".to_string(); + let gc_no_other = "systemd Network Management,,,".to_string(); let res_detail = crate::Gecos::try_from(gcdetail).unwrap(); let res_simple = crate::Gecos::try_from(gcsimple).unwrap(); let res_no_other = crate::Gecos::try_from(gc_no_other).unwrap(); @@ -196,9 +196,9 @@ fn test_parse_gecos() { #[test] fn test_gecos_getters() { // test if the Gecos field can be parsed and the resulting struct is populated correctly. - let gcdetail = "Full Name,504,11345342,ä1-2312,myemail@test.com"; - let gcsimple = "A böring comment →"; - let gc_no_other: &str = "systemd Network Management,,,"; + let gcdetail = "Full Name,504,11345342,ä1-2312,myemail@test.com".to_string(); + let gcsimple = "A böring comment →".to_string(); + let gc_no_other = "systemd Network Management,,,".to_string(); let res_detail = crate::Gecos::try_from(gcdetail).unwrap(); let res_simple = crate::Gecos::try_from(gcsimple).unwrap(); let res_no_other = crate::Gecos::try_from(gc_no_other).unwrap(); @@ -210,7 +210,10 @@ fn test_gecos_getters() { assert_eq!(res_detail.get_room(), Some("504")); assert_eq!(res_detail.get_phone_work(), Some("11345342")); assert_eq!(res_detail.get_phone_home(), Some("ä1-2312")); - assert_eq!(res_detail.get_other(), Some(&vec!["myemail@test.com"])); + assert_eq!( + res_detail.get_other(), + Some(&vec!["myemail@test.com".to_string()]) + ); assert_eq!( res_no_other.get_full_name(), diff --git a/src/user/mod.rs b/src/user/mod.rs index cde6916..7b0c8b8 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -8,47 +8,47 @@ use std::fmt::{self, Display}; /// A record(line) in the user database `/etc/passwd` found in most linux systems. #[derive(Debug, PartialEq, Eq)] -pub struct User<'a> { - source: &'a str, - username: crate::Username<'a>, /* Username. */ - password: crate::Password<'a>, /* Hashed passphrase, if shadow database not in use (see shadow.h). */ - uid: crate::Uid, /* User ID. */ - gid: crate::Gid, /* Group ID. */ - gecos: crate::Gecos<'a>, /* Real name. */ - home_dir: crate::HomeDir<'a>, /* Home directory. */ - shell_path: crate::ShellPath<'a>, /* Shell program. */ +pub struct User { + source: String, + username: crate::Username, /* Username. */ + password: crate::Password, /* Hashed passphrase, if shadow database not in use (see shadow.h). */ + uid: crate::Uid, /* User ID. */ + gid: crate::Gid, /* Group ID. */ + gecos: crate::Gecos, /* Real name. */ + home_dir: crate::HomeDir, /* Home directory. */ + shell_path: crate::ShellPath, /* Shell program. */ } -impl<'a> NewFromString<'a> for User<'a> { +impl NewFromString for User { /// Parse a line formatted like one in `/etc/passwd` and construct a matching [`adduser::User`] instance /// /// # Example /// ``` + /// use adduser::NewFromString; /// let pwd = adduser::User::new_from_string( - /// "testuser:testpassword:1001:1001:full Name,,,,:/home/test:/bin/test" - /// ).unwrap(); + /// "testuser:testpassword:1001:1001:full Name,,,,:/home/test:/bin/test".to_string()).unwrap(); /// assert_eq!(pwd.get_username(), "testuser"); /// ``` /// /// # Errors /// When parsing fails this function returns a `UserLibError::Message` containing some information as to why the function failed. - fn new_from_string(line: &'a str) -> Result + fn new_from_string(line: String) -> Result where Self: Sized, { - let elements: Vec<&str> = line.split(':').collect(); + let elements: Vec = line.split(':').map(ToString::to_string).collect(); if elements.len() == 7 { Ok(Self { source: line, - username: crate::Username::try_from(*elements.get(0).unwrap())?, + username: crate::Username::try_from(elements.get(0).unwrap().to_string())?, password: crate::Password::Encrypted(crate::EncryptedPassword::try_from( - *elements.get(1).unwrap(), + elements.get(1).unwrap().to_string(), )?), - uid: crate::Uid::try_from(*elements.get(2).unwrap())?, - gid: crate::Gid::try_from(*elements.get(3).unwrap())?, - gecos: crate::Gecos::try_from(*elements.get(4).unwrap())?, - home_dir: crate::HomeDir::try_from(*elements.get(5).unwrap())?, - shell_path: crate::ShellPath::try_from(*elements.get(6).unwrap())?, + uid: crate::Uid::try_from(elements.get(2).unwrap().to_string())?, + gid: crate::Gid::try_from(elements.get(3).unwrap().to_string())?, + gecos: crate::Gecos::try_from(elements.get(4).unwrap().to_string())?, + home_dir: crate::HomeDir::try_from(elements.get(5).unwrap().to_string())?, + shell_path: crate::ShellPath::try_from(elements.get(6).unwrap().to_string())?, }) } else { Err("Failed to parse: not enough elements".into()) @@ -56,17 +56,17 @@ impl<'a> NewFromString<'a> for User<'a> { } } -impl<'a> User<'a> { +impl User { #[must_use] - pub const fn get_username(&self) -> &'a str { - self.username.username + pub fn get_username(&self) -> &str { + &self.username.username } #[must_use] - pub const fn get_password(&self) -> &'a str { - match self.password { - crate::Password::Encrypted(crate::EncryptedPassword { password }) => password, - crate::Password::Shadow(crate::Shadow { ref password, .. }) => password.password, - crate::Password::Disabled => "x", + pub fn get_password(&self) -> &str { + match &self.password { + crate::Password::Encrypted(crate::EncryptedPassword { password }) => &password, + crate::Password::Shadow(crate::Shadow { ref password, .. }) => &password.password, + crate::Password::Disabled => &"x", } } #[must_use] @@ -82,39 +82,41 @@ impl<'a> User<'a> { &self.gecos } #[must_use] - pub const fn get_home_dir(&self) -> &'a str { - self.home_dir.dir + pub fn get_home_dir(&self) -> &str { + &self.home_dir.dir } #[must_use] - pub const fn get_shell_path(&self) -> &'a str { - self.shell_path.shell + pub fn get_shell_path(&self) -> &str { + &self.shell_path.shell } } -impl Default for User<'_> { +impl Default for User { fn default() -> Self { Self { - source: "", + source: "".to_owned(), username: crate::Username { - username: "defaultuser", + username: "defaultuser".to_owned(), }, password: crate::Password::Encrypted(crate::EncryptedPassword { - password: "notencrypted", + password: "notencrypted".to_owned(), }), uid: crate::Uid { uid: 1001 }, gid: crate::Gid { gid: 1001 }, gecos: crate::Gecos::Simple { - comment: "gecos default comment", + comment: "gecos default comment".to_string(), }, home_dir: crate::HomeDir { - dir: "/home/default", + dir: "/home/default".to_owned(), + }, + shell_path: crate::ShellPath { + shell: "/bin/bash".to_owned(), }, - shell_path: crate::ShellPath { shell: "/bin/bash" }, } } } -impl Display for User<'_> { +impl Display for User { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, @@ -142,16 +144,17 @@ fn test_default_user() { #[test] fn test_new_from_string() { // Test if a single line can be parsed and if the resulting struct is populated correctly. - let fail = User::new_from_string("").err().unwrap(); + let fail = User::new_from_string("".into()).err().unwrap(); assert_eq!( fail, crate::UserLibError::Message("Failed to parse: not enough elements".into()) ); - let pwd = - User::new_from_string("testuser:testpassword:1001:1001:testcomment:/home/test:/bin/test") - .unwrap(); + let pwd = User::new_from_string( + "testuser:testpassword:1001:1001:testcomment:/home/test:/bin/test".into(), + ) + .unwrap(); let pwd2 = - User::new_from_string("testuser:testpassword:1001:1001:full Name,004,000342,001-2312,myemail@test.com:/home/test:/bin/test") + User::new_from_string("testuser:testpassword:1001:1001:full Name,004,000342,001-2312,myemail@test.com:/home/test:/bin/test".into()) .unwrap(); assert_eq!(pwd.username.username, "testuser"); assert_eq!(pwd.home_dir.dir, "/home/test"); @@ -189,7 +192,7 @@ fn test_parse_passwd() { for line in reader.lines() { let lineorig: String = line.unwrap(); let linecopy = lineorig.clone(); - let pass_struc = User::new_from_string(&linecopy).unwrap(); + let pass_struc = User::new_from_string(linecopy).unwrap(); assert_eq!( // ignoring the numbers of `,` since the implementation does not (yet) reproduce a missing comment field. lineorig, diff --git a/src/user/passwd_fields.rs b/src/user/passwd_fields.rs index 69f2ee4..811b8cf 100644 --- a/src/user/passwd_fields.rs +++ b/src/user/passwd_fields.rs @@ -10,7 +10,7 @@ use log::warn; use regex::Regex; -use crate::userlib_error::UserLibError; +use crate::UserLibError; use std::cmp::Eq; use std::convert::TryFrom; use std::fmt::{self, Display}; @@ -21,25 +21,25 @@ use std::fmt::{self, Display}; /// /// In the future some extra fields might be added. #[derive(Debug, PartialEq, Eq)] -pub struct Username<'a> { +pub struct Username { /// The username value - pub(in crate::user) username: &'a str, + pub(in crate::user) username: String, } -impl Display for Username<'_> { +impl Display for Username { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.username,) } } -impl<'a> TryFrom<&'a str> for Username<'a> { +impl TryFrom for Username { type Error = UserLibError; - fn try_from(source: &'a str) -> std::result::Result { + fn try_from(source: String) -> std::result::Result { lazy_static! { static ref USERVALIDATION: Regex = Regex::new("^[a-z_]([a-z0-9_\\-]{0,31}|[a-z0-9_\\-]{0,30}\\$)$").unwrap(); } - if USERVALIDATION.is_match(source) { + if USERVALIDATION.is_match(&source) { Ok(Self { username: source }) } else if source == "Debian-exim" { warn!("username {} is not a valid username. This might cause problems. (It is default in Debian and Ubuntu)", source); @@ -54,13 +54,13 @@ impl<'a> TryFrom<&'a str> for Username<'a> { } #[derive(Debug, PartialEq, Eq)] -pub enum Password<'a> { - Encrypted(crate::EncryptedPassword<'a>), - Shadow(crate::Shadow<'a>), +pub enum Password { + Encrypted(crate::EncryptedPassword), + Shadow(crate::Shadow), Disabled, } -impl Display for Password<'_> { +impl Display for Password { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Password::Encrypted(EncryptedPassword { password }) => write!(f, "{}", password,), @@ -71,19 +71,19 @@ impl Display for Password<'_> { } #[derive(Debug, PartialEq, Eq)] -pub struct EncryptedPassword<'a> { - pub(in crate::user) password: &'a str, +pub struct EncryptedPassword { + pub(in crate::user) password: String, } -impl Display for EncryptedPassword<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +impl Display for EncryptedPassword { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.password,) } } -impl<'a> TryFrom<&'a str> for EncryptedPassword<'a> { +impl TryFrom for EncryptedPassword { type Error = UserLibError; - fn try_from(source: &'a str) -> std::result::Result { + fn try_from(source: String) -> std::result::Result { if source == "x" { warn!("password from shadow not loaded!") } else { @@ -104,9 +104,9 @@ impl Display for Uid { } } -impl TryFrom<&str> for Uid { +impl TryFrom for Uid { type Error = UserLibError; - fn try_from(source: &str) -> std::result::Result { + fn try_from(source: String) -> std::result::Result { Ok(Self { uid: source.parse::().unwrap(), }) @@ -132,9 +132,9 @@ impl Display for Gid { } } -impl TryFrom<&str> for Gid { +impl TryFrom for Gid { type Error = UserLibError; - fn try_from(source: &str) -> std::result::Result { + fn try_from(source: String) -> std::result::Result { Ok(Self { gid: source.parse::().unwrap(), }) @@ -151,38 +151,38 @@ impl Gid { /// The home directory of a user #[derive(Debug, PartialEq, Eq)] -pub struct HomeDir<'a> { - pub(in crate::user) dir: &'a str, +pub struct HomeDir { + pub(in crate::user) dir: String, } -impl Display for HomeDir<'_> { +impl Display for HomeDir { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.dir,) } } -impl<'a> TryFrom<&'a str> for HomeDir<'a> { +impl TryFrom for HomeDir { type Error = UserLibError; - fn try_from(source: &'a str) -> std::result::Result { + fn try_from(source: String) -> std::result::Result { Ok(Self { dir: source }) } } /// The path to the Shell binary #[derive(Debug, PartialEq, Eq)] -pub struct ShellPath<'a> { - pub(in crate::user) shell: &'a str, +pub struct ShellPath { + pub(in crate::user) shell: String, } -impl Display for ShellPath<'_> { +impl Display for ShellPath { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.shell,) } } -impl<'a> TryFrom<&'a str> for ShellPath<'a> { +impl TryFrom for ShellPath { type Error = UserLibError; - fn try_from(source: &'a str) -> std::result::Result { + fn try_from(source: String) -> std::result::Result { Ok(ShellPath { shell: source }) } } @@ -192,47 +192,51 @@ impl<'a> TryFrom<&'a str> for ShellPath<'a> { #[test] fn test_username_validation() { // Failing tests - let umlauts = Username::try_from("täst"); // umlauts + let umlauts: Result = Username::try_from("täst".to_owned()); // umlauts assert_eq!( Err(UserLibError::Message("Invalid username täst".into())), umlauts ); - let number_first = Username::try_from("11elf"); // numbers first + let number_first = Username::try_from("11elf".to_owned()); // numbers first assert_eq!( Err(UserLibError::Message("Invalid username 11elf".into())), number_first ); - let slashes = Username::try_from("test/name"); // slashes in the name + let slashes = Username::try_from("test/name".to_owned()); // slashes in the name assert_eq!( Err(UserLibError::Message("Invalid username test/name".into())), slashes ); - let long = Username::try_from("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); // maximum size 32 letters + let long = Username::try_from("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_owned()); // maximum size 32 letters assert_eq!( Err(UserLibError::Message( - "Invalid username aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".into() + "Invalid username aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_owned() )), long ); // Working tests - let ubuntu_exception = Username::try_from("Debian-exim"); // for some reason ubuntu and debian have a capital user. + let ubuntu_exception = Username::try_from("Debian-exim".to_owned()); // for some reason ubuntu and debian have a capital user. assert_eq!(ubuntu_exception.unwrap().username, "Debian-exim"); - let single = Username::try_from("t"); // single characters are ok + let single = Username::try_from("t".to_owned()); // single characters are ok assert_eq!(single.unwrap().username, "t"); - let normal = Username::try_from("superman"); // regular username + let normal = Username::try_from("superman".to_owned()); // regular username assert_eq!(normal.unwrap().username, "superman"); - let normal = Username::try_from("anna3pete"); // regular username containing a number + let normal = Username::try_from("anna3pete".to_owned()); // regular username containing a number assert_eq!(normal.unwrap().username, "anna3pete"); - let normal = Username::try_from("enya$"); // regular username ending in a $ + let normal = Username::try_from("enya$".to_owned()); // regular username ending in a $ assert_eq!(normal.unwrap().username, "enya$"); } #[test] fn test_guid_system_user() { // Check uids of system users. - let values = vec![("999", true), ("0", true), ("1000", false)]; + let values = vec![ + ("999".to_owned(), true), + ("0".to_owned(), true), + ("1000".to_owned(), false), + ]; for val in values { - assert_eq!(Uid::try_from(val.0).unwrap().is_system_uid(), val.1); - assert_eq!(Gid::try_from(val.0).unwrap().is_system_gid(), val.1); + assert_eq!(Uid::try_from(val.0.clone()).unwrap().is_system_uid(), val.1); + assert_eq!(Gid::try_from(val.0.clone()).unwrap().is_system_gid(), val.1); } } diff --git a/src/user/shadow_fields.rs b/src/user/shadow_fields.rs index ec53357..3e0494f 100644 --- a/src/user/shadow_fields.rs +++ b/src/user/shadow_fields.rs @@ -17,30 +17,30 @@ use std::fmt::{self, Debug, Display}; /// A record(line) in the user database `/etc/shadow` found in most linux systems. #[derive(Debug, PartialEq, Eq)] -pub struct Shadow<'a> { - username: crate::Username<'a>, /* Username. */ - pub(crate) password: crate::EncryptedPassword<'a>, /* Hashed passphrase */ - last_change: Option, /* User ID. */ - earliest_change: Option, /* Group ID. */ - latest_change: Option, /* Real name. */ - warn_period: Option, /* Home directory. */ - deactivated: Option, /* Shell program. */ - deactivated_since: Option, /* Shell program. */ - extensions: Option, /* Shell program. */ +pub struct Shadow { + username: crate::Username, /* Username. */ + pub(crate) password: crate::EncryptedPassword, /* Hashed passphrase */ + last_change: Option, /* User ID. */ + earliest_change: Option, /* Group ID. */ + latest_change: Option, /* Real name. */ + warn_period: Option, /* Home directory. */ + deactivated: Option, /* Shell program. */ + deactivated_since: Option, /* Shell program. */ + extensions: Option, /* Shell program. */ } -impl<'a> Shadow<'a> { +impl Shadow { #[must_use] - pub const fn get_username(&self) -> &'a str { - self.username.username + pub fn get_username(&self) -> &str { + &self.username.username } #[must_use] - pub const fn get_password(&self) -> &'a str { - self.password.password + pub fn get_password(&self) -> &str { + &self.password.password } } -impl<'a> Display for Shadow<'a> { +impl Display for Shadow { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!( f, @@ -78,27 +78,28 @@ fn show_option_duration(input: Option) -> String { } } -impl<'a> NewFromString<'a> for Shadow<'a> { +impl NewFromString for Shadow { /// Parse a line formatted like one in `/etc/shadow` and construct a matching `Shadow` instance /// /// # Example /// ``` + /// use adduser::NewFromString; /// let shad = adduser::Shadow::new_from_string( - /// "test:!!$6$/RotIe4VZzzAun4W$7YUONvru1rDnllN5TvrnOMsWUD5wSDUPAD6t6/Xwsr/0QOuWF3HcfAhypRkGa8G1B9qqWV5kZSnCb8GKMN9N61:18260:0:99999:7:::" + /// "test:!!$6$/RotIe4VZzzAun4W$7YUONvru1rDnllN5TvrnOMsWUD5wSDUPAD6t6/Xwsr/0QOuWF3HcfAhypRkGa8G1B9qqWV5kZSnCb8GKMN9N61:18260:0:99999:7:::".to_string() /// ).unwrap(); /// assert_eq!(shad.get_username(), "test"); /// ``` /// /// # Errors /// When parsing fails this function returns a `UserLibError::Message` containing some information as to why the function failed. - fn new_from_string(line: &'a str) -> Result { + fn new_from_string(line: String) -> Result { println!("{}", &line); - let elements: Vec<&str> = line.split(':').collect(); + let elements: Vec = line.split(':').map(ToString::to_string).collect(); if elements.len() == 9 { let extra = elements.get(8).unwrap(); Ok(Shadow { - username: crate::Username::try_from(*elements.get(0).unwrap())?, - password: crate::EncryptedPassword::try_from(*elements.get(1).unwrap())?, + username: crate::Username::try_from(elements.get(0).unwrap().to_string())?, + password: crate::EncryptedPassword::try_from(elements.get(1).unwrap().to_string())?, last_change: date_since_epoch(elements.get(2).unwrap()), earliest_change: date_since_epoch(elements.get(3).unwrap()), latest_change: date_since_epoch(elements.get(4).unwrap()), @@ -145,7 +146,7 @@ fn duration_for_days(days_source: &str) -> Option { fn test_parse_and_back_identity() { println!("Test"); let line = "test:!!$6$/RotIe4VZzzAun4W$7YUONvru1rDnllN5TvrnOMsWUD5wSDUPAD6t6/Xwsr/0QOuWF3HcfAhypRkGa8G1B9qqWV5kZSnCb8GKMN9N61:18260:0:99999:7:::"; - let line2 = Shadow::new_from_string(line).unwrap(); + let line2 = Shadow::new_from_string(line.to_owned()).unwrap(); println!("{:#?}", line2); assert_eq!(format!("{}", line2), line); } diff --git a/src/userlib.rs b/src/userlib.rs index a15b06d..93be679 100644 --- a/src/userlib.rs +++ b/src/userlib.rs @@ -12,11 +12,11 @@ use std::fs::File; use std::io::{BufReader, Read}; use std::path::PathBuf; -pub struct UserDBLocal<'a> { +pub struct UserDBLocal { source_files: Files, - pub passwd_entries: Vec>, - pub shadow_entries: Vec>, - pub group_entries: Vec>, + pub passwd_entries: Vec, + pub shadow_entries: Vec, + pub group_entries: Vec, } pub struct Files { @@ -35,12 +35,12 @@ impl Default for Files { } } -impl<'a> UserDBLocal<'a> { +impl UserDBLocal { #[must_use] pub fn import_from_strings( - passwd_content: &'a str, - shadow_content: &'a str, - group_content: &'a str, + passwd_content: String, + shadow_content: String, + group_content: String, ) -> Self { let res = UserDBLocal { source_files: Files { @@ -53,7 +53,10 @@ impl<'a> UserDBLocal<'a> { .filter_map(|line| { if line.len() > 5 { println!("{}", line); - Some(crate::User::new_from_string(line).expect("failed to read lines")) + Some( + crate::User::new_from_string(line.to_owned()) + .expect("failed to read lines"), + ) } else { None } @@ -63,7 +66,9 @@ impl<'a> UserDBLocal<'a> { .lines() .filter_map(|line| { if line.len() > 5 { - Some(crate::Group::new_from_string(line).expect("Parsing failed")) + Some( + crate::Group::new_from_string(line.to_owned()).expect("Parsing failed"), + ) } else { None } @@ -73,7 +78,10 @@ impl<'a> UserDBLocal<'a> { .lines() .filter_map(|line| { if line.len() > 5 { - Some(crate::Shadow::new_from_string(line).expect("Parsing failed")) + Some( + crate::Shadow::new_from_string(line.to_owned()) + .expect("Parsing failed"), + ) } else { None } @@ -83,19 +91,31 @@ impl<'a> UserDBLocal<'a> { res } + #[must_use] pub fn load_files(files: Files) -> Self { - let passwd_file = - File::open(files.group.expect("passwd file path cannot be None")).unwrap(); + let passwd_file = File::open( + files + .group + .clone() + .expect("passwd file path cannot be None"), + ) + .unwrap(); let mut passwd_reader = BufReader::new(passwd_file); let mut my_passwd_lines = String::new(); passwd_reader.read_to_string(&mut my_passwd_lines).unwrap(); - let group_file = File::open(files.group.expect("group file path cannot be None")).unwrap(); + let group_file = + File::open(files.group.clone().expect("group file path cannot be None")).unwrap(); let mut group_reader = BufReader::new(group_file); let mut my_group_lines = String::new(); group_reader.read_to_string(&mut my_group_lines).unwrap(); - let shadow_file = File::open(files.shadow.expect("shadow file path cannot be None")) - .expect("Failed to read the shadow file. Most of the time root permissions are needed"); - let mut shadow_reader = BufReader::new(group_file); + let shadow_file = File::open( + files + .shadow + .clone() + .expect("shadow file path cannot be None"), + ) + .expect("Failed to read the shadow file. Most of the time root permissions are needed"); + let mut shadow_reader = BufReader::new(shadow_file); let mut my_shadow_lines = String::new(); shadow_reader.read_to_string(&mut my_shadow_lines).unwrap(); @@ -108,22 +128,22 @@ impl<'a> UserDBLocal<'a> { } } -pub trait NewFromString<'a> { - fn new_from_string(line: &'a str) -> Result +pub trait NewFromString { + fn new_from_string(line: String) -> Result where Self: Sized; } -fn string_to<'a, T>(source: &'a str) -> Vec +fn string_to(source: &str) -> Vec where - T: NewFromString<'a>, + T: NewFromString, { source .lines() .filter_map(|line| { if line.len() > 5 { println!("{}", line); - Some(T::new_from_string(line).expect("failed to read lines")) + Some(T::new_from_string(line.to_owned()).expect("failed to read lines")) } else { None } @@ -133,7 +153,7 @@ where #[test] fn test_creator_user_db_local() { - let data = UserDBLocal::import_from_strings("testuser: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,teste"); + let data = UserDBLocal::import_from_strings("testuser:x:1001:1001:full Name,004,000342,001-2312,myemail@test.com:/home/test:/bin/test".to_string(), "test:!!$6$/RotIe4VZzzAun4W$7YUONvru1rDnllN5TvrnOMsWUD5wSDUPAD6t6/Xwsr/0QOuWF3HcfAhypRkGa8G1B9qqWV5kZSnCb8GKMN9N61:18260:0:99999:7:::".to_string(), "teste:x:1002:test,teste".to_string()); assert_eq!( data.passwd_entries.get(0).unwrap().get_username(), "testuser" @@ -152,6 +172,6 @@ fn test_parsing_local_database() { let mut group_reader = BufReader::new(group_file); let mut my_group_lines = "".to_string(); group_reader.read_to_string(&mut my_group_lines).unwrap(); - let data = UserDBLocal::import_from_strings(&my_passwd_lines, "", &my_group_lines); + let data = UserDBLocal::import_from_strings(my_passwd_lines, "".to_string(), my_group_lines); assert_eq!(data.group_entries.get(0).unwrap().get_groupname(), "root"); } -- 2.46.1