Objavljen vir
Prenos vir ZIP
CH-J Tools
Brskajte po imenikih in datotekah za določeno izdajo programa.
CH-J / Server Manager
1704 datoteke
0.1.2-beta
Dodano: · Zgrajena:
1
//! Installer hand-off (port of `updates/updateInstallerLauncher.js`).2
//! Windows/macOS: the verified EXE/DMG is opened by the OS (launched ≠ installed).3
//! Linux: fixed `/usr/bin/pkexec /usr/bin/apt-get install --reinstall --yes <verified .deb>`,4
//! no shell, root-owned non-writable executables, 30 min timeout.6
use crate::core::error::{CoreError, CoreResult};7
use std::path::Path;8
use std::time::Duration;10
pub const PKEXEC_PATH: &str = "/usr/bin/pkexec";11
pub const APT_GET_PATH: &str = "/usr/bin/apt-get";13
#[derive(Debug, Clone, Copy, PartialEq, Eq)]14
pub struct LaunchResult {15
pub launched: bool,16
pub installed: bool,17
pub restart: bool,18
}20
fn install_error(code: &str, message: &str) -> CoreError {21
CoreError::new(code, message)22
}24
pub fn assert_safe_regular_file(path: &Path, label: &str) -> CoreResult<std::fs::Metadata> {25
if !path.is_absolute() {26
return Err(install_error("UPDATE_INSTALL_UNSAFE_PATH", &format!("{label} path must be absolute.")));27
}28
let meta = std::fs::symlink_metadata(path).map_err(|_| install_error("UPDATE_INSTALL_FILE_MISSING", &format!("{label} is missing.")))?;29
#[cfg(unix)]30
let links = {31
use std::os::unix::fs::MetadataExt;32
meta.nlink()33
};34
#[cfg(not(unix))]35
let links = 1;36
if !meta.is_file() || meta.file_type().is_symlink() || links != 1 || meta.len() == 0 {37
return Err(install_error("UPDATE_INSTALL_UNSAFE_PATH", &format!("{label} must be a non-empty regular file.")));38
}39
Ok(meta)40
}42
#[cfg(unix)]43
pub fn assert_trusted_system_executable(path: &Path) -> CoreResult<()> {44
use std::os::unix::fs::MetadataExt;45
let meta = assert_safe_regular_file(path, "System installer executable")?;46
if meta.uid() != 0 {47
return Err(install_error("UPDATE_INSTALL_UNSAFE_EXECUTABLE", "System installer executable is not owned by root."));48
}49
if meta.mode() & 0o111 == 0 {50
return Err(install_error("UPDATE_INSTALL_UNSAFE_EXECUTABLE", "System installer executable is not executable."));51
}52
if meta.mode() & 0o022 != 0 {53
return Err(install_error("UPDATE_INSTALL_UNSAFE_EXECUTABLE", "System installer executable is writable by an untrusted account."));54
}55
Ok(())56
}58
pub async fn install(app: &tauri::AppHandle, path: &Path, logger: &crate::core::logging::Logger) -> CoreResult<LaunchResult> {59
if cfg!(target_os = "linux") {60
return install_deb(path, logger).await;61
}62
use tauri_plugin_opener::OpenerExt;63
app.opener().open_path(path.to_string_lossy(), None::<&str>).map_err(|e| install_error("UPDATE_INSTALL_FAILED", &format!("The installer could not be opened: {e}")))?;64
Ok(LaunchResult { launched: true, installed: false, restart: false })65
}67
#[cfg(unix)]68
async fn install_deb(path: &Path, logger: &crate::core::logging::Logger) -> CoreResult<LaunchResult> {69
if !path.is_absolute() {70
return Err(install_error("UPDATE_INSTALL_UNSAFE_PATH", "Verified Debian update package path must be absolute."));71
}72
if path.extension().map(|e| e.to_string_lossy().to_lowercase()) != Some("deb".into()) {73
return Err(install_error("UPDATE_INSTALL_UNSUPPORTED_PACKAGE", "Linux updates must use a Debian package."));74
}75
assert_safe_regular_file(path, "Verified Debian update package")?;76
assert_trusted_system_executable(Path::new(PKEXEC_PATH))?;77
assert_trusted_system_executable(Path::new(APT_GET_PATH))?;78
let name = path.file_name().map(|n| n.to_string_lossy().to_string()).unwrap_or_default();79
logger.info("Starting privileged Debian update installation.", serde_json::json!({ "filename": name }));80
let mut command = tokio::process::Command::new(PKEXEC_PATH);81
command.args([APT_GET_PATH, "install", "--reinstall", "--yes"]).arg(path).stdin(std::process::Stdio::null()).stdout(std::process::Stdio::piped()).stderr(std::process::Stdio::piped()).kill_on_drop(true);82
let child = command.spawn().map_err(|_| install_error("UPDATE_INSTALL_LAUNCH_FAILED", "The privileged installer could not be started."))?;83
let output = tokio::time::timeout(Duration::from_secs(30 * 60), child.wait_with_output())84
.await85
.map_err(|_| install_error("UPDATE_INSTALL_TIMEOUT", "The Debian update installation timed out."))?86
.map_err(|_| install_error("UPDATE_INSTALL_LAUNCH_FAILED", "The privileged installer could not be started."))?;87
if output.status.code() == Some(0) {88
logger.info("Debian update installation completed.", serde_json::json!({ "filename": name }));89
return Ok(LaunchResult { launched: true, installed: true, restart: true });90
}91
let denied = matches!(output.status.code(), Some(126) | Some(127));92
let text = format!("{}\n{}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr));93
let tail: String = text.trim().chars().rev().take(4096).collect::<Vec<_>>().into_iter().rev().collect();94
logger.warn("Debian update installation failed.", serde_json::json!({ "code": output.status.code(), "output": tail }));95
Err(if denied {96
install_error("UPDATE_INSTALL_AUTHORIZATION_DENIED", "Administrator authorization for the update was canceled or denied.")97
} else {98
install_error("UPDATE_INSTALL_FAILED", "The Debian update installation failed.")99
})100
}102
#[cfg(not(unix))]103
async fn install_deb(_path: &Path, _logger: &crate::core::logging::Logger) -> CoreResult<LaunchResult> {104
Err(install_error("UPDATE_INSTALL_UNSUPPORTED_PACKAGE", "Linux updates must use a Debian package."))105
}107
#[cfg(all(test, unix))]108
mod tests {109
use super::*;111
#[test]112
fn unsafe_files_rejected() {113
let dir = tempfile::tempdir().unwrap();114
assert_eq!(assert_safe_regular_file(Path::new("relative.deb"), "x").unwrap_err().code, "UPDATE_INSTALL_UNSAFE_PATH");115
let file = dir.path().join("a.deb");116
std::fs::write(&file, b"x").unwrap();117
let link = dir.path().join("b.deb");118
std::os::unix::fs::symlink(&file, &link).unwrap();119
assert!(assert_safe_regular_file(&file, "x").is_ok());120
assert!(assert_safe_regular_file(&link, "x").is_err());121
// A file owned by the current (non-root) user is not a trusted system executable.122
if unsafe { libc::getuid() } != 0 {123
assert_eq!(assert_trusted_system_executable(&file).unwrap_err().code, "UPDATE_INSTALL_UNSAFE_EXECUTABLE");124
}125
}126
}SHA-256
ff6c1a3267969b2be18e54a3e5c602f72f7085007412e9529ea3e856784fa8e9CH-J Proprietary Software License 1.14 · Vir je na voljo pod CH-J Proprietary Software License 1.14. Njena razpoložljivost ne spreminja pogojev licence ali dodeljuje dodatnih pravic.
Arhiv SHA-256
ac8fe790f4ba60fc098f8c985fc2656cfcc6cdc21fb34b30e308a2b54e62258f