#include "snort.h"
#include "protocols/packet.h"
#include "nhttp_enum.h"
+#include "nhttp_test_manager.h"
#include "nhttp_test_input.h"
#include "nhttp_stream_splitter.h"
#include "nhttp_inspect.h"
// Convenience function. All the housekeeping that must be done before we can return FLUSH to stream.
void NHttpStreamSplitter::prepare_flush(NHttpFlowData* session_data, uint32_t* flush_offset, SourceId source_id,
- SectionType section_type, bool tcp_close, uint64_t infractions, uint32_t num_octets) {
+ SectionType section_type, bool tcp_close, uint64_t infractions, uint32_t num_octets, uint32_t length) {
session_data->section_type[source_id] = section_type;
session_data->tcp_close[source_id] = tcp_close;
session_data->infractions[source_id] = infractions;
if (tcp_close) {
session_data->type_expected[source_id] = SEC_CLOSED;
}
- if (!NHttpTestInput::test_input) {
+ if (!NHttpTestManager::use_test_input()) {
*flush_offset = num_octets;
}
else {
- NHttpTestInput::test_input_source->flush(num_octets);
+ NHttpTestManager::get_test_input_source()->flush(num_octets);
}
session_data->octets_seen[source_id] = 0;
session_data->num_crlf[source_id] = 0;
+ session_data->peek_ahead_octets[source_id] = 0;
+ session_data->unused_octets_visible[source_id] = length - num_octets;
+ session_data->header_octets_visible[source_id] = 0;
}
-const StreamBuffer* NHttpStreamSplitter::reassemble(Flow* flow, unsigned /*total*/, unsigned offset, const uint8_t* data,
+StreamSplitter::Status NHttpStreamSplitter::scan (Flow* flow, const uint8_t* data, uint32_t length, uint32_t, uint32_t* flush_offset) {
+ // When the system begins providing TCP connection close information this won't always be false. FIXIT-H
+ bool tcp_close = false;
+
+ // This is the session state information we share with HTTP Inspect and store with stream. A session is defined
+ // by a TCP connection. Since PAF is the first to see a new TCP connection the new flow data object is created here.
+ NHttpFlowData* session_data = (NHttpFlowData*)flow->get_application_data(NHttpFlowData::nhttp_flow_id);
+ if (session_data == nullptr) flow->set_application_data(session_data = new NHttpFlowData);
+ assert(session_data != nullptr);
+
+ SourceId source_id = to_server() ? SRC_CLIENT : SRC_SERVER;
+
+ if (NHttpTestManager::use_test_input()) {
+ // This block substitutes a completely new data buffer supplied by the test tool in place of the "real" data.
+ // It also rewrites the buffer length, source ID, and TCP close indicator.
+ *flush_offset = length;
+ bool need_break;
+ uint8_t* test_data = nullptr;
+ NHttpTestManager::get_test_input_source()->scan(test_data, length, source_id, tcp_close, need_break);
+ if (length == 0) {
+ return StreamSplitter::FLUSH;
+ }
+ data = test_data;
+ if (need_break) {
+ session_data = new NHttpFlowData;
+ flow->set_application_data(session_data);
+ }
+ assert(session_data->type_expected[source_id] != SEC_ABORT);
+ assert(session_data->type_expected[source_id] != SEC_CLOSED);
+ }
+
+ SectionType type = session_data->type_expected[source_id];
+
+ // Check for header section previously found during peek ahead
+ if ((type == SEC_HEADER) && (session_data->header_octets_visible[source_id] > 0)) {
+ prepare_flush(session_data, flush_offset, source_id, type,
+ tcp_close && (session_data->header_octets_visible[source_id] == length),
+ 0, session_data->peek_ahead_octets[source_id], length);
+ return StreamSplitter::FLUSH;
+ }
+
+ switch (type) {
+ case SEC_REQUEST:
+ case SEC_STATUS:
+ case SEC_HEADER:
+ case SEC_CHUNKHEAD:
+ case SEC_TRAILER:
+ paf_max = 63780;
+ for (uint32_t k = session_data->peek_ahead_octets[source_id]; k < length; k++) {
+ session_data->octets_seen[source_id]++;
+ // Count the alternating <CR> and <LF> characters we have seen in a row
+ if (((data[k] == '\r') && (session_data->num_crlf[source_id]%2 == 0)) ||
+ ((data[k] == '\n') && (session_data->num_crlf[source_id]%2 == 1))) {
+ session_data->num_crlf[source_id]++;
+ }
+ else {
+ session_data->num_crlf[source_id] = 0;
+ }
+
+ // If the first two octets are CRLF then flush them separately. We are 1) DISCARDing CRLF some
+ // 1.0 implementation put following previous message, 2) DISCARDing CRLF between chunk and following
+ // chunk header, and 3) flushing normal empty header or trailer.
+ if ((session_data->num_crlf[source_id] == 2) && (session_data->octets_seen[source_id] == 2)) {
+ prepare_flush(session_data, flush_offset, source_id,
+ ((type == SEC_REQUEST) || (type == SEC_STATUS) || (type == SEC_CHUNKHEAD)) ? SEC_DISCARD : type,
+ tcp_close && (k == length-1), 0, k+1, length);
+ return StreamSplitter::FLUSH;
+ }
+ // The start line and chunk header section always end with the first <CRLF>
+ else if ((session_data->num_crlf[source_id] == 2) &&
+ ((type == SEC_REQUEST) || (type == SEC_STATUS) || (type == SEC_CHUNKHEAD))) {
+ prepare_flush(session_data, flush_offset, source_id, type, tcp_close && (k == length-1), 0, k+1, length);
+ if ((type == SEC_REQUEST) || (type == SEC_STATUS)) {
+ // Look ahead to see if entire header section is already here so we can aggregate it for detection.
+ for (uint32_t m = k+1; m < length; m++) {
+ session_data->octets_seen[source_id]++;
+ // Count the alternating <CR> and <LF> characters we have seen in a row
+ if (((data[m] == '\r') && (session_data->num_crlf[source_id]%2 == 0)) ||
+ ((data[m] == '\n') && (session_data->num_crlf[source_id]%2 == 1))) {
+ session_data->num_crlf[source_id]++;
+ }
+ else {
+ session_data->num_crlf[source_id] = 0;
+ }
+ if ( (session_data->num_crlf[source_id] == 4) ||
+ ((session_data->num_crlf[source_id] == 2) && (session_data->octets_seen[source_id] == 2))) {
+ session_data->header_octets_visible[source_id] = m-k;
+ return StreamSplitter::FLUSH;
+ }
+ }
+ session_data->peek_ahead_octets[source_id] = length - (k+1);
+ }
+ return StreamSplitter::FLUSH;
+ }
+ // The header and trailer sections always end with the first double <CRLF>
+ else if (session_data->num_crlf[source_id] == 4) {
+ prepare_flush(session_data, flush_offset, source_id, type, tcp_close && (k == length-1), 0, k+1, length);
+ return StreamSplitter::FLUSH;
+ }
+ // We must do this to protect ourself from buffer overrun.
+ else if (session_data->octets_seen[source_id] >= 63780) {
+ // FIXIT-M need to implement processing and detection instead of just discarding this data
+ session_data->type_expected[source_id] = SEC_ABORT;
+ return StreamSplitter::ABORT;
+ }
+ }
+ session_data->peek_ahead_octets[source_id] = 0;
+ // Incomplete headers wait patiently for more data
+ if (!tcp_close) {
+ return StreamSplitter::SEARCH;
+ }
+ // Discard the oddball case where the new "message" starts with <CR><close>
+ else if ((session_data->octets_seen[source_id] == 1) && (session_data->num_crlf[source_id] == 1)) {
+ prepare_flush(session_data, flush_offset, source_id, SEC_DISCARD, true, 0, length, length);
+ }
+ // TCP connection close, flush the partial header
+ else {
+ prepare_flush(session_data, flush_offset, source_id, type, true, INF_TRUNCATED, length, length);
+ }
+ return StreamSplitter::FLUSH;
+ case SEC_BODY:
+ case SEC_CHUNKBODY:
+ paf_max = 16384 - session_data->chunk_buffer_length[source_id];
+ if ((!tcp_close) || (length > session_data->data_length[source_id])) {
+ prepare_flush(session_data, flush_offset, source_id, type, false, 0, session_data->data_length[source_id],
+ length);
+ }
+ else {
+ // The TCP connection has closed and this is the possibly incomplete final section
+ prepare_flush(session_data, flush_offset, source_id, type, true, 0, length, length);
+ }
+ return StreamSplitter::FLUSH;
+ case SEC_ABORT:
+ return StreamSplitter::ABORT;
+ default:
+ assert(0);
+ return StreamSplitter::ABORT;
+ }
+}
+
+const StreamBuffer* NHttpStreamSplitter::reassemble(Flow* flow, unsigned /*total FIXIT-H */, unsigned offset, const uint8_t* data,
unsigned len, uint32_t flags, unsigned& copied)
{
static THREAD_LOCAL StreamBuffer nhttp_buf;
SourceId source_id = to_server() ? SRC_CLIENT : SRC_SERVER;
copied = len;
- if (NHttpTestInput::test_input) {
+ if (NHttpTestManager::use_test_input()) {
if (!(flags & PKT_PDU_TAIL))
{
return nullptr;
}
uint8_t* test_buffer;
- NHttpTestInput::test_input_source->reassemble(&test_buffer, len, source_id, session_data);
+ NHttpTestManager::get_test_input_source()->reassemble(&test_buffer, len, source_id, session_data);
if (len == 0) {
// There is no more test data
return nullptr;
nhttp_buf.length = buffer_length + offset + len;
buffer = nullptr;
buffer_length = 0;
+ if (my_inspector->test_manager.use_test_output()) {
+ FILE* out_file = NHttpTestManager::use_test_input() ? my_inspector->test_manager.get_output_file() : stdout;
+ fprintf(out_file, "Sent to detection %u octets\n\n", nhttp_buf.length);
+ }
return &nhttp_buf;
case RES_IGNORE:
buffer = nullptr;
nhttp_buf.length = chunk_buffer_length;
chunk_buffer = nullptr;
chunk_buffer_length = 0;
+ if (my_inspector->test_manager.use_test_output()) {
+ FILE* out_file = NHttpTestManager::use_test_input() ? my_inspector->test_manager.get_output_file() : stdout;
+ fprintf(out_file, "Flushed chunks for detection %u octets\n\n", nhttp_buf.length);
+ }
return &nhttp_buf;
}
return nullptr;
return nullptr;
}
-StreamSplitter::Status NHttpStreamSplitter::scan (Flow* flow, const uint8_t* data, uint32_t length, uint32_t, uint32_t* flush_offset) {
- // When the system begins providing TCP connection close information this won't always be false. FIXIT-H
- bool tcp_close = false;
-
- // This is the session state information we share with HTTP Inspect and store with stream. A session is defined
- // by a TCP connection. Since PAF is the first to see a new TCP connection the new flow data object is created here.
- NHttpFlowData* session_data = (NHttpFlowData*)flow->get_application_data(NHttpFlowData::nhttp_flow_id);
- if (session_data == nullptr) flow->set_application_data(session_data = new NHttpFlowData);
- assert(session_data != nullptr);
-
- SourceId source_id = to_server() ? SRC_CLIENT : SRC_SERVER;
-
- if (NHttpTestInput::test_input) {
- // This block substitutes a completely new data buffer supplied by the test tool in place of the "real" data.
- // It also rewrites the buffer length, source ID, and TCP close indicator.
- *flush_offset = length;
- bool need_break;
- uint8_t* test_data = nullptr;
- NHttpTestInput::test_input_source->scan(test_data, length, source_id, tcp_close, need_break);
- if (length == 0) {
- return StreamSplitter::FLUSH;
- }
- data = test_data;
- if (need_break) {
- flow->set_application_data(session_data = new NHttpFlowData);
- }
- }
-
- switch (SectionType type = session_data->type_expected[source_id]) {
- case SEC_REQUEST:
- case SEC_STATUS:
- case SEC_HEADER:
- case SEC_CHUNKHEAD:
- case SEC_TRAILER:
- paf_max = 63780;
- for (uint32_t k = 0; k < length; k++) {
- session_data->octets_seen[source_id]++;
- // Count the alternating <CR> and <LF> characters we have seen in a row
- if (((data[k] == '\r') && (session_data->num_crlf[source_id]%2 == 0)) ||
- ((data[k] == '\n') && (session_data->num_crlf[source_id]%2 == 1))) {
- session_data->num_crlf[source_id]++;
- }
- else {
- session_data->num_crlf[source_id] = 0;
- }
-
- // If the first two octets are CRLF then flush them separately. We are 1) DISCARDing CRLF some
- // 1.0 implementation put following previous message, 2) DISCARDing CRLF between chunk and following
- // chunk header, and 3) flushing normal empty header or trailer.
- if ((session_data->num_crlf[source_id] == 2) && (session_data->octets_seen[source_id] == 2)) {
- prepare_flush(session_data, flush_offset, source_id,
- ((type == SEC_REQUEST) || (type == SEC_STATUS) || (type == SEC_CHUNKHEAD)) ? SEC_DISCARD : type,
- tcp_close && (k == length-1), 0, k+1);
- return StreamSplitter::FLUSH;
- }
- // The start line and chunk header section always end with the first <CRLF>
- else if ((session_data->num_crlf[source_id] == 2) &&
- ((type == SEC_REQUEST) || (type == SEC_STATUS) || (type == SEC_CHUNKHEAD))) {
- prepare_flush(session_data, flush_offset, source_id, type, tcp_close && (k == length-1), 0, k+1);
- return StreamSplitter::FLUSH;
- }
- // The header and trailer sections always end with the first double <CRLF>
- else if (session_data->num_crlf[source_id] == 4) {
- prepare_flush(session_data, flush_offset, source_id, type, tcp_close && (k == length-1), 0, k+1);
- return StreamSplitter::FLUSH;
- }
- // We must do this to protect ourself from buffer overrun.
- else if (session_data->octets_seen[source_id] >= 63780) {
- prepare_flush(session_data, flush_offset, source_id, type, tcp_close && (k == length-1), INF_HEADTOOLONG, k+1);
- return StreamSplitter::FLUSH;
- }
- }
- // Incomplete headers wait patiently for more data
- if (!tcp_close) {
- return StreamSplitter::SEARCH;
- }
- // Discard the oddball case where the new "message" starts with <CR><close>
- else if ((session_data->octets_seen[source_id] == 1) && (session_data->num_crlf[source_id] == 1)) {
- prepare_flush(session_data, flush_offset, source_id, SEC_DISCARD, true, 0, length);
- }
- // TCP connection close, flush the partial header
- else {
- prepare_flush(session_data, flush_offset, source_id, type, true, INF_TRUNCATED, length);
- }
- return StreamSplitter::FLUSH;
- case SEC_BODY:
- case SEC_CHUNKBODY:
- paf_max = 16384 - session_data->chunk_buffer_length[source_id];
- if ((!tcp_close) || (length > session_data->data_length[source_id])) {
- prepare_flush(session_data, flush_offset, source_id, type, false, 0, session_data->data_length[source_id]);
- }
- else {
- // The TCP connection has closed and this is the possibly incomplete final section
- prepare_flush(session_data, flush_offset, source_id, type, true, 0, length);
- }
- return StreamSplitter::FLUSH;
- case SEC_ABORT:
- return StreamSplitter::ABORT;
- default:
- assert(0);
- return StreamSplitter::ABORT;
- }
-}
-
-
-
-
-