From 4af05c1134bb89378d92914e8b7242996fc84cfe Mon Sep 17 00:00:00 2001 From: Dietrich Date: Sat, 3 Oct 2020 15:18:45 +0200 Subject: [PATCH] initial parsing and testing for a shadow line --- src/bin/main.rs | 4 +++ src/shadow.rs | 73 ++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 64 insertions(+), 13 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index 1e3cbf3..59e9a66 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -1,6 +1,7 @@ extern crate adduser; use adduser::passwd::Passwd; +use adduser::shadow::Shadow; use std::fs::File; use std::io::{prelude::*, BufReader}; @@ -20,6 +21,9 @@ fn main() { println!("{}", Passwd::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 pwd2 = // Passwd::new_from_string("howdy:notencrypted:1001:1001:not done:/home/test:/bin/bash"); diff --git a/src/shadow.rs b/src/shadow.rs index 31ea94c..97b5975 100644 --- a/src/shadow.rs +++ b/src/shadow.rs @@ -8,14 +8,12 @@ #![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}; +use std::fmt::{self, Debug, Display}; /// A record(line) in the user database `/etc/shadow` found in most linux systems. #[derive(Debug, PartialEq, Eq)] @@ -24,42 +22,90 @@ pub struct Shadow<'a> { password: passwd::Password<'a>, /* Hashed passphrase */ last_change: Option, /* User ID. */ earliest_change: Option, /* Group ID. */ - lateste_change: Option, /* Real name. */ + 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> Display for Shadow<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!( + f, + "{}:{}:{}:{}:{}:{}:{}:{}:{}", + self.username, + self.password, + show_option_date(self.last_change), + show_option_date(self.earliest_change), + show_option_date(self.latest_change), + show_option_duration(self.warn_period), + show_option_duration(self.deactivated), + show_option_duration(self.deactivated_since), + if self.extensions.is_none() { + "".to_string() + } else { + self.extensions.unwrap().to_string() + } + ) + } +} + +fn show_option_date(input: Option) -> String { + if input.is_none() { + "".into() + } else { + format!("{}", input.unwrap().timestamp() / SECONDS_PER_DAY) + } +} + +fn show_option_duration(input: Option) -> String { + if input.is_none() { + "".into() + } else { + format!("{}", input.unwrap().num_days()) + } +} + 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( - /// "" + /// "test:!!$6$/RotIe4VZzzAun4W$7YUONvru1rDnllN5TvrnOMsWUD5wSDUPAD6t6/Xwsr/0QOuWF3HcfAhypRkGa8G1B9qqWV5kZSnCb8GKMN9N61:18260:0:99999:7:::" /// ).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 { + println!("{}", &line); let elements: Vec<&str> = line.split(':').collect(); if elements.len() == 9 { + let extra = elements.get(8).unwrap(); 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()), + latest_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), + extensions: if extra.is_empty() { + None + } else { + Some(extra.parse::().unwrap()) + }, }) } else { - Err("Failed to parse: not enough elements".into()) + Err(UserLibError::Message(format!( + "Failed to parse: not enough elements ({}): {:?}", + elements.len(), + elements + ))) } } } @@ -85,8 +131,9 @@ fn duration_for_days(days_source: &str) -> Option { #[test] fn test_since_epoch() { - panic!(format!( - "{:?}", - Shadow::new_from_string("dietrich:$6$SCJjPV7$SZ7XgOdEMiqZ3v5n9Q2AR2yJKN0PLbSHlrdiZcp/NcB41JEtT12Ke3Zy6XThfiFemJheC0IrM3..JVCAagqxg.:18110:0:99999:7:::") - )); + println!("Test"); + let line = "test:!!$6$/RotIe4VZzzAun4W$7YUONvru1rDnllN5TvrnOMsWUD5wSDUPAD6t6/Xwsr/0QOuWF3HcfAhypRkGa8G1B9qqWV5kZSnCb8GKMN9N61:18260:0:99999:7:::"; + let line2 = Shadow::new_from_string(line).unwrap(); + println!("{:#?}", line2); + assert_eq!(format!("{}", line2), line); }