Skip to content

Lua ssh 7607 v4 #13027

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
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
12 changes: 6 additions & 6 deletions doc/userguide/devguide/extending/app-layer/transactions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,10 @@ Code snippet from: rust/src/ssh/ssh.rs:
.. code-block:: rust

pub enum SSHConnectionState {
SshStateInProgress = 0,
SshStateBannerWaitEol = 1,
SshStateBannerDone = 2,
SshStateFinished = 3,
InProgress = 0,
BannerWaitEol = 1,
BannerDone = 2,
Finished = 3,
}

From src/app-layer-ftp.h:
Expand Down Expand Up @@ -209,8 +209,8 @@ rust/src/ssh/ssh.rs:

.. code-block:: rust

tx_comp_st_ts: SSHConnectionState::SshStateFinished as i32,
tx_comp_st_tc: SSHConnectionState::SshStateFinished as i32,
tx_comp_st_ts: SSHConnectionState::Finished as i32,
tx_comp_st_tc: SSHConnectionState::Finished as i32,

In C, callback usage would be as follows:

Expand Down
1 change: 1 addition & 0 deletions doc/userguide/lua/libs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ environment without access to additional modules.
hashlib
http
packetlib
ssh
91 changes: 91 additions & 0 deletions doc/userguide/lua/libs/ssh.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
SSH
---

SSH transaction details are exposes to Lua scripts with the
``suricata.ssh`` library, For example::

local ssh = require("suricata.ssh")

Setup
^^^^^

If your purpose is to create a logging script, initialize the buffer as:

::

function init (args)
local needs = {}
needs["protocol"] = "ssh"
return needs
end

If you are going to use the script for rule matching, choose one of
the available SSH buffers listed in :ref:`lua-detection` and follow
the pattern:

::

function init (args)
local needs = {}
needs["ssh.server_proto"] = tostring(true)
return needs
end

Transaction
~~~~~~~~~~~

SSH is transaction based, and the current transaction must be obtained before use::

local tx, err = ssh.get_tx()
if tx == err then
print(err)
end

All other functions are methods on the transaction table.

Transaction Methods
~~~~~~~~~~~~~~~~~~~

``server_proto()``
^^^^^^^^^^^^^^^^^^

Get the ``server_proto`` value as a string.

Example::

local tx = ssh.get_tx()
local proto = tx:server_proto();
print (proto)

``client_proto()``
^^^^^^^^^^^^^^^^^^

Get the ``client_proto`` value as a string.

Example::

local tx = ssh.get_tx()
local proto = tx:client_proto();
print (proto)

``server_software()``
^^^^^^^^^^^^^^^^^^^^^

Get the ``server_software`` value as a string.

Example::

local tx = ssh.get_tx()
local software = tx:server_software();
print (software)

``client_software()``
^^^^^^^^^^^^^^^^^^^^^

Get the ``client_software`` value as a string.

Example::

local tx = ssh.get_tx()
local software = tx:client_software();
print (software)
65 changes: 0 additions & 65 deletions doc/userguide/lua/lua-functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -481,71 +481,6 @@ Initialize with:
return needs
end

SshGetServerProtoVersion
~~~~~~~~~~~~~~~~~~~~~~~~

Get SSH protocol version used by the server through SshGetServerProtoVersion.

Example:

::

function log (args)
version = SshGetServerProtoVersion()
if version == nil then
return 0
end
end

SshGetServerSoftwareVersion
~~~~~~~~~~~~~~~~~~~~~~~~~~~

Get SSH software used by the server through SshGetServerSoftwareVersion.

Example:

::

function log (args)
software = SshGetServerSoftwareVersion()
if software == nil then
return 0
end
end

SshGetClientProtoVersion
~~~~~~~~~~~~~~~~~~~~~~~~

Get SSH protocol version used by the client through SshGetClientProtoVersion.

Example:

::

function log (args)
version = SshGetClientProtoVersion()
if version == nil then
return 0
end
end

SshGetClientSoftwareVersion
~~~~~~~~~~~~~~~~~~~~~~~~~~~

Get SSH software used by the client through SshGetClientSoftwareVersion.

Example:

::

function log (args)
software = SshGetClientSoftwareVersion()
if software == nil then
return 0
end
end


HasshGet
~~~~~~~~

