Compare commits
No commits in common. "b8c544a8460cffe2ff4cf56dddcd5d590ec3630b" and "56a6679ccbfcd6e5341125964e924ac71ce5b295" have entirely different histories.
b8c544a846
...
56a6679ccb
@ -7,9 +7,9 @@ fn main() {
|
|||||||
simplelog::TerminalMode::Mixed,
|
simplelog::TerminalMode::Mixed,
|
||||||
)])
|
)])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
//use adduser::api::UserDBWrite;
|
use adduser::api::UserDBWrite;
|
||||||
|
|
||||||
let _db = adduser::UserDBLocal::load_files(adduser::Files::default());
|
let mut db = adduser::UserDBLocal::load_files(adduser::Files::default());
|
||||||
|
|
||||||
let user = adduser::User::default()
|
let user = adduser::User::default()
|
||||||
.username("fest".into())
|
.username("fest".into())
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
extern crate adduser;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
simplelog::CombinedLogger::init(vec![simplelog::TermLogger::new(
|
|
||||||
simplelog::LevelFilter::Warn,
|
|
||||||
simplelog::Config::default(),
|
|
||||||
simplelog::TerminalMode::Mixed,
|
|
||||||
)])
|
|
||||||
.unwrap();
|
|
||||||
use adduser::api::UserDBWrite;
|
|
||||||
|
|
||||||
let mut db = adduser::UserDBLocal::load_files(adduser::Files::default());
|
|
||||||
|
|
||||||
let user = db.delete_user("teste").expect("failed to delete the user");
|
|
||||||
println!("The user {} has been deleted!", user);
|
|
||||||
}
|
|
@ -123,27 +123,22 @@ impl crate::api::UserRead for User {
|
|||||||
Some(&self.shell_path.shell)
|
Some(&self.shell_path.shell)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
fn get_full_name(&self) -> Option<&str> {
|
fn get_full_name(&self) -> Option<&str> {
|
||||||
self.gecos.get_full_name()
|
self.gecos.get_full_name()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
fn get_room(&self) -> Option<&str> {
|
fn get_room(&self) -> Option<&str> {
|
||||||
self.gecos.get_room()
|
self.gecos.get_room()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
fn get_phone_work(&self) -> Option<&str> {
|
fn get_phone_work(&self) -> Option<&str> {
|
||||||
self.gecos.get_phone_work()
|
self.gecos.get_phone_work()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
fn get_phone_home(&self) -> Option<&str> {
|
fn get_phone_home(&self) -> Option<&str> {
|
||||||
self.gecos.get_phone_home()
|
self.gecos.get_phone_home()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
fn get_other(&self) -> Option<&Vec<String>> {
|
fn get_other(&self) -> Option<&Vec<String>> {
|
||||||
self.gecos.get_other()
|
self.gecos.get_other()
|
||||||
}
|
}
|
||||||
@ -205,8 +200,8 @@ impl Display for User {
|
|||||||
fn test_default_user() {
|
fn test_default_user() {
|
||||||
// Check if a user can be created.
|
// Check if a user can be created.
|
||||||
let mut pwd = User::default();
|
let mut pwd = User::default();
|
||||||
assert_eq!(pwd.username.username, "defaultusername");
|
assert_eq!(pwd.username.username, "defaultuser");
|
||||||
assert_eq!(pwd.home_dir.dir, "/");
|
assert_eq!(pwd.home_dir.dir, "/home/default");
|
||||||
assert_eq!(pwd.uid.uid, 1001);
|
assert_eq!(pwd.uid.uid, 1001);
|
||||||
let npw = pwd.username("test".to_owned()).clone();
|
let npw = pwd.username("test".to_owned()).clone();
|
||||||
assert_eq!(npw.username.username, "test");
|
assert_eq!(npw.username.username, "test");
|
||||||
|
@ -89,9 +89,9 @@ impl TryFrom<String> for EncryptedPassword {
|
|||||||
type Error = UserLibError;
|
type Error = UserLibError;
|
||||||
fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
|
fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
|
||||||
if source == "x" {
|
if source == "x" {
|
||||||
//warn!("password from shadow not loaded!")
|
warn!("password from shadow not loaded!")
|
||||||
} else {
|
} else {
|
||||||
//warn!("Password field has an unexpected value")
|
warn!("Password field has an unexpected value")
|
||||||
};
|
};
|
||||||
Ok(Self { password: source })
|
Ok(Self { password: source })
|
||||||
}
|
}
|
||||||
|
205
src/userlib.rs
205
src/userlib.rs
@ -1,6 +1,6 @@
|
|||||||
#![warn(
|
#![warn(
|
||||||
clippy::all,
|
clippy::all,
|
||||||
//clippy::restriction,
|
clippy::restriction,
|
||||||
clippy::pedantic,
|
clippy::pedantic,
|
||||||
clippy::nursery,
|
clippy::nursery,
|
||||||
clippy::cargo
|
clippy::cargo
|
||||||
@ -9,11 +9,11 @@
|
|||||||
|
|
||||||
use crate::api::GroupRead;
|
use crate::api::GroupRead;
|
||||||
use crate::api::UserRead;
|
use crate::api::UserRead;
|
||||||
use log::{debug, error, info, warn};
|
use log::warn;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{BufReader, Read};
|
use std::io::{BufReader, Read};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::{collections::HashMap, io::Write};
|
|
||||||
|
|
||||||
pub struct UserDBLocal {
|
pub struct UserDBLocal {
|
||||||
source_files: Files,
|
source_files: Files,
|
||||||
@ -38,168 +38,6 @@ impl Default for Files {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Files {
|
|
||||||
pub fn lock_and_get_passwd(&self) -> Result<LockedFileGuard, crate::UserLibError> {
|
|
||||||
LockedFileGuard::new(self.passwd.as_ref().unwrap())
|
|
||||||
}
|
|
||||||
pub fn lock_and_get_shadow(&self) -> Result<LockedFileGuard, crate::UserLibError> {
|
|
||||||
LockedFileGuard::new(self.shadow.as_ref().unwrap())
|
|
||||||
}
|
|
||||||
pub fn lock_and_get_group(&self) -> Result<LockedFileGuard, crate::UserLibError> {
|
|
||||||
LockedFileGuard::new(self.group.as_ref().unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct LockedFileGuard {
|
|
||||||
lockfile: PathBuf,
|
|
||||||
pub(crate) file: File,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LockedFileGuard {
|
|
||||||
pub fn new(path: &PathBuf) -> Result<Self, crate::userlib_error::UserLibError> {
|
|
||||||
let locked = Self::try_to_lock_file(path);
|
|
||||||
match locked {
|
|
||||||
Ok((lockfile, file)) => Ok(Self { lockfile, file }),
|
|
||||||
Err(e) => Err(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn try_to_lock_file(path: &PathBuf) -> Result<(PathBuf, File), crate::UserLibError> {
|
|
||||||
// Ok write "/etc/passwd.12397" to file
|
|
||||||
// Ok write "/etc/passwd.lock" to lock
|
|
||||||
// Ok get the pid
|
|
||||||
// Ok open the file
|
|
||||||
// Ok write all to the tempfile
|
|
||||||
// Ok try to make a link from the file created to the lockfile
|
|
||||||
// Ok ensure that the file has been linked successfully
|
|
||||||
// Ok Open the lockfile
|
|
||||||
// Ok read the contents of the lockfile
|
|
||||||
// Ok check if the lockfile contains a pid if not error out
|
|
||||||
// Ok check if the containing pid is in a valid format. If not create a matching error
|
|
||||||
// test if this process could be killed. If so disclose the pid in the error.
|
|
||||||
// try to delete the lockfile as it is apparently not used by the process anmore. (cleanup)
|
|
||||||
// try to lock again now that the old logfile has been safely removed.
|
|
||||||
// remove the original file and only keep the lock hardlink
|
|
||||||
|
|
||||||
info!("locking file {}", path.to_string_lossy());
|
|
||||||
let mut tempfilepath = path.clone();
|
|
||||||
// get the pid
|
|
||||||
let pid = std::process::id();
|
|
||||||
debug!("using pid {}", std::process::id());
|
|
||||||
// get the filename
|
|
||||||
let filename = tempfilepath.file_name().unwrap().to_owned();
|
|
||||||
// and the base path which is the base for tempfile and lockfile.
|
|
||||||
tempfilepath.pop();
|
|
||||||
let mut lockfilepath = tempfilepath.clone();
|
|
||||||
// push the filenames to the paths
|
|
||||||
tempfilepath.push(format!("{}.{}", filename.to_str().unwrap(), pid));
|
|
||||||
lockfilepath.push(format!("{}.lock", filename.to_str().unwrap()));
|
|
||||||
debug!(
|
|
||||||
"Lockfile paths: {:?} (temporary) {:?} (final)",
|
|
||||||
tempfilepath, lockfilepath
|
|
||||||
);
|
|
||||||
// write the pid into the tempfile
|
|
||||||
{
|
|
||||||
let mut tempfile = File::create(&tempfilepath)
|
|
||||||
.expect(&format!("Failed to open {}", filename.to_str().unwrap()));
|
|
||||||
match write!(tempfile, "{}", pid) {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(_) => {
|
|
||||||
let _ = std::fs::remove_file(&tempfilepath);
|
|
||||||
error!("could not write to {}", filename.to_string_lossy())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to make a hardlink from the lockfile to the tempfile
|
|
||||||
let linkresult = std::fs::hard_link(&tempfilepath, &lockfilepath);
|
|
||||||
match linkresult {
|
|
||||||
Ok(()) => {
|
|
||||||
debug!("successfully locked");
|
|
||||||
let _ = std::fs::remove_file(&tempfilepath);
|
|
||||||
|
|
||||||
// open the file
|
|
||||||
let resfile = File::open(&path);
|
|
||||||
return match resfile {
|
|
||||||
Ok(file) => Ok((lockfilepath, file)),
|
|
||||||
Err(e) => {
|
|
||||||
// failed to open the file undo the locks
|
|
||||||
let _ = std::fs::remove_file(&lockfilepath);
|
|
||||||
let ret: crate::UserLibError = format!(
|
|
||||||
"Failed to open the file: {}, error: {}",
|
|
||||||
path.to_str().unwrap(),
|
|
||||||
e
|
|
||||||
)
|
|
||||||
.into();
|
|
||||||
Err(ret)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Err(e) => match e.kind() {
|
|
||||||
// analyze the error further
|
|
||||||
std::io::ErrorKind::AlreadyExists => {
|
|
||||||
warn!("The file is already locked by another process! – testing the validity of the lock");
|
|
||||||
{
|
|
||||||
let mut lf = match File::open(&lockfilepath) {
|
|
||||||
Ok(file) => file,
|
|
||||||
Err(e) => {
|
|
||||||
let _ = std::fs::remove_file(&tempfilepath);
|
|
||||||
panic!("failed to open the lockfile: {}", e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let mut content = String::new();
|
|
||||||
match lf.read_to_string(&mut content) {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(_) => {
|
|
||||||
let _ = std::fs::remove_file(&tempfilepath);
|
|
||||||
panic!("failed to read the lockfile{}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let content = content.trim().trim_matches(char::from(0));
|
|
||||||
let lock_pid = content.parse::<u32>();
|
|
||||||
match lock_pid {
|
|
||||||
Ok(pid) => {
|
|
||||||
warn!(
|
|
||||||
"found a pid: {}, checking if this process is still running",
|
|
||||||
pid
|
|
||||||
);
|
|
||||||
error!("The file could not be locked");
|
|
||||||
let _ = std::fs::remove_file(&tempfilepath);
|
|
||||||
todo!("Validate the lock and delete the file if the process does not exist anymore");
|
|
||||||
/*let sent = nix::sys::signal::kill(
|
|
||||||
nix::unistd::Pid::from_raw(pid as i32),
|
|
||||||
nix::sys::signal::Signal::from(0),
|
|
||||||
);*/
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
let _ = std::fs::remove_file(&tempfilepath);
|
|
||||||
error!(
|
|
||||||
"existing lock file {} with an invalid PID '{}' Error: {}",
|
|
||||||
lockfilepath.to_str().unwrap(),
|
|
||||||
content,
|
|
||||||
e
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => {
|
|
||||||
let _ = std::fs::remove_file(&tempfilepath);
|
|
||||||
panic!("failed to lock the file: {}", e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
Err("was not able to lock!".into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for LockedFileGuard {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
info!("removing lock");
|
|
||||||
std::fs::remove_file(&self.lockfile).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UserDBLocal {
|
impl UserDBLocal {
|
||||||
/// Import the database from strings
|
/// Import the database from strings
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@ -227,20 +65,9 @@ impl UserDBLocal {
|
|||||||
/// Import the database from a [`Files`] struct
|
/// Import the database from a [`Files`] struct
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn load_files(files: Files) -> Self {
|
pub fn load_files(files: Files) -> Self {
|
||||||
// Get the Strings for the files use an inner block to drop references after read.
|
let my_passwd_lines = file_to_string(files.passwd.as_ref());
|
||||||
let (my_passwd_lines, my_shadow_lines, my_group_lines) = {
|
let my_group_lines = file_to_string(files.group.as_ref());
|
||||||
let locked_p = files.lock_and_get_passwd();
|
let my_shadow_lines = file_to_string(files.shadow.as_ref());
|
||||||
let locked_s = files.lock_and_get_shadow();
|
|
||||||
let locked_g = files.lock_and_get_group();
|
|
||||||
use std::{thread, time};
|
|
||||||
|
|
||||||
let ten_millis = time::Duration::from_millis(10000);
|
|
||||||
thread::sleep(ten_millis);
|
|
||||||
let p = file_to_string(&locked_p.unwrap().file);
|
|
||||||
let s = file_to_string(&locked_s.unwrap().file);
|
|
||||||
let g = file_to_string(&locked_g.unwrap().file);
|
|
||||||
(p, s, g)
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut users = user_vec_to_hashmap(string_to(&my_passwd_lines));
|
let mut users = user_vec_to_hashmap(string_to(&my_passwd_lines));
|
||||||
let passwds: Vec<crate::Shadow> = string_to(&my_shadow_lines);
|
let passwds: Vec<crate::Shadow> = string_to(&my_shadow_lines);
|
||||||
@ -306,7 +133,7 @@ impl UserDBWrite for UserDBLocal {
|
|||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_group(&mut self, _group: &crate::Group) -> Result<(), crate::UserLibError> {
|
fn delete_group(&mut self, group: &crate::Group) -> Result<(), crate::UserLibError> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -389,7 +216,7 @@ impl UserDBValidation for UserDBLocal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_nth_line(path: &File, n: u32) -> String {
|
fn get_nth_line(path: Option<&PathBuf>, n: u32) -> String {
|
||||||
let lines = file_to_string(path);
|
let lines = file_to_string(path);
|
||||||
let line = lines.lines().nth(n as usize);
|
let line = lines.lines().nth(n as usize);
|
||||||
match line {
|
match line {
|
||||||
@ -399,7 +226,9 @@ fn get_nth_line(path: &File, n: u32) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a file to a string
|
/// Parse a file to a string
|
||||||
fn file_to_string(file: &File) -> String {
|
fn file_to_string(path: Option<&PathBuf>) -> String {
|
||||||
|
let file = File::open(path.expect("Path cannot be None".into()))
|
||||||
|
.expect("Failed to read the file. Most of the time root permissions are needed".into());
|
||||||
let mut reader = BufReader::new(file);
|
let mut reader = BufReader::new(file);
|
||||||
let mut lines = String::new();
|
let mut lines = String::new();
|
||||||
reader.read_to_string(&mut lines).unwrap();
|
reader.read_to_string(&mut lines).unwrap();
|
||||||
@ -475,20 +304,16 @@ fn test_creator_user_db_local() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_parsing_local_database() {
|
fn test_parsing_local_database() {
|
||||||
// Parse the worldreadable user database ignore the shadow database as this would require root privileges.
|
// Parse the worldreadable user database ignore the shadow database as this would require root privileges.
|
||||||
let pwdfile = File::open(PathBuf::from("/etc/passwd")).unwrap();
|
let my_passwd_lines = file_to_string(Some(&PathBuf::from("/etc/passwd")));
|
||||||
let grpfile = File::open(PathBuf::from("/etc/group")).unwrap();
|
let my_group_lines = file_to_string(Some(&PathBuf::from("/etc/group")));
|
||||||
let my_passwd_lines = file_to_string(&pwdfile);
|
|
||||||
let my_group_lines = file_to_string(&grpfile);
|
|
||||||
let data = UserDBLocal::import_from_strings(&my_passwd_lines, "", &my_group_lines);
|
let data = UserDBLocal::import_from_strings(&my_passwd_lines, "", &my_group_lines);
|
||||||
assert_eq!(data.groups.get(0).unwrap().get_groupname().unwrap(), "root");
|
assert_eq!(data.groups.get(0).unwrap().get_groupname().unwrap(), "root");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_user_db_read_implementation() {
|
fn test_user_db_read_implementation() {
|
||||||
let pwdfile = File::open(PathBuf::from("/etc/passwd")).unwrap();
|
let pass = file_to_string(Some(&PathBuf::from("/etc/passwd")));
|
||||||
let grpfile = File::open(PathBuf::from("/etc/group")).unwrap();
|
let group = file_to_string(Some(&PathBuf::from("/etc/group")));
|
||||||
let pass = file_to_string(&pwdfile);
|
|
||||||
let group = file_to_string(&grpfile);
|
|
||||||
let data = UserDBLocal::import_from_strings(&pass, "", &group);
|
let data = UserDBLocal::import_from_strings(&pass, "", &group);
|
||||||
// Usually there are more than 10 users
|
// Usually there are more than 10 users
|
||||||
assert!(data.get_all_users().len() > 10);
|
assert!(data.get_all_users().len() > 10);
|
||||||
|
@ -23,12 +23,9 @@ pub enum UserLibError {
|
|||||||
impl Display for UserLibError {
|
impl Display for UserLibError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::NotFound => write!(f, "not found"),
|
Self::NotFound => write!(f, "{}", self.to_string()),
|
||||||
Self::ParseError => write!(f, "failed to parse"),
|
Self::ParseError => write!(f, "{}", self.to_string()),
|
||||||
Self::FilesChanged => write!(
|
Self::FilesChanged => write!(f, "{}", self.to_string()),
|
||||||
f,
|
|
||||||
"The files changed. Updating could lead to conflict aborting."
|
|
||||||
),
|
|
||||||
Self::Message(message) => write!(f, "{}", message),
|
Self::Message(message) => write!(f, "{}", message),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -36,7 +33,12 @@ impl Display for UserLibError {
|
|||||||
|
|
||||||
impl Error for UserLibError {
|
impl Error for UserLibError {
|
||||||
fn description(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
todo!()
|
match self {
|
||||||
|
Self::NotFound => "not found",
|
||||||
|
Self::ParseError => "failed to parse",
|
||||||
|
Self::FilesChanged => "The files changed. Updating could lead to conflict aborting.",
|
||||||
|
Self::Message(message) => message,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user