Fix (remove) lifetimes and restructure the code Co-authored-by: Dietrich <dietrich@teilgedanken.de> Reviewed-on: http://git.teilgedanken.de/Rust/useradd/pulls/2
243 lines
6.8 KiB
Rust
243 lines
6.8 KiB
Rust
#![warn(
|
|
clippy::all,
|
|
/* clippy::restriction,*/
|
|
clippy::pedantic,
|
|
clippy::nursery,
|
|
clippy::cargo
|
|
)]
|
|
#![allow(clippy::non_ascii_literal)]
|
|
|
|
use log::warn;
|
|
use regex::Regex;
|
|
|
|
use crate::UserLibError;
|
|
use std::cmp::Eq;
|
|
use std::convert::TryFrom;
|
|
use std::fmt::{self, Display};
|
|
|
|
/// The username of the current user
|
|
///
|
|
/// When done the validity will automatically be checked in the `trait TryFrom`.
|
|
///
|
|
/// In the future some extra fields might be added.
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub struct Username {
|
|
/// The username value
|
|
pub(in crate::user) username: String,
|
|
}
|
|
|
|
impl Display for Username {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{}", self.username,)
|
|
}
|
|
}
|
|
|
|
impl TryFrom<String> for Username {
|
|
type Error = UserLibError;
|
|
fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
|
|
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 { 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);
|
|
Ok(Self { username: source })
|
|
} else {
|
|
Err(UserLibError::Message(format!(
|
|
"Invalid username {}",
|
|
source
|
|
)))
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub enum Password {
|
|
Encrypted(crate::EncryptedPassword),
|
|
Shadow(crate::Shadow),
|
|
Disabled,
|
|
}
|
|
|
|
impl Display for Password {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Password::Encrypted(EncryptedPassword { password }) => write!(f, "{}", password,),
|
|
Password::Shadow(_) => write!(f, "x"),
|
|
Password::Disabled => write!(f, "x"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub struct EncryptedPassword {
|
|
pub(in crate::user) password: String,
|
|
}
|
|
|
|
impl Display for EncryptedPassword {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(f, "{}", self.password,)
|
|
}
|
|
}
|
|
|
|
impl TryFrom<String> for EncryptedPassword {
|
|
type Error = UserLibError;
|
|
fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
|
|
if source == "x" {
|
|
warn!("password from shadow not loaded!")
|
|
} else {
|
|
warn!("Password field has an unexpected value")
|
|
};
|
|
Ok(Self { password: source })
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub struct Uid {
|
|
pub(in crate::user) uid: u32,
|
|
}
|
|
|
|
impl Display for Uid {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{}", self.uid,)
|
|
}
|
|
}
|
|
|
|
impl TryFrom<String> for Uid {
|
|
type Error = UserLibError;
|
|
fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
|
|
Ok(Self {
|
|
uid: source.parse::<u32>().unwrap(),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Uid {
|
|
#[must_use]
|
|
pub const fn is_system_uid(&self) -> bool {
|
|
// since it is a u32 it cannot be smaller than 0
|
|
self.uid < 1000
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub struct Gid {
|
|
pub(in crate::user) gid: u32,
|
|
}
|
|
|
|
impl Display for Gid {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{}", self.gid,)
|
|
}
|
|
}
|
|
|
|
impl TryFrom<String> for Gid {
|
|
type Error = UserLibError;
|
|
fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
|
|
Ok(Self {
|
|
gid: source.parse::<u32>().unwrap(),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Gid {
|
|
#[must_use]
|
|
pub const fn is_system_gid(&self) -> bool {
|
|
// since it is a u32 it cannot be smaller than 0
|
|
self.gid < 1000
|
|
}
|
|
}
|
|
|
|
/// The home directory of a user
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub struct HomeDir {
|
|
pub(in crate::user) dir: String,
|
|
}
|
|
|
|
impl Display for HomeDir {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{}", self.dir,)
|
|
}
|
|
}
|
|
|
|
impl TryFrom<String> for HomeDir {
|
|
type Error = UserLibError;
|
|
fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
|
|
Ok(Self { dir: source })
|
|
}
|
|
}
|
|
|
|
/// The path to the Shell binary
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub struct ShellPath {
|
|
pub(in crate::user) shell: String,
|
|
}
|
|
|
|
impl Display for ShellPath {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{}", self.shell,)
|
|
}
|
|
}
|
|
|
|
impl TryFrom<String> for ShellPath {
|
|
type Error = UserLibError;
|
|
fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
|
|
Ok(ShellPath { shell: source })
|
|
}
|
|
}
|
|
|
|
// Tests ----------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn test_username_validation() {
|
|
// Failing tests
|
|
let umlauts: Result<Username, UserLibError> = 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".to_owned()); // numbers first
|
|
assert_eq!(
|
|
Err(UserLibError::Message("Invalid username 11elf".into())),
|
|
number_first
|
|
);
|
|
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".to_owned()); // maximum size 32 letters
|
|
assert_eq!(
|
|
Err(UserLibError::Message(
|
|
"Invalid username aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_owned()
|
|
)),
|
|
long
|
|
);
|
|
// Working tests
|
|
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".to_owned()); // single characters are ok
|
|
assert_eq!(single.unwrap().username, "t");
|
|
let normal = Username::try_from("superman".to_owned()); // regular username
|
|
assert_eq!(normal.unwrap().username, "superman");
|
|
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$".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".to_owned(), true),
|
|
("0".to_owned(), true),
|
|
("1000".to_owned(), false),
|
|
];
|
|
for val in values {
|
|
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);
|
|
}
|
|
}
|