#![allow(clippy::missing_safety_doc)]
+use std::collections::TryReserveError;
use std::ffi::CStr;
use std::os::raw::c_char;
use std::str::Utf8Error;
pub enum JsonError {
InvalidState,
Utf8Error(Utf8Error),
+ Memory,
}
impl std::error::Error for JsonError {}
match self {
JsonError::InvalidState => write!(f, "invalid state"),
JsonError::Utf8Error(ref e) => e.fmt(f),
+ JsonError::Memory => write!(f, "memory error"),
}
}
}
+impl From<TryReserveError> for JsonError {
+ fn from(_: TryReserveError) -> Self {
+ JsonError::Memory
+ }
+}
+
impl From<Utf8Error> for JsonError {
fn from(e: Utf8Error) -> Self {
JsonError::Utf8Error(e)
}
}
+ /// Set a key and a string value (from bytes) on an object, with a limited size
+ pub fn set_string_from_bytes_limited(&mut self, key: &str, val: &[u8], limit: usize) -> Result<&mut Self, JsonError> {
+ let mut valtrunc = Vec::new();
+ let val = if val.len() > limit {
+ valtrunc.extend_from_slice(&val[..limit]);
+ valtrunc.extend_from_slice(b"[truncated]");
+ &valtrunc
+ } else {
+ val
+ };
+ match std::str::from_utf8(val) {
+ Ok(s) => self.set_string(key, s),
+ Err(_) => self.set_string(key, &try_string_from_bytes(val)?),
+ }
+ }
+
/// Set a key and an unsigned integer type on an object.
pub fn set_uint(&mut self, key: &str, val: u64) -> Result<&mut Self, JsonError> {
match self.current_state() {
return out;
}
+/// A Suricata specific function to create a string from bytes when UTF-8 decoding fails.
+///
+/// For bytes over 0x0f, we encode as hex like "\xf2".
+fn try_string_from_bytes(input: &[u8]) -> Result<String, JsonError> {
+ let mut out = String::new();
+
+ // Allocate enough data to handle the worst case scenario of every
+ // byte needing to be presented as a byte.
+ out.try_reserve(input.len() * 4)?;
+
+ for b in input.iter() {
+ if *b < 128 {
+ out.push(*b as char);
+ } else {
+ out.push_str(&format!("\\x{:02x}", *b));
+ }
+ }
+ return Ok(out);
+}
+
#[no_mangle]
pub extern "C" fn jb_new_object() -> *mut JsonBuilder {
let boxed = Box::new(JsonBuilder::new_object());
* 02110-1301, USA.
*/
-use super::ssh::SSHTransaction;
+use super::ssh::{SSHTransaction, SSH_MAX_BANNER_LEN};
use crate::jsonbuilder::{JsonBuilder, JsonError};
fn log_ssh(tx: &SSHTransaction, js: &mut JsonBuilder) -> Result<bool, JsonError> {
}
if tx.cli_hdr.protover.len() > 0 {
js.open_object("client")?;
- js.set_string_from_bytes("proto_version", &tx.cli_hdr.protover)?;
- if tx.cli_hdr.swver.len() > 0 {
- js.set_string_from_bytes("software_version", &tx.cli_hdr.swver)?;
+ js.set_string_from_bytes_limited("proto_version", &tx.cli_hdr.protover, SSH_MAX_BANNER_LEN)?;
+ if !tx.cli_hdr.swver.is_empty() {
+ js.set_string_from_bytes_limited("software_version", &tx.cli_hdr.swver, SSH_MAX_BANNER_LEN)?;
}
if tx.cli_hdr.hassh.len() > 0 || tx.cli_hdr.hassh_string.len() > 0 {
js.open_object("hassh")?;
}
if tx.srv_hdr.protover.len() > 0 {
js.open_object("server")?;
- js.set_string_from_bytes("proto_version", &tx.srv_hdr.protover)?;
- if tx.srv_hdr.swver.len() > 0 {
- js.set_string_from_bytes("software_version", &tx.srv_hdr.swver)?;
+ js.set_string_from_bytes_limited("proto_version", &tx.srv_hdr.protover, SSH_MAX_BANNER_LEN)?;
+ if !tx.srv_hdr.swver.is_empty() {
+ js.set_string_from_bytes_limited("software_version", &tx.srv_hdr.swver, SSH_MAX_BANNER_LEN)?;
}
if tx.srv_hdr.hassh.len() > 0 || tx.srv_hdr.hassh_string.len() > 0 {
js.open_object("hassh")?;