- Implementa Windows Service (raven-service) para operacoes privilegiadas - Comunicacao via Named Pipes sem necessidade de UAC adicional - Adiciona single-instance para evitar multiplos icones na bandeja - Corrige todos os warnings do clippy (rustdesk, lib, usb_control, agent) - Remove fallback de elevacao para evitar UAC desnecessario - USB Policy e RustDesk provisioning agora usam o servico quando disponivel 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
290 lines
9.2 KiB
Rust
290 lines
9.2 KiB
Rust
//! Modulo IPC - Servidor de Named Pipes
|
|
//!
|
|
//! Implementa comunicacao entre o Raven UI e o Raven Service
|
|
//! usando Named Pipes do Windows com protocolo JSON-RPC simplificado.
|
|
|
|
use crate::{rustdesk, usb_policy};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::io::{BufRead, BufReader, Write};
|
|
use thiserror::Error;
|
|
use tracing::{debug, info, warn};
|
|
|
|
#[derive(Debug, Error)]
|
|
pub enum IpcError {
|
|
#[error("Erro de IO: {0}")]
|
|
Io(#[from] std::io::Error),
|
|
|
|
#[error("Erro de serializacao: {0}")]
|
|
Json(#[from] serde_json::Error),
|
|
}
|
|
|
|
/// Requisicao JSON-RPC simplificada
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct Request {
|
|
pub id: String,
|
|
pub method: String,
|
|
#[serde(default)]
|
|
pub params: serde_json::Value,
|
|
}
|
|
|
|
/// Resposta JSON-RPC simplificada
|
|
#[derive(Debug, Serialize)]
|
|
pub struct Response {
|
|
pub id: String,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub result: Option<serde_json::Value>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub error: Option<ErrorResponse>,
|
|
}
|
|
|
|
#[derive(Debug, Serialize)]
|
|
pub struct ErrorResponse {
|
|
pub code: i32,
|
|
pub message: String,
|
|
}
|
|
|
|
impl Response {
|
|
pub fn success(id: String, result: serde_json::Value) -> Self {
|
|
Self {
|
|
id,
|
|
result: Some(result),
|
|
error: None,
|
|
}
|
|
}
|
|
|
|
pub fn error(id: String, code: i32, message: String) -> Self {
|
|
Self {
|
|
id,
|
|
result: None,
|
|
error: Some(ErrorResponse { code, message }),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Inicia o servidor de Named Pipes
|
|
pub async fn run_server(pipe_name: &str) -> Result<(), IpcError> {
|
|
info!("Iniciando servidor IPC em: {}", pipe_name);
|
|
|
|
loop {
|
|
match accept_connection(pipe_name).await {
|
|
Ok(()) => {
|
|
debug!("Conexao processada com sucesso");
|
|
}
|
|
Err(e) => {
|
|
warn!("Erro ao processar conexao: {}", e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Aceita uma conexao e processa requisicoes
|
|
async fn accept_connection(pipe_name: &str) -> Result<(), IpcError> {
|
|
use windows::Win32::Foundation::INVALID_HANDLE_VALUE;
|
|
use windows::Win32::Security::{
|
|
InitializeSecurityDescriptor, SetSecurityDescriptorDacl,
|
|
PSECURITY_DESCRIPTOR, SECURITY_ATTRIBUTES, SECURITY_DESCRIPTOR,
|
|
};
|
|
use windows::Win32::Storage::FileSystem::PIPE_ACCESS_DUPLEX;
|
|
use windows::Win32::System::Pipes::{
|
|
ConnectNamedPipe, CreateNamedPipeW, DisconnectNamedPipe,
|
|
PIPE_READMODE_MESSAGE, PIPE_TYPE_MESSAGE, PIPE_UNLIMITED_INSTANCES, PIPE_WAIT,
|
|
};
|
|
use windows::Win32::System::SystemServices::SECURITY_DESCRIPTOR_REVISION;
|
|
use windows::core::PCWSTR;
|
|
|
|
// Cria o named pipe com seguranca que permite acesso a todos os usuarios
|
|
let pipe_name_wide: Vec<u16> = pipe_name.encode_utf16().chain(std::iter::once(0)).collect();
|
|
|
|
// Cria security descriptor com DACL nulo (permite acesso a todos)
|
|
let mut sd = SECURITY_DESCRIPTOR::default();
|
|
unsafe {
|
|
let sd_ptr = PSECURITY_DESCRIPTOR(&mut sd as *mut _ as *mut _);
|
|
let _ = InitializeSecurityDescriptor(sd_ptr, SECURITY_DESCRIPTOR_REVISION);
|
|
// DACL nulo = acesso irrestrito
|
|
let _ = SetSecurityDescriptorDacl(sd_ptr, true, None, false);
|
|
}
|
|
|
|
let sa = SECURITY_ATTRIBUTES {
|
|
nLength: std::mem::size_of::<SECURITY_ATTRIBUTES>() as u32,
|
|
lpSecurityDescriptor: &mut sd as *mut _ as *mut _,
|
|
bInheritHandle: false.into(),
|
|
};
|
|
|
|
let pipe_handle = unsafe {
|
|
CreateNamedPipeW(
|
|
PCWSTR::from_raw(pipe_name_wide.as_ptr()),
|
|
PIPE_ACCESS_DUPLEX,
|
|
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
|
|
PIPE_UNLIMITED_INSTANCES,
|
|
4096, // out buffer
|
|
4096, // in buffer
|
|
0, // default timeout
|
|
Some(&sa), // seguranca permissiva
|
|
)
|
|
};
|
|
|
|
// Verifica se o handle e valido
|
|
if pipe_handle == INVALID_HANDLE_VALUE {
|
|
return Err(IpcError::Io(std::io::Error::last_os_error()));
|
|
}
|
|
|
|
// Aguarda conexao de um cliente
|
|
info!("Aguardando conexao de cliente...");
|
|
let connect_result = unsafe {
|
|
ConnectNamedPipe(pipe_handle, None)
|
|
};
|
|
|
|
if let Err(e) = connect_result {
|
|
// ERROR_PIPE_CONNECTED (535) significa que o cliente ja estava conectado
|
|
// o que e aceitavel
|
|
let error_code = e.code().0 as u32;
|
|
if error_code != 535 {
|
|
warn!("Erro ao aguardar conexao: {:?}", e);
|
|
}
|
|
}
|
|
|
|
info!("Cliente conectado");
|
|
|
|
// Processa requisicoes do cliente
|
|
let result = process_client(pipe_handle);
|
|
|
|
// Desconecta o cliente
|
|
unsafe {
|
|
let _ = DisconnectNamedPipe(pipe_handle);
|
|
}
|
|
|
|
result
|
|
}
|
|
|
|
/// Processa requisicoes de um cliente conectado
|
|
fn process_client(pipe_handle: windows::Win32::Foundation::HANDLE) -> Result<(), IpcError> {
|
|
use std::os::windows::io::{FromRawHandle, RawHandle};
|
|
use std::fs::File;
|
|
|
|
// Cria File handle a partir do pipe
|
|
let raw_handle = pipe_handle.0 as RawHandle;
|
|
let file = unsafe { File::from_raw_handle(raw_handle) };
|
|
|
|
let reader = BufReader::new(file.try_clone()?);
|
|
let mut writer = file;
|
|
|
|
// Le linhas (cada linha e uma requisicao JSON)
|
|
for line in reader.lines() {
|
|
let line = match line {
|
|
Ok(l) => l,
|
|
Err(e) => {
|
|
if e.kind() == std::io::ErrorKind::BrokenPipe {
|
|
info!("Cliente desconectou");
|
|
break;
|
|
}
|
|
return Err(e.into());
|
|
}
|
|
};
|
|
|
|
if line.is_empty() {
|
|
continue;
|
|
}
|
|
|
|
debug!("Requisicao recebida: {}", line);
|
|
|
|
// Parse da requisicao
|
|
let response = match serde_json::from_str::<Request>(&line) {
|
|
Ok(request) => handle_request(request),
|
|
Err(e) => Response::error(
|
|
"unknown".to_string(),
|
|
-32700,
|
|
format!("Parse error: {}", e),
|
|
),
|
|
};
|
|
|
|
// Serializa e envia resposta
|
|
let response_json = serde_json::to_string(&response)?;
|
|
debug!("Resposta: {}", response_json);
|
|
|
|
writeln!(writer, "{}", response_json)?;
|
|
writer.flush()?;
|
|
}
|
|
|
|
// IMPORTANTE: Nao fechar o handle aqui, pois DisconnectNamedPipe precisa dele
|
|
std::mem::forget(writer);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Processa uma requisicao e retorna a resposta
|
|
fn handle_request(request: Request) -> Response {
|
|
info!("Processando metodo: {}", request.method);
|
|
|
|
match request.method.as_str() {
|
|
"health_check" => handle_health_check(request.id),
|
|
"apply_usb_policy" => handle_apply_usb_policy(request.id, request.params),
|
|
"get_usb_policy" => handle_get_usb_policy(request.id),
|
|
"provision_rustdesk" => handle_provision_rustdesk(request.id, request.params),
|
|
"get_rustdesk_status" => handle_get_rustdesk_status(request.id),
|
|
_ => Response::error(
|
|
request.id,
|
|
-32601,
|
|
format!("Metodo nao encontrado: {}", request.method),
|
|
),
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Handlers de Requisicoes
|
|
// =============================================================================
|
|
|
|
fn handle_health_check(id: String) -> Response {
|
|
Response::success(
|
|
id,
|
|
serde_json::json!({
|
|
"status": "ok",
|
|
"service": "RavenService",
|
|
"version": env!("CARGO_PKG_VERSION"),
|
|
"timestamp": chrono::Utc::now().timestamp_millis()
|
|
}),
|
|
)
|
|
}
|
|
|
|
fn handle_apply_usb_policy(id: String, params: serde_json::Value) -> Response {
|
|
let policy = match params.get("policy").and_then(|p| p.as_str()) {
|
|
Some(p) => p,
|
|
None => {
|
|
return Response::error(id, -32602, "Parametro 'policy' e obrigatorio".to_string())
|
|
}
|
|
};
|
|
|
|
match usb_policy::apply_policy(policy) {
|
|
Ok(result) => Response::success(id, serde_json::to_value(result).unwrap()),
|
|
Err(e) => Response::error(id, -32000, format!("Erro ao aplicar politica: {}", e)),
|
|
}
|
|
}
|
|
|
|
fn handle_get_usb_policy(id: String) -> Response {
|
|
match usb_policy::get_current_policy() {
|
|
Ok(policy) => Response::success(
|
|
id,
|
|
serde_json::json!({
|
|
"policy": policy
|
|
}),
|
|
),
|
|
Err(e) => Response::error(id, -32000, format!("Erro ao obter politica: {}", e)),
|
|
}
|
|
}
|
|
|
|
fn handle_provision_rustdesk(id: String, params: serde_json::Value) -> Response {
|
|
let config_string = params.get("config").and_then(|c| c.as_str()).map(String::from);
|
|
let password = params.get("password").and_then(|p| p.as_str()).map(String::from);
|
|
let machine_id = params.get("machineId").and_then(|m| m.as_str()).map(String::from);
|
|
|
|
match rustdesk::ensure_rustdesk(config_string.as_deref(), password.as_deref(), machine_id.as_deref()) {
|
|
Ok(result) => Response::success(id, serde_json::to_value(result).unwrap()),
|
|
Err(e) => Response::error(id, -32000, format!("Erro ao provisionar RustDesk: {}", e)),
|
|
}
|
|
}
|
|
|
|
fn handle_get_rustdesk_status(id: String) -> Response {
|
|
match rustdesk::get_status() {
|
|
Ok(status) => Response::success(id, serde_json::to_value(status).unwrap()),
|
|
Err(e) => Response::error(id, -32000, format!("Erro ao obter status: {}", e)),
|
|
}
|
|
}
|