From: Oleksii Shumeiko -X (oshumeik - SOFTSERVE INC at Cisco) Date: Thu, 8 Jan 2026 15:27:40 +0000 (+0000) Subject: Pull request #5082: MIME to provide null-terminated string for logging X-Git-Tag: 3.10.1.0~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8cf679cf2b7408c4fc4cad9e61b4b5bc28fb4c6f;p=thirdparty%2Fsnort3.git Pull request #5082: MIME to provide null-terminated string for logging Merge in SNORT/snort3 from ~OSHUMEIK/snort3:mime_fix to master Squashed commit of the following: commit 14b21aec883ea11a6ef259613d5fb9f083b39ff5 Author: Oleksii Shumeiko 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 Date: Tue Jan 6 18:18:25 2026 +0200 mime: add unit tests for data over memory limit commit d0a87edddd87f9330ab8ff36ddb5cb45236b028f Author: Oleksii Shumeiko Date: Tue Jan 6 17:25:17 2026 +0200 mime: add unit tests for data fitting memory limit commit 6b790e39c1386c23732417abf19bb8ed1db45113 Author: Oleksii Shumeiko Date: Tue Jan 6 13:44:45 2026 +0200 mime: rename class field to comply with the style commit 6f6423c66d9fbe54b65c01d098a6fe7cf3c97ada Author: Oleksii Shumeiko Date: Tue Jan 6 13:28:09 2026 +0200 mime: return error code if cannot add headers for logging commit 54c5677f65c6e7acbfea1a8b471b30b118e129a8 Author: Oleksii Shumeiko Date: Tue Jan 6 13:20:59 2026 +0200 mime: ignore field collection if not configured commit 449671977200c825b958984e1de6a52d52c4000a Author: Oleksii Shumeiko Date: Tue Jan 6 11:36:22 2026 +0200 mime: add basic unit tests for file logging commit ed77619021828399f29d4e25412ef545920eb8c9 Author: Oleksii Shumeiko Date: Tue Jan 6 10:33:49 2026 +0200 mime: remove unused forward-declaration --- diff --git a/src/mime/CMakeLists.txt b/src/mime/CMakeLists.txt index 75c5a7e0b..f8133665c 100644 --- a/src/mime/CMakeLists.txt +++ b/src/mime/CMakeLists.txt @@ -35,3 +35,5 @@ add_library ( mime OBJECT install (FILES ${MIME_INCLUDES} DESTINATION "${INCLUDE_INSTALL_PATH}/mime" ) + +add_subdirectory(test) diff --git a/src/mime/file_mime_log.cc b/src/mime/file_mime_log.cc index faeb33246..af131b0cb 100644 --- a/src/mime/file_mime_log.cc +++ b/src/mime/file_mime_log.cc @@ -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; diff --git a/src/mime/file_mime_log.h b/src/mime/file_mime_log.h index 8756ca309..b5b1d1598 100644 --- a/src/mime/file_mime_log.h +++ b/src/mime/file_mime_log.h @@ -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 index 000000000..91edc7c7f --- /dev/null +++ b/src/mime/test/CMakeLists.txt @@ -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 index 000000000..0850c1b4c --- /dev/null +++ b/src/mime/test/file_mime_log_test.cc @@ -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 +#include + +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); +}