diff --git a/Cargo.toml b/Cargo.toml index 7a3cfa0..9421df3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,10 +13,19 @@ codegen-units = 1 panic = "unwind" [dependencies] +bitflags = { version = "1.3.2", optional = true } getrandom = "0.1" +lazy_static = { version = "1.4.0", optional = true } +libc = { version = "0.2.126", optional = true } +regex = { version = "1.6.0", optional = true } [features] +default = ["plugin"] + # Dynamically allocate and populate all memory needed for `--bytes` at runtime instead of streaming in fixed size blocks. # # This can slightly improve total performance for large outputs, at the cost of a proportionally, arbitrary sized memory allocation along with a proportionally increasing wait while the entire area is populated. -bytes-dynamic = [] \ No newline at end of file +bytes-dynamic = [] + +# Allow plugin-loading at runtime with `[@]... ` +plugin = ["libc", "bitflags", "lazy_static", "regex"] diff --git a/src/main.rs b/src/main.rs index f14669a..8b692b2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,16 +6,21 @@ use getrandom::*; mod parse; mod r#impl; +#[cfg(feature="plugin")] mod plugin; const BYTES_BUFFER_SIZE: usize = 4096; -fn get() -> Result +fn get() -> Result { - let mut value: T = Default::default(); - unsafe { - let mut slice = std::slice::from_raw_parts_mut(&mut value as *mut T as *mut u8, std::mem::size_of::()); + use core::mem::MaybeUninit; + let mut value: MaybeUninit = MaybeUninit::uninit(); + + let value = unsafe { + let mut slice = std::slice::from_raw_parts_mut(value.as_mut_ptr() as *mut u8, std::mem::size_of::()); populate(&mut slice)?; - } + + value.assume_init() + }; Ok(value) } diff --git a/src/plugin/loader.rs b/src/plugin/loader.rs new file mode 100644 index 0000000..6c85701 --- /dev/null +++ b/src/plugin/loader.rs @@ -0,0 +1,3 @@ +//! Loads dynamic object plugins for `rngcli` +use super::*; + diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs new file mode 100644 index 0000000..2ee70b1 --- /dev/null +++ b/src/plugin/mod.rs @@ -0,0 +1,4 @@ +//! Allow loading plugins at runtime. + +mod loader; +mod searcher; diff --git a/src/plugin/searcher.rs b/src/plugin/searcher.rs new file mode 100644 index 0000000..47eab37 --- /dev/null +++ b/src/plugin/searcher.rs @@ -0,0 +1,318 @@ +//! Searches paths for the plugin name to find it's shared object file +use super::*; +use std::path::{ + PathBuf, + Path, +}; +use std::{ + fs, io, + //collections::HashMap, +}; +use regex::{ + Regex, RegexBuilder, +}; +use lazy_static::lazy_static; + +/// Specifies locations and associated rules of these locations for path lookups when searching for plugins. +pub mod locations { + use super::*; + use bitflags::*; + + /// Describes a path an its trust level. + #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)] + pub enum PathTrust<'a> + { + Trusted(&'a str), + Untrusted(&'a str), + } + + bitflags! { + /// The rules a file in an unstructed lookup path must qualify for it to be considered for plugin candidacy. + pub struct UntrustedPathRules : u32 + { + /// No restrictions on the file. + const UNRESTRICTED = 0; + /// Name ends in valid string from `LOOKUP_NAME_MATCHES_IN_UNTRUSTED`. + const NAME_MATCH = 1 << 0; + /// Disallow symlinked files. + const NON_SYMLINK = 1 << 1; + /// Disqualify executable-marked files. + const NON_EXEC = 1 << 2; + /// Disqualify world-writable files. + const NON_WORLD_WRITABLE = 1 << 3; + /// Only qualify root-owned files (can be unioned with other `_OWNED_ONLY` constants) + const ROOT_OWNED_ONLY = 1 << 4; + /// Only qualify user-owned files (can be unioned with other `_OWNED_ONLY` constants) + const USER_OWNED_ONLY = 1 << 5; + /// Disqualify files that are accessible at all by anyone other than the current user (or root, which can accesss everything.) + const USER_ACCESSIBLE_ONLY = 1 << 6; + } + } + impl Default for UntrustedPathRules + { + #[inline] + fn default() -> Self + { + Self::NAME_MATCH + } + } + + impl<'a> AsRef for PathTrust<'a> + { + #[inline(always)] + fn as_ref(&self) -> &str + { + match self { + Trusted(a) | Untrusted(a) => a + } + } + } + impl<'a> From> for &'a str + { + #[inline] + fn from(from: PathTrust<'a>) -> Self + { + from.into_str() + } + } + + impl<'a> From> for (&'a str, bool) + { + #[inline] + fn from(from: PathTrust<'a>) -> Self + { + let trusted = from.is_trusted(); + (from.into_str(), trusted) + } + } + + // impl<'a> AsRef for PathTrust<'a> + // { + // #[inline] + // fn as_ref(&self) -> &Path + // { + // Path::new(AsRef::::as_ref(self)) + // } + // } + + use PathTrust::*; + // The ordering of these variables is the order in which the plugin is looked-up in. The first match found is used. + + /// Should paths be recursively travelled in lookups. (0 is for trusted paths, 1 is for untrusted) + pub static LOOKUP_PATH_RECURSION: (bool, bool) = (true, true); + + /// Should symlinks be followed in a recursive lookup. (0 is for trusted paths, 1 is for untrusted) + pub static LOOKUP_PATH_FOLLOW_SYMLINKS: (bool, bool) = (true, false); + + /// Trust plugin file lookups in non-contained `LOOKUP_PATH_HOME_BASE_NAME`s. + pub static LOOKUP_PATH_TRUST_HOME: bool = false; + + /// Base paths from user home to look-up plugin name. + pub static LOOKUP_PATH_HOME_BASE_NAME: &[PathTrust] = &[ + Trusted(".rngcli/plugins/"), + Untrusted(".rngcli/").map_trust(LOOKUP_PATH_TRUST_HOME), + Untrusted(".config/rngcli/").map_trust(LOOKUP_PATH_TRUST_HOME), + Untrusted(".config/").map_trust(LOOKUP_PATH_TRUST_HOME), + ]; + + /// Extra valid lookup paths + pub static LOOKUP_PATH_EXTRA_PATHS: &[PathTrust] = &[ + Trusted("/usr/share/rngcli/"), + Trusted("/usr/local/share/rngcli/") + ]; + + /// `PATH`-like env-vars to look up a plugin's name. + pub static LOOKUP_PATH_ENV_VARS: &[PathTrust] = &[ + Trusted("RNGCLI_PATH"), + Untrusted("PATH"), + ]; + + /// Allow lookup inside the current working directory. + pub static LOOKUP_PATH_ALLOW_CWD: bool = true; + + /// Files with these regex-matched names are considered in all lookup contexts (`Trusted` and `Untrusted`.) + pub static LOOKUP_NAME_MATCHES_IN_TRUSTED: &[&'static str] = &[ + r"\.rngp\.so$", + r"\.rngp$", + r"\.so$", + ]; + + /// Files with these regex-matched names are considered in `Untrusted` lookup contexts (if specified by `LOOKUP_UNTRUSTED_PATH_RULES`) + pub static LOOKUP_NAME_MATCHES_IN_UNTRUSTED: &[&'static str] = &[ + r"\.rngp\.so$", + r"\.rngp$", + ]; + + /// These rules must be met for lookups in untrusted paths. + pub static LOOKUP_UNTRUSTED_PATH_RULES: UntrustedPathRules = + UntrustedPathRules::NAME_MATCH + .union(UntrustedPathRules::NON_SYMLINK) + .union(UntrustedPathRules::NON_WORLD_WRITABLE); + + impl<'a> PathTrust<'a> + { + #[inline(always)] + pub const fn into_str(self) -> &'a str + { + match self { + Trusted(a) | Untrusted(a) => a + } + } + + #[inline(always)] + pub fn into_path(self) -> &'a Path + { + match self { + Trusted(a) | Untrusted(a) => Path::new(a) + } + } + #[inline(always)] + pub fn as_path(&self) -> &Path + { + Path::new(self.as_ref()) + } + + #[inline(always)] + pub const fn is_trusted(self) -> bool + { + if let Trusted(_) = self { + true + } else { + false + } + } + #[inline(always)] + pub const fn is_untrusted(self) -> bool + { + !self.is_trusted() + } + + /// Map the trust level of this `PathTrust`. + #[inline(always)] + pub const fn map_trust(self, trust: bool) -> Self + { + if trust { Trusted(self.into_str()) } + else { Untrusted(self.into_str()) } + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +struct LookupVisitor>(P); + +#[derive(Debug)] +struct PathLookup { + name: String, + candidates: Vec, + trusted: bool, +} + +impl PathLookup +{ + + #[inline] + pub fn check_valid_path(path: impl AsRef) -> bool + { + #[inline(always)] + fn regex_builder(pattern: impl AsRef) -> Regex { + RegexBuilder::new(pattern.as_ref()) + .case_insensitive(true) + .build().expect("Invalid regex in trusted pattern match array.") + } + /// Returns a function that returns `true` if any of the `matchers` match the input string. + #[inline] + const fn match_any<'a, I: 'a>(matchers: I) -> impl Fn(&str) -> bool + 'a + where I: IntoIterator + Clone, + { + move |s: &str| -> bool { + let matchers = matchers.clone().into_iter(); + for re in matchers { + if re.is_match(s) { + return true; + } + } + false + } + } + /// Validate a file in an untrusted lookup. + #[inline] + fn validate_file_untrusted(path: &Path, filename: &str) -> bool + { + // By this point, `filename` has already matched successfully. + // Also Note: `filename` is just the basename of `path` as an `&str` (same address-space.) + + todo!("TODO: extra Untrusted lookup rules checked on `path` here.") + } + lazy_static! { + static ref TRUSTED_EXT_REGEX_MAP: Vec = + locations::LOOKUP_NAME_MATCHES_IN_TRUSTED.into_iter().map(regex_builder).collect(); + static ref UNTRUSTED_EXT_REGEX_MAP: Vec = + locations::LOOKUP_NAME_MATCHES_IN_UNTRUSTED.into_iter().map(regex_builder).collect(); + } + + let path = path.as_ref(); + + if !path.exists() || path.is_dir() { + return false; + } + + let filename = match path.file_name() + .and_then(|os_fn| os_fn.to_str()) + { + Some(f) => f, + _ => return false, + }; + + // trait Checker // XXX: Was used for dynamic dispatch in the `if Trusted ...` block below, but is no longer needed as Trusted will never have one, and untrusted will always have the same one; we can just use Option pretty much + // { + // fn check_path(self, path: &Path) -> bool; + // } + // /*struct NoChecker; + // impl Checker for NoChecker { #[inline] fn check_path(&self, path: &Path) -> bool { + // true + // } }*/ + + // #[derive(Debug, Clone, Copy)] + // struct FuncChecker bool>(F); + // impl bool> Checker for FuncChecker + // { + // #[inline] + // fn check_path(self, path: &Path) -> bool { + // self.0(path) + // } + // } + + let (matcher, checker) = if Trusted { + // Trusted path lookup + (match_any(TRUSTED_EXT_REGEX_MAP.iter()), None)//&NoChecker) + } else { + // Untrusted path lookup + (match_any(UNTRUSTED_EXT_REGEX_MAP.iter()), Some(move |path| { + validate_file_untrusted(path, filename) + })) + }; + + matcher(filename) // Check filename first + && match checker { + Some(untrusted) => untrusted(path), // Check extra Untrusted path rules next + _ => true + } + } +} + +impl> LookupVisitor

+{ + pub fn start_lookup(&self, trusted: bool, name: impl Into) -> io::Result + { + Ok(PathLookup { + name: name.into(), + trusted, + candidates: fs::read_dir(&self.0)?.collect::>>()?, + }) + } +} + +pub fn lookup_plugin_name(name: impl AsRef) -> Option +{ + todo!("Create a LookupVisitor() for each path in `locations`, then call `start_lookup(, name)` on each *in order* specified in `locations`") +}