refactor: 配置文件引擎迁移至 mlua 并优化环境变量注入
- 配置文件解析引擎由 shim 替换为 mlua (Lua 5.4) - 优化路由策略:统一以根目录 shims.lua 为入口,支持 tools/ 目录模块化降级探测 - 自动注入 __SHIM_DIR__ 绝对路径与 package.path 模块搜索路径 - 环境变量改用 Command::envs 安全注入,并在 UAC 提权时透传
This commit is contained in:
57
src/main.rs
57
src/main.rs
@@ -3,9 +3,11 @@ use std::{
|
||||
ffi::CString,
|
||||
mem::size_of,
|
||||
path::Path,
|
||||
process::{exit, Command},
|
||||
process::{Command, exit},
|
||||
ptr::null_mut,
|
||||
};
|
||||
mod shims;
|
||||
use shims::Shim;
|
||||
|
||||
use winapi::{
|
||||
shared::minwindef::{BOOL, DWORD, FALSE, TRUE},
|
||||
@@ -14,7 +16,7 @@ use winapi::{
|
||||
consoleapi,
|
||||
objbase::{COINIT_APARTMENTTHREADED, COINIT_DISABLE_OLE1DDE},
|
||||
processthreadsapi::GetExitCodeProcess,
|
||||
shellapi::{ShellExecuteExA, SEE_MASK_NOASYNC, SEE_MASK_NOCLOSEPROCESS, SHELLEXECUTEINFOA},
|
||||
shellapi::{SEE_MASK_NOASYNC, SEE_MASK_NOCLOSEPROCESS, SHELLEXECUTEINFOA, ShellExecuteExA},
|
||||
synchapi::WaitForSingleObject,
|
||||
winbase::INFINITE,
|
||||
wincon,
|
||||
@@ -30,15 +32,12 @@ unsafe extern "system" fn routine_handler(evt: DWORD) -> BOOL {
|
||||
wincon::CTRL_LOGOFF_EVENT => TRUE, //eprintln!("ctrl_logoff handled!"),
|
||||
wincon::CTRL_SHUTDOWN_EVENT => TRUE, //eprintln!("ctrl_shutdown handled!"),
|
||||
other => {
|
||||
eprintln!("unknown event number: {}, unhandled!", other);
|
||||
return FALSE;
|
||||
eprintln!("未知的系统事件编号: {},未处理!", other);
|
||||
FALSE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod shims;
|
||||
use shims::Shim;
|
||||
|
||||
const EXIT_FAILED_LOAD_SHIM: i32 = 1;
|
||||
const EXIT_FAILED_SPAWN_PROG: i32 = 2;
|
||||
const EXIT_FAILED_WAIT_PROG: i32 = 3;
|
||||
@@ -48,31 +47,42 @@ const ERROR_ELEVATION_REQUIRED: i32 = 740;
|
||||
fn main() {
|
||||
let res: BOOL = unsafe { consoleapi::SetConsoleCtrlHandler(Some(routine_handler), TRUE) };
|
||||
if res == FALSE {
|
||||
eprintln!("shim: register Ctrl handler failed.");
|
||||
eprintln!("警告: 注册控制台中断事件处理器失败。");
|
||||
}
|
||||
|
||||
let calling_args: Vec<_> = env::args().skip(1).collect();
|
||||
let shim = match Shim::init() {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
eprintln!("Error while loading shim: {}", e);
|
||||
eprintln!("加载代理(shim)配置时发生错误: {}", e);
|
||||
exit(EXIT_FAILED_LOAD_SHIM);
|
||||
}
|
||||
};
|
||||
|
||||
let args = if let Some(mut shim_args) = shim.args {
|
||||
shim_args.extend_from_slice(calling_args.as_slice());
|
||||
shim_args
|
||||
} else {
|
||||
calling_args
|
||||
};
|
||||
let mut cmd = match Command::new(&shim.target_path).args(&args).spawn() {
|
||||
// ======= 【修改位置 1:构建 Command 并注入环境变量】 =======
|
||||
let mut cmd_builder = Command::new(&shim.target_path);
|
||||
cmd_builder.args(&args);
|
||||
|
||||
// 仅作用于目标子进程,完全 Safe 且隔离
|
||||
if let Some(ref envs) = shim.envs {
|
||||
cmd_builder.envs(envs);
|
||||
}
|
||||
let mut cmd = match cmd_builder.spawn() {
|
||||
Ok(v) => v,
|
||||
Err(e) if e.raw_os_error() == Some(ERROR_ELEVATION_REQUIRED) => {
|
||||
exit(execute_elevated(&shim.target_path, &args))
|
||||
}
|
||||
Err(e) if e.raw_os_error() == Some(ERROR_ELEVATION_REQUIRED) => exit(execute_elevated(
|
||||
&shim.target_path,
|
||||
&args,
|
||||
shim.envs.as_ref(),
|
||||
)),
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"Error while spawning target program `{}`: {}",
|
||||
"启动目标程序 [{}] 时发生错误: {}",
|
||||
shim.target_path.to_string_lossy(),
|
||||
e
|
||||
);
|
||||
@@ -83,7 +93,7 @@ fn main() {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"Error while waiting target program `{}`: {}",
|
||||
"等待目标程序 [{}] 执行完毕时发生错误: {}",
|
||||
shim.target_path.to_string_lossy(),
|
||||
e
|
||||
);
|
||||
@@ -93,7 +103,20 @@ fn main() {
|
||||
exit(status.code().unwrap_or(EXIT_PROG_TERMINATED))
|
||||
}
|
||||
|
||||
fn execute_elevated(program: &Path, args: &[String]) -> i32 {
|
||||
fn execute_elevated(
|
||||
program: &Path,
|
||||
args: &[String],
|
||||
envs: Option<&std::collections::HashMap<String, String>>,
|
||||
) -> i32 {
|
||||
// 若提权启动,在此处将环境变量设置给当前进程(即将弹窗 UAC 的进程,随后会被子进程继承)
|
||||
if let Some(env_map) = envs {
|
||||
for (k, v) in env_map {
|
||||
unsafe {
|
||||
env::set_var(k, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let runas = CString::new("runas").unwrap();
|
||||
let program = CString::new(program.to_str().unwrap()).unwrap();
|
||||
let mut params = String::new();
|
||||
@@ -141,5 +164,5 @@ fn execute_elevated(program: &Path, args: &[String]) -> i32 {
|
||||
return EXIT_FAILED_WAIT_PROG;
|
||||
}
|
||||
}
|
||||
return code as i32;
|
||||
code as i32
|
||||
}
|
||||
|
||||
225
src/shims.rs
225
src/shims.rs
@@ -1,89 +1,166 @@
|
||||
use fs_err as fs;
|
||||
use mlua::{Error as LuaError, Lua, Table, Value};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
env,
|
||||
env, fs,
|
||||
io::{Error, ErrorKind},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
pub struct Shim {
|
||||
pub target_path: PathBuf,
|
||||
pub args: Option<Vec<String>>,
|
||||
pub envs: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
impl Shim {
|
||||
pub fn init() -> Result<Self, Error> {
|
||||
let shim_path = get_shim_file_path()?;
|
||||
let kvs = parse_shim_file(&shim_path)?;
|
||||
let target_path = match kvs.get("path") {
|
||||
Some(p) => PathBuf::from(p),
|
||||
None => {
|
||||
return Err(Error::new(
|
||||
ErrorKind::NotFound,
|
||||
format!("no path key in {}", shim_path.to_string_lossy()),
|
||||
let (_exe_path, _exe_dir, root_dir, tools_dir, exe_name) = Self::get_exe_context()?;
|
||||
|
||||
let lua = Lua::new();
|
||||
let globals = lua.globals();
|
||||
|
||||
// 1. 将根目录 (Root) 注入为 __SHIM_DIR__,作为全局相对路径的锚点
|
||||
let root_dir_str = root_dir.to_string_lossy().replace('\\', "/");
|
||||
let tools_dir_str = tools_dir.to_string_lossy().replace('\\', "/");
|
||||
globals
|
||||
.set("__SHIM_DIR__", root_dir_str.clone())
|
||||
.map_err(|e| Error::new(ErrorKind::Other, format!("注入 __SHIM_DIR__ 失败: {}", e)))?;
|
||||
|
||||
// 2. 注册 get_env 获取环境变量的函数
|
||||
let get_env = lua
|
||||
.create_function(|_, key: String| -> Result<String, LuaError> {
|
||||
Ok(env::var(key).unwrap_or_default())
|
||||
})
|
||||
.map_err(|e| Error::new(ErrorKind::Other, format!("注册 get_env 失败: {}", e)))?;
|
||||
|
||||
globals
|
||||
.set("get_env", get_env)
|
||||
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
|
||||
|
||||
// 3. 增强 package.path,让 require 自动在当前目录及 conf.d 目录下寻找模块
|
||||
if let Ok(package) = globals.get::<Table>("package") {
|
||||
if let Ok(path) = package.get::<String>("path") {
|
||||
let new_path = format!(
|
||||
"{};{}/?.lua;{}/?/init.lua;{}/?.lua;{}/?/init.lua",
|
||||
path, root_dir_str, root_dir_str, tools_dir_str, tools_dir_str
|
||||
);
|
||||
let _ = package.set("path", new_path);
|
||||
}
|
||||
}
|
||||
|
||||
Self::resolve_config(&lua, &root_dir, &tools_dir, &exe_name)
|
||||
}
|
||||
|
||||
fn get_exe_context() -> Result<(PathBuf, PathBuf, PathBuf, PathBuf, String), Error> {
|
||||
let current_exe = env::current_exe().map_err(|e| {
|
||||
Error::new(
|
||||
ErrorKind::Other,
|
||||
format!("获取代理程序(shim)路径失败: {}", e),
|
||||
)
|
||||
})?;
|
||||
|
||||
let exe_dir = current_exe
|
||||
.parent()
|
||||
.unwrap_or_else(|| Path::new(""))
|
||||
.to_path_buf();
|
||||
// root_dir 是 bin 的上一级(项目的根目录)
|
||||
let root_dir = exe_dir
|
||||
.parent()
|
||||
.unwrap_or_else(|| Path::new(""))
|
||||
.to_path_buf();
|
||||
// tools_dir 是 root 下的 tools 目录
|
||||
let tools_dir = root_dir.join("tools");
|
||||
|
||||
let exe_name = current_exe
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or_default()
|
||||
.to_lowercase();
|
||||
|
||||
Ok((current_exe, exe_dir, root_dir, tools_dir, exe_name))
|
||||
}
|
||||
/// 执行 Lua 脚本文件并返回 Table
|
||||
fn evaluate_lua_script(lua: &Lua, config_file: &Path) -> Result<Table, Error> {
|
||||
let code = fs::read_to_string(config_file)?; // 这里不安全需要优化
|
||||
|
||||
lua.load(&code)
|
||||
.set_name(config_file.to_string_lossy().into_owned())
|
||||
.eval::<Table>()
|
||||
.map_err(|e| {
|
||||
Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
format!("Lua 脚本解析错误 ({}): {}", config_file.display(), e),
|
||||
)
|
||||
})
|
||||
}
|
||||
/// 利用 mlua 原生类型转换,将 Lua Table 映射为 Rust Shim 结构体
|
||||
fn from_table(table: Table) -> Result<Self, Error> {
|
||||
let path_str: String = table
|
||||
.get("path")
|
||||
.map_err(|_| Error::new(ErrorKind::InvalidData, "配置文件中缺少必需的 'path' 字段"))?;
|
||||
let target_path = PathBuf::from(path_str);
|
||||
|
||||
let args: Option<Vec<String>> = table.get("args").ok();
|
||||
let envs: Option<HashMap<String, String>> = table.get("env").ok();
|
||||
|
||||
Ok(Self {
|
||||
target_path,
|
||||
args,
|
||||
envs,
|
||||
})
|
||||
}
|
||||
fn resolve_config(
|
||||
lua: &Lua,
|
||||
root_dir: &Path,
|
||||
tools_dir: &Path,
|
||||
exe_name: &str,
|
||||
) -> Result<Self, Error> {
|
||||
// 1. 尝试全局统一入口 shims.lua
|
||||
let entry_config = root_dir.join("shims.lua");
|
||||
if entry_config.exists() {
|
||||
let root_table = Self::evaluate_lua_script(lua, &entry_config)?;
|
||||
|
||||
// 如果 shims.lua 中存在以当前程序名命名的配置项,直接接管并返回
|
||||
if let Ok(Value::Table(v)) = root_table.get::<Value>(exe_name) {
|
||||
return Self::from_table(v);
|
||||
}
|
||||
// 如果 shims.lua 存在但未接管 exe_name,代码会自然“穿透”流转到下方寻找独立配置
|
||||
}
|
||||
|
||||
// 2. 降级探测特定同名独立配置文件(优先 tools/{exe_name}.lua,次选 root/{exe_name}.lua)
|
||||
let specific_config_tools = tools_dir.join(format!("{}.lua", exe_name));
|
||||
let specific_config_root = root_dir.join(format!("{}.lua", exe_name));
|
||||
|
||||
let specific_config = if specific_config_tools.exists() {
|
||||
Some(specific_config_tools)
|
||||
} else if specific_config_root.exists() {
|
||||
Some(specific_config_root)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(config_file) = specific_config {
|
||||
let config_table = Self::evaluate_lua_script(lua, &config_file)?;
|
||||
return if config_table.contains_key("path").unwrap_or(false) {
|
||||
Self::from_table(config_table)
|
||||
} else {
|
||||
Err(Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
format!(
|
||||
"独立配置文件 [{}] 必须包含 'path' 字段",
|
||||
config_file.display()
|
||||
),
|
||||
))
|
||||
}
|
||||
};
|
||||
let args = kvs.get("args").map(|a| {
|
||||
a.split_whitespace()
|
||||
.map(|s| s.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
Ok(Self { target_path, args })
|
||||
};
|
||||
}
|
||||
|
||||
// 3. 两阶段探测均失败后的统一报错
|
||||
Err(Error::new(
|
||||
ErrorKind::NotFound,
|
||||
format!(
|
||||
"未找到关于 '{}' 的配置。请确保在 shims.lua 中进行了定义,或者存在独立的 {}.lua 配置文件。",
|
||||
exe_name, exe_name
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_shim_file_path() -> Result<PathBuf, Error> {
|
||||
let mut current_exe = env::current_exe().map_err(|e| {
|
||||
Error::new(
|
||||
ErrorKind::Other,
|
||||
format!("acquiring shim executable path: {}", e),
|
||||
)
|
||||
})?;
|
||||
if !current_exe.set_extension("shim") {
|
||||
return Err(Error::new(
|
||||
ErrorKind::Other,
|
||||
format!("{} is not a file", current_exe.to_string_lossy()),
|
||||
));
|
||||
}
|
||||
Ok(current_exe)
|
||||
}
|
||||
use unicode_bom::Bom;
|
||||
fn parse_shim_file(shim_path: &Path) -> Result<HashMap<String, String>, Error> {
|
||||
let mut kvs = HashMap::new();
|
||||
|
||||
let raw_content = fs::read_to_string(shim_path).map_err(|e| {
|
||||
Error::new(
|
||||
ErrorKind::Other,
|
||||
format!("reading {}: {}", shim_path.to_string_lossy(), e),
|
||||
)
|
||||
})?;
|
||||
//NOTE: expedient trick for utf-8 with bom
|
||||
let bom = Bom::from(raw_content.as_bytes());
|
||||
for line in raw_content[bom.len()..]
|
||||
.lines()
|
||||
.filter(|l| !l.trim().is_empty())
|
||||
{
|
||||
let mut components = line.split("=");
|
||||
let key = match components.next() {
|
||||
Some(k) => k.trim(),
|
||||
None => {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
format!("invalid line in shim file: {}", line),
|
||||
));
|
||||
}
|
||||
};
|
||||
let value = match components.next() {
|
||||
Some(v) => v.trim(),
|
||||
None => {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
format!("invalid line in shim file: {}", line),
|
||||
));
|
||||
}
|
||||
};
|
||||
kvs.insert(key.to_string(), value.to_string());
|
||||
}
|
||||
|
||||
Ok(kvs)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user