From b8559e1d645381c726254be54a8267f603ab2cd4 Mon Sep 17 00:00:00 2001 From: Gregor Reitzenstein Date: Fri, 11 Sep 2020 09:57:03 +0200 Subject: [PATCH] Adds --load/--dump. Role dumping implemented already These mutually exclusive flags allow dumping and loading of database contents in a textual format --- src/access.rs | 127 +++++++++++++++++++++++++++++++++++++++++++++++++- src/main.rs | 65 ++++++++++++++++++++++++-- 2 files changed, 186 insertions(+), 6 deletions(-) diff --git a/src/access.rs b/src/access.rs index 60e0637..d670de3 100644 --- a/src/access.rs +++ b/src/access.rs @@ -3,11 +3,17 @@ use std::collections::HashSet; +use std::convert::TryInto; + +use std::path::{Path, PathBuf}; +use std::fs; +use std::io::Write; + use flexbuffers; use serde::{Serialize, Deserialize}; use slog::Logger; -use lmdb::{Transaction, RoTransaction, RwTransaction}; +use lmdb::{Transaction, RwTransaction, Cursor}; use crate::config::Config; use crate::error::Result; @@ -118,6 +124,125 @@ impl PermissionsProvider { Ok(()) } + + pub fn dump_db(&mut self, txn: &T, mut path: PathBuf) -> Result<()> { + path.push("roles"); + fs::create_dir(&path); + // Rust's stdlib considers the last element the file name so we have to put a dummy here for + // .set_filename() to work correctly + path.push("dummy"); + self.dump_roles(txn, path.clone())?; + path.pop(); + + let mut perm_cursor = txn.open_ro_cursor(self.permdb)?; + info!(self.log, "================: PERMS :===================="); + for perm in perm_cursor.iter_start() { + info!(self.log, "{:?}", perm) + } + info!(self.log, "============================================="); + + let mut user_cursor = txn.open_ro_cursor(self.userdb)?; + info!(self.log, "================: USERS :===================="); + for user in user_cursor.iter_start() { + info!(self.log, "{:?}", user) + } + + Ok(()) + } + + fn dump_roles(&mut self, txn: &T, mut path: PathBuf) -> Result<()> { + let mut role_cursor = txn.open_ro_cursor(self.roledb)?; + for buf in role_cursor.iter_start() { + let (kbuf, vbuf) = buf?; + let (kbytes, _rest) = kbuf.split_at(std::mem::size_of::()); + let roleID = u64::from_ne_bytes(kbytes.try_into().unwrap()); + let role: Role = flexbuffers::from_slice(vbuf)?; + let filename = format!("{:x}.toml", roleID); + path.set_file_name(filename); + let mut fp = std::fs::File::create(&path)?; + let toml = toml::to_vec(&role)?; + fp.write_all(&toml); + } + + Ok(()) + } + + pub fn load_db(&mut self, txn: &mut RwTransaction, mut path: PathBuf) -> Result<()> { + // ====================: ROLES :==================== + path.push("roles"); + if !path.is_dir() { + error!(self.log, "Given load directory is malformed, no 'roles' subdir, not loading roles!"); + } else { + self.load_roles(txn, path.as_path())?; + } + path.pop(); + // ================================================= + + // ====================: PERMS :==================== + path.push("perms"); + if !path.is_dir() { + error!(self.log, "Given load directory is malformed, no 'perms' subdir, not loading perms!"); + } else { + //self.load_perms(txn, &path)?; + } + path.pop(); + // ================================================= + + // ====================: USERS :==================== + path.push("users"); + if !path.is_dir() { + error!(self.log, "Given load directory is malformed, no 'users' subdir, not loading users!"); + } else { + //self.load_users(txn, &path)?; + } + path.pop(); + // ================================================= + Ok(()) + } + + fn load_roles(&mut self, txn: &mut RwTransaction, path: &Path) -> Result<()> { + for entry in std::fs::read_dir(path)? { + let entry = entry?; + let path = entry.path(); + if path.is_file() { + // will only ever be none if the path has no file name and then how is it a file?! + let roleID_str = path + .file_stem().expect("Found a file with no filename?") + .to_str().expect("Found an OsStr that isn't valid Unicode. Fix your OS!"); + let roleID = match u64::from_str_radix(roleID_str, 16) { + Ok(i) => i, + Err(e) => { + warn!(self.log, "File {} had a invalid name. Expected an u64 in [0-9a-z] hex with optional file ending: {}. Skipping!", path.display(), e); + continue; + } + }; + let s = match fs::read_to_string(path.as_path()) { + Ok(s) => s, + Err(e) => { + warn!(self.log, "Failed to open file {}: {}, skipping!" + , path.display() + , e); + continue; + } + }; + let role: Role = match toml::from_str(&s) { + Ok(r) => r, + Err(e) => { + warn!(self.log, "Failed to parse role at path {}: {}, skipping!" + , path.display() + , e); + continue; + } + }; + self.put_role(txn, roleID, role)?; + debug!(self.log, "Loaded role {}", roleID); + } else { + warn!(self.log, "Path {} is not a file, skipping!", path.display()); + } + } + + Ok(()) + } } /// This line documents init diff --git a/src/main.rs b/src/main.rs index ea69ccf..56b19a5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,6 +33,8 @@ use std::str::FromStr; use std::sync::Arc; +use lmdb::Transaction; + use error::Error; const LMDB_MAX_DB: u32 = 16; @@ -63,6 +65,18 @@ fn main() -> Result<(), Error> { .help("Print a default config to stdout instead of running") .long("print-default") ) + .arg(Arg::with_name("dump") + .help("Dump all databases into the given directory") + .long("dump") + .conflicts_with("load") + .takes_value(true) + ) + .arg(Arg::with_name("load") + .help("Load databases from the given directory") + .long("load") + .conflicts_with("dump") + .takes_value(true) + ) .get_matches(); // Check for the --print-default option first because we don't need to do anything else in that @@ -81,6 +95,7 @@ fn main() -> Result<(), Error> { return Ok(()) } + // If no `config` option is given use a preset default. let configpath = matches.value_of("config").unwrap_or("/etc/diflouroborane.toml"); let config = config::read(&PathBuf::from_str(configpath).unwrap())?; @@ -92,11 +107,6 @@ fn main() -> Result<(), Error> { let log = Arc::new(log::init(&config)); info!(log, "Starting"); - // Kick up an executor - // Most initializations from now on do some amount of IO and are much better done in an - // asyncronous fashion. - let mut exec = LocalPool::new(); - // Initialize the LMDB environment. Since this would usually block untill the mmap() finishes // we wrap it in smol::unblock which runs this as future in a different thread. let e_config = config.clone(); @@ -106,6 +116,12 @@ fn main() -> Result<(), Error> { .set_max_dbs(LMDB_MAX_DB as libc::c_uint) .open(&PathBuf::from_str("/tmp/a.db").unwrap())?; + // Kick up an executor + // Most initializations from now on do some amount of IO and are much better done in an + // asyncronous fashion. + let mut exec = LocalPool::new(); + + // Start loading the machine database, authentication system and permission system // All of those get a custom logger so the source of a log message can be better traced and // filtered @@ -113,6 +129,45 @@ fn main() -> Result<(), Error> { let pdb = access::init(log.new(o!("system" => "permissions")), &config, &env); let authentication_f = auth::init(log.new(o!("system" => "authentication")), config.clone()); + // If --load or --dump is given we can stop at this point and load/dump the database and then + // exit. + if matches.is_present("load") { + if let Some(pathstr) = matches.value_of("load") { + let path = std::path::Path::new(pathstr); + if !path.is_dir() { + error!(log, "The provided path is not a directory or does not exist"); + return Ok(()) + } + + let mut txn = env.begin_rw_txn()?; + let path = path.to_path_buf(); + pdb?.load_db(&mut txn, path)?; + txn.commit(); + } else { + error!(log, "You must provide a directory path to load from"); + } + + return Ok(()) + } else if matches.is_present("dump") { + if let Some(pathstr) = matches.value_of("dump") { + let path = std::path::Path::new(pathstr); + if let Err(e) = std::fs::create_dir_all(path) { + error!(log, "The provided path could not be created: {}", e); + return Ok(()) + } + + let txn = env.begin_ro_txn()?; + let path = path.to_path_buf(); + pdb?.dump_db(&txn, path)?; + } else { + + error!(log, "You must provide a directory path to dump into"); + } + + return Ok(()) + } + + // Bind to each address in config.listen. // This is a Stream over Futures so it will do absolutely nothing unless polled to completion let listeners_s: futures::stream::Collect<_, Vec>