]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
detect/ftp: Add ftp.received_reply
authorJeff Lucovsky <jlucovsky@oisf.net>
Thu, 17 Apr 2025 12:30:50 +0000 (08:30 -0400)
committerVictor Julien <victor@inliniac.net>
Fri, 16 May 2025 19:33:57 +0000 (21:33 +0200)
Issue: 7506

Add a (non-sticky buffer) keyword for ftp.reply_received. This is not a
sticky buffer as the keyword relates to protocol state and not bytes
from the actual protocol exchange.

ftp.reply_received: yes|on|no|off

rust/src/ftp/ftp.rs
src/Makefile.am
src/detect-engine-register.c
src/detect-engine-register.h
src/detect-ftp-reply-received.c [new file with mode: 0644]
src/detect-ftp-reply-received.h [new file with mode: 0644]

index e739aabeb0cffcf57787b988fefd24b78ac95c12..1efa85bbae4e711bfc60eb2971422326481dab68 100644 (file)
@@ -30,6 +30,11 @@ pub struct DetectFtpModeData {
     pub active: bool,
 }
 
+#[repr(C)]
+pub struct DetectFtpReplyReceivedData {
+    pub received: bool,
+}
+
 /// cbindgen:ignore
 #[repr(C)]
 pub struct FtpCommand {
@@ -224,6 +229,35 @@ pub unsafe extern "C" fn SCFTPGetConfigValues(
     }
 }
 
