224 lines
7.2 KiB
Rust
224 lines
7.2 KiB
Rust
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};
|
|
|
|
/// A record(line) in the user database `/etc/passwd` found in most linux systems.
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub struct User {
|
|
source: String,
|
|
username: crate::Username, /* Username. */
|
|
pub(crate) 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 NewFromString for User {
|
|
/// Parse a line formatted like one in `/etc/passwd` and construct a matching [`User`] instance
|
|
///
|
|
/// # Example
|
|
/// ```
|
|
/// use crate::adduser::api::UserRead;
|
|
/// use adduser::NewFromString;
|
|
/// let pwd = adduser::User::new_from_string(
|
|
/// "testuser:testpassword:1001:1001:full Name,,,,:/home/test:/bin/test".to_string()).unwrap();
|
|
/// assert_eq!(pwd.get_username().unwrap(), "testuser");
|
|
/// ```
|
|
///
|
|
/// # Errors
|
|
/// When parsing fails this function returns a [`UserLibError::Message`](crate::userlib_error::UserLibError::Message) containing some information as to why the function failed.
|
|
fn new_from_string(line: String) -> Result<Self, crate::UserLibError>
|
|
where
|
|
Self: Sized,
|
|
{
|
|
let elements: Vec<String> = line.split(':').map(ToString::to_string).collect();
|
|
if elements.len() == 7 {
|
|
Ok(Self {
|
|
source: line,
|
|
username: crate::Username::try_from(elements.get(0).unwrap().to_string())?,
|
|
password: crate::Password::Encrypted(crate::EncryptedPassword::try_from(
|
|
elements.get(1).unwrap().to_string(),
|
|
)?),
|
|
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())
|
|
}
|
|
}
|
|
}
|
|
|
|
impl crate::api::UserRead for User {
|
|
#[must_use]
|
|
fn get_username(&self) -> Option<&str> {
|
|
Some(&self.username.username)
|
|
}
|
|
#[must_use]
|
|
fn get_password(&self) -> Option<&str> {
|
|
match &self.password {
|
|
crate::Password::Encrypted(crate::EncryptedPassword { password }) => Some(&password),
|
|
crate::Password::Shadow(crate::Shadow { ref password, .. }) => Some(&password.password),
|
|
crate::Password::Disabled => None,
|
|
}
|
|
}
|
|
#[must_use]
|
|
fn get_uid(&self) -> u32 {
|
|
self.uid.uid
|
|
}
|
|
#[must_use]
|
|
fn get_gid(&self) -> u32 {
|
|
self.gid.gid
|
|
}
|
|
#[must_use]
|
|
fn get_gecos(&self) -> Option<&crate::Gecos> {
|
|
Some(&self.gecos)
|
|
}
|
|
#[must_use]
|
|
fn get_home_dir(&self) -> Option<&str> {
|
|
Some(&self.home_dir.dir)
|
|
}
|
|
#[must_use]
|
|
fn get_shell_path(&self) -> Option<&str> {
|
|
Some(&self.shell_path.shell)
|
|
}
|
|
|
|
fn get_full_name(&self) -> Option<&str> {
|
|
self.gecos.get_full_name()
|
|
}
|
|
|
|
fn get_room(&self) -> Option<&str> {
|
|
self.gecos.get_room()
|
|
}
|
|
|
|
fn get_phone_work(&self) -> Option<&str> {
|
|
self.gecos.get_phone_work()
|
|
}
|
|
|
|
fn get_phone_home(&self) -> Option<&str> {
|
|
self.gecos.get_phone_home()
|
|
}
|
|
|
|
fn get_other(&self) -> Option<&Vec<String>> {
|
|
self.gecos.get_other()
|
|
}
|
|
}
|
|
|
|
impl Default for User {
|
|
fn default() -> Self {
|
|
Self {
|
|
source: "".to_owned(),
|
|
username: crate::Username {
|
|
username: "defaultuser".to_owned(),
|
|
},
|
|
password: crate::Password::Encrypted(crate::EncryptedPassword {
|
|
password: "notencrypted".to_owned(),
|
|
}),
|
|
uid: crate::Uid { uid: 1001 },
|
|
gid: crate::Gid { gid: 1001 },
|
|
gecos: crate::Gecos::Simple {
|
|
comment: "gecos default comment".to_string(),
|
|
},
|
|
home_dir: crate::HomeDir {
|
|
dir: "/home/default".to_owned(),
|
|
},
|
|
shell_path: crate::ShellPath {
|
|
shell: "/bin/bash".to_owned(),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
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("".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".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".into())
|
|
.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)
|
|
);
|
|
}
|
|
}
|