diff --git a/Cargo.lock b/Cargo.lock index 7057b39..c39b0c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,6 +28,8 @@ name = "burp_exporter" version = "0.1.0" dependencies = [ "openssl", + "serde", + "serde_json", "tokio", "tokio-native-tls", "tracing", @@ -101,6 +103,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "itoa" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + [[package]] name = "lazy_static" version = "1.4.0" @@ -297,6 +305,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "ryu" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" + [[package]] name = "schannel" version = "0.1.19" @@ -330,6 +344,37 @@ dependencies = [ "libc", ] +[[package]] +name = "serde" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "sharded-slab" version = "0.1.4" diff --git a/Cargo.toml b/Cargo.toml index f6ae94a..189644d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,9 +7,14 @@ edition = "2021" [dependencies] openssl = "^0.10.38" +serde_json = "1.0.78" tokio-native-tls = "^0.3.0" tracing = "^0.1.30" +[dependencies.serde] +version = "^1.0.136" +features = ["derive"] + [dependencies.tokio] version = "^1.16.1" features = ["io-util", "macros", "net", "rt", "signal"] diff --git a/src/burp/client.rs b/src/burp/client.rs index 0c9d8cf..aa6ef44 100644 --- a/src/burp/client.rs +++ b/src/burp/client.rs @@ -5,6 +5,7 @@ use std::io::prelude::*; use std::path::Path; use std::str; +use serde_json; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::TcpStream; use tokio_native_tls::native_tls::TlsConnector as NativeTlsConnector; @@ -14,6 +15,7 @@ use tokio_native_tls::TlsStream; use tracing::{debug, info, trace}; use super::error::Error; +use super::model::{BackupInfo, BackupStats, ClientInfo, ClientList}; /// The BURP client version emulated by this implementation const CLIENT_VERSION: &str = "2.1.32"; @@ -298,4 +300,148 @@ impl Client { } } } + + /// Retrieve information about all known clients + /// + /// This method returns a list of clients known to the BURP server. + /// Usually, a client can only retrieve information about itself; + /// additional configuration is required in order for a client to + /// view other clients' information. Specifically, the client must + /// be named as a `restore_client` in the BURP server configuration + /// AND it must have *at least* `client_can_list` set to `1` for at + /// least one other client (or globally). + pub async fn get_clients(&mut self) -> Result { + let res = self.command('c', "c:").await?; + Ok(serde_json::from_str(&res)?) + } + + /// Retrieve information about a specific client + /// + /// This method returns information about a specific client, if that + /// client is known to the BURP server. The same caveat applies + /// about client permissions as for [`get_clients`]. + pub async fn get_client>( + &mut self, + name: S, + ) -> Result { + let res = self.command('c', &format!("c:{}", name.as_ref())).await?; + let mut clientlist: ClientList = serde_json::from_str(&res)?; + match clientlist.clients.pop() { + Some(c) => Ok(c), + None => Err(Error::Protocol( + "Invalid response from server: no client listed".into(), + )), + } + } + + /// Retrieve information about a client backup + /// + /// This method returns details about a specific backup for a + /// particular client. + /// + /// * `name`: The client name + /// * `number`: The backup number + pub async fn get_backup>( + &mut self, + name: S, + number: u64, + ) -> Result { + let res = self + .command('c', &format!("c:{}:b:{}", name.as_ref(), number)) + .await?; + let mut clientlist: ClientList = serde_json::from_str(&res)?; + let mut client = match clientlist.clients.pop() { + Some(c) => c, + None => { + return Err(Error::Protocol( + "Invalid response from server: no client listed".into(), + )) + } + }; + match client.backups.pop() { + Some(b) => Ok(b), + None => Err(Error::Protocol( + "Invalid response from server: no backup listed".into(), + )), + } + } + + /// Retrieve the log for a client backup + /// + /// This method returns the log for a specific backup for a + /// particular client, as a string. + /// + /// * `name`: The client name + /// * `number`: The backup number + pub async fn get_backup_log>( + &mut self, + name: S, + number: u64, + ) -> Result>, Error> { + let res = self + .command( + 'c', + &format!("c:{}:b:{}:l:backup", name.as_ref(), number), + ) + .await?; + let mut clientlist: ClientList = serde_json::from_str(&res)?; + let mut client = match clientlist.clients.pop() { + Some(c) => c, + None => { + return Err(Error::Protocol( + "Invalid response from server: no client listed".into(), + )) + } + }; + let backup = match client.backups.pop() { + Some(b) => b, + None => { + return Err(Error::Protocol( + "Invalid response from server: no backup listed".into(), + )) + } + }; + Ok(backup.logs.backup) + } + + /// Retrieve the statistics for a client backup + /// + /// This method returns the statistics of a specific backup for a + /// particular client. + /// + /// * `name`: The client name + /// * `number`: The backup number + pub async fn get_backup_stats>( + &mut self, + name: S, + number: u64, + ) -> Result, Error> { + let res = self + .command( + 'c', + &format!("c:{}:b:{}:l:backup_stats", name.as_ref(), number), + ) + .await?; + let mut clientlist: ClientList = serde_json::from_str(&res)?; + let mut client = match clientlist.clients.pop() { + Some(c) => c, + None => { + return Err(Error::Protocol( + "Invalid response from server: no client listed".into(), + )) + } + }; + let backup = match client.backups.pop() { + Some(b) => b, + None => { + return Err(Error::Protocol( + "Invalid response from server: no backup listed".into(), + )) + } + }; + match backup.logs.backup_stats { + Some(lines) => Ok(Some(serde_json::from_str(&lines.join("\n"))?)), + None => Ok(None), + } + } } diff --git a/src/burp/error.rs b/src/burp/error.rs index 5498d91..8f69db5 100644 --- a/src/burp/error.rs +++ b/src/burp/error.rs @@ -1,5 +1,7 @@ //! Client error handling use std::io; + +use serde_json; use tokio_native_tls::native_tls; /// BURP client errors @@ -32,3 +34,9 @@ impl From for Error { Self::Tls(error) } } + +impl From for Error { + fn from(error: serde_json::Error) -> Self { + Self::Protocol(error.to_string()) + } +} diff --git a/src/burp/mod.rs b/src/burp/mod.rs index a5e6a34..358b9ce 100644 --- a/src/burp/mod.rs +++ b/src/burp/mod.rs @@ -8,3 +8,4 @@ #![warn(missing_docs)] pub mod client; pub mod error; +pub mod model; diff --git a/src/burp/model.rs b/src/burp/model.rs new file mode 100644 index 0000000..c16df93 --- /dev/null +++ b/src/burp/model.rs @@ -0,0 +1,51 @@ +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +pub struct Logs { + pub list: Vec, + #[serde(default)] + pub backup: Option>, + #[serde(default)] + pub backup_stats: Option>, +} + +#[derive(Debug, Deserialize)] +pub struct BackupInfo { + pub number: u64, + pub timestamp: u64, + pub flags: Vec, + pub logs: Logs, +} + +#[derive(Debug, Deserialize)] +pub struct ClientInfo { + pub name: String, + pub run_status: String, + pub protocol: u64, + pub backups: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct ClientList { + pub clients: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct Counter { + pub name: String, + pub r#type: String, + pub count: u64, + #[serde(default)] + pub changed: u64, + #[serde(default)] + pub same: u64, + #[serde(default)] + pub deleted: u64, + #[serde(default)] + pub scanned: u64, +} + +#[derive(Debug, Deserialize)] +pub struct BackupStats { + pub counters: Vec, +}