+#[no_mangle]
+pub unsafe extern "C" fn SCFTPParseReplyReceived(c_str: *const c_char) -> *mut DetectFtpReplyReceivedData {
+    if c_str.is_null() {
+        return ptr::null_mut();
+    }
+
+    // Convert C string to Rust string slice
+    let Ok(input_str) = CStr::from_ptr(c_str).to_str() else {
+        return ptr::null_mut();
+    };
+
+    // Check for case-insensitive match
+    let received_val = match input_str.trim().to_ascii_lowercase().as_str() {
+        "true" | "1" | "yes" | "on"  => true,
+        "false" | "0" | "no" | "off"=> false,
+        _ => return ptr::null_mut(), // invalid input
+    };
+
+    // Return a pointer to a heap-allocated struct
+    let boxed = Box::new(DetectFtpReplyReceivedData { received: received_val });
+    Box::into_raw(boxed)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn SCFTPFreeReplyReceivedData(ptr: *mut DetectFtpReplyReceivedData) {
+    if !ptr.is_null() {
+        drop(Box::from_raw(ptr));
+    }
+}
 #[no_mangle]
 pub unsafe extern "C" fn SCFTPParseMode(c_str: *const c_char) -> *mut DetectFtpModeData {
     if c_str.is_null() {
index d5c302bd3c829d2da4b2f5a14ddec78e325ba0c2..f502190573543c2f494820d1754e5309af3527c9 100755 (executable)
@@ -181,6 +181,7 @@ noinst_HEADERS = \
        detect-ftpbounce.h \
        detect-ftpdata.h \
        detect-ftp-mode.h \
+       detect-ftp-reply-received.h \
        detect-geoip.h \
        detect-gid.h \
        detect-hostbits.h \
@@ -771,6 +772,7 @@ libsuricata_c_a_SOURCES = \
        detect-ftpbounce.c \
        detect-ftpdata.c \
        detect-ftp-mode.c \
+       detect-ftp-reply-received.c \
        detect-geoip.c \
        detect-gid.c \
        detect-hostbits.c \
index b60555d590938c372921343a83fa3b0427dbfb05..c8a096308f8a6df94904ed15f393e1a17b29b8f7 100644 (file)
 #include "detect-ftp-command-data.h"
 #include "detect-ftp-reply.h"
 #include "detect-ftp-mode.h"
+#include "detect-ftp-reply-received.h"
 
 #include "detect-bypass.h"
 #include "detect-ftpdata.h"
@@ -728,6 +729,7 @@ void SigTableSetup(void)
     DetectFtpCommandDataRegister();
     DetectFtpReplyRegister();
     DetectFtpModeRegister();
+    DetectFtpReplyReceivedRegister();
 
     DetectBypassRegister();
     DetectConfigRegister();
index 5f9053e77705c38b1fe6a50f74610cc41c6aa394..11f033635bfd316f30d433840ab30fd58ea82856 100644 (file)
@@ -332,6 +332,7 @@ enum DetectKeywordId {
     DETECT_FTP_COMMAND_DATA,
     DETECT_FTP_REPLY,
     DETECT_FTP_MODE,
+    DETECT_FTP_REPLY_RECEIVED,
 
     DETECT_VLAN_ID,
     DETECT_VLAN_LAYERS,
diff --git a/src/detect-ftp-reply-received.c b/src/detect-ftp-reply-received.c
new file mode 100644 (file)
index 0000000..8a51730
--- /dev/null
@@ -0,0 +1,133 @@
+/* Copyright (C) 2025 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.
+ */
+
+/**
+ * \file
+ *
+ * \author Jeff Lucovsky <jlucovsky@oisf.net>
+ *
+ * Match on FTP reply received.
+ */
+
+#include "suricata-common.h"
+
+#include "detect-parse.h"
+#include "detect-engine.h"
+
+#include "app-layer-ftp.h"
+
+#include "detect-ftp-reply-received.h"
+
+static void DetectFtpReplyReceivedFree(DetectEngineCtx *, void *);
+static int g_ftp_reply_received_buffer_id = 0;
+
+static int DetectFtpReplyReceivedMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags,
+        void *state, void *txv, const Signature *s, const SigMatchCtx *m)
+{
+    FTPTransaction *tx = (FTPTransaction *)txv;
+    if (tx->command_descriptor.command_code == FTP_COMMAND_UNKNOWN) {
+        return 0;
+    }
+
+    const DetectFtpReplyReceivedData *ftprrd = (const DetectFtpReplyReceivedData *)m;
+    if (ftprrd->received == tx->done)
+        return 1;
+
+    return 0;
+}
+
+/**
+ * \brief This function is used to parse ftp.reply_received options passed via ftp.reply_received
+ * keyword
+ *
+ * \param str Pointer to the user provided ftp.reply_received options
+ *
+ * \retval  pointer to DetectFtpReplyReceivedData on success
+ * \retval NULL on failure
+ */
+static DetectFtpReplyReceivedData *DetectFtpdataParse(const char *optstr)
+{
+    DetectFtpReplyReceivedData *frrd = SCFTPParseReplyReceived(optstr);
+    if (unlikely(frrd == NULL)) {
+        SCLogError("invalid value; specify yes or no");
+        return NULL;
+    }
+
+    return frrd;
+}
+
+/**
+ * \brief parse the options from the 'ftp.reply_received' keyword in the rule into
+ *        the Signature data structure.
+ *
+ * \param de_ctx pointer to the Detection Engine Context
+ * \param s pointer to the Current Signature
+ * \param str pointer to the user provided ftp.reply_received options
+ *
+ * \retval 0 on Success
+ * \retval -1 on Failure
+ */
+static int DetectFtpReplyReceivedSetup(DetectEngineCtx *de_ctx, Signature *s, const char *str)
+{
+    DetectFtpReplyReceivedData *frrd = DetectFtpdataParse(str);
+    if (frrd == NULL)
+        return -1;
+
+    if (DetectSignatureSetAppProto(s, ALPROTO_FTP) != 0) {
+        DetectFtpReplyReceivedFree(de_ctx, frrd);
+        return -1;
+    }
+
+    if (SigMatchAppendSMToList(de_ctx, s, DETECT_FTP_REPLY_RECEIVED, (SigMatchCtx *)frrd,
+                g_ftp_reply_received_buffer_id) == NULL) {
+        DetectFtpReplyReceivedFree(de_ctx, frrd);
+        return -1;
+    }
+    return 0;
+}
+
+/**
+ * \brief this function will free memory associated with DetectFtpReplyReceivedData
+ *
+ * \param ptr pointer to DetectFtpReplyReceivedData
+ */
+static void DetectFtpReplyReceivedFree(DetectEngineCtx *de_ctx, void *ptr)
+{
+    if (ptr) {
+        DetectFtpReplyReceivedData *frrd = (DetectFtpReplyReceivedData *)ptr;
+        SCFTPFreeReplyReceivedData(frrd);
+    }
+}
+
+/**
+ * \brief Registration function for ftp.reply_received: keyword
+ *
+ * This function is called once in the 'lifetime' of the engine.
+ */
+void DetectFtpReplyReceivedRegister(void)
+{
+    sigmatch_table[DETECT_FTP_REPLY_RECEIVED].name = "ftp.reply_received";
+    sigmatch_table[DETECT_FTP_REPLY_RECEIVED].desc = "match on FTP whether a reply was received";
+    sigmatch_table[DETECT_FTP_REPLY_RECEIVED].url = "/rules/ftp-keywords.html#ftp.reply_received";
+    sigmatch_table[DETECT_FTP_REPLY_RECEIVED].AppLayerTxMatch = DetectFtpReplyReceivedMatch;
+    sigmatch_table[DETECT_FTP_REPLY_RECEIVED].Setup = DetectFtpReplyReceivedSetup;
+    sigmatch_table[DETECT_FTP_REPLY_RECEIVED].Free = DetectFtpReplyReceivedFree;
+
+    DetectAppLayerInspectEngineRegister("ftp.reply_received", ALPROTO_FTP, SIG_FLAG_TOCLIENT, 0,
+            DetectEngineInspectGenericList, NULL);
+    g_ftp_reply_received_buffer_id = DetectBufferTypeGetByName("ftp.reply_received");
+}
diff --git a/src/detect-ftp-reply-received.h b/src/detect-ftp-reply-received.h
new file mode 100644 (file)
index 0000000..40cc687
--- /dev/null
@@ -0,0 +1,29 @@
+/* Copyright (C) 2025 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.
+ */
+
+/**
+ * \file
+ *
+ * \author Jeff Lucovsky  <jlucovsky@oisf.net>
+ */
+
+#ifndef SURICATA_DETECT_FTP_REPLY_RECEIVED_H
+#define SURICATA_DETECT_FTP_REPLY_RECEIVED_H
+
+void DetectFtpReplyReceivedRegister(void);
+
+#endif /* SURICATA_DETECT_FTP_REPLY_RECEIVED_H */