]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Pull request #5082: MIME to provide null-terminated string for logging
authorOleksii Shumeiko -X (oshumeik - SOFTSERVE INC at Cisco) <oshumeik@cisco.com>
Thu, 8 Jan 2026 15:27:40 +0000 (15:27 +0000)
committerOleksii Shumeiko -X (oshumeik - SOFTSERVE INC at Cisco) <oshumeik@cisco.com>
Thu, 8 Jan 2026 15:27:40 +0000 (15:27 +0000)
Merge in SNORT/snort3 from ~OSHUMEIK/snort3:mime_fix to master

Squashed commit of the following:

commit 14b21aec883ea11a6ef259613d5fb9f083b39ff5
Author: Oleksii Shumeiko <oshumeik@cisco.com>
Date:   Tue Jan 6 18:39:44 2026 +0200

    mime: leave room for null-character in case of size limit hit

commit d1b484a9e46aecbd9c8608a6d2c3a809edb4bfe8
Author: Oleksii Shumeiko <oshumeik@cisco.com>
Date:   Tue Jan 6 18:18:25 2026 +0200

    mime: add unit tests for data over memory limit

commit d0a87edddd87f9330ab8ff36ddb5cb45236b028f
Author: Oleksii Shumeiko <oshumeik@cisco.com>
Date:   Tue Jan 6 17:25:17 2026 +0200

    mime: add unit tests for data fitting memory limit

commit 6b790e39c1386c23732417abf19bb8ed1db45113
Author: Oleksii Shumeiko <oshumeik@cisco.com>
Date:   Tue Jan 6 13:44:45 2026 +0200

    mime: rename class field to comply with the style

commit 6f6423c66d9fbe54b65c01d098a6fe7cf3c97ada
Author: Oleksii Shumeiko <oshumeik@cisco.com>
Date:   Tue Jan 6 13:28:09 2026 +0200

    mime: return error code if cannot add headers for logging

commit 54c5677f65c6e7acbfea1a8b471b30b118e129a8
Author: Oleksii Shumeiko <oshumeik@cisco.com>
Date:   Tue Jan 6 13:20:59 2026 +0200

    mime: ignore field collection if not configured

commit 449671977200c825b958984e1de6a52d52c4000a
Author: Oleksii Shumeiko <oshumeik@cisco.com>
Date:   Tue Jan 6 11:36:22 2026 +0200

    mime: add basic unit tests for file logging

commit ed77619021828399f29d4e25412ef545920eb8c9
Author: Oleksii Shumeiko <oshumeik@cisco.com>
Date:   Tue Jan 6 10:33:49 2026 +0200

    mime: remove unused forward-declaration

src/mime/CMakeLists.txt
src/mime/file_mime_log.cc
src/mime/file_mime_log.h
src/mime/test/CMakeLists.txt [new file with mode: 0644]
src/mime/test/file_mime_log_test.cc [new file with mode: 0644]

