Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,106 changes: 555 additions & 551 deletions Cargo.lock

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,30 @@
resolver = "3"
# Define the members of the workspace. Currently, it includes the main crate.
members = ["crates/*"]

[workspace.lints.clippy]
all = { level = "deny", priority = -1 }
pedantic = { level = "deny", priority = -1 }
nursery = { level = "deny", priority = -1 }
suspicious = { level = "deny", priority = -1 }
#cargo = { level = "warn", priority = -1 }


# pedantic exceptions
doc_markdown = { level = "allow" } # false positives for the word `PowerShell`
missing_errors_doc = { level = "allow" }
missing_panics_doc = { level = "allow" }

cast_lossless = { level = "allow" }
cast_possible_truncation = { level = "allow" }
cast_possible_wrap = { level = "allow" }
cast_sign_loss = { level = "allow" }

must_use_candidate = { level = "allow" }
return_self_not_must_use = { level = "allow" }
unreadable_literal = { level = "allow" }
used_underscore_binding = { level = "allow" }
wildcard_imports = { level = "allow" }

#nursery exceptions
missing_const_for_fn = { level = "allow" }
3 changes: 3 additions & 0 deletions crates/ironposh-async/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ uuid = "1.0"

[target.'cfg(target_arch = "wasm32")'.dependencies]
futures-timer = { version = "3", features = ["wasm-bindgen"] }

