CH-J Toolsservera pārvaldība virs SSH
Izvēlne
Publicētais avots

CH-J Tools

Pārlūkot mapes un failus konkrētai lietojumprogrammas izdošanai.

Lejupielādēt avota ZIP
CH-J / Server Manager 872 faili
core-20261030T220507Z-61e4558d889f Pievienots: · Būvēts:
windows_exec.rs Pievienots: Datums nav reģistrēts 6,2 KB · 118 rindas
Kods Lejupielādēt failu
1//! Fixed PowerShell helpers over Windows OpenSSH (port of `sessions/windowsPrivilegedExec.js`).
2//! The command line is a short `-EncodedCommand` bootstrap; the script and one JSON argument
3//! object follow on stdin as base64 lines, so user input never reaches a command line.
4//! Windows has no sudo: scripts refuse administrative work without an elevated token and
5//! never try to bypass UAC.
7use crate::core::error::{CoreError, CoreResult};
8use crate::core::ssh::session::{SessionManager, SessionRecord};
9use base64::engine::general_purpose::STANDARD as B64;
10use base64::Engine;
11use once_cell::sync::Lazy;
12use regex::Regex;
13use serde_json::Value;
14use std::sync::Arc;
15use std::time::Duration;
17pub const BOOTSTRAP: &str = "$ErrorActionPreference='Stop';$ProgressPreference='SilentlyContinue';$i=[Console]::In;$c=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($i.ReadLine()));$a=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($i.ReadLine()));& ([ScriptBlock]::Create($c)) $a";
18static ERR_LINE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^CHJ_ERR:([A-Z0-9_]{2,64}):([A-Za-z0-9+/=]*)\s*$").unwrap());
20pub fn encoded_command(source: &str) -> String {
21 let utf16: Vec<u8> = source.encode_utf16().flat_map(|u| u.to_le_bytes()).collect();
22 format!("powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand {}", B64.encode(utf16))
25pub fn bootstrap_input(script: &str, args: &Value) -> String {
26 format!("{}\n{}\n", B64.encode(script.as_bytes()), B64.encode(serde_json::to_string(args).unwrap_or_else(|_| "{}".into()).as_bytes()))
29fn clean(text: &str, limit: usize) -> String {
30 let replaced: String = text.chars().map(|c| if (c as u32) < 0x20 || c as u32 == 0x7f { ' ' } else { c }).collect();
31 replaced.split_whitespace().collect::<Vec<_>>().join(" ").chars().take(limit).collect()
34pub fn parse_windows_output(stdout: &str, stderr: &str, code: Option<i64>, prefix: &str) -> CoreResult<Value> {
35 let lines: Vec<&str> = stdout.split('\n').map(|l| l.trim_end_matches('\r')).collect();
36 if let Some(line) = lines.iter().find(|l| l.starts_with("CHJ_RESULT:")) {
37 return B64
38 .decode(line[11..].trim())
39 .ok()
40 .and_then(|bytes| serde_json::from_slice(&bytes).ok())
41 .ok_or_else(|| CoreError::new(format!("{prefix}PROTOCOL_ERROR"), "The Windows helper returned unreadable data."));
42 }
43 if let Some(line) = lines.iter().find(|l| l.starts_with("CHJ_ERR:")) {
44 if let Some(caps) = ERR_LINE.captures(line) {
45 let message = B64.decode(&caps[2]).ok().map(|b| clean(&String::from_utf8_lossy(&b), 600)).unwrap_or_default();
46 return Err(CoreError::new(format!("{prefix}{}", &caps[1]), if message.is_empty() { caps[1].to_string() } else { message }));
47 }
48 }
49 let text = format!("{stdout}\n{stderr}");
50 let not_found = Regex::new(r"(?i)is not recognized as (an internal or external command|the name of a cmdlet)|powershell(\.exe)?: (command )?not found").unwrap();
51 if code == Some(9009) || not_found.is_match(&text) {
52 return Err(CoreError::new(format!("{prefix}POWERSHELL_UNAVAILABLE"), "Windows PowerShell (powershell.exe) could not be started over this SSH session."));
53 }
54 if Regex::new(r"(?i)language mode|ConstrainedLanguage|cannot be loaded because running scripts is disabled").unwrap().is_match(&text) {
55 return Err(CoreError::new(
56 format!("{prefix}POWERSHELL_RESTRICTED"),
57 "PowerShell is restricted by policy (Constrained Language Mode or script blocking); CH-J does not bypass it.",
58 ));
59 }
60 let tags = Regex::new(r"<[^>]+>").unwrap();
61 Err(CoreError::new(
62 format!("{prefix}PROTOCOL_ERROR"),
63 format!("The Windows helper did not return a result (exit {}). {}", code.map(|c| c.to_string()).unwrap_or_else(|| "null".into()), clean(&tags.replace_all(stderr, " "), 300)),
64 ))
67/// Runs a fixed PowerShell script on a Windows session, serialized per session.
68pub async fn run_windows_script(manager: &SessionManager, record: &Arc<SessionRecord>, script: &str, args: &Value, timeout: Duration, output_limit: usize, prefix: &str) -> CoreResult<Value> {
69 let input = bootstrap_input(script, args);
70 let result = manager
71 .windows_serial(record, async {
72 let mut channel = manager.exec_channel(record, &encoded_command(BOOTSTRAP), timeout, output_limit).await?;
73 channel.write(input.as_bytes()).await;
74 channel.eof().await;
75 channel.finish().await
76 })
77 .await?;
78 parse_windows_output(&result.stdout, &result.stderr, result.code, prefix)
81pub struct WindowsExecutor {
82 pub sessions: Arc<SessionManager>,
85impl WindowsExecutor {
86 pub async fn run(&self, session_id: &str, script: &str, args: &Value, timeout: Duration, purpose: &str, prefix: &str) -> CoreResult<Value> {
87 let record = self.sessions.require_connected(session_id)?;
88 let result = run_windows_script(&self.sessions, &record, script, args, timeout, 16 * 1024 * 1024, prefix).await;
89 if let Err(error) = &result {
90 self.sessions.logger.warn("Windows remote operation failed.", serde_json::json!({ "sessionId": session_id, "purpose": purpose, "code": error.code }));
91 }
92 result
93 }
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use serde_json::json;
101 #[test]
102 fn encoded_command_is_utf16le_base64() {
103 let command = encoded_command("a");
104 assert!(command.ends_with("-EncodedCommand YQA="));
105 assert!(encoded_command(BOOTSTRAP).len() < 8191);
106 }
108 #[test]
109 fn output_parsing() {
110 let ok = format!("noise\r\nCHJ_RESULT:{}\r\n", B64.encode(br#"{"a":1}"#));
111 assert_eq!(parse_windows_output(&ok, "", Some(0), "USERS_").unwrap(), json!({"a": 1}));
112 let err = format!("CHJ_ERR:NOT_ELEVATED:{}\n", B64.encode("Run as admin"));
113 let error = parse_windows_output(&err, "", Some(1), "USERS_").unwrap_err();
114 assert_eq!(error.code, "USERS_NOT_ELEVATED");
115 assert_eq!(error.message, "Run as admin");
116 assert_eq!(parse_windows_output("", "'powershell.exe' is not recognized as an internal or external command", Some(1), "X_").unwrap_err().code, "X_POWERSHELL_UNAVAILABLE");
117 }
SHA-2561825ef1082d8898256406c23968b05f3ed0a95bc8ba104b8fa195162617af0f7

CH-J Proprietary Software License 1.14 · Avots ir sniegts saskaņā ar CH-J Proprietary Software License 1.14. Tā pieejamība nemaina licences noteikumus un nepiešķir papildu tiesības.

Arhīvs SHA-25677380d4805485ffa4027fbe7858b5836f197a4aa9d52358a43e2d12d018744df
Faili