From bf6908346e2ec3468df54ba98721fb2540cd71d2 Mon Sep 17 00:00:00 2001 From: Dietrich Date: Wed, 28 Oct 2020 09:39:42 +0100 Subject: [PATCH] first draft of working user deletion TODO: * remove user group * remove shadow entry --- src/bin/delete_user.rs | 23 ++++++++-- src/user/mod.rs | 27 +++++++++++ src/userlib.rs | 100 ++++++++++++++++++++++++++++++++++------- 3 files changed, 131 insertions(+), 19 deletions(-) diff --git a/src/bin/delete_user.rs b/src/bin/delete_user.rs index 4ccb95e..6b2ae79 100644 --- a/src/bin/delete_user.rs +++ b/src/bin/delete_user.rs @@ -1,14 +1,31 @@ +use std::path::PathBuf; + extern crate adduser; extern crate env_logger; +#[allow(unused_imports)] +use log::{debug, error, info, trace, warn}; fn main() { env_logger::init(); use adduser::api::UserDBWrite; + use adduser::api::UserRead; - let mut db = adduser::UserDBLocal::load_files(adduser::Files::default()); + let mf = adduser::Files { + passwd: Some(PathBuf::from("./passwd")), + shadow: Some(PathBuf::from("./shadow")), + group: Some(PathBuf::from("./group")), + }; - let user = db.delete_user("teste").expect("failed to delete the user"); - println!("The user {} has been deleted!", user); + let mut db = adduser::UserDBLocal::load_files(mf); + + let user_res: Result = db.delete_user("teste"); + match user_res { + Ok(u) => info!( + "The user <{}> has been deleted! ", + u.get_username().unwrap() + ), + Err(e) => error!("Failed to delete the user: {}", e), + } } diff --git a/src/user/mod.rs b/src/user/mod.rs index 63f45c3..76ed7a9 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -4,6 +4,8 @@ pub mod passwd_fields; pub mod shadow_fields; use crate::userlib::NewFromString; +#[allow(unused_imports)] +use log::{debug, error, info, trace, warn}; use std::convert::TryFrom; use std::fmt::{self, Display}; @@ -22,6 +24,31 @@ pub struct User { } impl User { + /*fn get_nth_line(content: &str, n: u32) -> (String, u64) { + use std::io::BufRead; + let mut cursor = std::io::Cursor::new(content); + + let mut offset: u64 = 0; + let mut tmp = String::new(); + for _ in 0..n + 1 { + tmp = String::new(); + offset += cursor.read_line(&mut tmp).expect("failed to read line") as u64; + //trace!("reading user{}: {}", i, &tmp); + } + (tmp.trim().to_owned(), offset) + } + pub(crate) fn is_unchanged_in(&self, content: &str) -> (u64, usize, bool) { + let (line, offset) = Self::get_nth_line(content, self.pos); + trace!("Olduser:\n\t{}\nNewuser\n\t{}", &self.source, &line); + (offset, line.len(), line == self.source) + }*/ + pub fn remove_in(&self, content: &str) -> String { + content + .split(&self.source) + .map(|x| x.trim()) + .collect::>() + .join("\n") + } pub fn username(&mut self, name: String) -> &mut Self { self.username = crate::Username { username: name.into(), diff --git a/src/userlib.rs b/src/userlib.rs index c976c49..f95ce42 100644 --- a/src/userlib.rs +++ b/src/userlib.rs @@ -9,7 +9,7 @@ use crate::api::GroupRead; use crate::api::UserRead; -use log::{debug, error, info, warn}; +use log::{debug, error, info, trace, warn}; use std::fs::File; use std::io::{BufReader, Read}; use std::ops::Deref; @@ -18,14 +18,15 @@ use std::{collections::HashMap, io::Write}; pub struct UserDBLocal { source_files: Files, + source_hashes: Hashes, // to detect changes pub users: HashMap, pub groups: Vec, } pub struct Files { - passwd: Option, - shadow: Option, - group: Option, + pub passwd: Option, + pub shadow: Option, + pub group: Option, } impl Default for Files { @@ -62,6 +63,7 @@ impl Files { pub struct LockedFileGuard { lockfile: PathBuf, + path: PathBuf, pub(crate) file: File, } @@ -69,11 +71,26 @@ impl LockedFileGuard { pub fn new(path: &PathBuf) -> Result { let locked = Self::try_to_lock_file(path); match locked { - Ok((lockfile, file)) => Ok(Self { lockfile, file }), + Ok((lockfile, file)) => Ok(Self { + lockfile, + path: path.to_owned(), + file, + }), Err(e) => Err(e), } } + pub fn replace_contents( + &mut self, + new_content: String, + ) -> Result<(), crate::userlib_error::UserLibError> { + self.file = File::create(&self.path).expect("Failed to truncate file."); + self.file + .write_all(&new_content.into_bytes()) + .expect("Failed to write all users."); + Ok(()) + } + /// This function tries to lock a file in the way other passwd locking mechanisms work. /// /// * get the pid @@ -246,6 +263,7 @@ impl UserDBLocal { }, users, groups, + source_hashes: Hashes::new(&passwd_content, &shadow_content, &group_content), }; res } @@ -272,17 +290,40 @@ impl UserDBLocal { source_files: files, users, groups: string_to(&my_group_lines), + source_hashes: Hashes::new(&my_passwd_lines, &my_shadow_lines, &my_group_lines), } } } use crate::api::UserDBWrite; impl UserDBWrite for UserDBLocal { - fn delete_user(&mut self, user: &str) -> Result { - let res = self.users.remove(user); - match res { - Some(user) => Ok(user), - None => Err(format!("Failed to delete the user {}", user).into()), + fn delete_user(&mut self, username: &str) -> Result { + let opened = self.source_files.lock_all_get(); + let (mut locked_p, locked_s, locked_g) = opened.expect("failed to lock files!"); + // read the files to strings + let p = file_to_string(&locked_p.file); + let _s = file_to_string(&locked_s.file); + let _g = file_to_string(&locked_g.file); + { + let user_opt = self.users.get(username); + let user = match user_opt { + Some(user) => user, + None => { + return Err(crate::UserLibError::NotFound); + } + }; + if self.source_hashes.passwd.has_changed(&p) { + error!("The source file has changed. Deleting the user could corrupt the userdatabase. Aborting!"); + } else { + // create the new content of passwd + let modified = user.remove_in(&p); + // write the new content to the file. + locked_p + .replace_contents(modified) + .expect("Error during write to the database. Please doublecheck as the userdatabase could be corrupted"); + return Ok(user.clone()); + } + Err(format!("The user has been changed {}", username).into()) } } @@ -412,12 +453,39 @@ impl UserDBValidation for UserDBLocal { } } -fn get_nth_line(path: &File, n: u32) -> String { - let lines = file_to_string(path); - let line = lines.lines().nth(n as usize); - match line { - Some(line) => line.to_owned(), - None => "".to_owned(), +pub struct SourceHash { + hashvalue: String, +} + +impl SourceHash { + pub fn new(src: &str) -> Self { + Self { + hashvalue: src.to_owned(), + } + } + pub fn has_changed(&self, new: &str) -> bool { + trace!( + "Old and new lengths: {}, {}", + self.hashvalue.len(), + new.len() + ); + !self.hashvalue.eq(new) + } +} + +pub struct Hashes { + pub passwd: SourceHash, + pub shadow: SourceHash, + pub group: SourceHash, +} + +impl Hashes { + pub fn new(passwd: &str, shadow: &str, group: &str) -> Self { + Self { + passwd: SourceHash::new(passwd), + shadow: SourceHash::new(shadow), + group: SourceHash::new(group), + } } }