CH-J Toolsserverihaldus üle SSH
Menüü
Avaldatud allikas

CH-J Tools

Vaata katalooge ja faile konkreetse rakenduse vabastamiseks.

Laadi alla allikas ZIP
CH-J / Server Manager 1704 failid
0.1.2-beta Lisatud: · Ehitatud:
windows_exec.rs Lisatud: Kuupäeva ei registreerita 6,5 KB · 127 read
Kood Faili allalaadimine
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::matrix;
9use crate::core::ssh::session::{SessionManager, SessionRecord};
10use base64::engine::general_purpose::STANDARD as B64;
11use base64::Engine;
12use once_cell::sync::Lazy;
13use regex::Regex;
14use serde_json::Value;
15use std::sync::Arc;
16use std::time::Duration;
18pub 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";
19static ERR_LINE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^CHJ_ERR:([A-Z0-9_]{2,64}):([A-Za-z0-9+/=]*)\s*$").unwrap());
21pub fn encoded_command(source: &str) -> String {
22 let utf16: Vec<u8> = source.encode_utf16().flat_map(|u| u.to_le_bytes()).collect();
23 format!("powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand {}", B64.encode(utf16))
26pub fn bootstrap_input(script: &str, args: &Value) -> String {
27 format!("{}\n{}\n", B64.encode(script.as_bytes()), B64.encode(serde_json::to_string(args).unwrap_or_else(|_| "{}".into()).as_bytes()))
30fn clean(text: &str, limit: usize) -> String {
31 let replaced: String = text.chars().map(|c| if (c as u32) < 0x20 || c as u32 == 0x7f { ' ' } else { c }).collect();
32 replaced.split_whitespace().collect::<Vec<_>>().join(" ").chars().take(limit).collect()
35pub fn parse_windows_output(stdout: &str, stderr: &str, code: Option<i64>, prefix: &str) -> CoreResult<Value> {
36 let lines: Vec<&str> = stdout.split('\n').map(|l| l.trim_end_matches('\r')).collect();
37 if let Some(line) = lines.iter().find(|l| l.starts_with("CHJ_RESULT:")) {
38 return B64
39 .decode(line[11..].trim())
40 .ok()
41 .and_then(|bytes| serde_json::from_slice(&bytes).ok())
42 .ok_or_else(|| CoreError::new(format!("{prefix}PROTOCOL_ERROR"), "The Windows helper returned unreadable data."));
43 }
44 if let Some(line) = lines.iter().find(|l| l.starts_with("CHJ_ERR:")) {
45 if let Some(caps) = ERR_LINE.captures(line) {
46 let message = B64.decode(&caps[2]).ok().map(|b| clean(&String::from_utf8_lossy(&b), 600)).unwrap_or_default();
47 return Err(CoreError::new(format!("{prefix}{}", &caps[1]), if message.is_empty() { caps[1].to_string() } else { message }));
48 }
49 }
50 let text = format!("{stdout}\n{stderr}");
51 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();
52 if code == Some(9009) || not_found.is_match(&text) {
53 return Err(CoreError::new(format!("{prefix}POWERSHELL_UNAVAILABLE"), "Windows PowerShell (powershell.exe) could not be started over this SSH session."));
54 }
55 if Regex::new(r"(?i)language mode|ConstrainedLanguage|cannot be loaded because running scripts is disabled").unwrap().is_match(&text) {
56 return Err(CoreError::new(
57 format!("{prefix}POWERSHELL_RESTRICTED"),
58 "PowerShell is restricted by policy (Constrained Language Mode or script blocking); CH-J does not bypass it.",
59 ));
60 }
61 let tags = Regex::new(r"<[^>]+>").unwrap();
62 Err(CoreError::new(
63 format!("{prefix}PROTOCOL_ERROR"),
64 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)),
65 ))
68/// Runs a fixed PowerShell script on a Windows session, serialized per session.
69pub async fn run_windows_script(manager: &SessionManager, record: &Arc<SessionRecord>, operation: &str, args: &Value, timeout: Duration, output_limit: usize, prefix: &str) -> CoreResult<Value> {
70 let binding = matrix::binding(operation, "windows")?;
71 let mut args = args.clone();
72 if let Some(op) = binding.argv.first() {
73 args["op"] = Value::from(op.as_str());
74 }
75 if let Some(action) = binding.argv.get(1) {
76 args["action"] = Value::from(action.as_str());
77 }
78 let input = bootstrap_input(binding.script, &args);
79 let result = manager
80 .windows_serial(record, async {
81 let mut channel = manager.exec_channel(record, &encoded_command(BOOTSTRAP), timeout, output_limit).await?;
82 channel.write(input.as_bytes()).await;
83 channel.eof().await;
84 channel.finish().await
85 })
86 .await?;
87 parse_windows_output(&result.stdout, &result.stderr, result.code, prefix)
90pub struct WindowsExecutor {
91 pub sessions: Arc<SessionManager>,
94impl WindowsExecutor {
95 pub async fn run(&self, session_id: &str, operation: &str, args: &Value, timeout: Duration, purpose: &str, prefix: &str) -> CoreResult<Value> {
96 let record = self.sessions.require_connected(session_id)?;
97 let result = run_windows_script(&self.sessions, &record, operation, args, timeout, 16 * 1024 * 1024, prefix).await;
98 if let Err(error) = &result {
99 self.sessions.logger.warn("Windows remote operation failed.", serde_json::json!({ "sessionId": session_id, "purpose": purpose, "code": error.code }));
100 }
101 result
102 }
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use serde_json::json;
110 #[test]
111 fn encoded_command_is_utf16le_base64() {
112 let command = encoded_command("a");
113 assert!(command.ends_with("-EncodedCommand YQA="));
114 assert!(encoded_command(BOOTSTRAP).len() < 8191);
115 }
117 #[test]
118 fn output_parsing() {
119 let ok = format!("noise\r\nCHJ_RESULT:{}\r\n", B64.encode(br#"{"a":1}"#));
120 assert_eq!(parse_windows_output(&ok, "", Some(0), "USERS_").unwrap(), json!({"a": 1}));
121 let err = format!("CHJ_ERR:NOT_ELEVATED:{}\n", B64.encode("Run as admin"));
122 let error = parse_windows_output(&err, "", Some(1), "USERS_").unwrap_err();
123 assert_eq!(error.code, "USERS_NOT_ELEVATED");
124 assert_eq!(error.message, "Run as admin");
125 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");
126 }
SHA-2564694d56fc3fa1a03264289a4a244949e85b4e726f579362908337912e41c9c9a

CH-J Proprietary Software License 1.14 · Allikas on esitatud CH-J Proprietary Software License 1.14 all. Selle kättesaadavus ei muuda litsentsitingimusi ega anna täiendavaid õigusi.

Arhiiv SHA-256ac8fe790f4ba60fc098f8c985fc2656cfcc6cdc21fb34b30e308a2b54e62258f
Failid