Started plugin lookup trust rules, paths, and env-var derived paths.

Fortune for rng's current commit: Future small blessing − 末小吉
plugin
Avril 2 years ago
parent 683864c727
commit 7e8a4ab553
Signed by: flanchan
GPG Key ID: 284488987C31F630

@ -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 = []
bytes-dynamic = []
# Allow plugin-loading at runtime with `[@<plugin-name>]... <options...>`
plugin = ["libc", "bitflags", "lazy_static", "regex"]

@ -6,16 +6,21 @@ use getrandom::*;
mod parse;
mod r#impl;
#[cfg(feature="plugin")] mod plugin;
const BYTES_BUFFER_SIZE: usize = 4096;
fn get<T: Default>() -> Result<T, Error>
fn get<T>() -> Result<T, Error>
{
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::<T>());
use core::mem::MaybeUninit;
let mut value: MaybeUninit<T> = 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::<T>());
populate(&mut slice)?;
}
value.assume_init()
};
Ok(value)
}

@ -0,0 +1,3 @@
//! Loads dynamic object plugins for `rngcli`
use super::*;

@ -0,0 +1,4 @@
//! Allow loading plugins at runtime.
mod loader;
mod searcher;

@ -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<str> for PathTrust<'a>
{
#[inline(always)]
fn as_ref(&self) -> &str
{
match self {
Trusted(a) | Untrusted(a) => a
}
}
}
impl<'a> From<PathTrust<'a>> for &'a str
{
#[inline]
fn from(from: PathTrust<'a>) -> Self
{
from.into_str()
}
}
impl<'a> From<PathTrust<'a>> for (&'a str, bool)
{
#[inline]
fn from(from: PathTrust<'a>) -> Self
{
let trusted = from.is_trusted();
(from.into_str(), trusted)
}
}
// impl<'a> AsRef<Path> for PathTrust<'a>
// {
// #[inline]
// fn as_ref(&self) -> &Path
// {
// Path::new(AsRef::<str>::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: AsRef<Path>>(P);
#[derive(Debug)]
struct PathLookup {
name: String,
candidates: Vec<fs::DirEntry>,
trusted: bool,
}
impl PathLookup
{
#[inline]
pub fn check_valid_path<const Trusted: bool>(path: impl AsRef<Path>) -> bool
{
#[inline(always)]
fn regex_builder(pattern: impl AsRef<str>) -> 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<Item = &'a Regex> + 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<Regex> =
locations::LOOKUP_NAME_MATCHES_IN_TRUSTED.into_iter().map(regex_builder).collect();
static ref UNTRUSTED_EXT_REGEX_MAP: Vec<Regex> =
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<impl FnOnce> 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<F: FnOnce(&Path) -> bool>(F);
// impl<F: FnOnce(&Path) -> bool> Checker for FuncChecker<F>
// {
// #[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<P: AsRef<Path>> LookupVisitor<P>
{
pub fn start_lookup(&self, trusted: bool, name: impl Into<String>) -> io::Result<PathLookup>
{
Ok(PathLookup {
name: name.into(),
trusted,
candidates: fs::read_dir(&self.0)?.collect::<io::Result<Vec<_>>>()?,
})
}
}
pub fn lookup_plugin_name(name: impl AsRef<str>) -> Option<PathBuf>
{
todo!("Create a LookupVisitor(<path>) for each path in `locations`, then call `start_lookup(<trusted path>, name)` on each *in order* specified in `locations`")
}
Loading…
Cancel
Save