refactor: 配置文件引擎迁移至 mlua 并优化环境变量注入

- 配置文件解析引擎由 shim 替换为 mlua (Lua 5.4)
- 优化路由策略:统一以根目录 shims.lua 为入口,支持 tools/ 目录模块化降级探测
- 自动注入 __SHIM_DIR__ 绝对路径与 package.path 模块搜索路径
- 环境变量改用 Command::envs 安全注入,并在 UAC 提权时透传
This commit is contained in:
2026-08-12 20:19:50 +08:00
parent 7934dde74d
commit 4aa619f07e
6 changed files with 516 additions and 105 deletions

View File

@@ -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
}

View File

@@ -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)
}