Fortune for rng's current commit: Future small blessing − 末小吉plugin
parent
683864c727
commit
7e8a4ab553
@ -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…
Reference in new issue