From 4db597c058da51660f728c948e4182f6c43af8c7 Mon Sep 17 00:00:00 2001 From: Dietrich Date: Fri, 2 Oct 2020 18:13:15 +0200 Subject: [PATCH] initial parsing of shadow file --- Cargo.lock | 1 + Cargo.toml | 1 + src/lib.rs | 3 +- src/shadow.rs | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 src/shadow.rs diff --git a/Cargo.lock b/Cargo.lock index dbc38c5..3ac4d1e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,6 +4,7 @@ name = "adduser" version = "0.1.0" dependencies = [ + "chrono", "lazy_static", "log", "regex", diff --git a/Cargo.toml b/Cargo.toml index 3b0b874..99436ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,4 @@ regex = "1" lazy_static = "1.4" log = "0.4" simplelog = "0.8" +chrono = "0.4" \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 4dd27f0..3be952e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,5 +4,6 @@ extern crate lazy_static; extern crate log; pub mod passwd; +pub mod shadow; pub mod userlib_error; -pub use passwd::{Gecos, Gid, HomeDir, Password, ShellPath, Uid, Username}; +pub use passwd::{Gecos, Gid, HomeDir, Passwd, Password, ShellPath, Uid, Username}; diff --git a/src/shadow.rs b/src/shadow.rs new file mode 100644 index 0000000..31ea94c --- /dev/null +++ b/src/shadow.rs @@ -0,0 +1,92 @@ +#![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 chrono; +use std::cmp::Eq; +use std::convert::TryFrom; +use std::fmt::{self, 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. */ + password: passwd::Password<'a>, /* Hashed passphrase */ + last_change: Option, /* User ID. */ + earliest_change: Option, /* Group ID. */ + lateste_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> { + /// Parse a line formatted like one in `/etc/shadow` and construct a matching `Shadow` instance + /// + /// # Example + /// ``` + /// let pwd = adduser::shadow::Shadow::new_from_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. + pub fn new_from_string(line: &'a str) -> Result { + let elements: Vec<&str> = line.split(':').collect(); + if elements.len() == 9 { + Ok(Shadow { + username: passwd::Username::try_from(*elements.get(0).unwrap())?, + password: passwd::Password::try_from(*elements.get(1).unwrap())?, + last_change: date_since_epoch(elements.get(2).unwrap()), + earliest_change: date_since_epoch(elements.get(3).unwrap()), + lateste_change: date_since_epoch(elements.get(4).unwrap()), + warn_period: duration_for_days(elements.get(5).unwrap()), + deactivated: duration_for_days(elements.get(6).unwrap()), + deactivated_since: duration_for_days(elements.get(7).unwrap()), + extensions: Some(0), + }) + } else { + Err("Failed to parse: not enough elements".into()) + } + } +} + +const SECONDS_PER_DAY: i64 = 86400; +fn date_since_epoch(days_since_epoch: &str) -> Option { + if days_since_epoch.is_empty() { + None + } else { + let days: i64 = days_since_epoch.parse::().unwrap(); + let seconds = days * SECONDS_PER_DAY; + Some(chrono::NaiveDateTime::from_timestamp(seconds, 0)) + } +} +fn duration_for_days(days_source: &str) -> Option { + if days_source.is_empty() { + None + } else { + let days: i64 = days_source.parse::().unwrap(); + Some(chrono::Duration::days(days)) + } +} + +#[test] +fn test_since_epoch() { + panic!(format!( + "{:?}", + Shadow::new_from_string("dietrich:$6$SCJjPV7$SZ7XgOdEMiqZ3v5n9Q2AR2yJKN0PLbSHlrdiZcp/NcB41JEtT12Ke3Zy6XThfiFemJheC0IrM3..JVCAagqxg.:18110:0:99999:7:::") + )); +}