index 75c5a7e0b89ebe61504a9351b5c7064231b15a2a..f8133665c89a574b9bf44982426299a388dc641d 100644 (file)
@@ -35,3 +35,5 @@ add_library ( mime OBJECT
 install (FILES ${MIME_INCLUDES}
     DESTINATION "${INCLUDE_INSTALL_PATH}/mime"
 )
+
+add_subdirectory(test)
index faeb3324675d6fb1b84e7aebd874b20e89aa2cbb..af131b0cb4d5b3e1b6add2353d28239a792d61ca 100644 (file)
@@ -44,7 +44,7 @@ using namespace snort;
 /* accumulate MIME attachment filenames. The filenames are appended by commas */
 int MailLogState::log_file_name(const uint8_t* start, int length)
 {
-    if (!start || (length <= 0))
+    if (!filenames || !start || length <= 0)
         return -1;
 
     uint8_t* alt_buf = filenames;
@@ -54,11 +54,10 @@ int MailLogState::log_file_name(const uint8_t* start, int length)
     int sep = (*alt_len > 0) ? 1 : 0;
     int log_avail = alt_size - *alt_len - sep;
 
-    if (!alt_buf || (log_avail <= 0))
+    if (!alt_buf || log_avail <= 0)
         return -1;
 
-    else if (log_avail < length )
-        length = log_avail;
+    length = std::min(length, log_avail - 1);
 
     if (length > 0)
     {
@@ -80,23 +79,17 @@ int MailLogState::log_file_name(const uint8_t* start, int length)
 /* Accumulate EOL separated headers, one or more at a time */
 int MailLogState::log_email_hdrs(const uint8_t* start, int length)
 {
-    if (length <= 0)
+    if (!hdrs || length <= 0)
         return -1;
 
     int log_avail = log_depth - hdrs_logged;
-    uint8_t* log_buf = (uint8_t*)emailHdrs;
+    uint8_t* log_buf = (uint8_t*)hdrs;
 
     if (log_avail <= 0)
-        return 0;
-
-    if (length > log_avail)
-        length = log_avail;
-
-    /* appended by the EOL \r\n */
-
-    if (length > log_avail)
         return -1;
 
+    length = std::min(length, log_avail - 1);
+
     if (length > 0)
         memcpy_s(log_buf + hdrs_logged, log_avail, start, length);
 
@@ -152,11 +145,10 @@ int MailLogState::log_email_id(const uint8_t* start, int length, EmailUserType t
     int sep = (*alt_len > 0) ? 1 : 0;
     int log_avail = alt_size - *alt_len - sep;
 
-    if (log_avail <= 0 || !alt_buf)
+    if (!alt_buf || log_avail <= 0)
         return -1;
 
-    else if (log_avail < length )
-        length = log_avail;
+    length = std::min(length, log_avail - 1);
 
     if (length > 0)
     {
@@ -185,7 +177,7 @@ void MailLogState::get_file_name(uint8_t** buf, uint32_t* len)
 
 void MailLogState::get_email_hdrs(uint8_t** buf, uint32_t* len)
 {
-    *buf = emailHdrs;
+    *buf = hdrs;
     *len = hdrs_logged;
 }
 
@@ -231,11 +223,11 @@ MailLogState::MailLogState(MailLogConfig* conf)
         uint32_t bufsz = (2 * MAX_EMAIL) + MAX_FILE + conf->email_hdrs_log_depth;
         buf = (uint8_t*)snort_calloc(bufsz);
 
-        log_depth = conf->email_hdrs_log_depth;
-        recipients = buf;
-        senders = buf + MAX_EMAIL;
-        filenames = buf + (2 * MAX_EMAIL);
-        emailHdrs = buf + (2 * MAX_EMAIL) + MAX_FILE;
+        log_depth = conf->log_email_hdrs ? conf->email_hdrs_log_depth : 0;
+        recipients = conf->log_rcptto ? buf : nullptr;
+        senders = conf->log_mailfrom ? buf + MAX_EMAIL : nullptr;
+        filenames = conf->log_filename ? buf + (2 * MAX_EMAIL) : nullptr;
+        hdrs = conf->log_email_hdrs ? buf + (2 * MAX_EMAIL) + MAX_FILE : nullptr;
     }
 
     rcpts_logged = 0;
index 8756ca309b35a7b7f0a172143c39b36b86fa0f20..b5b1d1598a975d872f9a2b4a84b86a515f9768f0 100644 (file)
@@ -46,20 +46,17 @@ struct MailLogConfig
     uint32_t email_hdrs_log_depth = 0;
 };
 
-class Flow;
-
 class SO_PUBLIC MailLogState : public snort::StashGenericObject
 {
 public:
     MailLogState(MailLogConfig* conf);
     ~MailLogState() override;
 
-    /* accumulate MIME attachment filenames. The filenames are appended by commas */
     int log_file_name(const uint8_t* start, int length);
-
     int log_email_hdrs(const uint8_t* start, int length);
     int log_email_id(const uint8_t* start, int length, EmailUserType);
 
+    // if length is greater than 0 then buffer points to a null-terminated string
     void get_file_name(uint8_t** buf, uint32_t* len);
     void get_email_hdrs(uint8_t** buf, uint32_t* len);
     void get_email_id(uint8_t** buf, uint32_t* len, EmailUserType);
@@ -72,7 +69,7 @@ public:
 private:
     int log_flags = 0;
     uint8_t* buf = nullptr;
-    unsigned char* emailHdrs = nullptr;
+    unsigned char* hdrs = nullptr;
     uint32_t log_depth = 0;
     uint32_t hdrs_logged;
     uint8_t* recipients = nullptr;
diff --git a/src/mime/test/CMakeLists.txt b/src/mime/test/CMakeLists.txt
new file mode 100644 (file)
index 0000000..91edc7c
--- /dev/null
@@ -0,0 +1,11 @@
+if ( HAVE_SAFEC )
+    LIST(APPEND EXTERNAL_LIBRARIES ${SAFEC_LIBRARIES})
+endif ()
+
+add_cpputest( file_mime_log_test
+    SOURCES
+        ../file_mime_log.cc
+        file_mime_log_test.cc
+    LIBS
+        ${EXTERNAL_LIBRARIES}
+)
diff --git a/src/mime/test/file_mime_log_test.cc b/src/mime/test/file_mime_log_test.cc
new file mode 100644 (file)
index 0000000..0850c1b
--- /dev/null
@@ -0,0 +1,1194 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2026-2026 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// 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 along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// file_mime_log_test.cc author Cisco
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "../file_mime_log.h"
+
+#include <CppUTest/CommandLineTestRunner.h>
+#include <CppUTest/TestHarness.h>
+
+static uint8_t* const PTR_UNSET = (uint8_t*)-10;
+static const uint32_t LEN_UNSET = -11;
+
+TEST_GROUP(mail_log_basic)
+{
+    uint8_t* filename;
+    uint32_t filename_len;
+    uint8_t* headers;
+    uint32_t headers_len;
+    uint8_t* senders;
+    uint32_t senders_len;
+    uint8_t* recipients;
+    uint32_t recipients_len;
+
+    void setup() override
+    {
+        filename = PTR_UNSET;
+        filename_len = LEN_UNSET;
+        headers = PTR_UNSET;
+        headers_len = LEN_UNSET;
+        senders = PTR_UNSET;
+        senders_len = LEN_UNSET;
+        recipients = PTR_UNSET;
+        recipients_len = LEN_UNSET;
+    }
+};
+
+TEST(mail_log_basic, empty_no_logging)
+{
+    snort::MailLogState state(nullptr);
+
+    CHECK(false == state.is_file_name_present());
+    CHECK(false == state.is_email_hdrs_present());
+    CHECK(false == state.is_email_from_present());
+    CHECK(false == state.is_email_to_present());
+
+    state.get_file_name(&filename, &filename_len);
+    state.get_email_hdrs(&headers, &headers_len);
+    state.get_email_id(&senders, &senders_len, snort::EMAIL_SENDER);
+    state.get_email_id(&recipients, &recipients_len, snort::EMAIL_RECIPIENT);
+
+    CHECK(nullptr == filename);
+    CHECK(0 == filename_len);
+    CHECK(nullptr == headers);
+    CHECK(0 == headers_len);
+    CHECK(nullptr == senders);
+    CHECK(0 == senders_len);
+    CHECK(nullptr == recipients);
+    CHECK(0 == recipients_len);
+}
+
+TEST(mail_log_basic, empty_with_logging)
+{
+    snort::MailLogConfig config{true, true, true, true, 0};
+    snort::MailLogState state(&config);
+
+    CHECK(false == state.is_file_name_present());
+    CHECK(false == state.is_email_hdrs_present());
+    CHECK(false == state.is_email_from_present());
+    CHECK(false == state.is_email_to_present());
+
+    state.get_file_name(&filename, &filename_len);
+    state.get_email_hdrs(&headers, &headers_len);
+    state.get_email_id(&senders, &senders_len, snort::EMAIL_SENDER);
+    state.get_email_id(&recipients, &recipients_len, snort::EMAIL_RECIPIENT);
+
+    CHECK(nullptr != filename);
+    CHECK(0 == filename_len);
+    CHECK(nullptr != headers);
+    CHECK(0 == headers_len);
+    CHECK(nullptr != senders);
+    CHECK(0 == senders_len);
+    CHECK(nullptr != recipients);
+    CHECK(0 == recipients_len);
+}
+
+TEST(mail_log_basic, no_logging)
+{
+    snort::MailLogConfig config;
+    snort::MailLogState state(&config);
+
+    state.get_file_name(&filename, &filename_len);
+    state.get_email_hdrs(&headers, &headers_len);
+    state.get_email_id(&senders, &senders_len, snort::EMAIL_SENDER);
+    state.get_email_id(&recipients, &recipients_len, snort::EMAIL_RECIPIENT);
+
+    CHECK(false == state.is_file_name_present());
+    CHECK(false == state.is_email_hdrs_present());
+    CHECK(false == state.is_email_from_present());
+    CHECK(false == state.is_email_to_present());
+    CHECK(nullptr == filename);
+    CHECK(0 == filename_len);
+    CHECK(nullptr == headers);
+    CHECK(0 == headers_len);
+    CHECK(nullptr == senders);
+    CHECK(0 == senders_len);
+    CHECK(nullptr == recipients);
+    CHECK(0 == recipients_len);
+
+    const char* data1 = "1";
+    int res1 = state.log_file_name((const uint8_t*)data1, 1);
+
+    const char* data2 = "2";
+    int res2 = state.log_email_hdrs((const uint8_t*)data2, 1);
+
+    const char* data3 = ":3";
+    int res3 = state.log_email_id((const uint8_t*)data3, 2, snort::EMAIL_SENDER);
+
+    const char* data4 = ":4";
+    int res4 = state.log_email_id((const uint8_t*)data4, 2, snort::EMAIL_RECIPIENT);
+
+    CHECK(-1 == res1);
+    CHECK(-1 == res2);
+    CHECK(-1 == res3);
+    CHECK(-1 == res4);
+
+    CHECK(false == state.is_file_name_present());
+    CHECK(false == state.is_email_hdrs_present());
+    CHECK(false == state.is_email_from_present());
+    CHECK(false == state.is_email_to_present());
+    CHECK(nullptr == filename);
+    CHECK(0 == filename_len);
+    CHECK(nullptr == headers);
+    CHECK(0 == headers_len);
+    CHECK(nullptr == senders);
+    CHECK(0 == senders_len);
+    CHECK(nullptr == recipients);
+    CHECK(0 == recipients_len);
+}
+
+TEST(mail_log_basic, name_logging)
+{
+    snort::MailLogConfig config;
+    config.log_filename = true;
+    snort::MailLogState state(&config);
+
+    state.get_file_name(&filename, &filename_len);
+    state.get_email_hdrs(&headers, &headers_len);
+    state.get_email_id(&senders, &senders_len, snort::EMAIL_SENDER);
+    state.get_email_id(&recipients, &recipients_len, snort::EMAIL_RECIPIENT);
+
+    CHECK(false == state.is_file_name_present());
+    CHECK(false == state.is_email_hdrs_present());
+    CHECK(false == state.is_email_from_present());
+    CHECK(false == state.is_email_to_present());
+    CHECK(nullptr != filename);
+    CHECK(0 == filename_len);
+    CHECK(nullptr == headers);
+    CHECK(0 == headers_len);
+    CHECK(nullptr == senders);
+    CHECK(0 == senders_len);
+    CHECK(nullptr == recipients);
+    CHECK(0 == recipients_len);
+
+    const char* data1 = "1";
+    int res1 = state.log_file_name((const uint8_t*)data1, 1);
+
+    const char* data2 = "2";
+    int res2 = state.log_email_hdrs((const uint8_t*)data2, 1);
+
+    const char* data3 = ":3";
+    int res3 = state.log_email_id((const uint8_t*)data3, 2, snort::EMAIL_SENDER);
+
+    const char* data4 = ":4";
+    int res4 = state.log_email_id((const uint8_t*)data4, 2, snort::EMAIL_RECIPIENT);
+
+    CHECK(0 == res1);
+    CHECK(-1 == res2);
+    CHECK(-1 == res3);
+    CHECK(-1 == res4);
+
+    CHECK(true == state.is_file_name_present());
+    CHECK(false == state.is_email_hdrs_present());
+    CHECK(false == state.is_email_from_present());
+    CHECK(false == state.is_email_to_present());
+
+    state.get_file_name(&filename, &filename_len);
+    state.get_email_hdrs(&headers, &headers_len);
+    state.get_email_id(&senders, &senders_len, snort::EMAIL_SENDER);
+    state.get_email_id(&recipients, &recipients_len, snort::EMAIL_RECIPIENT);
+
+    CHECK(1 == filename_len);
+    CHECK(0 == headers_len);
+    CHECK(0 == senders_len);
+    CHECK(0 == recipients_len);
+
+    CHECK(nullptr != filename);
+    CHECK(nullptr == headers);
+    CHECK(nullptr == senders);
+    CHECK(nullptr == recipients);
+
+    CHECK('1' == filename[0]);
+}
+
+TEST(mail_log_basic, header_logging)
+{
+    snort::MailLogConfig config;
+    config.log_email_hdrs = true;
+    config.email_hdrs_log_depth = 64;
+    snort::MailLogState state(&config);
+
+    state.get_file_name(&filename, &filename_len);
+    state.get_email_hdrs(&headers, &headers_len);
+    state.get_email_id(&senders, &senders_len, snort::EMAIL_SENDER);
+    state.get_email_id(&recipients, &recipients_len, snort::EMAIL_RECIPIENT);
+
+    CHECK(false == state.is_file_name_present());
+    CHECK(false == state.is_email_hdrs_present());
+    CHECK(false == state.is_email_from_present());
+    CHECK(false == state.is_email_to_present());
+    CHECK(nullptr == filename);
+    CHECK(0 == filename_len);
+    CHECK(nullptr != headers);
+    CHECK(0 == headers_len);
+    CHECK(nullptr == senders);
+    CHECK(0 == senders_len);
+    CHECK(nullptr == recipients);
+    CHECK(0 == recipients_len);
+
+    const char* data1 = "1";
+    int res1 = state.log_file_name((const uint8_t*)data1, 1);
+
+    const char* data2 = "2";
+    int res2 = state.log_email_hdrs((const uint8_t*)data2, 1);
+
+    const char* data3 = ":3";
+    int res3 = state.log_email_id((const uint8_t*)data3, 2, snort::EMAIL_SENDER);
+
+    const char* data4 = ":4";
+    int res4 = state.log_email_id((const uint8_t*)data4, 2, snort::EMAIL_RECIPIENT);
+
+    CHECK(-1 == res1);
+    CHECK(0 == res2);
+    CHECK(-1 == res3);
+    CHECK(-1 == res4);
+
+    CHECK(false == state.is_file_name_present());
+    CHECK(true == state.is_email_hdrs_present());
+    CHECK(false == state.is_email_from_present());
+    CHECK(false == state.is_email_to_present());
+
+    state.get_file_name(&filename, &filename_len);
+    state.get_email_hdrs(&headers, &headers_len);
+    state.get_email_id(&senders, &senders_len, snort::EMAIL_SENDER);
+    state.get_email_id(&recipients, &recipients_len, snort::EMAIL_RECIPIENT);
+
+    CHECK(0 == filename_len);
+    CHECK(1 == headers_len);
+    CHECK(0 == senders_len);
+    CHECK(0 == recipients_len);
+
+    CHECK(nullptr == filename);
+    CHECK(nullptr != headers);
+    CHECK(nullptr == senders);
+    CHECK(nullptr == recipients);
+
+    CHECK('2' == headers[0]);
+}
+
+TEST(mail_log_basic, sender_logging)
+{
+    snort::MailLogConfig config;
+    config.log_mailfrom = true;
+    snort::MailLogState state(&config);
+
+    state.get_file_name(&filename, &filename_len);
+    state.get_email_hdrs(&headers, &headers_len);
+    state.get_email_id(&senders, &senders_len, snort::EMAIL_SENDER);
+    state.get_email_id(&recipients, &recipients_len, snort::EMAIL_RECIPIENT);
+
+    CHECK(false == state.is_file_name_present());
+    CHECK(false == state.is_email_hdrs_present());
+    CHECK(false == state.is_email_from_present());
+    CHECK(false == state.is_email_to_present());
+    CHECK(nullptr == filename);
+    CHECK(0 == filename_len);
+    CHECK(nullptr == headers);
+    CHECK(0 == headers_len);
+    CHECK(nullptr != senders);
+    CHECK(0 == senders_len);
+    CHECK(nullptr == recipients);
+    CHECK(0 == recipients_len);
+
+    const char* data1 = "1";
+    int res1 = state.log_file_name((const uint8_t*)data1, 1);
+
+    const char* data2 = "2";
+    int res2 = state.log_email_hdrs((const uint8_t*)data2, 1);
+
+    const char* data3 = ":3";
+    int res3 = state.log_email_id((const uint8_t*)data3, 2, snort::EMAIL_SENDER);
+
+    const char* data4 = ":4";
+    int res4 = state.log_email_id((const uint8_t*)data4, 2, snort::EMAIL_RECIPIENT);
+
+    CHECK(-1 == res1);
+    CHECK(-1 == res2);
+    CHECK(0 == res3);
+    CHECK(-1 == res4);
+
+    CHECK(false == state.is_file_name_present());
+    CHECK(false == state.is_email_hdrs_present());
+    CHECK(true == state.is_email_from_present());
+    CHECK(false == state.is_email_to_present());
+
+    state.get_file_name(&filename, &filename_len);
+    state.get_email_hdrs(&headers, &headers_len);
+    state.get_email_id(&senders, &senders_len, snort::EMAIL_SENDER);
+    state.get_email_id(&recipients, &recipients_len, snort::EMAIL_RECIPIENT);
+
+    CHECK(0 == filename_len);
+    CHECK(0 == headers_len);
+    CHECK(1 == senders_len);
+    CHECK(0 == recipients_len);
+
+    CHECK(nullptr == filename);
+    CHECK(nullptr == headers);
+    CHECK(nullptr != senders);
+    CHECK(nullptr == recipients);
+
+    CHECK('3' == senders[0]);
+}
+
+TEST(mail_log_basic, recipient_logging)
+{
+    snort::MailLogConfig config;
+    config.log_rcptto = true;
+    snort::MailLogState state(&config);
+
+    state.get_file_name(&filename, &filename_len);
+    state.get_email_hdrs(&headers, &headers_len);
+    state.get_email_id(&senders, &senders_len, snort::EMAIL_SENDER);
+    state.get_email_id(&recipients, &recipients_len, snort::EMAIL_RECIPIENT);
+
+    CHECK(false == state.is_file_name_present());
+    CHECK(false == state.is_email_hdrs_present());
+    CHECK(false == state.is_email_from_present());
+    CHECK(false == state.is_email_to_present());
+    CHECK(nullptr == filename);
+    CHECK(0 == filename_len);
+    CHECK(nullptr == headers);
+    CHECK(0 == headers_len);
+    CHECK(nullptr == senders);
+    CHECK(0 == senders_len);
+    CHECK(nullptr != recipients);
+    CHECK(0 == recipients_len);
+
+    const char* data1 = "1";
+    int res1 = state.log_file_name((const uint8_t*)data1, 1);
+
+    const char* data2 = "2";
+    int res2 = state.log_email_hdrs((const uint8_t*)data2, 1);
+
+    const char* data3 = ":3";
+    int res3 = state.log_email_id((const uint8_t*)data3, 2, snort::EMAIL_SENDER);
+
+    const char* data4 = ":4";
+    int res4 = state.log_email_id((const uint8_t*)data4, 2, snort::EMAIL_RECIPIENT);
+
+    CHECK(-1 == res1);
+    CHECK(-1 == res2);
+    CHECK(-1 == res3);
+    CHECK(0 == res4);
+
+    CHECK(false == state.is_file_name_present());
+    CHECK(false == state.is_email_hdrs_present());
+    CHECK(false == state.is_email_from_present());
+    CHECK(true == state.is_email_to_present());
+
+    state.get_file_name(&filename, &filename_len);
+    state.get_email_hdrs(&headers, &headers_len);
+    state.get_email_id(&senders, &senders_len, snort::EMAIL_SENDER);
+    state.get_email_id(&recipients, &recipients_len, snort::EMAIL_RECIPIENT);
+
+    CHECK(0 == filename_len);
+    CHECK(0 == headers_len);
+    CHECK(0 == senders_len);
+    CHECK(1 == recipients_len);
+
+    CHECK(nullptr == filename);
+    CHECK(nullptr == headers);
+    CHECK(nullptr == senders);
+    CHECK(nullptr != recipients);
+
+    CHECK('4' == recipients[0]);
+}
+
+TEST_GROUP(mail_log_limit_preset)
+{
+    static constexpr int SIZE_LIMIT = 1024; // expected limit for filename, headers, senders and recipients
+    snort::MailLogState* state;
+
+    const char* mark1 = " filename mark";
+    const char* mark2 = " header mark";
+    const char* mark3 = ":sender mark";
+    const char* mark4 = ":recipient mark";
+
+    const int mark1_count = 3;
+    const int mark2_count = 3;
+    const int mark3_count = 3;
+    const int mark4_count = 3;
+
+    const char* expected1 = " filename mark, filename mark, filename mark";
+    const char* expected2 = " header mark header mark header mark";
+    const char* expected3 = "sender mark,sender mark,sender mark";
+    const char* expected4 = "recipient mark,recipient mark,recipient mark";
+
+    void setup() override
+    {
+        snort::MailLogConfig config;
+
+        config.log_mailfrom = true;
+        config.log_rcptto = true;
+        config.log_filename = true;
+        config.log_email_hdrs = true;
+        config.email_hdrs_log_depth = SIZE_LIMIT;
+
+        state = new snort::MailLogState(&config);
+        fill();
+    }
+
+    void teardown() override
+    {
+        check();
+        delete state;
+    }
+
+    void fill()
+    {
+        CHECK(nullptr != state);
+
+        CHECK(false == state->is_file_name_present());
+        CHECK(false == state->is_email_hdrs_present());
+        CHECK(false == state->is_email_from_present());
+        CHECK(false == state->is_email_to_present());
+
+        for (int i = 0; i < mark1_count; ++i)
+            state->log_file_name((const uint8_t*)mark1, strlen(mark1));
+
+        for (int i = 0; i < mark2_count; ++i)
+            state->log_email_hdrs((const uint8_t*)mark2, strlen(mark2));
+
+        for (int i = 0; i < mark3_count; ++i)
+            state->log_email_id((const uint8_t*)mark3, strlen(mark3), snort::EMAIL_SENDER);
+
+        for (int i = 0; i < mark4_count; ++i)
+            state->log_email_id((const uint8_t*)mark4, strlen(mark4), snort::EMAIL_RECIPIENT);
+    }
+
+    void check()
+    {
+        CHECK(nullptr != state);
+
+        CHECK(true == state->is_file_name_present());
+        CHECK(true == state->is_email_hdrs_present());
+        CHECK(true == state->is_email_from_present());
+        CHECK(true == state->is_email_to_present());
+
+        uint8_t* filename = PTR_UNSET;
+        uint32_t filename_len = LEN_UNSET;
+        uint8_t* headers = PTR_UNSET;
+        uint32_t headers_len = LEN_UNSET;
+        uint8_t* senders = PTR_UNSET;
+        uint32_t senders_len = LEN_UNSET;
+        uint8_t* recipients = PTR_UNSET;
+        uint32_t recipients_len = LEN_UNSET;
+
+        state->get_file_name(&filename, &filename_len);
+        state->get_email_hdrs(&headers, &headers_len);
+        state->get_email_id(&senders, &senders_len, snort::EMAIL_SENDER);
+        state->get_email_id(&recipients, &recipients_len, snort::EMAIL_RECIPIENT);
+
+        CHECK(strlen(expected1) <= filename_len);
+        CHECK(strlen(expected2) <= headers_len);
+        CHECK(strlen(expected3) <= senders_len);
+        CHECK(strlen(expected4) <= recipients_len);
+
+        CHECK(nullptr != filename);
+        CHECK(nullptr != headers);
+        CHECK(nullptr != senders);
+        CHECK(nullptr != recipients);
+
+        STRNCMP_EQUAL(expected1, (const char*)filename, strlen(expected1));
+        STRNCMP_EQUAL(expected2, (const char*)headers, strlen(expected2));
+        STRNCMP_EQUAL(expected3, (const char*)senders, strlen(expected3));
+        STRNCMP_EQUAL(expected4, (const char*)recipients, strlen(expected4));
+    }
+};
+
+TEST(mail_log_limit_preset, name_fit)
+{
+    uint8_t data[SIZE_LIMIT];
+    memset(data, 'a', sizeof(data));
+
+    // account for preexisting data, extra comma and null-character
+    int data_len = sizeof(data) - strlen(expected1) - 1 - 1;
+
+    int ret = state->log_file_name(data, data_len);
+    CHECK(0 == ret);
+
+    uint8_t* filename = PTR_UNSET;
+    uint32_t filename_len = LEN_UNSET;
+
+    state->get_file_name(&filename, &filename_len);
+    CHECK_COMPARE(SIZE_LIMIT - 1, ==, filename_len);
+
+    std::string written((const char*)data, data_len);
+    std::string expected;
+    expected += expected1;
+    expected += ",";
+    expected += written;
+
+    std::string actual((const char*)filename);
+    CHECK_COMPARE(expected, ==, actual);
+}
+
+TEST(mail_log_limit_preset, header_fit)
+{
+    uint8_t data[SIZE_LIMIT];
+    memset(data, 'a', sizeof(data));
+
+    // account for preexisting data, no comma and null-character
+    int data_len = sizeof(data) - strlen(expected2) - 0 - 1;
+
+    int ret = state->log_email_hdrs(data, data_len);
+    CHECK(0 == ret);
+
+    uint8_t* headers = PTR_UNSET;
+    uint32_t headers_len = LEN_UNSET;
+
+    state->get_email_hdrs(&headers, &headers_len);
+    CHECK_COMPARE(SIZE_LIMIT - 1, ==, headers_len);
+
+    std::string written((const char*)data, data_len);
+    std::string expected;
+    expected += expected2;
+    expected += written;
+
+    std::string actual((const char*)headers);
+    CHECK_COMPARE(expected, ==, actual);
+}
+
+TEST(mail_log_limit_preset, sender_fit)
+{
+    uint8_t data[SIZE_LIMIT];
+    memset(data, 'a', sizeof(data));
+    data[0] = ':';
+
+    // account for preexisting data, extra comma, null-character and eaten colon
+    int data_len = sizeof(data) - strlen(expected3) - 1 - 1 + 1;
+
+    int ret = state->log_email_id(data, data_len, snort::EMAIL_SENDER);
+    CHECK(0 == ret);
+
+    uint8_t* senders = PTR_UNSET;
+    uint32_t senders_len = LEN_UNSET;
+
+    state->get_email_id(&senders, &senders_len, snort::EMAIL_SENDER);
+    CHECK_COMPARE(SIZE_LIMIT - 1, ==, senders_len);
+
+    std::string written((const char*)data + 1, data_len - 1);
+    std::string expected;
+    expected += expected3;
+    expected += ",";
+    expected += written;
+
+    std::string actual((const char*)senders);
+    CHECK_COMPARE(expected, ==, actual);
+}
+
+TEST(mail_log_limit_preset, recipient_fit)
+{
+    uint8_t data[SIZE_LIMIT];
+    memset(data, 'a', sizeof(data));
+    data[0] = ':';
+
+    // account for preexisting data, extra comma, null-character and eaten colon
+    int data_len = sizeof(data) - strlen(expected4) - 1 - 1 + 1;
+
+    int ret = state->log_email_id(data, data_len, snort::EMAIL_RECIPIENT);
+    CHECK(0 == ret);
+
+    uint8_t* recipients = PTR_UNSET;
+    uint32_t recipients_len = LEN_UNSET;
+
+    state->get_email_id(&recipients, &recipients_len, snort::EMAIL_RECIPIENT);
+    CHECK_COMPARE(SIZE_LIMIT - 1, ==, recipients_len);
+
+    std::string written((const char*)data + 1, data_len - 1);
+    std::string expected;
+    expected += expected4;
+    expected += ",";
+    expected += written;
+
+    std::string actual((const char*)recipients);
+    CHECK_COMPARE(expected, ==, actual);
+}
+
+TEST(mail_log_limit_preset, name_over_1)
+{
+    uint8_t data[SIZE_LIMIT];
+    memset(data, 'a', sizeof(data));
+
+    // account for preexisting data, extra comma and no room for null-character
+    int data_len = sizeof(data) - strlen(expected1) - 1 - 0;
+
+    int ret = state->log_file_name(data, data_len);
+    CHECK(0 == ret);
+
+    uint8_t* filename = PTR_UNSET;
+    uint32_t filename_len = LEN_UNSET;
+
+    state->get_file_name(&filename, &filename_len);
+    CHECK_COMPARE(SIZE_LIMIT - 1, ==, filename_len);
+
+    std::string written((const char*)data, data_len - 1);
+    std::string expected;
+    expected += expected1;
+    expected += ",";
+    expected += written;
+
+    std::string actual((const char*)filename);
+    CHECK_COMPARE(expected, ==, actual);
+}
+
+TEST(mail_log_limit_preset, header_over_1)
+{
+    uint8_t data[SIZE_LIMIT];
+    memset(data, 'a', sizeof(data));
+
+    // account for preexisting data, no comma and no room for null-character
+    int data_len = sizeof(data) - strlen(expected2) - 0 - 0;
+
+    int ret = state->log_email_hdrs(data, data_len);
+    CHECK(0 == ret);
+
+    uint8_t* headers = PTR_UNSET;
+    uint32_t headers_len = LEN_UNSET;
+
+    state->get_email_hdrs(&headers, &headers_len);
+    CHECK_COMPARE(SIZE_LIMIT - 1, ==, headers_len);
+
+    std::string written((const char*)data, data_len - 1);
+    std::string expected;
+    expected += expected2;
+    expected += written;
+
+    std::string actual((const char*)headers);
+    CHECK_COMPARE(expected, ==, actual);
+}
+
+TEST(mail_log_limit_preset, sender_over_1)
+{
+    uint8_t data[SIZE_LIMIT];
+    memset(data, 'a', sizeof(data));
+    data[0] = ':';
+
+    // account for preexisting data, extra comma, no null-character and eaten colon
+    int data_len = sizeof(data) - strlen(expected3) - 1 - 0 + 1;
+
+    int ret = state->log_email_id(data, data_len, snort::EMAIL_SENDER);
+    CHECK(0 == ret);
+
+    uint8_t* senders = PTR_UNSET;
+    uint32_t senders_len = LEN_UNSET;
+
+    state->get_email_id(&senders, &senders_len, snort::EMAIL_SENDER);
+    CHECK_COMPARE(SIZE_LIMIT - 1, ==, senders_len);
+
+    std::string written((const char*)data + 1, data_len - 2);
+    std::string expected;
+    expected += expected3;
+    expected += ",";
+    expected += written;
+
+    std::string actual((const char*)senders);
+    CHECK_COMPARE(expected, ==, actual);
+}
+
+TEST(mail_log_limit_preset, recipient_over_1)
+{
+    uint8_t data[SIZE_LIMIT];
+    memset(data, 'a', sizeof(data));
+    data[0] = ':';
+
+    // account for preexisting data, extra comma, no null-character and eaten colon
+    int data_len = sizeof(data) - strlen(expected4) - 1 - 0 + 1;
+
+    int ret = state->log_email_id(data, data_len, snort::EMAIL_RECIPIENT);
+    CHECK(0 == ret);
+
+    uint8_t* recipients = PTR_UNSET;
+    uint32_t recipients_len = LEN_UNSET;
+
+    state->get_email_id(&recipients, &recipients_len, snort::EMAIL_RECIPIENT);
+    CHECK_COMPARE(SIZE_LIMIT - 1, ==, recipients_len);
+
+    std::string written((const char*)data + 1, data_len - 2);
+    std::string expected;
+    expected += expected4;
+    expected += ",";
+    expected += written;
+
+    std::string actual((const char*)recipients);
+    CHECK_COMPARE(expected, ==, actual);
+}
+
+TEST(mail_log_limit_preset, name_doubled)
+{
+    uint8_t data[SIZE_LIMIT * 2];
+    memset(data, 'a', sizeof(data));
+    int data_len = sizeof(data);
+
+    int ret = state->log_file_name(data, data_len);
+    CHECK(0 == ret);
+
+    uint8_t* filename = PTR_UNSET;
+    uint32_t filename_len = LEN_UNSET;
+
+    state->get_file_name(&filename, &filename_len);
+    CHECK_COMPARE(SIZE_LIMIT - 1, ==, filename_len);
+
+    // see [mail_log_limit_preset.name_fit] for size calculation
+    std::string written((const char*)data, SIZE_LIMIT - strlen(expected1) - 1 - 1);
+    std::string expected;
+    expected += expected1;
+    expected += ",";
+    expected += written;
+
+    std::string actual((const char*)filename);
+    CHECK_COMPARE(expected, ==, actual);
+}
+
+TEST(mail_log_limit_preset, header_doubled)
+{
+    uint8_t data[SIZE_LIMIT * 2];
+    memset(data, 'a', sizeof(data));
+    int data_len = sizeof(data);
+
+    int ret = state->log_email_hdrs(data, data_len);
+    CHECK(0 == ret);
+
+    uint8_t* headers = PTR_UNSET;
+    uint32_t headers_len = LEN_UNSET;
+
+    state->get_email_hdrs(&headers, &headers_len);
+    CHECK_COMPARE(SIZE_LIMIT - 1, ==, headers_len);
+
+    // see [mail_log_limit_preset.header_fit] for size calculation
+    std::string written((const char*)data, SIZE_LIMIT - strlen(expected2) - 0 - 1);
+    std::string expected;
+    expected += expected2;
+    expected += written;
+
+    std::string actual((const char*)headers);
+    CHECK_COMPARE(expected, ==, actual);
+}
+
+TEST(mail_log_limit_preset, sender_doubled)
+{
+    uint8_t data[SIZE_LIMIT * 2];
+    memset(data, 'a', sizeof(data));
+    data[0] = ':';
+    int data_len = sizeof(data);
+
+    int ret = state->log_email_id(data, data_len, snort::EMAIL_SENDER);
+    CHECK(0 == ret);
+
+    uint8_t* senders = PTR_UNSET;
+    uint32_t senders_len = LEN_UNSET;
+
+    state->get_email_id(&senders, &senders_len, snort::EMAIL_SENDER);
+    CHECK_COMPARE(SIZE_LIMIT - 1, ==, senders_len);
+
+    // see [mail_log_limit_preset.sender_fit] for size calculation
+    std::string written((const char*)data + 1, SIZE_LIMIT - strlen(expected3) - 1 - 1 + 1 - 1);
+    std::string expected;
+    expected += expected3;
+    expected += ",";
+    expected += written;
+
+    std::string actual((const char*)senders);
+    CHECK_COMPARE(expected, ==, actual);
+}
+
+TEST(mail_log_limit_preset, recipient_doubled)
+{
+    uint8_t data[SIZE_LIMIT * 2];
+    memset(data, 'a', sizeof(data));
+    data[0] = ':';
+    int data_len = sizeof(data);
+
+    int ret = state->log_email_id(data, data_len, snort::EMAIL_RECIPIENT);
+    CHECK(0 == ret);
+
+    uint8_t* recipients = PTR_UNSET;
+    uint32_t recipients_len = LEN_UNSET;
+
+    state->get_email_id(&recipients, &recipients_len, snort::EMAIL_RECIPIENT);
+    CHECK_COMPARE(SIZE_LIMIT - 1, ==, recipients_len);
+
+    // see [mail_log_limit_preset.recipients_fit] for size calculation
+    std::string written((const char*)data + 1, SIZE_LIMIT - strlen(expected4) - 1 - 1 + 1 - 1);
+    std::string expected;
+    expected += expected4;
+    expected += ",";
+    expected += written;
+
+    std::string actual((const char*)recipients);
+    CHECK_COMPARE(expected, ==, actual);
+}
+
+TEST_GROUP(mail_log_limit_once)
+{
+    static constexpr int SIZE_LIMIT = 1024; // expected limit for filename, headers, senders and recipients
+    snort::MailLogState* state;
+
+    void setup() override
+    {
+        snort::MailLogConfig config;
+
+        config.log_mailfrom = true;
+        config.log_rcptto = true;
+        config.log_filename = true;
+        config.log_email_hdrs = true;
+        config.email_hdrs_log_depth = SIZE_LIMIT;
+
+        state = new snort::MailLogState(&config);
+        CHECK(nullptr != state);
+    }
+
+    void teardown() override
+    {
+        delete state;
+    }
+};
+
+TEST(mail_log_limit_once, name_fit)
+{
+    uint8_t data[SIZE_LIMIT];
+    memset(data, 'a', sizeof(data));
+
+    // account for null-character
+    int data_len = sizeof(data) - 1;
+
+    int ret = state->log_file_name(data, data_len);
+
+    CHECK(0 == ret);
+    CHECK(true == state->is_file_name_present());
+    CHECK(false == state->is_email_hdrs_present());
+    CHECK(false == state->is_email_from_present());
+    CHECK(false == state->is_email_to_present());
+
+    uint8_t* filename = PTR_UNSET;
+    uint32_t filename_len = LEN_UNSET;
+
+    state->get_file_name(&filename, &filename_len);
+    CHECK_COMPARE(SIZE_LIMIT - 1, ==, filename_len);
+
+    std::string expected((const char*)data, data_len);
+    std::string actual((const char*)filename);
+    CHECK_COMPARE(expected, ==, actual);
+}
+
+TEST(mail_log_limit_once, header_fit)
+{
+    uint8_t data[SIZE_LIMIT];
+    memset(data, 'a', sizeof(data));
+
+    // account for null-character
+    int data_len = sizeof(data) - 1;
+
+    int ret = state->log_email_hdrs(data, data_len);
+
+    CHECK(0 == ret);
+    CHECK(false == state->is_file_name_present());
+    CHECK(true == state->is_email_hdrs_present());
+    CHECK(false == state->is_email_from_present());
+    CHECK(false == state->is_email_to_present());
+
+    uint8_t* headers = PTR_UNSET;
+    uint32_t headers_len = LEN_UNSET;
+
+    state->get_email_hdrs(&headers, &headers_len);
+    CHECK_COMPARE(SIZE_LIMIT - 1, ==, headers_len);
+
+    std::string expected((const char*)data, data_len);
+    std::string actual((const char*)headers);
+    CHECK_COMPARE(expected, ==, actual);
+}
+
+TEST(mail_log_limit_once, sender_fit)
+{
+    uint8_t data[SIZE_LIMIT];
+    memset(data, 'a', sizeof(data));
+    data[0] = ':';
+
+    // account for null-character and eaten colon
+    int data_len = sizeof(data) - 1 + 1;
+
+    int ret = state->log_email_id(data, data_len, snort::EMAIL_SENDER);
+
+    CHECK(0 == ret);
+    CHECK(false == state->is_file_name_present());
+    CHECK(false == state->is_email_hdrs_present());
+    CHECK(true == state->is_email_from_present());
+    CHECK(false == state->is_email_to_present());
+
+    uint8_t* senders = PTR_UNSET;
+    uint32_t senders_len = LEN_UNSET;
+
+    state->get_email_id(&senders, &senders_len, snort::EMAIL_SENDER);
+    CHECK_COMPARE(SIZE_LIMIT - 1, ==, senders_len);
+
+    std::string expected((const char*)data + 1, data_len - 1);
+    std::string actual((const char*)senders);
+    CHECK_COMPARE(expected, ==, actual);
+}
+
+TEST(mail_log_limit_once, recipient_fit)
+{
+    uint8_t data[SIZE_LIMIT];
+    memset(data, 'a', sizeof(data));
+    data[0] = ':';
+
+    // account for null-character and eaten colon
+    int data_len = sizeof(data) - 1 + 1;
+
+    int ret = state->log_email_id(data, data_len, snort::EMAIL_RECIPIENT);
+
+    CHECK(0 == ret);
+    CHECK(false == state->is_file_name_present());
+    CHECK(false == state->is_email_hdrs_present());
+    CHECK(false == state->is_email_from_present());
+    CHECK(true == state->is_email_to_present());
+
+    uint8_t* recipients = PTR_UNSET;
+    uint32_t recipients_len = LEN_UNSET;
+
+    state->get_email_id(&recipients, &recipients_len, snort::EMAIL_RECIPIENT);
+    CHECK_COMPARE(SIZE_LIMIT - 1, ==, recipients_len);
+
+    std::string expected((const char*)data + 1, data_len - 1);
+    std::string actual((const char*)recipients);
+    CHECK_COMPARE(expected, ==, actual);
+}
+
+TEST(mail_log_limit_once, name_over_1)
+{
+    uint8_t data[SIZE_LIMIT];
+    memset(data, 'a', sizeof(data));
+
+    // no room for null-character
+    int data_len = sizeof(data) - 0;
+
+    int ret = state->log_file_name(data, data_len);
+
+    CHECK(0 == ret);
+    CHECK(true == state->is_file_name_present());
+    CHECK(false == state->is_email_hdrs_present());
+    CHECK(false == state->is_email_from_present());
+    CHECK(false == state->is_email_to_present());
+
+    uint8_t* filename = PTR_UNSET;
+    uint32_t filename_len = LEN_UNSET;
+
+    state->get_file_name(&filename, &filename_len);
+    CHECK_COMPARE(SIZE_LIMIT - 1, ==, filename_len);
+
+    std::string expected((const char*)data, data_len - 1);
+    std::string actual((const char*)filename);
+    CHECK_COMPARE(expected, ==, actual);
+}
+
+TEST(mail_log_limit_once, header_over_1)
+{
+    uint8_t data[SIZE_LIMIT];
+    memset(data, 'a', sizeof(data));
+
+    // no room for null-character
+    int data_len = sizeof(data) - 0;
+
+    int ret = state->log_email_hdrs(data, data_len);
+
+    CHECK(0 == ret);
+    CHECK(false == state->is_file_name_present());
+    CHECK(true == state->is_email_hdrs_present());
+    CHECK(false == state->is_email_from_present());
+    CHECK(false == state->is_email_to_present());
+
+    uint8_t* headers = PTR_UNSET;
+    uint32_t headers_len = LEN_UNSET;
+
+    state->get_email_hdrs(&headers, &headers_len);
+    CHECK_COMPARE(SIZE_LIMIT - 1, ==, headers_len);
+
+    std::string expected((const char*)data, data_len - 1);
+    std::string actual((const char*)headers);
+    CHECK_COMPARE(expected, ==, actual);
+}
+
+TEST(mail_log_limit_once, sender_over_1)
+{
+    // account for eaten colon
+    uint8_t data[SIZE_LIMIT + 1];
+    memset(data, 'a', sizeof(data));
+    data[0] = ':';
+
+    // no room for null-character
+    int data_len = sizeof(data) - 0;
+
+    int ret = state->log_email_id(data, data_len, snort::EMAIL_SENDER);
+
+    CHECK(0 == ret);
+    CHECK(false == state->is_file_name_present());
+    CHECK(false == state->is_email_hdrs_present());
+    CHECK(true == state->is_email_from_present());
+    CHECK(false == state->is_email_to_present());
+
+    uint8_t* senders = PTR_UNSET;
+    uint32_t senders_len = LEN_UNSET;
+
+    state->get_email_id(&senders, &senders_len, snort::EMAIL_SENDER);
+    CHECK_COMPARE(SIZE_LIMIT - 1, ==, senders_len);
+
+    std::string expected((const char*)data + 1, data_len - 2);
+    std::string actual((const char*)senders);
+    CHECK_COMPARE(expected, ==, actual);
+}
+
+TEST(mail_log_limit_once, recipient_over_1)
+{
+    // account for eaten colon
+    uint8_t data[SIZE_LIMIT + 1];
+    memset(data, 'a', sizeof(data));
+    data[0] = ':';
+
+    // no room for null-character
+    int data_len = sizeof(data) - 0;
+
+    int ret = state->log_email_id(data, data_len, snort::EMAIL_RECIPIENT);
+
+    CHECK(0 == ret);
+    CHECK(false == state->is_file_name_present());
+    CHECK(false == state->is_email_hdrs_present());
+    CHECK(false == state->is_email_from_present());
+    CHECK(true == state->is_email_to_present());
+
+    uint8_t* recipients = PTR_UNSET;
+    uint32_t recipients_len = LEN_UNSET;
+
+    state->get_email_id(&recipients, &recipients_len, snort::EMAIL_RECIPIENT);
+    CHECK_COMPARE(SIZE_LIMIT - 1, ==, recipients_len);
+
+    std::string expected((const char*)data + 1, data_len - 2);
+    std::string actual((const char*)recipients);
+    CHECK_COMPARE(expected, ==, actual);
+}
+
+TEST(mail_log_limit_once, name_doubled)
+{
+    uint8_t data[SIZE_LIMIT * 2];
+    memset(data, 'a', sizeof(data));
+    int data_len = sizeof(data);
+
+    int ret = state->log_file_name(data, data_len);
+
+    CHECK(0 == ret);
+    CHECK(true == state->is_file_name_present());
+    CHECK(false == state->is_email_hdrs_present());
+    CHECK(false == state->is_email_from_present());
+    CHECK(false == state->is_email_to_present());
+
+    uint8_t* filename = PTR_UNSET;
+    uint32_t filename_len = LEN_UNSET;
+
+    state->get_file_name(&filename, &filename_len);
+    CHECK_COMPARE(SIZE_LIMIT - 1, ==, filename_len);
+
+    std::string expected((const char*)data, SIZE_LIMIT - 1);
+    std::string actual((const char*)filename);
+    CHECK_COMPARE(expected, ==, actual);
+}
+
+TEST(mail_log_limit_once, header_doubled)
+{
+    uint8_t data[SIZE_LIMIT * 2];
+    memset(data, 'a', sizeof(data));
+    int data_len = sizeof(data);
+
+    int ret = state->log_email_hdrs(data, data_len);
+
+    CHECK(0 == ret);
+    CHECK(false == state->is_file_name_present());
+    CHECK(true == state->is_email_hdrs_present());
+    CHECK(false == state->is_email_from_present());
+    CHECK(false == state->is_email_to_present());
+
+    uint8_t* headers = PTR_UNSET;
+    uint32_t headers_len = LEN_UNSET;
+
+    state->get_email_hdrs(&headers, &headers_len);
+    CHECK_COMPARE(SIZE_LIMIT - 1, ==, headers_len);
+
+    std::string expected((const char*)data, SIZE_LIMIT - 1);
+    std::string actual((const char*)headers);
+    CHECK_COMPARE(expected, ==, actual);
+}
+
+TEST(mail_log_limit_once, sender_doubled)
+{
+    uint8_t data[SIZE_LIMIT * 2];
+    memset(data, 'a', sizeof(data));
+    data[0] = ':';
+    int data_len = sizeof(data);
+
+    int ret = state->log_email_id(data, data_len, snort::EMAIL_SENDER);
+
+    CHECK(0 == ret);
+    CHECK(false == state->is_file_name_present());
+    CHECK(false == state->is_email_hdrs_present());
+    CHECK(true == state->is_email_from_present());
+    CHECK(false == state->is_email_to_present());
+
+    uint8_t* senders = PTR_UNSET;
+    uint32_t senders_len = LEN_UNSET;
+
+    state->get_email_id(&senders, &senders_len, snort::EMAIL_SENDER);
+    CHECK_COMPARE(SIZE_LIMIT - 1, ==, senders_len);
+
+    std::string expected((const char*)data + 1, SIZE_LIMIT - 1);
+    std::string actual((const char*)senders);
+    CHECK_COMPARE(expected, ==, actual);
+}
+
+TEST(mail_log_limit_once, recipient_doubled)
+{
+    uint8_t data[SIZE_LIMIT * 2];
+    memset(data, 'a', sizeof(data));
+    data[0] = ':';
+    int data_len = sizeof(data);
+
+    int ret = state->log_email_id(data, data_len, snort::EMAIL_RECIPIENT);
+
+    CHECK(0 == ret);
+    CHECK(false == state->is_file_name_present());
+    CHECK(false == state->is_email_hdrs_present());
+    CHECK(false == state->is_email_from_present());
+    CHECK(true == state->is_email_to_present());
+
+    uint8_t* recipients = PTR_UNSET;
+    uint32_t recipients_len = LEN_UNSET;
+
+    state->get_email_id(&recipients, &recipients_len, snort::EMAIL_RECIPIENT);
+    CHECK_COMPARE(SIZE_LIMIT - 1, ==, recipients_len);
+
+    std::string expected((const char*)data + 1, SIZE_LIMIT - 1);
+    std::string actual((const char*)recipients);
+    CHECK_COMPARE(expected, ==, actual);
+}
+
+int main(int argc, char** argv)
+{
+    return CommandLineTestRunner::RunAllTests(argc, argv);
+}