Compiles agin.
This commit is contained in:
parent
a645ebac75
commit
6f59cf17ba
@ -1,5 +1,6 @@
|
|||||||
extern crate adduser;
|
extern crate adduser;
|
||||||
|
|
||||||
|
use adduser::NewFromString;
|
||||||
use adduser::Shadow;
|
use adduser::Shadow;
|
||||||
use adduser::User;
|
use adduser::User;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
@ -17,11 +18,14 @@ fn main() {
|
|||||||
|
|
||||||
for line in reader.lines() {
|
for line in reader.lines() {
|
||||||
let line = line.unwrap();
|
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:::";
|
let line = "test:!!$6$/RotIe4VZzzAun4W$7YUONvru1rDnllN5TvrnOMsWUD5wSDUPAD6t6/Xwsr/0QOuWF3HcfAhypRkGa8G1B9qqWV5kZSnCb8GKMN9N61:18260:0:99999:7:::".to_string();
|
||||||
assert_eq!(format!("{}", Shadow::new_from_string(line).unwrap()), line);
|
assert_eq!(
|
||||||
|
format!("{}", Shadow::new_from_string(line.clone()).unwrap()),
|
||||||
|
line
|
||||||
|
);
|
||||||
|
|
||||||
// let pwd = User::default();
|
// let pwd = User::default();
|
||||||
// let pwd2 =
|
// let pwd2 =
|
||||||
|
@ -17,24 +17,24 @@ use std::convert::TryFrom;
|
|||||||
use std::fmt::{self, Debug, Display};
|
use std::fmt::{self, Debug, Display};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct Groupname<'a> {
|
pub struct Groupname {
|
||||||
groupname: &'a str,
|
groupname: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Groupname<'_> {
|
impl Display for Groupname {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{}", self.groupname,)
|
write!(f, "{}", self.groupname,)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TryFrom<&'a str> for Groupname<'a> {
|
impl TryFrom<String> for Groupname {
|
||||||
type Error = UserLibError;
|
type Error = UserLibError;
|
||||||
fn try_from(source: &'a str) -> std::result::Result<Self, Self::Error> {
|
fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref USERVALIDATION: Regex =
|
static ref USERVALIDATION: Regex =
|
||||||
Regex::new("^[a-z_]([a-z0-9_\\-]{0,31}|[a-z0-9_\\-]{0,30}\\$)$").unwrap();
|
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 })
|
Ok(Self { groupname: source })
|
||||||
} else if source == "Debian-exim" {
|
} else if source == "Debian-exim" {
|
||||||
warn!("username {} is not a valid username. This might cause problems. (It is default in Debian and Ubuntu)", source);
|
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.
|
/// A record(line) in the user database `/etc/shadow` found in most linux systems.
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct Group<'a> {
|
pub struct Group {
|
||||||
groupname: Groupname<'a>, /* Username. */
|
groupname: Groupname, /* Username. */
|
||||||
pub(crate) password: crate::Password<'a>, /* Usually not used (disabled with x) */
|
pub(crate) password: crate::Password, /* Usually not used (disabled with x) */
|
||||||
gid: crate::Gid, /* Group ID. */
|
gid: crate::Gid, /* Group ID. */
|
||||||
members: Vec<crate::Username<'a>>, /* Real name. */
|
members: Vec<crate::Username>, /* Real name. */
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Group<'a> {
|
impl Group {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn get_groupname(&self) -> &'a str {
|
pub fn get_groupname(&self) -> &str {
|
||||||
self.groupname.groupname
|
&self.groupname.groupname
|
||||||
}
|
}
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn get_members(&self) -> &Vec<crate::Username<'a>> {
|
pub const fn get_members(&self) -> &Vec<crate::Username> {
|
||||||
&self.members
|
&self.members
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Display for Group<'a> {
|
impl Display for Group {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||||
write!(
|
write!(
|
||||||
f,
|
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
|
/// Parse a line formatted like one in `/etc/shadow` and construct a matching `Shadow` instance
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
@ -98,15 +98,15 @@ impl<'a> NewFromString<'a> for Group<'a> {
|
|||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// When parsing fails this function returns a `UserLibError::Message` containing some information as to why the function failed.
|
/// 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<Self, UserLibError> {
|
fn new_from_string(line: String) -> Result<Self, UserLibError> {
|
||||||
println!("{}", &line);
|
println!("{}", &line);
|
||||||
let elements: Vec<&str> = line.split(':').collect();
|
let elements: Vec<String> = line.split(':').map(ToString::to_string).collect();
|
||||||
if elements.len() == 4 {
|
if elements.len() == 4 {
|
||||||
Ok(Group {
|
Ok(Group {
|
||||||
groupname: Groupname::try_from(*elements.get(0).unwrap())?,
|
groupname: Groupname::try_from(elements.get(0).unwrap().to_string())?,
|
||||||
password: crate::Password::Disabled,
|
password: crate::Password::Disabled,
|
||||||
gid: crate::Gid::try_from(*elements.get(2).unwrap())?,
|
gid: crate::Gid::try_from(elements.get(2).unwrap().to_string())?,
|
||||||
members: parse_members_list(*elements.get(3).unwrap()),
|
members: parse_members_list(elements.get(3).unwrap().to_string()),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(UserLibError::Message(format!(
|
Err(UserLibError::Message(format!(
|
||||||
@ -118,9 +118,13 @@ impl<'a> NewFromString<'a> for Group<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_members_list<'a>(source: &'a str) -> Vec<crate::Username<'a>> {
|
fn parse_members_list(source: String) -> Vec<crate::Username> {
|
||||||
let mut res = 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.push(crate::Username::try_from(mem).expect("failed to parse username"));
|
||||||
}
|
}
|
||||||
res
|
res
|
||||||
@ -129,19 +133,19 @@ fn parse_members_list<'a>(source: &'a str) -> Vec<crate::Username<'a>> {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_parse_and_back_identity() {
|
fn test_parse_and_back_identity() {
|
||||||
let line = "teste:x:1002:test,teste";
|
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);
|
assert_eq!(format!("{}", line2), line);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_groupname() {
|
fn test_groupname() {
|
||||||
let line = "teste:x:1002:test,teste";
|
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");
|
assert_eq!(line2.get_groupname(), "teste");
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_root_group() {
|
fn test_root_group() {
|
||||||
let line = "root:x:0:";
|
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");
|
assert_eq!(line2.get_groupname(), "root");
|
||||||
}
|
}
|
||||||
|
@ -15,4 +15,5 @@ pub use user::passwd_fields::{
|
|||||||
};
|
};
|
||||||
pub use user::shadow_fields::Shadow;
|
pub use user::shadow_fields::Shadow;
|
||||||
pub use user::User;
|
pub use user::User;
|
||||||
|
pub use userlib::NewFromString;
|
||||||
pub use userlib_error::UserLibError;
|
pub use userlib_error::UserLibError;
|
||||||
|
@ -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`].
|
/// 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)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum Gecos<'a> {
|
pub enum Gecos {
|
||||||
Detail {
|
Detail {
|
||||||
full_name: &'a str,
|
full_name: String,
|
||||||
room: &'a str,
|
room: String,
|
||||||
phone_work: &'a str,
|
phone_work: String,
|
||||||
phone_home: &'a str,
|
phone_home: String,
|
||||||
other: Option<Vec<&'a str>>,
|
other: Option<Vec<String>>,
|
||||||
},
|
},
|
||||||
Simple {
|
Simple {
|
||||||
comment: &'a str,
|
comment: String,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Gecos<'a> {
|
impl<'a> Gecos {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn get_comment(&'a self) -> Option<&'a str> {
|
pub fn get_comment(&'a self) -> Option<&'a str> {
|
||||||
match *self {
|
match &self {
|
||||||
Gecos::Simple { comment, .. } => Some(comment),
|
Gecos::Simple { comment, .. } => Some(&comment),
|
||||||
Gecos::Detail { .. } => None,
|
Gecos::Detail { .. } => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn get_full_name(&'a self) -> Option<&'a str> {
|
pub fn get_full_name(&self) -> Option<&str> {
|
||||||
match *self {
|
match &self {
|
||||||
Gecos::Simple { .. } => None,
|
Gecos::Simple { .. } => None,
|
||||||
Gecos::Detail { full_name, .. } => {
|
Gecos::Detail { full_name, .. } => {
|
||||||
if full_name.is_empty() {
|
if full_name.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(full_name)
|
Some(&full_name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn get_room(&'a self) -> Option<&'a str> {
|
pub fn get_room(&self) -> Option<&str> {
|
||||||
match *self {
|
match &self {
|
||||||
Gecos::Simple { .. } => None,
|
Gecos::Simple { .. } => None,
|
||||||
Gecos::Detail { room, .. } => {
|
Gecos::Detail { room, .. } => {
|
||||||
if room.is_empty() {
|
if room.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(room)
|
Some(&room)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn get_phone_work(&'a self) -> Option<&'a str> {
|
pub fn get_phone_work(&self) -> Option<&str> {
|
||||||
match *self {
|
match &self {
|
||||||
Gecos::Simple { .. } => None,
|
Gecos::Simple { .. } => None,
|
||||||
Gecos::Detail { phone_work, .. } => {
|
Gecos::Detail { phone_work, .. } => {
|
||||||
if phone_work.is_empty() {
|
if phone_work.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(phone_work)
|
Some(&phone_work)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn get_phone_home(&'a self) -> Option<&'a str> {
|
pub fn get_phone_home(&'_ self) -> Option<&'_ str> {
|
||||||
match *self {
|
match &self {
|
||||||
Gecos::Simple { .. } => None,
|
Gecos::Simple { .. } => None,
|
||||||
Gecos::Detail { phone_home, .. } => {
|
Gecos::Detail { phone_home, .. } => {
|
||||||
if phone_home.is_empty() {
|
if phone_home.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(phone_home)
|
Some(&phone_home)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn get_other(&'a self) -> Option<&Vec<&'a str>> {
|
pub const fn get_other(&self) -> Option<&Vec<String>> {
|
||||||
match self {
|
match self {
|
||||||
Gecos::Simple { .. } => None,
|
Gecos::Simple { .. } => None,
|
||||||
Gecos::Detail { other, .. } => match other {
|
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 {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match &self {
|
match &self {
|
||||||
Gecos::Simple { comment } => write!(f, "{}", comment),
|
Gecos::Simple { comment } => write!(f, "{}", comment),
|
||||||
@ -120,16 +120,16 @@ impl Display for Gecos<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TryFrom<&'a str> for Gecos<'a> {
|
impl TryFrom<String> for Gecos {
|
||||||
type Error = UserLibError;
|
type Error = UserLibError;
|
||||||
fn try_from(source: &'a str) -> std::result::Result<Self, Self::Error> {
|
fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
|
||||||
let vals: Vec<&str> = source.split(',').collect();
|
let vals: Vec<String> = source.split(',').map(ToString::to_string).collect();
|
||||||
if vals.len() > 3 {
|
if vals.len() > 3 {
|
||||||
Ok(Gecos::Detail {
|
Ok(Gecos::Detail {
|
||||||
full_name: vals[0],
|
full_name: vals[0].clone(),
|
||||||
room: vals[1],
|
room: vals[1].clone(),
|
||||||
phone_work: vals[2],
|
phone_work: vals[2].clone(),
|
||||||
phone_home: vals[3],
|
phone_home: vals[3].clone(),
|
||||||
other: if vals.len() == 4 {
|
other: if vals.len() == 4 {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
@ -138,7 +138,7 @@ impl<'a> TryFrom<&'a str> for Gecos<'a> {
|
|||||||
})
|
})
|
||||||
} else if vals.len() == 1 {
|
} else if vals.len() == 1 {
|
||||||
Ok(Gecos::Simple {
|
Ok(Gecos::Simple {
|
||||||
comment: vals.get(0).unwrap(),
|
comment: vals.get(0).unwrap().into(),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
panic!(format!("Could not parse this string: {}", source))
|
panic!(format!("Could not parse this string: {}", source))
|
||||||
@ -149,9 +149,9 @@ impl<'a> TryFrom<&'a str> for Gecos<'a> {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_parse_gecos() {
|
fn test_parse_gecos() {
|
||||||
// test if the Gecos field can be parsed and the resulting struct is populated correctly.
|
// 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 gcdetail = "Full Name,504,11345342,ä1-2312,myemail@test.com".to_string();
|
||||||
let gcsimple = "A böring comment →";
|
let gcsimple = "A böring comment →".to_string();
|
||||||
let gc_no_other: &str = "systemd Network Management,,,";
|
let gc_no_other = "systemd Network Management,,,".to_string();
|
||||||
let res_detail = crate::Gecos::try_from(gcdetail).unwrap();
|
let res_detail = crate::Gecos::try_from(gcdetail).unwrap();
|
||||||
let res_simple = crate::Gecos::try_from(gcsimple).unwrap();
|
let res_simple = crate::Gecos::try_from(gcsimple).unwrap();
|
||||||
let res_no_other = crate::Gecos::try_from(gc_no_other).unwrap();
|
let res_no_other = crate::Gecos::try_from(gc_no_other).unwrap();
|
||||||
@ -196,9 +196,9 @@ fn test_parse_gecos() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_gecos_getters() {
|
fn test_gecos_getters() {
|
||||||
// test if the Gecos field can be parsed and the resulting struct is populated correctly.
|
// 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 gcdetail = "Full Name,504,11345342,ä1-2312,myemail@test.com".to_string();
|
||||||
let gcsimple = "A böring comment →";
|
let gcsimple = "A böring comment →".to_string();
|
||||||
let gc_no_other: &str = "systemd Network Management,,,";
|
let gc_no_other = "systemd Network Management,,,".to_string();
|
||||||
let res_detail = crate::Gecos::try_from(gcdetail).unwrap();
|
let res_detail = crate::Gecos::try_from(gcdetail).unwrap();
|
||||||
let res_simple = crate::Gecos::try_from(gcsimple).unwrap();
|
let res_simple = crate::Gecos::try_from(gcsimple).unwrap();
|
||||||
let res_no_other = crate::Gecos::try_from(gc_no_other).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_room(), Some("504"));
|
||||||
assert_eq!(res_detail.get_phone_work(), Some("11345342"));
|
assert_eq!(res_detail.get_phone_work(), Some("11345342"));
|
||||||
assert_eq!(res_detail.get_phone_home(), Some("ä1-2312"));
|
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!(
|
assert_eq!(
|
||||||
res_no_other.get_full_name(),
|
res_no_other.get_full_name(),
|
||||||
|
@ -8,47 +8,47 @@ use std::fmt::{self, Display};
|
|||||||
|
|
||||||
/// A record(line) in the user database `/etc/passwd` found in most linux systems.
|
/// A record(line) in the user database `/etc/passwd` found in most linux systems.
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct User<'a> {
|
pub struct User {
|
||||||
source: &'a str,
|
source: String,
|
||||||
username: crate::Username<'a>, /* Username. */
|
username: crate::Username, /* Username. */
|
||||||
password: crate::Password<'a>, /* Hashed passphrase, if shadow database not in use (see shadow.h). */
|
password: crate::Password, /* Hashed passphrase, if shadow database not in use (see shadow.h). */
|
||||||
uid: crate::Uid, /* User ID. */
|
uid: crate::Uid, /* User ID. */
|
||||||
gid: crate::Gid, /* Group ID. */
|
gid: crate::Gid, /* Group ID. */
|
||||||
gecos: crate::Gecos<'a>, /* Real name. */
|
gecos: crate::Gecos, /* Real name. */
|
||||||
home_dir: crate::HomeDir<'a>, /* Home directory. */
|
home_dir: crate::HomeDir, /* Home directory. */
|
||||||
shell_path: crate::ShellPath<'a>, /* Shell program. */
|
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
|
/// Parse a line formatted like one in `/etc/passwd` and construct a matching [`adduser::User`] instance
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
|
/// use adduser::NewFromString;
|
||||||
/// let pwd = adduser::User::new_from_string(
|
/// let pwd = adduser::User::new_from_string(
|
||||||
/// "testuser:testpassword:1001:1001:full Name,,,,:/home/test:/bin/test"
|
/// "testuser:testpassword:1001:1001:full Name,,,,:/home/test:/bin/test".to_string()).unwrap();
|
||||||
/// ).unwrap();
|
|
||||||
/// assert_eq!(pwd.get_username(), "testuser");
|
/// assert_eq!(pwd.get_username(), "testuser");
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// When parsing fails this function returns a `UserLibError::Message` containing some information as to why the function failed.
|
/// 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<Self, crate::UserLibError>
|
fn new_from_string(line: String) -> Result<Self, crate::UserLibError>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
let elements: Vec<&str> = line.split(':').collect();
|
let elements: Vec<String> = line.split(':').map(ToString::to_string).collect();
|
||||||
if elements.len() == 7 {
|
if elements.len() == 7 {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
source: line,
|
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(
|
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())?,
|
uid: crate::Uid::try_from(elements.get(2).unwrap().to_string())?,
|
||||||
gid: crate::Gid::try_from(*elements.get(3).unwrap())?,
|
gid: crate::Gid::try_from(elements.get(3).unwrap().to_string())?,
|
||||||
gecos: crate::Gecos::try_from(*elements.get(4).unwrap())?,
|
gecos: crate::Gecos::try_from(elements.get(4).unwrap().to_string())?,
|
||||||
home_dir: crate::HomeDir::try_from(*elements.get(5).unwrap())?,
|
home_dir: crate::HomeDir::try_from(elements.get(5).unwrap().to_string())?,
|
||||||
shell_path: crate::ShellPath::try_from(*elements.get(6).unwrap())?,
|
shell_path: crate::ShellPath::try_from(elements.get(6).unwrap().to_string())?,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Err("Failed to parse: not enough elements".into())
|
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]
|
#[must_use]
|
||||||
pub const fn get_username(&self) -> &'a str {
|
pub fn get_username(&self) -> &str {
|
||||||
self.username.username
|
&self.username.username
|
||||||
}
|
}
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn get_password(&self) -> &'a str {
|
pub fn get_password(&self) -> &str {
|
||||||
match self.password {
|
match &self.password {
|
||||||
crate::Password::Encrypted(crate::EncryptedPassword { password }) => password,
|
crate::Password::Encrypted(crate::EncryptedPassword { password }) => &password,
|
||||||
crate::Password::Shadow(crate::Shadow { ref password, .. }) => password.password,
|
crate::Password::Shadow(crate::Shadow { ref password, .. }) => &password.password,
|
||||||
crate::Password::Disabled => "x",
|
crate::Password::Disabled => &"x",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@ -82,39 +82,41 @@ impl<'a> User<'a> {
|
|||||||
&self.gecos
|
&self.gecos
|
||||||
}
|
}
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn get_home_dir(&self) -> &'a str {
|
pub fn get_home_dir(&self) -> &str {
|
||||||
self.home_dir.dir
|
&self.home_dir.dir
|
||||||
}
|
}
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn get_shell_path(&self) -> &'a str {
|
pub fn get_shell_path(&self) -> &str {
|
||||||
self.shell_path.shell
|
&self.shell_path.shell
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for User<'_> {
|
impl Default for User {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
source: "",
|
source: "".to_owned(),
|
||||||
username: crate::Username {
|
username: crate::Username {
|
||||||
username: "defaultuser",
|
username: "defaultuser".to_owned(),
|
||||||
},
|
},
|
||||||
password: crate::Password::Encrypted(crate::EncryptedPassword {
|
password: crate::Password::Encrypted(crate::EncryptedPassword {
|
||||||
password: "notencrypted",
|
password: "notencrypted".to_owned(),
|
||||||
}),
|
}),
|
||||||
uid: crate::Uid { uid: 1001 },
|
uid: crate::Uid { uid: 1001 },
|
||||||
gid: crate::Gid { gid: 1001 },
|
gid: crate::Gid { gid: 1001 },
|
||||||
gecos: crate::Gecos::Simple {
|
gecos: crate::Gecos::Simple {
|
||||||
comment: "gecos default comment",
|
comment: "gecos default comment".to_string(),
|
||||||
},
|
},
|
||||||
home_dir: crate::HomeDir {
|
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 {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
@ -142,16 +144,17 @@ fn test_default_user() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_new_from_string() {
|
fn test_new_from_string() {
|
||||||
// Test if a single line can be parsed and if the resulting struct is populated correctly.
|
// 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!(
|
assert_eq!(
|
||||||
fail,
|
fail,
|
||||||
crate::UserLibError::Message("Failed to parse: not enough elements".into())
|
crate::UserLibError::Message("Failed to parse: not enough elements".into())
|
||||||
);
|
);
|
||||||
let pwd =
|
let pwd = User::new_from_string(
|
||||||
User::new_from_string("testuser:testpassword:1001:1001:testcomment:/home/test:/bin/test")
|
"testuser:testpassword:1001:1001:testcomment:/home/test:/bin/test".into(),
|
||||||
.unwrap();
|
)
|
||||||
|
.unwrap();
|
||||||
let pwd2 =
|
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();
|
.unwrap();
|
||||||
assert_eq!(pwd.username.username, "testuser");
|
assert_eq!(pwd.username.username, "testuser");
|
||||||
assert_eq!(pwd.home_dir.dir, "/home/test");
|
assert_eq!(pwd.home_dir.dir, "/home/test");
|
||||||
@ -189,7 +192,7 @@ fn test_parse_passwd() {
|
|||||||
for line in reader.lines() {
|
for line in reader.lines() {
|
||||||
let lineorig: String = line.unwrap();
|
let lineorig: String = line.unwrap();
|
||||||
let linecopy = lineorig.clone();
|
let linecopy = lineorig.clone();
|
||||||
let pass_struc = User::new_from_string(&linecopy).unwrap();
|
let pass_struc = User::new_from_string(linecopy).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
// ignoring the numbers of `,` since the implementation does not (yet) reproduce a missing comment field.
|
// ignoring the numbers of `,` since the implementation does not (yet) reproduce a missing comment field.
|
||||||
lineorig,
|
lineorig,
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
use log::warn;
|
use log::warn;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
use crate::userlib_error::UserLibError;
|
use crate::UserLibError;
|
||||||
use std::cmp::Eq;
|
use std::cmp::Eq;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
@ -21,25 +21,25 @@ use std::fmt::{self, Display};
|
|||||||
///
|
///
|
||||||
/// In the future some extra fields might be added.
|
/// In the future some extra fields might be added.
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct Username<'a> {
|
pub struct Username {
|
||||||
/// The username value
|
/// 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 {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{}", self.username,)
|
write!(f, "{}", self.username,)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TryFrom<&'a str> for Username<'a> {
|
impl TryFrom<String> for Username {
|
||||||
type Error = UserLibError;
|
type Error = UserLibError;
|
||||||
fn try_from(source: &'a str) -> std::result::Result<Self, Self::Error> {
|
fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref USERVALIDATION: Regex =
|
static ref USERVALIDATION: Regex =
|
||||||
Regex::new("^[a-z_]([a-z0-9_\\-]{0,31}|[a-z0-9_\\-]{0,30}\\$)$").unwrap();
|
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 })
|
Ok(Self { username: source })
|
||||||
} else if source == "Debian-exim" {
|
} else if source == "Debian-exim" {
|
||||||
warn!("username {} is not a valid username. This might cause problems. (It is default in Debian and Ubuntu)", source);
|
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)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum Password<'a> {
|
pub enum Password {
|
||||||
Encrypted(crate::EncryptedPassword<'a>),
|
Encrypted(crate::EncryptedPassword),
|
||||||
Shadow(crate::Shadow<'a>),
|
Shadow(crate::Shadow),
|
||||||
Disabled,
|
Disabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Password<'_> {
|
impl Display for Password {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Password::Encrypted(EncryptedPassword { password }) => write!(f, "{}", password,),
|
Password::Encrypted(EncryptedPassword { password }) => write!(f, "{}", password,),
|
||||||
@ -71,19 +71,19 @@ impl Display for Password<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct EncryptedPassword<'a> {
|
pub struct EncryptedPassword {
|
||||||
pub(in crate::user) password: &'a str,
|
pub(in crate::user) password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for EncryptedPassword<'_> {
|
impl Display for EncryptedPassword {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "{}", self.password,)
|
write!(f, "{}", self.password,)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TryFrom<&'a str> for EncryptedPassword<'a> {
|
impl TryFrom<String> for EncryptedPassword {
|
||||||
type Error = UserLibError;
|
type Error = UserLibError;
|
||||||
fn try_from(source: &'a str) -> std::result::Result<Self, Self::Error> {
|
fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
|
||||||
if source == "x" {
|
if source == "x" {
|
||||||
warn!("password from shadow not loaded!")
|
warn!("password from shadow not loaded!")
|
||||||
} else {
|
} else {
|
||||||
@ -104,9 +104,9 @@ impl Display for Uid {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&str> for Uid {
|
impl TryFrom<String> for Uid {
|
||||||
type Error = UserLibError;
|
type Error = UserLibError;
|
||||||
fn try_from(source: &str) -> std::result::Result<Self, Self::Error> {
|
fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
uid: source.parse::<u32>().unwrap(),
|
uid: source.parse::<u32>().unwrap(),
|
||||||
})
|
})
|
||||||
@ -132,9 +132,9 @@ impl Display for Gid {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&str> for Gid {
|
impl TryFrom<String> for Gid {
|
||||||
type Error = UserLibError;
|
type Error = UserLibError;
|
||||||
fn try_from(source: &str) -> std::result::Result<Self, Self::Error> {
|
fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
gid: source.parse::<u32>().unwrap(),
|
gid: source.parse::<u32>().unwrap(),
|
||||||
})
|
})
|
||||||
@ -151,38 +151,38 @@ impl Gid {
|
|||||||
|
|
||||||
/// The home directory of a user
|
/// The home directory of a user
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct HomeDir<'a> {
|
pub struct HomeDir {
|
||||||
pub(in crate::user) dir: &'a str,
|
pub(in crate::user) dir: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for HomeDir<'_> {
|
impl Display for HomeDir {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{}", self.dir,)
|
write!(f, "{}", self.dir,)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TryFrom<&'a str> for HomeDir<'a> {
|
impl TryFrom<String> for HomeDir {
|
||||||
type Error = UserLibError;
|
type Error = UserLibError;
|
||||||
fn try_from(source: &'a str) -> std::result::Result<Self, Self::Error> {
|
fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
|
||||||
Ok(Self { dir: source })
|
Ok(Self { dir: source })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The path to the Shell binary
|
/// The path to the Shell binary
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct ShellPath<'a> {
|
pub struct ShellPath {
|
||||||
pub(in crate::user) shell: &'a str,
|
pub(in crate::user) shell: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ShellPath<'_> {
|
impl Display for ShellPath {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{}", self.shell,)
|
write!(f, "{}", self.shell,)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TryFrom<&'a str> for ShellPath<'a> {
|
impl TryFrom<String> for ShellPath {
|
||||||
type Error = UserLibError;
|
type Error = UserLibError;
|
||||||
fn try_from(source: &'a str) -> std::result::Result<Self, Self::Error> {
|
fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
|
||||||
Ok(ShellPath { shell: source })
|
Ok(ShellPath { shell: source })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -192,47 +192,51 @@ impl<'a> TryFrom<&'a str> for ShellPath<'a> {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_username_validation() {
|
fn test_username_validation() {
|
||||||
// Failing tests
|
// Failing tests
|
||||||
let umlauts = Username::try_from("täst"); // umlauts
|
let umlauts: Result<Username, UserLibError> = Username::try_from("täst".to_owned()); // umlauts
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Err(UserLibError::Message("Invalid username täst".into())),
|
Err(UserLibError::Message("Invalid username täst".into())),
|
||||||
umlauts
|
umlauts
|
||||||
);
|
);
|
||||||
let number_first = Username::try_from("11elf"); // numbers first
|
let number_first = Username::try_from("11elf".to_owned()); // numbers first
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Err(UserLibError::Message("Invalid username 11elf".into())),
|
Err(UserLibError::Message("Invalid username 11elf".into())),
|
||||||
number_first
|
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!(
|
assert_eq!(
|
||||||
Err(UserLibError::Message("Invalid username test/name".into())),
|
Err(UserLibError::Message("Invalid username test/name".into())),
|
||||||
slashes
|
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!(
|
assert_eq!(
|
||||||
Err(UserLibError::Message(
|
Err(UserLibError::Message(
|
||||||
"Invalid username aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".into()
|
"Invalid username aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_owned()
|
||||||
)),
|
)),
|
||||||
long
|
long
|
||||||
);
|
);
|
||||||
// Working tests
|
// 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");
|
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");
|
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");
|
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");
|
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$");
|
assert_eq!(normal.unwrap().username, "enya$");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_guid_system_user() {
|
fn test_guid_system_user() {
|
||||||
// Check uids of system users.
|
// 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 {
|
for val in values {
|
||||||
assert_eq!(Uid::try_from(val.0).unwrap().is_system_uid(), val.1);
|
assert_eq!(Uid::try_from(val.0.clone()).unwrap().is_system_uid(), val.1);
|
||||||
assert_eq!(Gid::try_from(val.0).unwrap().is_system_gid(), val.1);
|
assert_eq!(Gid::try_from(val.0.clone()).unwrap().is_system_gid(), val.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,30 +17,30 @@ use std::fmt::{self, Debug, Display};
|
|||||||
|
|
||||||
/// A record(line) in the user database `/etc/shadow` found in most linux systems.
|
/// A record(line) in the user database `/etc/shadow` found in most linux systems.
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct Shadow<'a> {
|
pub struct Shadow {
|
||||||
username: crate::Username<'a>, /* Username. */
|
username: crate::Username, /* Username. */
|
||||||
pub(crate) password: crate::EncryptedPassword<'a>, /* Hashed passphrase */
|
pub(crate) password: crate::EncryptedPassword, /* Hashed passphrase */
|
||||||
last_change: Option<chrono::NaiveDateTime>, /* User ID. */
|
last_change: Option<chrono::NaiveDateTime>, /* User ID. */
|
||||||
earliest_change: Option<chrono::NaiveDateTime>, /* Group ID. */
|
earliest_change: Option<chrono::NaiveDateTime>, /* Group ID. */
|
||||||
latest_change: Option<chrono::NaiveDateTime>, /* Real name. */
|
latest_change: Option<chrono::NaiveDateTime>, /* Real name. */
|
||||||
warn_period: Option<chrono::Duration>, /* Home directory. */
|
warn_period: Option<chrono::Duration>, /* Home directory. */
|
||||||
deactivated: Option<chrono::Duration>, /* Shell program. */
|
deactivated: Option<chrono::Duration>, /* Shell program. */
|
||||||
deactivated_since: Option<chrono::Duration>, /* Shell program. */
|
deactivated_since: Option<chrono::Duration>, /* Shell program. */
|
||||||
extensions: Option<u64>, /* Shell program. */
|
extensions: Option<u64>, /* Shell program. */
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Shadow<'a> {
|
impl Shadow {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn get_username(&self) -> &'a str {
|
pub fn get_username(&self) -> &str {
|
||||||
self.username.username
|
&self.username.username
|
||||||
}
|
}
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn get_password(&self) -> &'a str {
|
pub fn get_password(&self) -> &str {
|
||||||
self.password.password
|
&self.password.password
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Display for Shadow<'a> {
|
impl Display for Shadow {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
@ -78,27 +78,28 @@ fn show_option_duration(input: Option<chrono::Duration>) -> 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
|
/// Parse a line formatted like one in `/etc/shadow` and construct a matching `Shadow` instance
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
|
/// use adduser::NewFromString;
|
||||||
/// let shad = adduser::Shadow::new_from_string(
|
/// 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();
|
/// ).unwrap();
|
||||||
/// assert_eq!(shad.get_username(), "test");
|
/// assert_eq!(shad.get_username(), "test");
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// When parsing fails this function returns a `UserLibError::Message` containing some information as to why the function failed.
|
/// 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<Self, UserLibError> {
|
fn new_from_string(line: String) -> Result<Self, UserLibError> {
|
||||||
println!("{}", &line);
|
println!("{}", &line);
|
||||||
let elements: Vec<&str> = line.split(':').collect();
|
let elements: Vec<String> = line.split(':').map(ToString::to_string).collect();
|
||||||
if elements.len() == 9 {
|
if elements.len() == 9 {
|
||||||
let extra = elements.get(8).unwrap();
|
let extra = elements.get(8).unwrap();
|
||||||
Ok(Shadow {
|
Ok(Shadow {
|
||||||
username: crate::Username::try_from(*elements.get(0).unwrap())?,
|
username: crate::Username::try_from(elements.get(0).unwrap().to_string())?,
|
||||||
password: crate::EncryptedPassword::try_from(*elements.get(1).unwrap())?,
|
password: crate::EncryptedPassword::try_from(elements.get(1).unwrap().to_string())?,
|
||||||
last_change: date_since_epoch(elements.get(2).unwrap()),
|
last_change: date_since_epoch(elements.get(2).unwrap()),
|
||||||
earliest_change: date_since_epoch(elements.get(3).unwrap()),
|
earliest_change: date_since_epoch(elements.get(3).unwrap()),
|
||||||
latest_change: date_since_epoch(elements.get(4).unwrap()),
|
latest_change: date_since_epoch(elements.get(4).unwrap()),
|
||||||
@ -145,7 +146,7 @@ fn duration_for_days(days_source: &str) -> Option<chrono::Duration> {
|
|||||||
fn test_parse_and_back_identity() {
|
fn test_parse_and_back_identity() {
|
||||||
println!("Test");
|
println!("Test");
|
||||||
let line = "test:!!$6$/RotIe4VZzzAun4W$7YUONvru1rDnllN5TvrnOMsWUD5wSDUPAD6t6/Xwsr/0QOuWF3HcfAhypRkGa8G1B9qqWV5kZSnCb8GKMN9N61:18260:0:99999:7:::";
|
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);
|
println!("{:#?}", line2);
|
||||||
assert_eq!(format!("{}", line2), line);
|
assert_eq!(format!("{}", line2), line);
|
||||||
}
|
}
|
||||||
|
@ -12,11 +12,11 @@ use std::fs::File;
|
|||||||
use std::io::{BufReader, Read};
|
use std::io::{BufReader, Read};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
pub struct UserDBLocal<'a> {
|
pub struct UserDBLocal {
|
||||||
source_files: Files,
|
source_files: Files,
|
||||||
pub passwd_entries: Vec<crate::User<'a>>,
|
pub passwd_entries: Vec<crate::User>,
|
||||||
pub shadow_entries: Vec<crate::Shadow<'a>>,
|
pub shadow_entries: Vec<crate::Shadow>,
|
||||||
pub group_entries: Vec<crate::Group<'a>>,
|
pub group_entries: Vec<crate::Group>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Files {
|
pub struct Files {
|
||||||
@ -35,12 +35,12 @@ impl Default for Files {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> UserDBLocal<'a> {
|
impl UserDBLocal {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn import_from_strings(
|
pub fn import_from_strings(
|
||||||
passwd_content: &'a str,
|
passwd_content: String,
|
||||||
shadow_content: &'a str,
|
shadow_content: String,
|
||||||
group_content: &'a str,
|
group_content: String,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let res = UserDBLocal {
|
let res = UserDBLocal {
|
||||||
source_files: Files {
|
source_files: Files {
|
||||||
@ -53,7 +53,10 @@ impl<'a> UserDBLocal<'a> {
|
|||||||
.filter_map(|line| {
|
.filter_map(|line| {
|
||||||
if line.len() > 5 {
|
if line.len() > 5 {
|
||||||
println!("{}", line);
|
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 {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -63,7 +66,9 @@ impl<'a> UserDBLocal<'a> {
|
|||||||
.lines()
|
.lines()
|
||||||
.filter_map(|line| {
|
.filter_map(|line| {
|
||||||
if line.len() > 5 {
|
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 {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -73,7 +78,10 @@ impl<'a> UserDBLocal<'a> {
|
|||||||
.lines()
|
.lines()
|
||||||
.filter_map(|line| {
|
.filter_map(|line| {
|
||||||
if line.len() > 5 {
|
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 {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -83,19 +91,31 @@ impl<'a> UserDBLocal<'a> {
|
|||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn load_files(files: Files) -> Self {
|
pub fn load_files(files: Files) -> Self {
|
||||||
let passwd_file =
|
let passwd_file = File::open(
|
||||||
File::open(files.group.expect("passwd file path cannot be None")).unwrap();
|
files
|
||||||
|
.group
|
||||||
|
.clone()
|
||||||
|
.expect("passwd file path cannot be None"),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
let mut passwd_reader = BufReader::new(passwd_file);
|
let mut passwd_reader = BufReader::new(passwd_file);
|
||||||
let mut my_passwd_lines = String::new();
|
let mut my_passwd_lines = String::new();
|
||||||
passwd_reader.read_to_string(&mut my_passwd_lines).unwrap();
|
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 group_reader = BufReader::new(group_file);
|
||||||
let mut my_group_lines = String::new();
|
let mut my_group_lines = String::new();
|
||||||
group_reader.read_to_string(&mut my_group_lines).unwrap();
|
group_reader.read_to_string(&mut my_group_lines).unwrap();
|
||||||
let shadow_file = File::open(files.shadow.expect("shadow file path cannot be None"))
|
let shadow_file = File::open(
|
||||||
.expect("Failed to read the shadow file. Most of the time root permissions are needed");
|
files
|
||||||
let mut shadow_reader = BufReader::new(group_file);
|
.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();
|
let mut my_shadow_lines = String::new();
|
||||||
shadow_reader.read_to_string(&mut my_shadow_lines).unwrap();
|
shadow_reader.read_to_string(&mut my_shadow_lines).unwrap();
|
||||||
|
|
||||||
@ -108,22 +128,22 @@ impl<'a> UserDBLocal<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait NewFromString<'a> {
|
pub trait NewFromString {
|
||||||
fn new_from_string(line: &'a str) -> Result<Self, crate::UserLibError>
|
fn new_from_string(line: String) -> Result<Self, crate::UserLibError>
|
||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn string_to<'a, T>(source: &'a str) -> Vec<T>
|
fn string_to<T>(source: &str) -> Vec<T>
|
||||||
where
|
where
|
||||||
T: NewFromString<'a>,
|
T: NewFromString,
|
||||||
{
|
{
|
||||||
source
|
source
|
||||||
.lines()
|
.lines()
|
||||||
.filter_map(|line| {
|
.filter_map(|line| {
|
||||||
if line.len() > 5 {
|
if line.len() > 5 {
|
||||||
println!("{}", line);
|
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 {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -133,7 +153,7 @@ where
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_creator_user_db_local() {
|
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!(
|
assert_eq!(
|
||||||
data.passwd_entries.get(0).unwrap().get_username(),
|
data.passwd_entries.get(0).unwrap().get_username(),
|
||||||
"testuser"
|
"testuser"
|
||||||
@ -152,6 +172,6 @@ fn test_parsing_local_database() {
|
|||||||
let mut group_reader = BufReader::new(group_file);
|
let mut group_reader = BufReader::new(group_file);
|
||||||
let mut my_group_lines = "".to_string();
|
let mut my_group_lines = "".to_string();
|
||||||
group_reader.read_to_string(&mut my_group_lines).unwrap();
|
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");
|
assert_eq!(data.group_entries.get(0).unwrap().get_groupname(), "root");
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user