[lints]
workspace = true
7 changes: 4 additions & 3 deletions crates/ironposh-async/src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ use crate::{HostIo, HostSubmitter, HttpClient, session};
///
/// This function creates the connection channels and establishes a WinRM connection,
/// then starts the active session loop in the background.
pub fn establish_connection<C: HttpClient>(
#[expect(clippy::too_many_lines)]
pub fn establish_connection<C>(
config: WinRmConfig,
client: C,
) -> (
Expand All @@ -27,7 +28,7 @@ pub fn establish_connection<C: HttpClient>(
impl std::future::Future<Output = anyhow::Result<()>>,
)
where
C: 'static,
C: HttpClient + 'static,
{
let (mut user_input_tx, user_input_rx) = mpsc::channel(10);
let (server_output_tx, mut server_output_rx) = mpsc::channel(10);
Expand Down Expand Up @@ -113,7 +114,7 @@ where
.await;

match result {
Ok(_) => {
Ok(()) => {
info!("Active session loop ended");
let _ = session_event_tx.unbounded_send(crate::SessionEvent::ActiveSessionEnded);
Ok(())
Expand Down
2 changes: 1 addition & 1 deletion crates/ironposh-async/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ pub struct HostResponse {

impl HostSubmitter {
/// Submit a host call response back to the session
pub async fn submit(&self, resp: HostResponse) -> anyhow::Result<()> {
pub fn submit(&self, resp: HostResponse) -> anyhow::Result<()> {
self.0
.unbounded_send(resp)
.map_err(|_| anyhow::anyhow!("Host response channel closed"))?;
Expand Down
149 changes: 74 additions & 75 deletions crates/ironposh-async/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ fn launch<C: HttpClient>(
}

/// Main active session loop that handles network responses and user operations
#[expect(clippy::too_many_arguments)]
#[expect(clippy::too_many_lines)]
#[instrument(skip_all)]
pub async fn start_active_session_loop(
runspace_polling_request: TrySend,
Expand Down Expand Up @@ -135,81 +137,78 @@ pub async fn start_active_session_loop(
// 2) user operations
user_op = user_input_rx.next() => {
info!(target: "user", "processing user operation");
match user_op {
Some(user_operation) => {
info!(target: "user", operation = ?user_operation, "processing user operation");

let step_result = active_session
.accept_client_operation(user_operation)
.map_err(|e| {
error!(target: "user", error = %e, "failed to accept user operation");
e
})
.context("Failed to accept user operation")?;

match step_result {
ActiveSessionOutput::SendBack(reqs) => {
info!(
target: "network",
request_count = reqs.len(),
"launching HTTP requests from user operation"
);
for r in reqs {
inflight.push(launch(&client, r));
}
}
ActiveSessionOutput::UserEvent(event) => {
info!(target: "user", event = ?event, "sending user event from user operation");
if user_output_tx.send(event).await.is_err() {
return Err(anyhow::anyhow!("User output channel disconnected"));
}
}
ActiveSessionOutput::HostCall(host_call) => {
debug!(host_call = ?host_call.method_name(), call_id = host_call.call_id(), scope = ?host_call.scope());

// Forward to consumer
if host_call_tx.unbounded_send(host_call).is_err() {
return Err(anyhow::anyhow!("Host-call channel closed"));
}

// Await the consumer's reply
let HostResponse { call_id, scope, submission } = host_resp_rx.next().await
.ok_or_else(|| anyhow::anyhow!("Host-response channel closed"))?;

let step_result = active_session
.accept_client_operation(
UserOperation::SubmitHostResponse {
call_id,
scope,
submission,
},
)
.map_err(|e| {
error!(target: "user", error = %e, "failed to submit host response");
e
})
.context("Failed to submit host response")?;

process_session_outputs(vec![step_result], &mut user_output_tx, &mut user_input_tx, &host_call_tx, &mut host_resp_rx).await?;
}
ActiveSessionOutput::OperationSuccess => {
info!(target: "session", "operation completed successfully");
}
ActiveSessionOutput::SendBackError(e) => {
error!(target: "session", error = %e, "session step failed");
return Err(anyhow::anyhow!("Session step failed: {e}"));
},
ActiveSessionOutput::Ignore => {
// Do nothing
}
}

}
None => {
info!("User input channel disconnected");
break; // UI side closed
}
}
if let Some(user_operation) = user_op {
info!(target: "user", operation = ?user_operation, "processing user operation");

let step_result = active_session
.accept_client_operation(user_operation)
.map_err(|e| {
error!(target: "user", error = %e, "failed to accept user operation");
e
})
.context("Failed to accept user operation")?;

match step_result {
ActiveSessionOutput::SendBack(reqs) => {
info!(
target: "network",
request_count = reqs.len(),
"launching HTTP requests from user operation"
);
for r in reqs {
inflight.push(launch(&client, r));
}
}
ActiveSessionOutput::UserEvent(event) => {
info!(target: "user", event = ?event, "sending user event from user operation");
if user_output_tx.send(event).await.is_err() {
return Err(anyhow::anyhow!("User output channel disconnected"));
}
}
ActiveSessionOutput::HostCall(host_call) => {
debug!(host_call = ?host_call.method_name(), call_id = host_call.call_id(), scope = ?host_call.scope());

// Forward to consumer
if host_call_tx.unbounded_send(host_call).is_err() {
return Err(anyhow::anyhow!("Host-call channel closed"));
}

// Await the consumer's reply
let HostResponse { call_id, scope, submission } = host_resp_rx.next().await
.ok_or_else(|| anyhow::anyhow!("Host-response channel closed"))?;

let step_result = active_session
.accept_client_operation(
UserOperation::SubmitHostResponse {
call_id,
scope,
submission,
},
)
.map_err(|e| {
error!(target: "user", error = %e, "failed to submit host response");
e
})
.context("Failed to submit host response")?;

process_session_outputs(vec![step_result], &mut user_output_tx, &mut user_input_tx, &host_call_tx, &mut host_resp_rx).await?;
}
ActiveSessionOutput::OperationSuccess => {
info!(target: "session", "operation completed successfully");
}
ActiveSessionOutput::SendBackError(e) => {
error!(target: "session", error = %e, "session step failed");
return Err(anyhow::anyhow!("Session step failed: {e}"));
},
ActiveSessionOutput::Ignore => {
// Do nothing
}
}

} else {
info!("User input channel disconnected");
break; // UI side closed
}
}
}
}
Expand Down
7 changes: 4 additions & 3 deletions crates/ironposh-client-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,17 @@ ironposh-xml = { path = "../ironposh-xml" }
typed-builder = "0.21.0"
base64 = "0.22.1"
tracing = "0.1.41"
sspi = { path = "C:\\dev\\sspi-rs", features = ["ring"] }
url = "2.5.7"
whoami = "1.6.1"
serde = { version = "1.0.228", features = ["derive"] }
# sspi = { version = "0.16.1", features = ["ring"] }

sspi = { version = "0.17", features = ["ring"] }

[dev-dependencies]
ureq = "2"
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
tokio = { version = "1", features = ["full"] }
anyhow = "1"
regex = "1"

[lints]
workspace = true
39 changes: 21 additions & 18 deletions crates/ironposh-client-core/src/connector/active_session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::{
use ironposh_psrp::{ErrorRecord, PipelineOutput, PsPrimitiveValue, PsValue};
use tracing::{error, info, instrument, warn};

#[expect(clippy::large_enum_variant)]
#[derive(Debug, PartialEq, Eq)]
pub enum UserEvent {
PipelineCreated {
Expand All @@ -32,21 +33,22 @@ pub enum UserEvent {
impl UserEvent {
pub fn pipeline_id(&self) -> uuid::Uuid {
match self {
UserEvent::PipelineCreated {
Self::PipelineCreated {
pipeline: powershell,
}
| UserEvent::PipelineFinished {
| Self::PipelineFinished {
pipeline: powershell,
}
| UserEvent::PipelineOutput {
| Self::PipelineOutput {
pipeline: powershell,
..
} => powershell.id(),
UserEvent::ErrorRecord { handle, .. } => handle.id(),
Self::ErrorRecord { handle, .. } => handle.id(),
}
}
}

#[expect(clippy::large_enum_variant)]
#[derive(Debug)]
pub enum ActiveSessionOutput {
SendBack(Vec<TrySend>),
Expand All @@ -60,12 +62,12 @@ pub enum ActiveSessionOutput {
impl ActiveSessionOutput {
pub fn priority(&self) -> u8 {
match self {
ActiveSessionOutput::HostCall { .. } => 1,
ActiveSessionOutput::SendBack(_) => 2,
ActiveSessionOutput::SendBackError(_) => 3,
ActiveSessionOutput::UserEvent(_) => 4,
ActiveSessionOutput::OperationSuccess => 5,
ActiveSessionOutput::Ignore => 6,
Self::HostCall { .. } => 1,
Self::SendBack(_) => 2,
Self::SendBackError(_) => 3,
Self::UserEvent(_) => 4,
Self::OperationSuccess => 5,
Self::Ignore => 6,
}
}
}
Expand All @@ -86,6 +88,7 @@ impl Ord for ActiveSessionOutput {
}
}

#[expect(clippy::large_enum_variant)]
#[derive(Debug)]
pub enum UserOperation {
InvokeWithSpec {
Expand All @@ -112,10 +115,10 @@ pub enum UserOperation {
impl UserOperation {
pub fn operation_type(&self) -> &str {
match self {
UserOperation::InvokeWithSpec { .. } => "InvokeWithSpec",
UserOperation::KillPipeline { .. } => "KillPipeline",
UserOperation::SubmitHostResponse { .. } => "SubmitHostResponse",
UserOperation::CancelHostCall { .. } => "CancelHostCall",
Self::InvokeWithSpec { .. } => "InvokeWithSpec",
Self::KillPipeline { .. } => "KillPipeline",
Self::SubmitHostResponse { .. } => "SubmitHostResponse",
Self::CancelHostCall { .. } => "CancelHostCall",
}
}
}
Expand Down Expand Up @@ -163,7 +166,7 @@ impl ActiveSession {
info!(pipeline_id = %pipeline.id(), "killing pipeline");

// 1) Build the Signal request
let kill_xml = self.runspace_pool.kill_pipeline(pipeline);
let kill_xml = self.runspace_pool.kill_pipeline(&pipeline);
let kill_xml = match kill_xml {
Ok(kill_xml) => kill_xml,
Err(e) => {
Expand Down Expand Up @@ -269,7 +272,7 @@ impl ActiveSession {
}

// 2) Feed PSRP
let results = self.runspace_pool.accept_response(xml_body).map_err(|e| {
let results = self.runspace_pool.accept_response(&xml_body).map_err(|e| {
error!("RunspacePool.accept_response failed: {:#}", e);
e
})?;
Expand Down Expand Up @@ -359,7 +362,7 @@ impl ActiveSession {
// 1) Fragment to XML
let send_xml = self
.runspace_pool
.send_pipeline_host_response(command_id, host_resp)?;
.send_pipeline_host_response(command_id, &host_resp)?;
info!(send_xml_length = send_xml.len(), "built host response XML");
info!(unencrypted_host_response_xml = %send_xml, "outgoing unencrypted pipeline host response SOAP");

Expand Down Expand Up @@ -408,7 +411,7 @@ impl ActiveSession {
// 1) Fragment to XML
let send_xml = self
.runspace_pool
.send_runspace_pool_host_response(host_resp)?;
.send_runspace_pool_host_response(&host_resp)?;
info!(
send_xml_length = send_xml.len(),
"built pool host response XML"
Expand Down
Loading