Expand Down
105 changes: 105 additions & 0 deletions rust/derive/src/applayerstate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/* Copyright (C) 2021 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/

extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{self, parse_macro_input, DeriveInput};

pub fn derive_app_layer_state(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = input.ident;

let mut fields = Vec::new();
let mut vals = Vec::new();
let mut cstrings = Vec::new();
let mut names = Vec::new();

match input.data {
syn::Data::Enum(ref data) => {
for (i, v) in (&data.variants).into_iter().enumerate() {
fields.push(v.ident.clone());
let name = transform_name(&v.ident.to_string());
let cname = format!("{}\0", name);
names.push(name);
cstrings.push(cname);
vals.push(i as u8);
}
}
_ => panic!("AppLayerState can only be derived for enums"),
}

let expanded = quote! {
impl crate::applayer::AppLayerState for #name {
fn from_u8(val: u8) -> Option<Self> {
match val {
#( #vals => Some(#name::#fields) ,)*
_ => None,
}
}

fn as_u8(&self) -> u8 {
match *self {
#( #name::#fields => #vals ,)*
}
}

fn to_cstring(&self) -> *const std::os::raw::c_char {
let s = match *self {
#( #name::#fields => #cstrings ,)*
};
s.as_ptr() as *const std::os::raw::c_char
}

fn from_str(s: &str) -> Option<#name> {
match s {
#( #names => Some(#name::#fields) ,)*
_ => None
}
}
}
};

proc_macro::TokenStream::from(expanded)
}

fn transform_name(name: &str) -> String {
let mut xname = String::new();
let chars: Vec<char> = name.chars().collect();
for i in 0..chars.len() {
if i > 0 && i < chars.len() - 1 && chars[i].is_uppercase() && chars[i + 1].is_lowercase() {
xname.push('_');
}
xname.push_str(&chars[i].to_lowercase().to_string());
}
xname
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_transform_name() {
assert_eq!(transform_name("One"), "one");
assert_eq!(transform_name("OneTwo"), "one_two");
assert_eq!(transform_name("OneTwoThree"), "one_two_three");
assert_eq!(transform_name("NBSS"), "nbss");
assert_eq!(transform_name("NBSSHdr"), "nbss_hdr");
assert_eq!(transform_name("SMB3Data"), "smb3_data");
}
}
6 changes: 6 additions & 0 deletions rust/derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use proc_macro::TokenStream;

mod applayerevent;
mod applayerframetype;
mod applayerstate;
mod stringenum;

/// The `AppLayerEvent` derive macro generates a `AppLayerEvent` trait
Expand Down Expand Up @@ -52,6 +53,11 @@ pub fn derive_app_layer_frame_type(input: TokenStream) -> TokenStream {
applayerframetype::derive_app_layer_frame_type(input)
}

#[proc_macro_derive(AppLayerState)]
pub fn derive_app_layer_state(input: TokenStream) -> TokenStream {
applayerstate::derive_app_layer_state(input)
}

#[proc_macro_derive(EnumStringU8, attributes(name))]
pub fn derive_enum_string_u8(input: TokenStream) -> TokenStream {
stringenum::derive_enum_string::<u8>(input, "u8")
Expand Down
64 changes: 64 additions & 0 deletions rust/src/applayer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -777,3 +777,67 @@ pub trait AppLayerFrameType {
Self::from_u8(id).map(|s| s.to_cstring()).unwrap_or_else(std::ptr::null)
}
}

/// AppLayerState trait.
///
/// This is the behavior expected from an enum of state progress. For most instances
/// this behavior can be derived.
///
/// Example:
///
/// #[derive(AppLayerState)]
/// enum SomeProtoState {
/// Start,
/// Complete,
/// }
pub trait AppLayerState {
/// Create a state progress variant from a u8.
///
/// None will be returned if there is no matching enum variant.
fn from_u8(value: u8) -> Option<Self>
where
Self: Sized;

/// Return the u8 value of the enum where the first entry has the value of 0.
fn as_u8(&self) -> u8;

/// Create a state progress variant from a &str.
///
/// None will be returned if there is no matching enum variant.
fn from_str(s: &str) -> Option<Self>
where
Self: Sized;

/// Return a pointer to a C string of the enum variant suitable as-is for
/// FFI.
fn to_cstring(&self) -> *const c_char;

/// Converts a C string formatted name to a state progress.
unsafe extern "C" fn ffi_id_from_name(name: *const c_char, _dir: u8) -> c_int
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not know where to put this, but this is for protocols which do not need the direction (like SSH)

Copy link
Member

@jasonish jasonish Apr 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably as good here as any. However, perhaps the function doc should contain the additional information:

for protocols which do not need the direction (like SSH)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adding

where
Self: Sized,
{
if name.is_null() {
return -1;
}
if let Ok(s) = std::ffi::CStr::from_ptr(name).to_str() {
Self::from_str(s).map(|t| t.as_u8() as c_int).unwrap_or(-1)
} else {
-1
}
}

/// Converts a variant ID to an FFI name.
unsafe extern "C" fn ffi_name_from_id(id: c_int, _dir: u8) -> *const c_char
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure c_int is the best API but it is what it is

where
Self: Sized,
{
if id < 0 || id > c_int::from(u8::MAX) {
return std::ptr::null();
}
if let Some(v) = Self::from_u8(id as u8) {
return v.to_cstring();
}
return std::ptr::null();
}
}
Loading
Loading