set (FILE_LIST
nhttp_inspect.cc
nhttp_inspect.h
- nhttp_msgheader.cc
- nhttp_msgheader.h
- nhttp_headnorm.cc
- nhttp_headnorm.h
- nhttp_strtocode.cc
- nhttp_strtocode.h
- nhttp_api.cc
- nhttp_api.h
+ nhttp_msg_section.cc
+ nhttp_msg_section.h
+ nhttp_msg_head_shared.cc
+ nhttp_msg_head_shared.h
+ nhttp_msg_head.cc
+ nhttp_msg_head.h
+ nhttp_msg_body.cc
+ nhttp_msg_body.h
+ nhttp_msg_chunk_head.cc
+ nhttp_msg_chunk_head.h
+ nhttp_msg_chunk_body.cc
+ nhttp_msg_chunk_body.h
+ nhttp_msg_trailer.cc
+ nhttp_msg_trailer.h
+ nhttp_head_norm.cc
+ nhttp_head_norm.h
+ nhttp_str_to_code.cc
+ nhttp_str_to_code.h
+ nhttp_api.cc nhttp_api.h
nhttp_tables.cc
nhttp_module.cc
nhttp_module.h
- nhttp_testinput.cc
- nhttp_testinput.h
- nhttp_flowdata.cc
- nhttp_flowdata.h
- nhttp_scratchpad.h
+ nhttp_test_input.cc
+ nhttp_test_input.h
+ nhttp_flow_data.cc
+ nhttp_flow_data.h
+ nhttp_scratch_pad.h
nhttp_enum.h
+ nhttp_stream_splitter.cc
+ nhttp_stream_splitter.h
)
#if (STATIC_INSPECTORS)
file_list = \
nhttp_inspect.cc nhttp_inspect.h \
-nhttp_msgheader.cc nhttp_msgheader.h \
-nhttp_headnorm.cc nhttp_headnorm.h \
-nhttp_strtocode.cc nhttp_strtocode.h \
+nhttp_msg_section.cc nhttp_msg_section.h \
+nhttp_msg_head_shared.cc nhttp_msg_head_shared.h \
+nhttp_msg_head.cc nhttp_msg_head.h \
+nhttp_msg_body.cc nhttp_msg_body.h \
+nhttp_msg_chunk_head.cc nhttp_msg_chunk_head.h \
+nhttp_msg_chunk_body.cc nhttp_msg_chunk_body.h \
+nhttp_msg_trailer.cc nhttp_msg_trailer.h \
+nhttp_head_norm.cc nhttp_head_norm.h \
+nhttp_str_to_code.cc nhttp_str_to_code.h \
nhttp_api.cc nhttp_api.h \
nhttp_tables.cc \
nhttp_module.cc nhttp_module.h \
-nhttp_testinput.cc nhttp_testinput.h \
-nhttp_flowdata.cc nhttp_flowdata.h \
-nhttp_scratchpad.h nhttp_enum.h
+nhttp_test_input.cc nhttp_test_input.h \
+nhttp_flow_data.cc nhttp_flow_data.h \
+nhttp_stream_splitter.cc nhttp_stream_splitter.h \
+nhttp_scratch_pad.h nhttp_enum.h
#if STATIC_INSPECTORS
#include <sys/types.h>
#include "snort.h"
-#include "framework/parameter.h"
-#include "framework/module.h"
-#include "framework/inspector.h"
-#include "flow/flow.h"
+#include "target_based/sftarget_protocol_reference.h"
#include "nhttp_enum.h"
-#include "nhttp_flowdata.h"
-#include "nhttp_scratchpad.h"
#include "nhttp_module.h"
-#include "nhttp_strtocode.h"
-#include "nhttp_headnorm.h"
-#include "nhttp_msgheader.h"
-#include "nhttp_testinput.h"
-#include "nhttp_api.h"
#include "nhttp_inspect.h"
+#include "nhttp_api.h"
+
+int16_t NHttpApi::appProtocolId;
Module* NHttpApi::nhttp_mod_ctor() { return new NHttpModule; }
void NHttpApi::nhttp_init()
{
- printf("nhttp_init()\n");
NHttpFlowData::init();
+ appProtocolId = AddProtocolReference("nhttp");
}
void NHttpApi::nhttp_term()
{
- printf("nhttp_term()\n");
}
Inspector* NHttpApi::nhttp_ctor(Module* mod)
{
const NHttpModule* nhttpMod = (NHttpModule*) mod;
- printf("nhttp_ctor()\n");
return new NHttpInspect(nhttpMod->get_test_mode());
}
void NHttpApi::nhttp_dtor(Inspector* p)
{
- printf("nhttp_dtor()\n");
delete p;
}
void NHttpApi::nhttp_pinit()
{
- printf("nhttp_pinit()\n");
NHttpInspect::msgHead = new NHttpMsgHeader;
+ NHttpInspect::msgBody = new NHttpMsgBody;
+ NHttpInspect::msgChunkHead = new NHttpMsgChunkHead;
+ NHttpInspect::msgChunkBody = new NHttpMsgChunkBody;
+ NHttpInspect::msgTrailer = new NHttpMsgTrailer;
}
void NHttpApi::nhttp_pterm()
{
- printf("nhttp_pterm()\n");
delete NHttpInspect::msgHead;
}
void NHttpApi::nhttp_sum()
{
- printf("nhttp_sum()\n");
}
void NHttpApi::nhttp_stats()
{
- printf("nhttp_stats()\n");
}
void NHttpApi::nhttp_reset()
{
- printf("nhttp_reset()\n");
}
const InspectApi NHttpApi::nhttp_api =
NHttpApi::nhttp_dtor,
NHttpApi::nhttp_pinit,
NHttpApi::nhttp_pterm,
- nullptr, // ssn
+ nullptr,
NHttpApi::nhttp_sum,
NHttpApi::nhttp_stats,
NHttpApi::nhttp_reset
#ifndef NHTTP_API_H
#define NHTTP_API_H
+#include "framework/parameter.h"
+#include "framework/module.h"
+#include "framework/inspector.h"
+
class NHttpApi {
public:
static const InspectApi nhttp_api;
+ static int16_t appProtocolId;
private:
NHttpApi() = delete;
static Module* nhttp_mod_ctor();
static void nhttp_dtor(Inspector* p);
static void nhttp_pinit();
static void nhttp_pterm();
- static void nhttp_purge();
static void nhttp_sum();
static void nhttp_stats();
static void nhttp_reset();
#ifndef NHTTP_ENUM_H
#define NHTTP_ENUM_H
-#define GID_HTTP_CLIENT 119
-#define GID_HTTP_SERVER 120
+#define NHTTP_GID 119
namespace NHttpEnums {
+static const uint32_t MAXOCTETS = 63780;
+
// Field status codes for when no valid value is present in length or integer value. Positive values are actual length or field value.
typedef enum { STAT_NOTCONFIGURED=-5, STAT_NOTCOMPUTE=-4, STAT_INSUFMEMORY=-3, STAT_PROBLEMATIC=-2, STAT_NOTPRESENT=-1, STAT_EMPTYSTRING=0, STAT_OTHER=1 } StatusCode;
// Message originator--client or server
-typedef enum { SRC__NOTCOMPUTE=-4, SRC__PROBLEMATIC=-2, SRC_CLIENT=1, SRC_SERVER } SourceId;
+typedef enum { SRC__NOTCOMPUTE=-4, SRC_CLIENT=0, SRC_SERVER=1 } SourceId;
// Type of message section
-typedef enum { SEC__PROBLEMATIC=-2, SEC_HEADER = 2, SEC_BODY, SEC_CHUNK } SectionType;
+typedef enum { SEC__NOTCOMPUTE=-4, SEC__NOTPRESENT=-1, SEC_HEADER = 2, SEC_BODY, SEC_CHUNKHEAD, SEC_CHUNKBODY, SEC_TRAILER, SEC_DISCARD, SEC_CLOSED, SEC_ABORT } SectionType;
// List of possible HTTP versions. Version 0.9 omitted because 0.9 predates creation of the HTTP/X.Y token. There would never be a message with "HTTP/0.9"
typedef enum { VERS__NOTCOMPUTE=-4, VERS__PROBLEMATIC=-2, VERS__NOTPRESENT=-1, VERS__OTHER=1, VERS_1_0, VERS_1_1, VERS_2_0 } VersionId;
HEAD_CONTENT_LOCATION, HEAD_CONTENT_MD5, HEAD_CONTENT_RANGE, HEAD_CONTENT_TYPE, HEAD_EXPIRES, HEAD_LAST_MODIFIED, HEAD__MAXVALUE } HeaderId;
// All the infractions we might find while parsing and analyzing a message
-typedef enum { INF_TRUNCATED=0x1, /*INF_CANTFINDVERS=0x2,*/ /*INF_STARTTOOSHORT=0x4,*/ INF_BADREQLINE=0x8, INF_BADSTATLINE=0x10, INF_TOOMANYHEADERS=0x20,
- INF_BADHEADER=0x40, INF_BADSTATCODE=0x80, INF_UNKNOWNVERSION=0x100, INF_BADVERSION=0x200, INF_NOSCRATCH=0x400, INF_BADHEADERREPS=0x800, INF_BADHEADERDATA=0x1000 } Infraction;
+typedef enum { INF_TRUNCATED=0x1, INF_HEADTOOLONG=0x2, /*INF_STARTTOOSHORT=0x4,*/ INF_BADREQLINE=0x8, INF_BADSTATLINE=0x10, INF_TOOMANYHEADERS=0x20,
+ INF_BADHEADER=0x40, INF_BADSTATCODE=0x80, INF_UNKNOWNVERSION=0x100, INF_BADVERSION=0x200, INF_NOSCRATCH=0x400, INF_BADHEADERREPS=0x800, INF_BADHEADERDATA=0x1000,
+ INF_BROKENCHUNK=0x2000, INF_BADCHUNKSIZE=0x4000, INF_BADPHRASE= 0x8000 } Infraction;
// Formats for output from a header normalization function
-typedef enum { NORM_NULL, NORM_FIELD, NORM_INTEGER, NORM_ENUM, NORM_ENUMLIST } NormFormat;
+typedef enum { NORM_NULL, NORM_FIELD, NORM_INT64, NORM_ENUM64, NORM_ENUM64LIST } NormFormat;
// Transfer codings
typedef enum { TRANSCODE__OTHER=1, TRANSCODE_CHUNKED, TRANSCODE_IDENTITY, TRANSCODE_GZIP, TRANSCODE_COMPRESS, TRANSCODE_DEFLATE } Transcoding;
int32_t length = -1;
} field;
+typedef enum
+{
+ EVENT_ASCII = 1,
+ EVENT_DOUBLE_DECODE,
+ EVENT_U_ENCODE,
+ EVENT_BARE_BYTE,
+ EVENT_OBSOLETE_1,
+ EVENT_UTF_8,
+ EVENT_IIS_UNICODE,
+ EVENT_MULTI_SLASH,
+ EVENT_IIS_BACKSLASH,
+ EVENT_SELF_DIR_TRAV,
+ EVENT_DIR_TRAV,
+ EVENT_APACHE_WS,
+ EVENT_IIS_DELIMITER,
+ EVENT_NON_RFC_CHAR,
+ EVENT_OVERSIZE_DIR,
+ EVENT_LARGE_CHUNK,
+ EVENT_PROXY_USE,
+ EVENT_WEBROOT_DIR,
+ EVENT_LONG_HDR,
+ EVENT_MAX_HEADERS,
+ EVENT_MULTIPLE_CONTLEN,
+ EVENT_CHUNK_SIZE_MISMATCH,
+ EVENT_INVALID_TRUEIP,
+ EVENT_MULTIPLE_HOST_HDRS,
+ EVENT_LONG_HOSTNAME,
+ EVENT_EXCEEDS_SPACES,
+ EVENT_CONSECUTIVE_SMALL_CHUNKS,
+ EVENT_UNBOUNDED_POST,
+ EVENT_MULTIPLE_TRUEIP_IN_SESSION,
+ EVENT_BOTH_TRUEIP_XFF_HDRS,
+ EVENT_UNKNOWN_METHOD,
+ EVENT_SIMPLE_REQUEST,
+ EVENT_UNESCAPED_SPACE_URI,
+ EVENT_PIPELINE_MAX,
+ EVENT_ANOM_SERVER,
+ EVENT_INVALID_STATCODE,
+ EVENT_NO_CONTLEN,
+ EVENT_UTF_NORM_FAIL,
+ EVENT_UTF7,
+ EVENT_DECOMPR_FAILED,
+ EVENT_CONSECUTIVE_SMALL_CHUNKS_S,
+ EVENT_MSG_SIZE_EXCEPTION,
+ EVENT_JS_OBFUSCATION_EXCD,
+ EVENT_JS_EXCESS_WS,
+ EVENT_MIXED_ENCODINGS,
+ EVENT_MAXVALUE
+} EventSid;
+
#endif
--- /dev/null
+/****************************************************************************
+ *
+** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved.
+ * Copyright (C) 2003-2013 Sourcefire, Inc.
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+//
+// @author Tom Peters <thopeter@cisco.com>
+//
+// @brief Flow Data object used to store session information with Streams
+//
+
+#include <assert.h>
+#include <string.h>
+#include <sys/types.h>
+#include "snort.h"
+#include "nhttp_enum.h"
+#include "nhttp_flow_data.h"
+
+using namespace NHttpEnums;
+
+unsigned NHttpFlowData::nhttp_flow_id = 0;
+
+NHttpFlowData::NHttpFlowData() : FlowData(nhttp_flow_id) {}
+
+void NHttpFlowData::halfReset(SourceId sourceId) {
+ assert((sourceId == SRC_CLIENT) || (sourceId == SRC_SERVER));
+ dataLength[sourceId] = STAT_NOTPRESENT;
+ octetsExpected[sourceId] = STAT_NOTPRESENT;
+ bodySections[sourceId] = STAT_NOTPRESENT;
+ bodyOctets[sourceId] = STAT_NOTPRESENT;
+ numChunks[sourceId] = STAT_NOTPRESENT;
+ chunkSections[sourceId] = STAT_NOTPRESENT;
+ chunkOctets[sourceId] = STAT_NOTPRESENT;
+}
+
+
--- /dev/null
+/****************************************************************************
+ *
+** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved.
+ * Copyright (C) 2003-2013 Sourcefire, Inc.
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+//
+// @author Tom Peters <thopeter@cisco.com>
+//
+// @brief Converts protocol constant string to enum
+//
+
+#ifndef NHTTP_FLOW_DATA_H
+#define NHTTP_FLOW_DATA_H
+
+#include "stream/stream_api.h"
+
+class NHttpInspect;
+class NHttpMsgSection;
+class NHttpMsgHeader;
+class NHttpMsgBody;
+class NHttpMsgChunkHead;
+class NHttpMsgChunkBody;
+class NHttpMsgTrailer;
+class NHttpTestInput;
+
+class NHttpFlowData : public FlowData
+{
+public:
+ NHttpFlowData();
+ static unsigned nhttp_flow_id;
+ static void init() { nhttp_flow_id = FlowData::get_flow_id(); };
+
+ friend class NHttpInspect;
+ friend class NHttpMsgSection;
+ friend class NHttpMsgHeader;
+ friend class NHttpMsgBody;
+ friend class NHttpMsgChunkHead;
+ friend class NHttpMsgChunkBody;
+ friend class NHttpMsgTrailer;
+ friend class NHttpTestInput;
+ friend class NHttpStreamSplitter;
+private:
+ void halfReset(NHttpEnums::SourceId sourceId);
+
+ // StreamSplitter => Inspector (facts about the most recent message section)
+ NHttpEnums::SourceId sourceId = NHttpEnums::SRC__NOTCOMPUTE;
+ NHttpEnums::SectionType sectionType = NHttpEnums::SEC__NOTCOMPUTE;
+ bool tcpClose = false;
+ uint64_t infractions = 0;
+
+ // Inspector => StreamSplitter (facts about the message section that is coming next)
+ // 0 element refers to client request, 1 element refers to server response
+ NHttpEnums::SectionType typeExpected[2] = { NHttpEnums::SEC_HEADER, NHttpEnums::SEC_HEADER };
+ int64_t octetsExpected[2] = { NHttpEnums::STAT_NOTPRESENT, NHttpEnums::STAT_NOTPRESENT }; // expected size of the upcoming body or chunk body section
+
+ // Inspector's internal data about the current message
+ int64_t dataLength[2] = { NHttpEnums::STAT_NOTPRESENT, NHttpEnums::STAT_NOTPRESENT }; // length of the data from Content-Length field or chunk header.
+ int64_t bodySections[2] = { NHttpEnums::STAT_NOTPRESENT, NHttpEnums::STAT_NOTPRESENT }; // number of body sections seen so far including chunk headers
+ int64_t bodyOctets[2] = { NHttpEnums::STAT_NOTPRESENT, NHttpEnums::STAT_NOTPRESENT }; // number of user data octets seen so far (either regular body or chunks)
+ int64_t numChunks[2] = { NHttpEnums::STAT_NOTPRESENT, NHttpEnums::STAT_NOTPRESENT }; // number of chunks seen so far
+ int64_t chunkSections[2] = { NHttpEnums::STAT_NOTPRESENT, NHttpEnums::STAT_NOTPRESENT }; // number of sections seen so far in the current chunk
+ int64_t chunkOctets[2] = { NHttpEnums::STAT_NOTPRESENT, NHttpEnums::STAT_NOTPRESENT }; // number of user data octets seen so far in the current chunk including terminating CRLF
+};
+
+#endif
+
+
+
+
+
+
+
+
+
+
--- /dev/null
+/****************************************************************************
+ *
+** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved.
+ * Copyright (C) 2003-2013 Sourcefire, Inc.
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+//
+// @author Tom Peters <thopeter@cisco.com>
+//
+// @brief HeaderNormalizer class
+//
+
+
+#include <assert.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "snort.h"
+#include "snort_types.h"
+
+#include "nhttp_enum.h"
+#include "nhttp_str_to_code.h"
+#include "nhttp_head_norm.h"
+
+using namespace NHttpEnums;
+
+// This derivation removes embedded CRLFs (wrapping), omits leading and trailing linear white space, and replaces internal strings of <SP> and <LF> with a single <SP>
+int32_t HeaderNormalizer::deriveHeaderContent(const uint8_t *value, int32_t length, uint8_t *buffer) {
+ int32_t outLength = 0;
+ bool lastWhite = true;
+ for (int32_t k=0; k < length; k++) {
+ if ((value[k] == '\r') && (k+1 < length) && (value[k+1] == '\n')) k++;
+ else if ((value[k] != ' ') && (value[k] != '\t')) {
+ lastWhite = false;
+ buffer[outLength++] = value[k];
+ }
+ else if (!lastWhite) {
+ lastWhite = true;
+ buffer[outLength++] = ' ';
+ }
+ }
+ if ((outLength > 0) && (buffer[outLength - 1] == ' ')) outLength--;
+ return outLength;
+}
+
+void HeaderNormalizer::normalize(ScratchPad &scratchPad, uint64_t &infractions, HeaderId headId, const HeaderId headerNameId[], const field headerValue[], int32_t numHeaders,
+ field &resultField) const {
+ // This method normalizes the header field value for headId.
+ if (format == NORM_NULL) {
+ resultField.length = STAT_NOTCONFIGURED;
+ return;
+ }
+
+ // Search Header IDs from all the headers in this message. A critical issue is whether the header can be present more than once in a message. concatenateRepeats means the
+ // header can be present more than once. The standard normalization is to concatenate all the repeated field values into a comma-separated list. Otherwise there should not
+ // be more than one instance of this header. infractRepeats causes us to inspect for improper repeated headers. Regardless of whether we look for these extra values only
+ // the first value will be normalized.
+
+ int numMatches = 0;
+ int32_t bufferLength = 0;
+ int firstMatch = -1;
+ for (int k=0; k < numHeaders; k++) {
+ if (headerNameId[k] == HEAD__NOTCOMPUTE) break;
+ if (headerNameId[k] == headId) {
+ numMatches++;
+ if (numMatches == 1) firstMatch = k;
+ if ((numMatches == 1) || concatenateRepeats) bufferLength += headerValue[k].length;
+ if (!concatenateRepeats && !infractRepeats) break;
+ }
+ }
+ if (numMatches == 0) {
+ resultField.length = STAT_NOTPRESENT;
+ return;
+ }
+ if (infractRepeats && (numMatches >= 2)) infractions |= INF_BADHEADERREPS;
+
+ // The scratchPad provides the space to store the normalized value. We are allocating twice as much memory as we need to store the normalized field value. The raw field
+ // value will be copied into one half of the buffer. Concatenation and white space normalization happen during this step. Next a series of normalization functions will
+ // transform the value into final form. Each normalization copies the value from one half of the buffer to the other. Based on whether the number of normalization functions
+ // is odd or even, the initial placement in the buffer is chosen so that the final normalization leaves the field value at the front of the buffer. The buffer space actually
+ // used is locked down in the scratchPad. The remainder of the first half and all of the second half are returned to the scratchPad for future use.
+ if (concatenateRepeats) bufferLength += numMatches - 1; // allow space for concatenation commas
+ // Round up to multiple of eight so that both halves are 64-bit aligned.
+ // 200 is a "way too big" fudge factor to allow for modest expansion of field size during normalization. Needs improvement.
+ bufferLength += (8-bufferLength%8)%8 + 200;
+ uint8_t * const scratch = scratchPad.request(2*bufferLength);
+ if (scratch == nullptr) {
+ resultField.length = STAT_INSUFMEMORY;
+ return;
+ }
+
+ uint8_t * const frontHalf = scratch;
+ uint8_t * const backHalf = scratch + bufferLength;
+ uint8_t *working = (numNormalizers%2 == 0) ? frontHalf : backHalf;
+ int currMatch = firstMatch;
+ int32_t dataLength = 0;
+ for (int j=0; j < numMatches; j++) {
+ if (j >= 1) {
+ *working++ = ',';
+ dataLength++;
+ while (headerNameId[++currMatch] != headId);
+ }
+ int32_t growth = deriveHeaderContent(headerValue[currMatch].start, headerValue[currMatch].length, working);
+ working += growth;
+ dataLength += growth;
+ if (!concatenateRepeats) break;
+ }
+
+ for (int i=0; i < numNormalizers; i++) {
+ if (i%2 != numNormalizers%2) dataLength = normalizer[i](backHalf, dataLength, frontHalf, infractions, normArg[i]);
+ else dataLength = normalizer[i](frontHalf, dataLength, backHalf, infractions, normArg[i]);
+ if (dataLength <= 0) {
+ resultField.length = dataLength;
+ return;
+ }
+ }
+ resultField.start = scratch;
+ resultField.length = dataLength;
+ scratchPad.commit(dataLength);
+}
+
+// Collection of stock normalization functions. This will probably grow throughout the life of the software. New functions must follow the standard signature.
+// The void* at the end is for any special configuration data the function requires.
+
+int32_t normDecimalInteger(const uint8_t* inBuf, int32_t inLength, uint8_t* outBuf, uint64_t& infractions, const void *) {
+ // Limited to 18 decimal digits, not including leading zeros, to fit comfortably into int64_t
+ int64_t total = 0;
+ int nonLeadingZeros = 0;
+ for (int32_t k=0; k < inLength; k++) {
+ int value = inBuf[k] - '0';
+ if (nonLeadingZeros || (value != 0)) nonLeadingZeros++;
+ if (nonLeadingZeros > 18) {
+ infractions |= INF_BADHEADERDATA;
+ return STAT_PROBLEMATIC;
+ }
+ if ((value < 0) || (value > 9)) {
+ infractions |= INF_BADHEADERDATA;
+ return STAT_PROBLEMATIC;
+ }
+ total = total*10 + value;
+ }
+ ((int64_t*)outBuf)[0] = total;
+ return sizeof(int64_t);
+}
+
+
+int32_t norm2Lower(const uint8_t* inBuf, int32_t inLength, uint8_t *outBuf, uint64_t&, const void *) {
+ for (int32_t k=0; k < inLength; k++) {
+ outBuf[k] = ((inBuf[k] < 'A') || (inBuf[k] > 'Z')) ? inBuf[k] : inBuf[k] - ('A' - 'a');
+ }
+ return inLength;
+}
+
+
+int32_t normStrCode(const uint8_t* inBuf, int32_t inLength, uint8_t *outBuf, uint64_t&, const void *table) {
+ ((int64_t*)outBuf)[0] = strToCode(inBuf, inLength, (const StrCode*)table);
+ return sizeof(int64_t);
+}
+
+int32_t normSeqStrCode(const uint8_t* inBuf, int32_t inLength, uint8_t *outBuf, uint64_t&, const void *table) {
+ int32_t numCodes = 0;
+ const uint8_t* start = inBuf;
+ while (true) {
+ int32_t length;
+ for (length = 0; (start + length < inBuf + inLength) && (start[length] != ','); length++);
+ if (length == 0) ((uint32_t*)outBuf)[numCodes++] = STAT_EMPTYSTRING;
+ else ((int64_t*)outBuf)[numCodes++] = strToCode(start, length, (const StrCode*)table);
+ if (start + length >= inBuf + inLength) break;
+ start += length + 1;
+ }
+ return numCodes * sizeof(int64_t);
+}
+
+// Remove all space and tab characters (known as LWS or linear white space in the RFC)
+int32_t normRemoveLws(const uint8_t* inBuf, int32_t inLength, uint8_t *outBuf, uint64_t&, const void *) {
+ int32_t length = 0;
+ for (int32_t k = 0; k < inLength; k++) {
+ if ((inBuf[k] != ' ') && (inBuf[k] != '\t')) outBuf[length++] = inBuf[k];
+ }
+ return length;
+}
+
+
+
+
+
+
--- /dev/null
+/****************************************************************************
+ *
+** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved.
+ * Copyright (C) 2003-2013 Sourcefire, Inc.
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+//
+// @author Tom Peters <thopeter@cisco.com>
+//
+// @brief HeaderNormalizer class declaration
+//
+
+#ifndef NHTTP_HEAD_NORM_H
+#define NHTTP_HEAD_NORM_H
+
+#include "nhttp_scratch_pad.h"
+
+//-------------------------------------------------------------------------
+// HeaderNormalizer class
+// Strategies for normalizing HTTP header field values
+//-------------------------------------------------------------------------
+
+// Three normalization functions per HeaderNormalizer seems likely to be enough. Nothing subtle will break if you choose to expand it to four or more. Just a whole bunch of
+// signatures and initializers to update.
+// When defining a HeaderNormalizer don't leave holes in the normalizer list. E.g. if you have two normalizers they must be first and second. If you do first and third
+// instead it won't explode but the third one won't be used either.
+
+class HeaderNormalizer {
+public:
+ constexpr HeaderNormalizer(NHttpEnums::NormFormat _format, bool _concatenateRepeats, bool _infractRepeats, int32_t (*f1)(const uint8_t*, int32_t, uint8_t*, uint64_t&, const void*),
+ const void *f1Arg, int32_t (*f2)(const uint8_t*, int32_t, uint8_t*, uint64_t&, const void*), const void *f2Arg, int32_t (*f3)(const uint8_t*, int32_t, uint8_t*, uint64_t&,
+ const void*), const void *f3Arg) :
+ format(_format),
+ concatenateRepeats(_concatenateRepeats),
+ infractRepeats(_infractRepeats),
+ normalizer { f1, f2, f3 },
+ normArg { f1Arg, f2Arg, f3Arg },
+ numNormalizers((f1 != nullptr) + (f1 != nullptr)*(f2 != nullptr) + (f1 != nullptr)*(f2 != nullptr)*(f3 != nullptr)) {};
+ void normalize(ScratchPad &scratchPad, uint64_t &infractions, NHttpEnums::HeaderId headId, const NHttpEnums::HeaderId headerNameId[], const field headerName[], int32_t numHeaders,
+ field &resultField) const;
+ NHttpEnums::NormFormat getFormat() const {return format;};
+
+private:
+ static int32_t deriveHeaderContent(const uint8_t *value, int32_t length, uint8_t *buffer);
+
+ const NHttpEnums::NormFormat format;
+ const bool concatenateRepeats;
+ const bool infractRepeats;
+ int32_t (* const normalizer[3])(const uint8_t*, int32_t, uint8_t*, uint64_t&, const void*);
+ const void * normArg[3];
+ const int numNormalizers;
+};
+
+// Normalizer functions
+
+int32_t normDecimalInteger(const uint8_t*, int32_t, uint8_t*, uint64_t&, const void* notUsed);
+int32_t norm2Lower(const uint8_t*, int32_t, uint8_t*, uint64_t&, const void* notUsed);
+int32_t normStrCode(const uint8_t*, int32_t, uint8_t*, uint64_t&, const void*);
+int32_t normSeqStrCode(const uint8_t*, int32_t, uint8_t*, uint64_t&, const void*);
+int32_t normRemoveLws(const uint8_t*, int32_t, uint8_t*, uint64_t&, const void* notUsed);
+
+#endif
+
+
#include <stdexcept>
#include "snort.h"
-#include "framework/inspector.h"
-#include "flow/flow.h"
+#include "stream/stream_api.h"
#include "nhttp_enum.h"
-#include "nhttp_scratchpad.h"
-#include "nhttp_strtocode.h"
-#include "nhttp_headnorm.h"
-#include "nhttp_flowdata.h"
-#include "nhttp_msgheader.h"
-#include "nhttp_testinput.h"
+#include "nhttp_stream_splitter.h"
#include "nhttp_api.h"
#include "nhttp_inspect.h"
-const char* NHttpInspect::testInputFile = "nhttptestmsgs.txt";
-const char* NHttpInspect::testOutputPrefix = "nhttpresults/testcase";
+using namespace NHttpEnums;
+
THREAD_LOCAL NHttpMsgHeader* NHttpInspect::msgHead;
+THREAD_LOCAL NHttpMsgBody* NHttpInspect::msgBody;
+THREAD_LOCAL NHttpMsgChunkHead* NHttpInspect::msgChunkHead;
+THREAD_LOCAL NHttpMsgChunkBody* NHttpInspect::msgChunkBody;
+THREAD_LOCAL NHttpMsgTrailer* NHttpInspect::msgTrailer;
-NHttpInspect::NHttpInspect(bool _test_mode) : test_mode(_test_mode)
+NHttpInspect::NHttpInspect(bool test_mode)
{
- printf("NHttpInspect constructor()\n");
- if (test_mode) {
- testInput = new NHttpTestInput(testInputFile);
+ NHttpTestInput::test_mode = test_mode;
+ if (NHttpTestInput::test_mode) {
+ NHttpTestInput::testInput = new NHttpTestInput(testInputFile);
}
}
NHttpInspect::~NHttpInspect ()
{
- printf("NHttpInspect destructor()\n");
- if (test_mode) {
- delete testInput;
+ if (NHttpTestInput::test_mode) {
+ delete NHttpTestInput::testInput;
if (testOut) fclose(testOut);
}
}
bool NHttpInspect::enabled ()
{
- printf("NHttpInspect enabled()\n");
return true;
}
-void NHttpInspect::configure (SnortConfig *sc, const char*, char *args)
+bool NHttpInspect::configure (SnortConfig *)
{
- printf("NHttpInspect configure()\n");
+ return true;
}
-int NHttpInspect::verify(SnortConfig* sc)
+int NHttpInspect::verify(SnortConfig*)
{
- printf("NHttpInspect verify()\n");
return 0; // 0 = good, -1 = bad
}
void NHttpInspect::pinit()
{
- printf("NHttpInspect pinit()\n");
}
void NHttpInspect::pterm()
{
- printf("NHttpInspect pterm()\n");
}
void NHttpInspect::show(SnortConfig*)
{
- printf("NHttpInspect show()\n");
LogMessage("NHttpInspect\n");
}
void NHttpInspect::eval (Packet* p)
{
- printf("NHttpInspect eval()\n");
+ // Only packets from the StreamSplitter can be processed
+ if (!PacketHasPAFPayload(p)) return;
Flow *flow = p->flow;
NHttpFlowData* sessionData = (NHttpFlowData*)flow->get_application_data(NHttpFlowData::nhttp_flow_id);
- if (sessionData == nullptr) flow->set_application_data(sessionData = new NHttpFlowData);
-
- if (!test_mode) msgHead->loadMessage(p->data, p->dsize, sessionData);
+ assert(sessionData);
+
+ NHttpMsgSection *msgSect = nullptr;
+
+ if (!NHttpTestInput::test_mode) {
+ switch (sessionData->sectionType) {
+ case SEC_HEADER: msgSect = msgHead; break;
+ case SEC_BODY: msgSect = msgBody; break;
+ case SEC_CHUNKHEAD: msgSect = msgChunkHead; break;
+ case SEC_CHUNKBODY: msgSect = msgChunkBody; break;
+ case SEC_TRAILER: msgSect = msgTrailer; break;
+ case SEC_DISCARD: return;
+ default: assert(0); return;
+ }
+ msgSect->loadSection(p->data, p->dsize, sessionData);
+ }
else {
uint8_t *testBuffer;
- int32_t testLength;
- if ((testLength = testInput->ntiGet(&testBuffer, sessionData, testNumber)) > 0) {
- msgHead->loadMessage(testBuffer, testLength, sessionData);
+ uint16_t testLength;
+ if ((testLength = NHttpTestInput::testInput->toEval(&testBuffer, testNumber)) > 0) {
+ switch (sessionData->sectionType) {
+ case SEC_HEADER: msgSect = msgHead; break;
+ case SEC_BODY: msgSect = msgBody; break;
+ case SEC_CHUNKHEAD: msgSect = msgChunkHead; break;
+ case SEC_CHUNKBODY: msgSect = msgChunkBody; break;
+ case SEC_TRAILER: msgSect = msgTrailer; break;
+ case SEC_DISCARD: return;
+ default: assert(0); return;
+ }
+ msgSect->loadSection(testBuffer, testLength, sessionData);
}
else {
- printf("Out of test data.\n");
+ printf("Zero length test data.\n");
return;
}
}
+ msgSect->initSection();
+ msgSect->analyze();
+ msgSect->updateFlow();
+ msgSect->genEvents();
+ msgSect->legacyClients();
- msgHead->analyze();
-
- msgHead->genEvents();
-
- // Interface to the old Snort clients
- msgHead->oldClients();
-
- if (!test_mode) msgHead->printMessage(stdout);
+ if (!NHttpTestInput::test_mode) msgSect->printMessage(stdout);
else {
if (testNumber != fileTestNumber) {
if (testOut) fclose (testOut);
fileTestNumber = testNumber;
char fileName[100];
- sprintf(fileName, "%s%d.txt", testOutputPrefix, testNumber);
+ snprintf(fileName, sizeof(fileName), "%s%" PRIi64 ".txt", testOutputPrefix, testNumber);
if ((testOut = fopen(fileName, "w+")) == nullptr) throw std::runtime_error("Cannot open test output file");
}
- msgHead->printMessage(testOut);
+ msgSect->printMessage(testOut);
}
}
-
-
// NHttpInspect class
//-------------------------------------------------------------------------
+#include "framework/inspector.h"
+#include "nhttp_msg_head.h"
+#include "nhttp_msg_body.h"
+#include "nhttp_msg_chunk_head.h"
+#include "nhttp_msg_chunk_body.h"
+#include "nhttp_msg_trailer.h"
+#include "nhttp_stream_splitter.h"
+#include "nhttp_test_input.h"
+
+class NHttpApi;
+
class NHttpInspect : public Inspector {
public:
NHttpInspect(bool _test_mode);
~NHttpInspect();
- void configure(SnortConfig*, const char*, char *args);
+ bool configure(SnortConfig*);
int verify(SnortConfig*);
void show(SnortConfig*);
void eval(Packet*);
bool enabled();
void pinit();
void pterm();
+ NHttpStreamSplitter* get_splitter(bool isClientToServer) { return new NHttpStreamSplitter(isClientToServer); };
private:
friend NHttpApi;
static THREAD_LOCAL NHttpMsgHeader *msgHead;
+ static THREAD_LOCAL NHttpMsgBody *msgBody;
+ static THREAD_LOCAL NHttpMsgChunkHead *msgChunkHead;
+ static THREAD_LOCAL NHttpMsgChunkBody *msgChunkBody;
+ static THREAD_LOCAL NHttpMsgTrailer *msgTrailer;
// Test mode
- bool test_mode;
- static const char *testInputFile;
- static const char *testOutputPrefix;
- NHttpTestInput *testInput = nullptr;
+ const char *testInputFile = "nhttp_test_msgs.txt";
+ const char *testOutputPrefix = "nhttpresults/testcase";
FILE *testOut = nullptr;
int64_t testNumber = 0;
int64_t fileTestNumber = -1;
// @brief Module class for NHttpInspect
//
-
#include <assert.h>
#include <string.h>
#include <sys/types.h>
#include "snort.h"
-#include "framework/module.h"
#include "nhttp_enum.h"
#include "nhttp_module.h"
+NHttpModule::NHttpModule() : Module("nhttp_inspect", nhttpParams, nhttpEvents) {
+}
+
+
const Parameter NHttpModule::nhttpParams[] =
{{ "test_mode", Parameter::PT_BOOL, nullptr, "false", "read HTTP messages from text file" },
{ nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }};
bool NHttpModule::begin(const char*, int, SnortConfig*) {
- printf("NHttpModule begin()\n");
test_mode = false;
return true;
}
bool NHttpModule::end(const char*, int, SnortConfig*) {
- printf("NHttpModule end()\n");
return true;
}
bool NHttpModule::set(const char*, Value &val, SnortConfig*) {
- printf("NHttpModule set()\n");
if (val.is("test_mode")) {
test_mode = val.get_bool();
return true;
}
unsigned NHttpModule::get_gid() const {
- return GID_HTTP_CLIENT;
+ return NHTTP_GID;
}
#ifndef NHTTP_MODULE_H
#define NHTTP_MODULE_H
+#include "framework/module.h"
+
class NHttpModule : public Module
{
public:
- NHttpModule() : Module("nhttp_inspect", nhttpParams) { };
+ NHttpModule();
bool begin(const char*, int, SnortConfig*);
bool end(const char*, int, SnortConfig*);
bool set(const char*, Value&, SnortConfig*);
bool get_test_mode() const { return test_mode; };
private:
static const Parameter nhttpParams[];
+ static const RuleMap nhttpEvents[];
bool test_mode = false;
};
--- /dev/null
+/****************************************************************************
+ *
+** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved.
+ * Copyright (C) 2003-2013 Sourcefire, Inc.
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+//
+// @author Tom Peters <thopeter@cisco.com>
+//
+// @brief NHttpMsgBody class analyzes individual HTTP message bodies. Message chunks are handled in NHttpMsgChunk.
+//
+
+
+#include <assert.h>
+#include <string.h>
+#include <sys/types.h>
+#include <stdio.h>
+
+#include "snort.h"
+#include "nhttp_enum.h"
+#include "nhttp_msg_body.h"
+
+using namespace NHttpEnums;
+
+void NHttpMsgBody::loadSection(const uint8_t *buffer, const uint16_t bufSize, NHttpFlowData *sessionData_) {
+ NHttpMsgSection::loadSection(buffer, bufSize, sessionData_);
+
+ dataLength = sessionData->dataLength[sourceId];
+ bodySections = sessionData->bodySections[sourceId];
+ bodyOctets = sessionData->bodyOctets[sourceId];
+}
+
+void NHttpMsgBody::initSection() {
+ data.length = STAT_NOTCOMPUTE;
+}
+
+void NHttpMsgBody::analyze() {
+ bodySections++;
+ bodyOctets += length;
+ data.start = msgText;
+ data.length = length;
+
+ // The following statement tests for the case where streams underfulfilled flush due to a TCP connection close
+ if ((length < 16384) && (bodyOctets < dataLength)) tcpClose = true;
+ if (tcpClose && (bodyOctets < dataLength)) infractions |= INF_TRUNCATED;
+}
+
+void NHttpMsgBody::genEvents() {
+ if (infractions != 0) SnortEventqAdd(NHTTP_GID, EVENT_ASCII); // I'm just an example event
+}
+
+void NHttpMsgBody::printMessage(FILE *output) const {
+ NHttpMsgSection::printMessageTitle(output, "body");
+ fprintf(output, "Expected data length %" PRIi64 ", sections seen %" PRIi64 ", octets seen %" PRIi64 "\n", dataLength, bodySections, bodyOctets);
+ printInterval(output, "Data", data.start, data.length);
+ NHttpMsgSection::printMessageWrapup(output);
+}
+
+void NHttpMsgBody::updateFlow() const {
+ if (tcpClose) {
+ sessionData->typeExpected[sourceId] = SEC_CLOSED;
+ sessionData->halfReset(sourceId);
+ }
+ else if (bodyOctets < dataLength) {
+ // More body coming
+ sessionData->bodySections[sourceId] = bodySections;
+ sessionData->bodyOctets[sourceId] = bodyOctets;
+ }
+ else {
+ // End of message
+ sessionData->typeExpected[sourceId] = SEC_HEADER;
+ sessionData->halfReset(sourceId);
+ }
+}
+
+
+// Legacy support function. Puts message fields into the buffers used by old Snort.
+void NHttpMsgBody::legacyClients() const {
+ ClearHttpBuffers();
+ if (data.length > 0) SetHttpBuffer(HTTP_BUFFER_CLIENT_BODY, data.start, (unsigned)data.length);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
--- /dev/null
+/****************************************************************************
+ *
+** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved.
+ * Copyright (C) 2003-2013 Sourcefire, Inc.
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+//
+// @author Tom Peters <thopeter@cisco.com>
+//
+// @brief NHttpMsgBody class declaration
+//
+
+#ifndef NHTTP_MSG_BODY_H
+#define NHTTP_MSG_BODY_H
+
+#include "nhttp_msg_section.h"
+
+//-------------------------------------------------------------------------
+// NHttpMsgBody class
+//-------------------------------------------------------------------------
+
+class NHttpMsgBody : public NHttpMsgSection {
+public:
+ NHttpMsgBody() {};
+ void loadSection(const uint8_t *buffer, const uint16_t bufsize, NHttpFlowData *sessionData_);
+ void initSection();
+ void analyze();
+ void printMessage(FILE *output) const;
+ void genEvents();
+ void updateFlow() const;
+ void legacyClients() const;
+
+protected:
+ int64_t dataLength;
+ int64_t bodySections;
+ int64_t bodyOctets;
+
+ field data;
+};
+
+#endif
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
--- /dev/null
+/****************************************************************************
+ *
+** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved.
+ * Copyright (C) 2003-2013 Sourcefire, Inc.
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+//
+// @author Tom Peters <thopeter@cisco.com>
+//
+// @brief NHttpMsgChunkBody class analyzes data portion (not start line) of an HTTP chunk.
+//
+
+
+#include <assert.h>
+#include <string.h>
+#include <sys/types.h>
+#include <stdio.h>
+
+#include "snort.h"
+#include "nhttp_enum.h"
+#include "nhttp_msg_chunk_body.h"
+
+using namespace NHttpEnums;
+
+void NHttpMsgChunkBody::loadSection(const uint8_t *buffer, const uint16_t bufSize, NHttpFlowData *sessionData_) {
+ NHttpMsgBody::loadSection(buffer, bufSize, sessionData_);
+
+ numChunks = sessionData->numChunks[sourceId];
+ chunkSections = sessionData->chunkSections[sourceId];
+ chunkOctets = sessionData->chunkOctets[sourceId];
+}
+
+void NHttpMsgChunkBody::initSection() {
+ NHttpMsgBody::initSection();
+}
+
+void NHttpMsgChunkBody::analyze() {
+ bodySections++;
+ chunkOctets += length;
+ bodyOctets += length;
+ int termCrlfBytes = 0;
+ if (chunkOctets > dataLength) {
+ // Final <CR><LF> are not data and do not belong in octet total or data field
+ termCrlfBytes = chunkOctets - dataLength;
+ assert(termCrlfBytes <= 2);
+ bodyOctets -= termCrlfBytes;
+ // Check for correct CRLF termination. Beware the section might break just before chunk end.
+ if ( ! ( ((termCrlfBytes == 2) && (length >= 2) && (msgText[length-2] == '\r') && (msgText[length-1] == '\n')) ||
+ ((termCrlfBytes == 2) && (length == 1) && (msgText[length-1] == '\n')) ||
+ ((termCrlfBytes == 1) && (msgText[length-1] == '\r')) ) ) {
+ infractions |= INF_BROKENCHUNK;
+ }
+ }
+
+ data.start = msgText;
+ data.length = length - termCrlfBytes;
+
+ chunkSections++;
+ // The following statement tests for the case where streams underfulfilled flush due to a TCP connection close
+ if ((length < 16384) && (bodyOctets + termCrlfBytes < dataLength + 2)) tcpClose = true;
+ if (tcpClose) infractions |= INF_TRUNCATED;
+}
+
+void NHttpMsgChunkBody::genEvents() {
+ if (infractions != 0) SnortEventqAdd(NHTTP_GID, EVENT_ASCII); // I'm just an example event
+}
+
+void NHttpMsgChunkBody::printMessage(FILE *output) const {
+ NHttpMsgSection::printMessageTitle(output, "chunk body");
+ fprintf(output, "Expected chunk length %" PRIi64 ", cumulative sections %" PRIi64 ", cumulative octets %" PRIi64 "\n", dataLength, bodySections, bodyOctets);
+ fprintf(output, "cumulative chunk sections %" PRIi64 ", cumulative chunk octets %" PRIi64 "\n", chunkSections, chunkOctets);
+ printInterval(output, "Data", data.start, data.length);
+ NHttpMsgSection::printMessageWrapup(output);
+}
+
+void NHttpMsgChunkBody::updateFlow() const {
+ if (tcpClose) {
+ sessionData->typeExpected[sourceId] = SEC_CLOSED;
+ sessionData->halfReset(sourceId);
+ }
+ else if (chunkOctets < dataLength + 2) {
+ sessionData->bodySections[sourceId] = bodySections;
+ sessionData->bodyOctets[sourceId] = bodyOctets;
+ sessionData->chunkSections[sourceId] = chunkSections;
+ sessionData->chunkOctets[sourceId] = chunkOctets;
+ }
+ else {
+ sessionData->typeExpected[sourceId] = SEC_CHUNKHEAD;
+ sessionData->octetsExpected[sourceId] = STAT_NOTPRESENT;
+ sessionData->dataLength[sourceId] = STAT_NOTPRESENT;
+ sessionData->bodySections[sourceId] = bodySections;
+ sessionData->bodyOctets[sourceId] = bodyOctets;
+ sessionData->chunkSections[sourceId] = STAT_NOTPRESENT;
+ sessionData->chunkOctets[sourceId] = STAT_NOTPRESENT;
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
--- /dev/null
+/****************************************************************************
+ *
+** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved.
+ * Copyright (C) 2003-2013 Sourcefire, Inc.
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+//
+// @author Tom Peters <thopeter@cisco.com>
+//
+// @brief NHttpMsgChunkBody class declaration
+//
+
+#ifndef NHTTP_MSG_CHUNK_BODY_H
+#define NHTTP_MSG_CHUNK_BODY_H
+
+#include "nhttp_msg_body.h"
+
+//-------------------------------------------------------------------------
+// NHttpMsgChunkBody class
+//-------------------------------------------------------------------------
+
+class NHttpMsgChunkBody : public NHttpMsgBody {
+public:
+ NHttpMsgChunkBody() {};
+ void loadSection(const uint8_t *buffer, const uint16_t bufsize, NHttpFlowData *sessionData_);
+ void initSection();
+ void analyze();
+ void printMessage(FILE *output) const;
+ void genEvents();
+ void updateFlow() const;
+
+private:
+ int64_t numChunks;
+ int64_t chunkSections;
+ int64_t chunkOctets;
+};
+
+#endif
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
--- /dev/null
+/****************************************************************************
+ *
+** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved.
+ * Copyright (C) 2003-2013 Sourcefire, Inc.
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+//
+// @author Tom Peters <thopeter@cisco.com>
+//
+// @brief NHttpMsgChunkHead class analyzes header line for a chunk.
+//
+
+
+#include <assert.h>
+#include <string.h>
+#include <sys/types.h>
+#include <stdio.h>
+
+#include "snort.h"
+#include "nhttp_enum.h"
+#include "nhttp_msg_chunk_head.h"
+
+using namespace NHttpEnums;
+
+// Convert the hexadecimal chunk length.
+// RFC says that zero may be written with multiple digits "000000".
+// Arbitrary limit of 15 hex digits not including leading zeros ensures in a simple way against 64-bit overflow and should be
+// vastly bigger than any legitimate chunk.
+void NHttpMsgChunkHead::deriveChunkLength() {
+ if (chunkSize.length <= 0) {
+ dataLength = STAT_PROBLEMATIC;
+ infractions |= INF_BADCHUNKSIZE;
+ return;
+ }
+ dataLength = 0;
+ int nonLeadingZeros = 0;
+ for (int k=0; k < chunkSize.length; k++) {
+ if (nonLeadingZeros || (chunkSize.start[k] != '0')) nonLeadingZeros++;
+ if (nonLeadingZeros > 15) {
+ dataLength = STAT_PROBLEMATIC;
+ infractions |= INF_BADCHUNKSIZE;
+ return;
+ }
+
+ dataLength *= 16;
+ if ((chunkSize.start[k] >= '0') && (chunkSize.start[k] <= '9')) dataLength += chunkSize.start[k] - '0';
+ else if ((chunkSize.start[k] >= 'A') && (chunkSize.start[k] <= 'F')) dataLength += chunkSize.start[k] - 'A' + 10;
+ else if ((chunkSize.start[k] >= 'a') && (chunkSize.start[k] <= 'f')) dataLength += chunkSize.start[k] - 'a' + 10;
+ else {
+ dataLength = STAT_PROBLEMATIC;
+ infractions |= INF_BADCHUNKSIZE;
+ return;
+ }
+ }
+}
+
+void NHttpMsgChunkHead::loadSection(const uint8_t *buffer, const uint16_t bufSize, NHttpFlowData *sessionData_) {
+ NHttpMsgSection::loadSection(buffer, bufSize, sessionData_);
+
+ bodySections = sessionData->bodySections[sourceId];
+ numChunks = sessionData->numChunks[sourceId];
+}
+
+void NHttpMsgChunkHead::initSection() {
+ startLine.length = STAT_NOTCOMPUTE;
+ chunkSize.length = STAT_NOTCOMPUTE;
+ chunkExtensions.length = STAT_NOTCOMPUTE;
+}
+
+void NHttpMsgChunkHead::analyze() {
+ bodySections++;
+ // First section in a new chunk is just the start line.
+ numChunks++;
+ startLine.start = msgText;
+ if (!tcpClose) startLine.length = length - 2;
+ else startLine.length = findCrlf(startLine.start, length, false);
+ chunkSize.start = msgText;
+ // Start line format is chunk size in hex followed by optional semicolon and extensions field
+ for (chunkSize.length = 0; (chunkSize.length < startLine.length) && (startLine.start[chunkSize.length] != ';'); chunkSize.length++);
+ if (chunkSize.length == startLine.length) {
+ chunkExtensions.length = STAT_NOTPRESENT;
+ }
+ else if (chunkSize.length == startLine.length - 1) {
+ chunkExtensions.length = STAT_EMPTYSTRING;
+ }
+ else {
+ chunkExtensions.start = msgText + chunkSize.length + 1;
+ chunkExtensions.length = startLine.length - chunkSize.length - 1;
+ }
+ deriveChunkLength();
+ if (tcpClose) infractions |= INF_TRUNCATED;
+}
+
+void NHttpMsgChunkHead::genEvents() {
+ if (infractions != 0) SnortEventqAdd(NHTTP_GID, EVENT_ASCII); // I'm just an example event
+}
+
+void NHttpMsgChunkHead::printMessage(FILE *output) const {
+ NHttpMsgSection::printMessageTitle(output, "chunk header");
+ fprintf(output, "Chunk size: %" PRIi64 "\n", dataLength);
+ printInterval(output, "Chunk extensions", chunkExtensions.start, chunkExtensions.length);
+ NHttpMsgSection::printMessageWrapup(output);
+}
+
+void NHttpMsgChunkHead::updateFlow() const {
+ if (tcpClose) {
+ sessionData->typeExpected[sourceId] = SEC_CLOSED;
+ sessionData->halfReset(sourceId);
+ }
+ else if (dataLength > 0) {
+ sessionData->typeExpected[sourceId] = SEC_CHUNKBODY;
+ sessionData->octetsExpected[sourceId] = dataLength+2;
+ sessionData->bodySections[sourceId] = bodySections;
+ sessionData->numChunks[sourceId] = numChunks;
+ sessionData->dataLength[sourceId] = dataLength;
+ sessionData->chunkSections[sourceId] = 0;
+ sessionData->chunkOctets[sourceId] = 0;
+ }
+ else {
+ // This was zero-length last chunk, trailer comes next
+ sessionData->typeExpected[sourceId] = SEC_TRAILER;
+ sessionData->halfReset(sourceId);
+ }
+}
+
+
+// Legacy support function. Puts message fields into the buffers used by old Snort.
+void NHttpMsgChunkHead::legacyClients() const {
+ ClearHttpBuffers();
+ if (startLine.length > 0) SetHttpBuffer(HTTP_BUFFER_CLIENT_BODY, startLine.start, (unsigned)startLine.length);
+}
+
+
+
+
+
+
+
+
+
+
--- /dev/null
+/****************************************************************************
+ *
+** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved.
+ * Copyright (C) 2003-2013 Sourcefire, Inc.
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+//
+// @author Tom Peters <thopeter@cisco.com>
+//
+// @brief NHttpMsgChunkHead class declaration
+//
+
+#ifndef NHTTP_MSG_CHUNK_HEAD_H
+#define NHTTP_MSG_CHUNK_HEAD_H
+
+#include "nhttp_msg_section.h"
+
+//-------------------------------------------------------------------------
+// NHttpMsgChunkHead class
+//-------------------------------------------------------------------------
+
+class NHttpMsgChunkHead : public NHttpMsgSection {
+public:
+ NHttpMsgChunkHead() {};
+ void loadSection(const uint8_t *buffer, const uint16_t bufsize, NHttpFlowData *sessionData_);
+ void initSection();
+ void analyze();
+ void printMessage(FILE *output) const;
+ void genEvents();
+ void updateFlow() const;
+ void legacyClients() const;
+
+private:
+ void deriveChunkLength();
+
+ field startLine;
+ field chunkSize;
+ field chunkExtensions;
+
+ int64_t dataLength;
+ int64_t bodySections;
+ int64_t numChunks;
+};
+
+#endif
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
--- /dev/null
+/****************************************************************************
+ *
+** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved.
+ * Copyright (C) 2003-2013 Sourcefire, Inc.
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+//
+// @author Tom Peters <thopeter@cisco.com>
+//
+// @brief NHttpMsgHeader class analyzes individual HTTP message headers.
+//
+
+
+#include <assert.h>
+#include <string.h>
+#include <sys/types.h>
+#include <stdio.h>
+
+#include "snort.h"
+#include "nhttp_enum.h"
+#include "nhttp_test_input.h" // &&& temporary to support TCP close workaround
+#include "nhttp_msg_head.h"
+
+using namespace NHttpEnums;
+
+// Reinitialize everything derived in preparation for analyzing a new message
+void NHttpMsgHeader::initSection() {
+ NHttpMsgSharedHead::initSection();
+ startLine.length = STAT_NOTCOMPUTE;
+ version.length = STAT_NOTCOMPUTE;
+ versionId = VERS__NOTCOMPUTE;
+ method.length = STAT_NOTCOMPUTE;
+ methodId = METH__NOTCOMPUTE;
+ uri.length = STAT_NOTCOMPUTE;
+ statusCode.length = STAT_NOTCOMPUTE;
+ statusCodeNum = STAT_NOTCOMPUTE;
+ reasonPhrase.length = STAT_NOTCOMPUTE;
+}
+
+// All the header processing that is done for every message (i.e. not just-in-time) is done here.
+void NHttpMsgHeader::analyze() {
+ NHttpMsgSharedHead::analyze();
+ if (sourceId == SRC_CLIENT) {
+ parseRequestLine();
+ deriveMethodId();
+ }
+ else if (sourceId == SRC_SERVER) {
+ parseStatusLine();
+ deriveStatusCodeNum();
+ }
+ deriveVersionId();
+}
+
+// All we do here is separate the start line from the header fields.
+// It is so complicated because 1) there might not be any header fields and 2) the message may have been truncated by a TCP connection close.
+// The asserts are very useful in test mode because they pick up bad test case data that we are not designed to handle. Otherwise they should
+// never go off unless PAF is broken and feeding us bad stuff.
+void NHttpMsgHeader::parseWhole() {
+ startLine.start = msgText;
+ startLine.length = findCrlf(startLine.start, length, false);
+ // findCrtl() guarentees that either the start line is the whole message or there must be at least two more characters and the first two are <CR><LF>.
+ assert((length == startLine.length) || ((length >= startLine.length+2) && !memcmp(msgText + startLine.length, "\r\n", 2)));
+
+// &&& The else clause is a workaround for the lack of TCP close notification support in the framework.
+// &&& Eventually the contents of the if clause should become the only code and the if-statement should go away.
+if (NHttpTestInput::test_mode) {
+ // We trust PAF. !tcpClose guarentees that either there are exactly four more characters <CR><LF><CR><LF> or there are at least seven more characters
+ // with the first two being <CR><LF> and the last four being <CR><LF><CR><LF>.
+ assert(tcpClose ||
+ ((length == startLine.length+4) && !memcmp(msgText + startLine.length, "\r\n\r\n", 4)) ||
+ ((length >= startLine.length+7) && !memcmp(msgText + startLine.length, "\r\n", 2) && !memcmp(msgText + length - 4, "\r\n\r\n", 4)));
+}
+else {
+ // We trust PAF. Therefore if the invariants don't hold it must be because we got the leftovers after a TCP close.
+ // So we kludge the close notification as a workaround.
+ if ( ! ( ((length == startLine.length+4) && !memcmp(msgText + startLine.length, "\r\n\r\n", 4)) ||
+ ((length >= startLine.length+7) && !memcmp(msgText + startLine.length, "\r\n", 2) && !memcmp(msgText + length - 4, "\r\n\r\n", 4)) ) ) tcpClose = true;
+}
+
+ // The following if-else ladder puts the extremely common normal cases at the beginning and the rare pathological cases at the end
+ // Normal case with header fields
+ if (!tcpClose && (length >= startLine.length+7)) {
+ headers.start = msgText + startLine.length + 2;
+ headers.length = length - startLine.length - 6;
+ }
+ // Normal case no header fields (only a start line)
+ else if (!tcpClose) {
+ headers.length = STAT_NOTPRESENT;
+ }
+ // Normal case with header fields and TCP connection close
+ else if ((length >= startLine.length+7) && !memcmp(msgText+length-4, "\r\n\r\n", 4)) {
+ headers.start = msgText + startLine.length + 2;
+ headers.length = length - startLine.length - 6;
+ }
+ // Normal case no header fields and TCP connection close
+ else if ((length == startLine.length + 4) && !memcmp(msgText+length-2, "\r\n", 2)) {
+ headers.length = STAT_NOTPRESENT;
+ }
+ // Abnormal cases truncated by TCP connection close
+ else {
+ infractions |= INF_TRUNCATED;
+ // Either start line incomplete or start line complete but no leftover octets for anything else
+ if (length <= startLine.length+2) {
+ headers.length = STAT_NOTPRESENT;
+ }
+ // Start line complete followed by lone <CR>
+ else if ((length == startLine.length+3) && (msgText[length-1] == '\r')) {
+ headers.length = STAT_NOTPRESENT;
+ }
+ // Truncation occurred somewhere in the header fields
+ else {
+ headers.start = msgText + startLine.length + 2;
+ headers.length = length - startLine.length - 2;
+ // When present, remove partial <CR><LF><CR><LF> sequence from the very end
+ if ((length > startLine.length+6) && !memcmp(msgText+length-3, "\r\n\r", 3)) headers.length -= 3;
+ else if ((length > startLine.length+5) && !memcmp(msgText+length-2, "\r\n", 2)) headers.length -= 2;
+ else if ((length > startLine.length+4) && (msgText[length-1] == '\r')) headers.length -= 1;
+ }
+ }
+}
+
+void NHttpMsgHeader::parseRequestLine() {
+ // There should be exactly two spaces. One following the method and one before "HTTP/".
+ // Eventually we may need to cater to certain format errors, but for now exact match or treat as error.
+ // <method><SP><URI><SP>HTTP/X.Y
+ if (startLine.start[startLine.length-9] != ' ') {
+ // space before "HTTP" missing or in wrong place
+ infractions |= INF_BADREQLINE;
+ return;
+ }
+
+ int space = -1;
+ for (int32_t k=0; k < startLine.length-9; k++) {
+ if (startLine.start[k] == ' ') {
+ if (space == -1) space = k;
+ else {
+ // too many spaces
+ infractions |= INF_BADREQLINE;
+ return;
+ }
+ }
+ }
+ if ((space <= 0)) {
+ // no first space or a leading space
+ infractions |= INF_BADREQLINE;
+ return;
+ }
+
+ method.start = startLine.start;
+ method.length = space;
+ uri.start = startLine.start + method.length + 1;
+ uri.length = startLine.length - method.length - 10;
+ version.start = startLine.start + (startLine.length - 8);
+ version.length = 8;
+ assert (startLine.length == method.length + uri.length + version.length + 2);
+}
+
+void NHttpMsgHeader::parseStatusLine() {
+ // Eventually we may need to cater to certain format errors, but for now exact match or treat as error.
+ // HTTP/X.Y<SP>###<SP><text>
+ if ((startLine.length < 13) || (startLine.start[8] != ' ') || (startLine.start[12] != ' ')) {
+ infractions |= INF_BADSTATLINE;
+ return;
+ }
+ version.start = startLine.start;
+ version.length = 8;
+ statusCode.start = startLine.start + 9;
+ statusCode.length = 3;
+ reasonPhrase.start = startLine.start + 13;
+ reasonPhrase.length = startLine.length - 13;
+ for (int32_t k = 0; k < reasonPhrase.length; k++) {
+ if ((reasonPhrase.start[k] <= 31) || (reasonPhrase.start[k] >= 127)) {
+ // Illegal character in reason phrase
+ infractions |= INF_BADPHRASE;
+ break;
+ }
+ }
+ assert (startLine.length == version.length + statusCode.length + reasonPhrase.length + 2);
+}
+
+void NHttpMsgHeader::deriveStatusCodeNum() {
+ if (statusCode.length != 3) {
+ statusCodeNum = STAT_PROBLEMATIC;
+ return;
+ }
+ if ((statusCode.start[0] < '0') || (statusCode.start[0] > '9') || (statusCode.start[1] < '0') || (statusCode.start[1] > '9') ||
+ (statusCode.start[2] < '0') || (statusCode.start[2] > '9')) {
+ infractions |= INF_BADSTATCODE;
+ statusCodeNum = STAT_PROBLEMATIC;
+ return;
+ }
+ statusCodeNum = (statusCode.start[0] - '0') * 100 + (statusCode.start[1] - '0') * 10 + (statusCode.start[2] - '0');
+ if ((statusCodeNum < 100) || (statusCodeNum > 599)) {
+ infractions |= INF_BADSTATCODE;
+ }
+}
+
+void NHttpMsgHeader::deriveVersionId() {
+ if (version.length != 8) {
+ versionId = VERS__PROBLEMATIC;
+ return;
+ }
+ if (memcmp(version.start, "HTTP/", 5) || (version.start[6] != '.')) {
+ versionId = VERS__PROBLEMATIC;
+ infractions |= INF_BADVERSION;
+ }
+ else if ((version.start[5] == '1') && (version.start[7] == '1')) {
+ versionId = VERS_1_1;
+ }
+ else if ((version.start[5] == '1') && (version.start[7] == '0')) {
+ versionId = VERS_1_0;
+ }
+ else if ((version.start[5] == '2') && (version.start[7] == '0')) {
+ versionId = VERS_2_0;
+ }
+ else if ((version.start[5] >= '0') && (version.start[5] <= '9') && (version.start[7] >= '0') && (version.start[7] <= '9')) {
+ versionId = VERS__OTHER;
+ infractions |= INF_UNKNOWNVERSION;
+ }
+ else {
+ versionId = VERS__PROBLEMATIC;
+ infractions |= INF_BADVERSION;
+ }
+}
+
+void NHttpMsgHeader::deriveMethodId() {
+ methodId = (MethodId) strToCode(method.start, method.length, methodList);
+}
+
+void NHttpMsgHeader::genEvents() {
+ if (infractions != 0) SnortEventqAdd(NHTTP_GID, EVENT_ASCII); // I'm just an example event
+}
+
+void NHttpMsgHeader::printMessage(FILE *output) const {
+ NHttpMsgSection::printMessageTitle(output, "header");
+
+ if (sourceId != SRC__NOTCOMPUTE) fprintf(output, "Source Id: %d\n", sourceId);
+ if (versionId != VERS__NOTCOMPUTE) fprintf(output, "Version Id: %d\n", versionId);
+ if (methodId != METH__NOTCOMPUTE) fprintf(output, "Method Id: %d\n", methodId);
+ if (statusCodeNum != STAT_NOTCOMPUTE) fprintf(output, "Status Code Num: %d\n", statusCodeNum);
+ printInterval(output, "Reason Phrase", reasonPhrase.start, reasonPhrase.length);
+ printInterval(output, "URI", uri.start, uri.length);
+
+ NHttpMsgSharedHead::printMessageHead(output);
+ NHttpMsgSection::printMessageWrapup(output);
+}
+
+
+void NHttpMsgHeader::updateFlow() const {
+ const uint64_t disasterMask = INF_BADREQLINE | INF_BADSTATLINE | INF_BROKENCHUNK | INF_BADCHUNKSIZE;
+
+ // The following logic to determine body type is by no means the last word on this topic.
+ if (tcpClose) {
+ sessionData->typeExpected[sourceId] = SEC_CLOSED;
+ sessionData->halfReset(sourceId);
+ }
+ else if (infractions & disasterMask) {
+ sessionData->typeExpected[sourceId] = SEC_ABORT;
+ sessionData->halfReset(sourceId);
+ }
+ else if ((sourceId == SRC_SERVER) && ((statusCodeNum <= 199) || (statusCodeNum == 204) || (statusCodeNum == 304))) {
+ // No body allowed by RFC for these response codes
+ sessionData->typeExpected[sourceId] = SEC_HEADER;
+ sessionData->halfReset(sourceId);
+ }
+ // If there is a Transfer-Encoding header, see if the last of the encoded values is "chunked".
+ else if ( (headerValueNorm[HEAD_TRANSFER_ENCODING].length > 0) &&
+ ((*(int64_t *)(headerValueNorm[HEAD_TRANSFER_ENCODING].start + (headerValueNorm[HEAD_TRANSFER_ENCODING].length - 8))) == TRANSCODE_CHUNKED) ) {
+ // Chunked body
+ sessionData->typeExpected[sourceId] = SEC_CHUNKHEAD;
+ sessionData->bodySections[sourceId] = 0;
+ sessionData->bodyOctets[sourceId] = 0;
+ sessionData->numChunks[sourceId] = 0;
+ }
+ else if ((headerValueNorm[HEAD_CONTENT_LENGTH].length > 0) && (*(int64_t*)headerValueNorm[HEAD_CONTENT_LENGTH].start > 0)) {
+ // Regular body
+ sessionData->typeExpected[sourceId] = SEC_BODY;
+ sessionData->octetsExpected[sourceId] = *(int64_t*)headerValueNorm[HEAD_CONTENT_LENGTH].start;
+ sessionData->dataLength[sourceId] = *(int64_t*)headerValueNorm[HEAD_CONTENT_LENGTH].start;
+ sessionData->bodySections[sourceId] = 0;
+ sessionData->bodyOctets[sourceId] = 0;
+ }
+ else {
+ // No body
+ sessionData->typeExpected[sourceId] = SEC_HEADER;
+ sessionData->halfReset(sourceId);
+ }
+}
+
+// Legacy support function. Puts message fields into the buffers used by old Snort.
+void NHttpMsgHeader::legacyClients() const {
+ NHttpMsgSharedHead::legacyClients();
+ if (method.length > 0) SetHttpBuffer(HTTP_BUFFER_METHOD, method.start, (unsigned)method.length);
+ if (uri.length > 0) SetHttpBuffer(HTTP_BUFFER_RAW_URI, uri.start, (unsigned)uri.length);
+ if (uri.length > 0) SetHttpBuffer(HTTP_BUFFER_URI, uri.start, (unsigned)uri.length);
+ if (statusCode.length > 0) SetHttpBuffer(HTTP_BUFFER_STAT_CODE, statusCode.start, (unsigned)statusCode.length);
+ if (reasonPhrase.length > 0) SetHttpBuffer(HTTP_BUFFER_STAT_MSG, reasonPhrase.start, (unsigned)reasonPhrase.length);
+}
+
+
+
+
+
--- /dev/null
+/****************************************************************************
+ *
+** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved.
+ * Copyright (C) 2003-2013 Sourcefire, Inc.
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+//
+// @author Tom Peters <thopeter@cisco.com>
+//
+// @brief NHttpMsgHeader class declaration
+//
+
+#ifndef NHTTP_MSG_HEAD_H
+#define NHTTP_MSG_HEAD_H
+
+#include "nhttp_msg_head_shared.h"
+
+//-------------------------------------------------------------------------
+// NHttpMsgHeader class
+//-------------------------------------------------------------------------
+
+class NHttpMsgHeader: public NHttpMsgSharedHead {
+public:
+ NHttpMsgHeader() {};
+ void initSection();
+ void analyze();
+ void printMessage(FILE *output) const;
+ void genEvents();
+ void updateFlow() const;
+ void legacyClients() const;
+
+private:
+ // Code conversion tables are for turning token strings into enums.
+ static const StrCode methodList[];
+
+ // "Parse" methods cut things into pieces. "Derive" methods convert things into a new format such as an integer or enum token. "Normalize" methods convert
+ // things into a standard form without changing the underlying format.
+ void parseWhole();
+ void parseRequestLine();
+ void parseStatusLine();
+ void deriveStatusCodeNum();
+ void deriveVersionId();
+ void deriveMethodId();
+
+ // This is where all the derived values, extracted message parts, and normalized values are.
+ // Note that this is all scalars, buffer pointers, and buffer sizes. The actual buffers are in the message buffer (raw pieces) or the
+ // scratchPad (normalized pieces).
+ field startLine;
+ field version;
+ NHttpEnums::VersionId versionId;
+ field method;
+ NHttpEnums::MethodId methodId;
+ field uri;
+ field statusCode;
+ int32_t statusCodeNum;
+ field reasonPhrase;
+};
+
+#endif
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
--- /dev/null
+/****************************************************************************
+ *
+** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved.
+ * Copyright (C) 2003-2013 Sourcefire, Inc.
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+//
+// @author Tom Peters <thopeter@cisco.com>
+//
+// @brief NHttpMsgSharedHead virtual class rolls up all the common elements of header processing and trailer processing.
+//
+
+#include <assert.h>
+#include <string.h>
+#include <sys/types.h>
+#include <stdio.h>
+
+#include "snort.h"
+#include "nhttp_enum.h"
+#include "nhttp_msg_head_shared.h"
+
+using namespace NHttpEnums;
+
+// Reinitialize everything derived in preparation for analyzing a new message
+void NHttpMsgSharedHead::initSection() {
+ headers.length = STAT_NOTCOMPUTE;
+ numHeaders = STAT_NOTCOMPUTE;
+ for(int k = 0; k < MAXHEADERS; k++) {
+ headerLine[k].length = STAT_NOTCOMPUTE;
+ headerName[k].length = STAT_NOTCOMPUTE;
+ headerNameId[k] = HEAD__NOTCOMPUTE;
+ headerValue[k].length = STAT_NOTCOMPUTE;
+ }
+ for (int k = 1; k < HEAD__MAXVALUE; k++) {
+ headerValueNorm[k].length = STAT_NOTCOMPUTE;
+ }
+}
+
+// All the header processing that is done for every message (i.e. not just-in-time) is done here.
+void NHttpMsgSharedHead::analyze() {
+ parseWhole();
+ parseHeaderBlock();
+ parseHeaderLines();
+ for (int j=0; j < MAXHEADERS; j++) {
+ if (headerName[j].length <= 0) break;
+ deriveHeaderNameId(j);
+ }
+ for (int k=1; k <= numNorms; k++) {
+ headerNorms[k]->normalize(scratchPad, infractions, (HeaderId)k, headerNameId, headerValue, MAXHEADERS, headerValueNorm[k]);
+ }
+}
+
+// Divide up the block of header fields into individual header field lines.
+void NHttpMsgSharedHead::parseHeaderBlock() {
+ if (headers.length <= 0) return;
+ int32_t bytesused = 0;
+ numHeaders = 0;
+ while (bytesused < headers.length) {
+ headerLine[numHeaders].start = headers.start + bytesused;
+ headerLine[numHeaders].length = findCrlf(headerLine[numHeaders].start, headers.length - bytesused, true);
+ bytesused += headerLine[numHeaders++].length + 2;
+ if (numHeaders >= MAXHEADERS) {
+ break;
+ }
+ }
+ if (bytesused < headers.length) {
+ infractions |= INF_TOOMANYHEADERS;
+ }
+}
+
+// Divide header field lines into field name and field value
+void NHttpMsgSharedHead::parseHeaderLines() {
+ int colon;
+ for (int k=0; k < numHeaders; k++) {
+ for (colon=0; colon < headerLine[k].length; colon++) {
+ if (headerLine[k].start[colon] == ':') break;
+ }
+ if (colon < headerLine[k].length) {
+ headerName[k].start = headerLine[k].start;
+ headerName[k].length = colon;
+ headerValue[k].start = headerLine[k].start + colon + 1;
+ headerValue[k].length = headerLine[k].length - colon - 1;
+ }
+ else {
+ infractions |= INF_BADHEADER;
+ }
+ }
+}
+
+void NHttpMsgSharedHead::deriveHeaderNameId(int index) {
+ if (headerName[index].length <= 0) return;
+ // Normalize header field name to lower case for matching purposes
+ uint8_t *lowerName;
+ if ((lowerName = scratchPad.request(headerName[index].length)) == nullptr) {
+ infractions |= INF_NOSCRATCH;
+ headerNameId[index] = HEAD__INSUFMEMORY;
+ return;
+ }
+ int32_t lowerLength = norm2Lower(headerName[index].start, headerName[index].length, lowerName, infractions, nullptr);
+ headerNameId[index] = (HeaderId) strToCode(lowerName, lowerLength, headerList);
+}
+
+void NHttpMsgSharedHead::genEvents() {
+ if (infractions != 0) SnortEventqAdd(NHTTP_GID, EVENT_ASCII); // I'm just an example event
+}
+
+// Legacy support function. Puts message fields into the buffers used by old Snort.
+void NHttpMsgSharedHead::legacyClients() const {
+ ClearHttpBuffers();
+
+ if (headers.length > 0) SetHttpBuffer(HTTP_BUFFER_RAW_HEADER, headers.start, (unsigned)headers.length);
+ if (headers.length > 0) SetHttpBuffer(HTTP_BUFFER_HEADER, headers.start, (unsigned)headers.length);
+
+ for (int k=0; (headerNameId[k] != HEAD__NOTCOMPUTE) && (k < MAXHEADERS); k++) {
+ if (((headerNameId[k] == HEAD_COOKIE) && (sourceId == SRC_CLIENT)) || ((headerNameId[k] == HEAD_SET_COOKIE) && (sourceId == SRC_SERVER))) {
+ if (headerValue[k].length > 0) SetHttpBuffer(HTTP_BUFFER_RAW_COOKIE, headerValue[k].start, (unsigned)headerValue[k].length);
+ break;
+ }
+ }
+
+ if ((sourceId == SRC_CLIENT) && (headerValueNorm[HEAD_COOKIE].length > 0))
+ SetHttpBuffer(HTTP_BUFFER_COOKIE, headerValueNorm[HEAD_COOKIE].start, (unsigned)headerValueNorm[HEAD_COOKIE].length);
+ else if ((sourceId == SRC_SERVER) && (headerValueNorm[HEAD_SET_COOKIE].length > 0))
+ SetHttpBuffer(HTTP_BUFFER_COOKIE, headerValueNorm[HEAD_SET_COOKIE].start, (unsigned)headerValueNorm[HEAD_SET_COOKIE].length);
+}
+
+void NHttpMsgSharedHead::printMessageHead(FILE *output) const {
+ char titleBuf[100];
+ if (numHeaders != STAT_NOTCOMPUTE) fprintf(output, "Number of headers: %d\n", numHeaders);
+ for (int j=0; j < numHeaders && j < 200; j++) {
+ snprintf(titleBuf, sizeof(titleBuf), "Header ID %d", headerNameId[j]);
+ printInterval(output, titleBuf, headerValue[j].start, headerValue[j].length);
+ }
+ for (int k=1; k <= numNorms; k++) {
+ if (headerValueNorm[k].length != STAT_NOTPRESENT) {
+ snprintf(titleBuf, sizeof(titleBuf), "Normalized header %d", k);
+ printInterval(output, titleBuf, headerValueNorm[k].start, headerValueNorm[k].length, true);
+ }
+ }
+}
+
+
+
+
+
+
+
+
+
+
--- /dev/null
+/****************************************************************************
+ *
+** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved.
+ * Copyright (C) 2003-2013 Sourcefire, Inc.
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+//
+// @author Tom Peters <thopeter@cisco.com>
+//
+// @brief NHttpMsgSharedHead class declaration
+//
+
+#ifndef NHTTP_MSG_HEAD_SHARED_H
+#define NHTTP_MSG_HEAD_SHARED_H
+
+#include "nhttp_str_to_code.h"
+#include "nhttp_head_norm.h"
+#include "nhttp_msg_section.h"
+
+//-------------------------------------------------------------------------
+// NHttpMsgSharedHead class
+//-------------------------------------------------------------------------
+
+class NHttpMsgSharedHead: public NHttpMsgSection {
+public:
+ void initSection();
+ void analyze();
+ void genEvents();
+ void legacyClients() const;
+
+protected:
+ // Header normalization. There should be one of these for every different way we can process a header field value.
+ static const HeaderNormalizer NORMALIZER_NIL;
+ static const HeaderNormalizer NORMALIZER_BASIC;
+ static const HeaderNormalizer NORMALIZER_CAT;
+ static const HeaderNormalizer NORMALIZER_NOREPEAT;
+ static const HeaderNormalizer NORMALIZER_DECIMAL;
+ static const HeaderNormalizer NORMALIZER_TRANSCODE;
+
+ // Master table of known header fields and their normalization strategies.
+ static const HeaderNormalizer* const headerNorms[];
+ static const int32_t numNorms;
+
+ // Code conversion tables are for turning token strings into enums.
+ static const StrCode headerList[];
+ static const StrCode transCodeList[];
+
+ // "Parse" methods cut things into pieces. "Derive" methods convert things into a new format such as an integer or enum token. "Normalize" methods convert
+ // things into a standard form without changing the underlying format.
+ virtual void parseWhole() = 0;
+ void parseHeaderBlock();
+ void parseHeaderLines();
+ void deriveHeaderNameId(int index);
+
+ void printMessageHead(FILE *output) const;
+
+ // This is where all the derived values, extracted message parts, and normalized values are.
+ // Note that this is all scalars, buffer pointers, and buffer sizes. The actual buffers are in the message buffer (raw pieces) or the
+ // scratchPad (normalized pieces).
+ field headers;
+ static const int MAXHEADERS = 200; // I'm an arbitrary number. Need to revisit.
+ int32_t numHeaders;
+ field headerLine[MAXHEADERS];
+ field headerName[MAXHEADERS];
+ NHttpEnums::HeaderId headerNameId[MAXHEADERS];
+ field headerValue[MAXHEADERS];
+ field headerValueNorm[NHttpEnums::HEAD__MAXVALUE];
+};
+
+#endif
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
--- /dev/null
+/****************************************************************************
+ *
+** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved.
+ * Copyright (C) 2003-2013 Sourcefire, Inc.
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+//
+// @author Tom Peters <thopeter@cisco.com>
+//
+// @brief NHttpMsgSection class is virtual parent for classes that analyze individual HTTP message sections.
+//
+
+#include <assert.h>
+#include <string.h>
+#include <sys/types.h>
+#include <stdio.h>
+
+#include "snort.h"
+#include "nhttp_enum.h"
+#include "nhttp_msg_section.h"
+
+using namespace NHttpEnums;
+
+// Return the number of octets before the first CRLF. Return length if CRLF not present.
+//
+// wrappable: CRLF does not count in a header field when immediately followed by <SP> or <LF>. These whitespace characters
+// at the beginning of the next line indicate that the previous header has wrapped and is continuing on the next line.
+uint32_t NHttpMsgSection::findCrlf(const uint8_t* buffer, int32_t length, bool wrappable) {
+ for (int32_t k=0; k < length-1; k++) {
+ if ((buffer[k] == '\r') && (buffer[k+1] == '\n'))
+ if (!wrappable || (k+2 >= length) || ((buffer[k+2] != ' ') && (buffer[k+2] != '\t'))) return k;
+ }
+ return length;
+}
+
+// Load a new message section
+void NHttpMsgSection::loadSection(const uint8_t *buffer, const uint16_t bufsize, NHttpFlowData *sessionData_) {
+ length = bufsize;
+ memcpy(rawBuf, buffer, length);
+
+ sessionData = sessionData_;
+ infractions = sessionData->infractions;
+ sourceId = sessionData->sourceId;
+ tcpClose = sessionData->tcpClose;
+
+ scratchPad.reinit();
+}
+
+void NHttpMsgSection::printInterval(FILE *output, const char* name, const uint8_t *text, int32_t length, bool intVals) {
+ if ((length == STAT_NOTPRESENT) || (length == STAT_NOTCOMPUTE)) return;
+ int outCount = fprintf(output, "%s, length = %d, ", name, length);
+ if (length <= 0) {
+ fprintf(output, "\n");
+ return;
+ }
+ if (text == nullptr) {
+ fprintf(output, "nullptr\n");
+ return;
+ }
+ if (length > 1000) length = 1000; // Limit the amount of data printed
+ for (int k=0; k < length; k++) {
+ if ((text[k] >= 0x20) && (text[k] <= 0x7E)) fprintf(output, "%c", (char)text[k]);
+ else if (text[k] == 0xD) fprintf(output, "~");
+ else if (text[k] == 0xA) fprintf(output, "^");
+ else fprintf(output, "*");
+ if ((k%120 == (119 - outCount)) && (k+1 < length)) fprintf(output, "\n");
+ }
+
+ if (intVals && (length%8 == 0)) {
+ fprintf(output, "\nInteger values =");
+ for (int j=0; j < length; j+=8) {
+ fprintf(output, " %" PRIu64 , *((const uint64_t*)(text+j)));
+ }
+ }
+ fprintf(output, "\n");
+}
+
+void NHttpMsgSection::printMessageTitle(FILE *output, const char *title) const {
+ fprintf(output, "HTTP message %s:\n", title);
+ printInterval(output, "Input", msgText, length);
+}
+
+void NHttpMsgSection::printMessageWrapup(FILE *output) const {
+ fprintf(output, "Infractions: %lx, TCP Close: %s\n", infractions, tcpClose ? "True" : "False");
+ fprintf(output, "Interface to old clients. http_mask = %x.\n", http_mask);
+ for (int i=0; i < HTTP_BUFFER_MAX; i++) {
+ if ((1 << i) & http_mask) printInterval(output, http_buffer_name[i], http_buffer[i].buf, http_buffer[i].length);
+ }
+ fprintf(output, "\n");
+}
+
--- /dev/null
+/****************************************************************************
+ *
+** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved.
+ * Copyright (C) 2003-2013 Sourcefire, Inc.
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+//
+// @author Tom Peters <thopeter@cisco.com>
+//
+// @brief NHttpMsgSection class declaration
+//
+
+#ifndef NHTTP_MSG_SECTION_H
+#define NHTTP_MSG_SECTION_H
+
+#include "detection/detection_util.h"
+#include "nhttp_scratch_pad.h"
+#include "nhttp_flow_data.h"
+
+//-------------------------------------------------------------------------
+// NHttpMsgSection class
+//-------------------------------------------------------------------------
+
+class NHttpMsgSection {
+public:
+ virtual void loadSection(const uint8_t *buffer, const uint16_t bufsize, NHttpFlowData *sessionData_);
+ virtual ~NHttpMsgSection() = default;
+ virtual void initSection() = 0;
+ virtual void analyze() = 0;
+ virtual void printMessage(FILE *output) const = 0;
+ virtual void genEvents() = 0;
+ virtual void updateFlow() const = 0;
+ virtual void legacyClients() const = 0;
+
+protected:
+ // Convenience methods
+ static uint32_t findCrlf(const uint8_t* buffer, int32_t length, bool wrappable);
+ static void printInterval(FILE *output, const char* name, const uint8_t *text, int32_t length, bool intVals = false);
+ void printMessageTitle(FILE *output, const char *title) const;
+ void printMessageWrapup(FILE *output) const;
+
+ // The current strategy is to copy the entire raw message section into this object. Here it is.
+ int32_t length; // Length of the original message section in octets
+ uint8_t rawBuf[NHttpEnums::MAXOCTETS]; // The original HTTP message section octets
+ // This pointer is the handle for working with the original message data. It makes it simple to later replace rawBuf with some other form of storage
+ // such as the buffer in the packet structure or something dynamic. Const x 2 because this pointer should never change and people working with the
+ // original message should not be changing it. Only loading a completely new message into rawBuf should do that.
+ const uint8_t * const msgText = rawBuf;
+
+ // Working space and storage for all the derived fields. See scratchPad.h for usage instructions.
+ // Allocation size may be complete overkill. Need to revisit this.
+ uint64_t derivedBuf[NHttpEnums::MAXOCTETS/8];
+ NHttpFlowData* sessionData;
+ ScratchPad scratchPad {derivedBuf, NHttpEnums::MAXOCTETS/8};
+
+ // This is where all the derived values, extracted message parts, and normalized values are.
+ // Note that this is all scalars, buffer pointers, and buffer sizes. The actual buffers are in message buffer (raw pieces) or the
+ // scratchPad (normalized pieces).
+ uint64_t infractions;
+ bool tcpClose;
+ NHttpEnums::SourceId sourceId;
+};
+
+#endif
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
--- /dev/null
+/****************************************************************************
+ *
+** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved.
+ * Copyright (C) 2003-2013 Sourcefire, Inc.
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+//
+// @author Tom Peters <thopeter@cisco.com>
+//
+// @brief NHttpMsgTrailer class analyzes HTTP chunked message trailers.
+//
+
+#include <assert.h>
+#include <string.h>
+#include <sys/types.h>
+#include <stdio.h>
+
+#include "snort.h"
+#include "nhttp_enum.h"
+#include "nhttp_msg_trailer.h"
+
+using namespace NHttpEnums;
+
+void NHttpMsgTrailer::parseWhole() {
+ // The following if-else ladder puts the extremely common normal cases at the beginning and the rare pathological cases at the end
+ // Normal case with no trailer fields
+ if (!tcpClose && (length == 2)) {
+ headers.length = STAT_NOTPRESENT;
+ }
+ // Normal case with trailer fields
+ else if (!tcpClose) {
+ headers.start = msgText;
+ headers.length = length - 4;
+ }
+ // Normal case no trailer fields and TCP connection close
+ else if ((length == 2) && !memcmp(msgText, "\r\n", 2)) {
+ headers.length = STAT_NOTPRESENT;
+ }
+ // Normal case with trailer fields and TCP connection close
+ else if ((length >= 5) && !memcmp(msgText+length-4, "\r\n\r\n", 4)) {
+ headers.start = msgText;
+ headers.length = length - 4;
+ }
+ // Abnormal cases truncated by TCP connection close
+ else {
+ infractions |= INF_TRUNCATED;
+
+ // Lone <CR>
+ if ((length == 1) && (msgText[0] == '\r')) {
+ headers.length = STAT_NOTPRESENT;
+ }
+ // Truncation occurred somewhere in the trailer fields
+ else {
+ headers.start = msgText;
+ headers.length = length;
+ // When present, remove partial <CR><LF><CR><LF> sequence from the very end
+ if ((length > 4) && !memcmp(msgText+length-3, "\r\n\r", 3)) headers.length -= 3;
+ else if ((length > 3) && !memcmp(msgText+length-2, "\r\n", 2)) headers.length -= 2;
+ else if ((length > 2) && (msgText[length-1] == '\r')) headers.length -= 1;
+ }
+ }
+}
+
+void NHttpMsgTrailer::genEvents() {
+ if (infractions != 0) SnortEventqAdd(NHTTP_GID, EVENT_ASCII); // I'm just an example event
+}
+
+void NHttpMsgTrailer::printMessage(FILE *output) const {
+ NHttpMsgSection::printMessageTitle(output, "trailer");
+ NHttpMsgSharedHead::printMessageHead(output);
+ NHttpMsgSection::printMessageWrapup(output);
+}
+
+
+void NHttpMsgTrailer::updateFlow() const {
+ if (tcpClose) {
+ sessionData->typeExpected[sourceId] = SEC_CLOSED;
+ sessionData->halfReset(sourceId);
+ }
+ else {
+ sessionData->typeExpected[sourceId] = SEC_HEADER;
+ sessionData->halfReset(sourceId);
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
--- /dev/null
+/****************************************************************************
+ *
+** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved.
+ * Copyright (C) 2003-2013 Sourcefire, Inc.
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+//
+// @author Tom Peters <thopeter@cisco.com>
+//
+// @brief NHttpMsgTrailer class declaration
+//
+
+#ifndef NHTTP_MSG_TRAILER_H
+#define NHTTP_MSG_TRAILER_H
+
+#include "nhttp_msg_head_shared.h"
+
+//-------------------------------------------------------------------------
+// NHttpMsgTrailer class
+//-------------------------------------------------------------------------
+
+class NHttpMsgTrailer: public NHttpMsgSharedHead {
+public:
+ NHttpMsgTrailer() {};
+ void printMessage(FILE *output) const;
+ void genEvents();
+ void updateFlow() const;
+
+private:
+ void parseWhole();
+};
+
+#endif
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
--- /dev/null
+/****************************************************************************
+ *
+** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved.
+ * Copyright (C) 2003-2013 Sourcefire, Inc.
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+//
+// @author Tom Peters <thopeter@cisco.com>
+//
+// @brief ScratchPad class declaration
+//
+
+#ifndef NHTTP_SCRATCH_PAD_H
+#define NHTTP_SCRATCH_PAD_H
+
+
+//-------------------------------------------------------------------------
+// ScratchPad class
+// Memory management for NHttpMsgHeader class
+//-------------------------------------------------------------------------
+
+// Working space and storage for all the derived fields
+// Return value of request is 64-bit aligned and may be freely cast to uint64_t*
+// 1. request the maximum number of bytes you might need
+// 2. use what you need
+// 3. commit() what you actually used if you want to keep it
+// Anything you do not commit will be reused by the next request.
+
+class ScratchPad {
+public:
+ ScratchPad(uint64_t *buff, uint32_t length) : buffer(buff), capacity(length*8), used(0) {}; // Careful: length must be number of uint64_ts provided, not octets.
+ void reinit() {used = 0;};
+ uint8_t *request(uint32_t needed) const {return (needed <= capacity-used) ? (uint8_t*)(buffer+used) : nullptr;};
+ void commit(uint32_t taken) { used += taken + (8-(taken%8))%8; }; // round up to multiple of 8 to preserve alignment
+
+private:
+ uint64_t *buffer;
+ uint32_t capacity;
+ uint32_t used;
+};
+
+#endif
+
--- /dev/null
+/****************************************************************************
+ *
+** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved.
+ * Copyright (C) 2003-2013 Sourcefire, Inc.
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+//
+// @author Tom Peters <thopeter@cisco.com>
+//
+// @brief Converts token strings to enum codes
+//
+
+
+#include <assert.h>
+#include <string.h>
+#include <sys/types.h>
+#include "snort.h"
+#include "nhttp_enum.h"
+#include "nhttp_str_to_code.h"
+
+// Need to replace this simple algorithm for better performance
+int32_t strToCode(const uint8_t *text, int32_t textLen, const StrCode table[]) {
+ if (textLen <= 0) return NHttpEnums::STAT_PROBLEMATIC;
+ for (int32_t k=0; table[k].name != nullptr; k++) {
+ if ((textLen == (int) strlen(table[k].name)) && (memcmp(text, table[k].name, textLen) == 0)) {
+ return table[k].code;
+ }
+ }
+ return NHttpEnums::STAT_OTHER;
+}
+
--- /dev/null
+/****************************************************************************
+ *
+** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved.
+ * Copyright (C) 2003-2013 Sourcefire, Inc.
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+//
+// @author Tom Peters <thopeter@cisco.com>
+//
+// @brief Converts protocol constant string to enum
+//
+
+#ifndef NHTTP_STR_TO_CODE_H
+#define NHTTP_STR_TO_CODE_H
+
+struct StrCode {
+ int32_t code;
+ const char *name;
+};
+
+int32_t strToCode(const uint8_t *text, int32_t textLen, const StrCode table[]);
+
+#endif
+
--- /dev/null
+/****************************************************************************
+ *
+** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved.
+ * Copyright (C) 2003-2013 Sourcefire, Inc.
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+//
+// @author Tom Peters <thopeter@cisco.com>
+//
+// @brief HTTP Stream Splitter Class
+//
+
+#include <assert.h>
+#include <string.h>
+#include <sys/types.h>
+#include "snort.h"
+#include "nhttp_enum.h"
+#include "nhttp_test_input.h"
+#include "nhttp_stream_splitter.h"
+
+using namespace NHttpEnums;
+
+// Convenience function. All the housekeeping that must be done before we can return PAF_FLUSH to stream.
+void NHttpStreamSplitter::prepareFlush(NHttpFlowData* sessionData, uint32_t* flushOffset, SourceId sourceId, SectionType sectionType, bool tcpClose,
+ uint64_t infractions, uint32_t numOctets) {
+ sessionData->sourceId = sourceId;
+ sessionData->sectionType = sectionType;
+ sessionData->tcpClose = tcpClose;
+ sessionData->infractions = infractions;
+ if (tcpClose) sessionData->typeExpected[sourceId] = SEC_CLOSED;
+ if (!NHttpTestInput::test_mode) *flushOffset = numOctets;
+ else NHttpTestInput::testInput->pafFlush(numOctets);
+ octetsSeen = 0;
+ numCrlf = 0;
+}
+
+PAF_Status NHttpStreamSplitter::scan (Flow* flow, const uint8_t* data, uint32_t length, uint32_t flags, uint32_t* flushOffset) {
+ // When the system begins providing TCP connection close information this won't always be false. &&&
+ bool tcpClose = 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* sessionData = (NHttpFlowData*)flow->get_application_data(NHttpFlowData::nhttp_flow_id);
+ if (sessionData == nullptr) flow->set_application_data(sessionData = new NHttpFlowData);
+ assert(sessionData != nullptr);
+
+ SourceId sourceId = (flags & PKT_FROM_CLIENT) ? SRC_CLIENT : SRC_SERVER;
+
+ if (NHttpTestInput::test_mode) {
+ *flushOffset = length;
+ bool needBreak;
+ NHttpTestInput::testInput->toPaf((uint8_t*&)data, length, sourceId, tcpClose, needBreak);
+ if (length == 0) return PAF_FLUSH;
+ if (needBreak) flow->set_application_data(sessionData = new NHttpFlowData);
+ }
+
+ switch (SectionType type = sessionData->typeExpected[sourceId]) {
+ case SEC_HEADER:
+ case SEC_CHUNKHEAD:
+ case SEC_TRAILER:
+ pafMax = 63780;
+ for (uint32_t k = 0; k < length; k++) {
+ octetsSeen++;
+
+ // Count the alternating <CR> and <LF> characters we have seen in a row
+ if (((data[k] == '\r') && (numCrlf%2 == 0)) || ((data[k] == '\n') && (numCrlf%2 == 1))) numCrlf++;
+ else numCrlf = 0;
+
+ // Check header for leading CRLF because some 1.0 implementations put extra blank lines between messages. We tolerate this by quietly ignoring them.
+ // Trailer may also have leading CRLF. That is completely normal and means there is no trailer.
+ if (((type == SEC_HEADER) || (type == SEC_TRAILER)) && (numCrlf == 2) && (octetsSeen == 2)) {
+ prepareFlush(sessionData, flushOffset, sourceId, (type == SEC_HEADER) ? SEC_DISCARD : type, tcpClose && (k == length-1), 0, k+1);
+ return PAF_FLUSH;
+ }
+ // The chunk header section always ends with the first <CRLF>
+ else if ((type == SEC_CHUNKHEAD) && (numCrlf == 2)) {
+ prepareFlush(sessionData, flushOffset, sourceId, type, tcpClose && (k == length-1), 0, k+1);
+ return PAF_FLUSH;
+ }
+ // The header and trailer sections always end with the first double <CRLF>
+ else if (numCrlf == 4) {
+ prepareFlush(sessionData, flushOffset, sourceId, type, tcpClose && (k == length-1), 0, k+1);
+ return PAF_FLUSH;
+ }
+ // We must do this to protect ourself from buffer overrun.
+ else if (octetsSeen >= 63780) {
+ prepareFlush(sessionData, flushOffset, sourceId, type, tcpClose && (k == length-1), INF_HEADTOOLONG, k+1);
+ return PAF_FLUSH;
+ }
+ }
+ // Incomplete headers wait patiently for more data
+ if (!tcpClose) return PAF_SEARCH;
+ // Discard the oddball case where the new "message" starts with <CR><close>
+ else if ((octetsSeen == 1) && (numCrlf == 1)) prepareFlush(sessionData, flushOffset, sourceId, SEC_DISCARD, true, 0, length);
+ // TCP connection close, flush the partial header
+ else prepareFlush(sessionData, flushOffset, sourceId, type, true, INF_TRUNCATED, length);
+ return PAF_FLUSH;
+ case SEC_BODY:
+ case SEC_CHUNKBODY:
+ pafMax = 16384;
+ prepareFlush(sessionData, flushOffset, sourceId, type, tcpClose && (sessionData->octetsExpected[sourceId] >= length), 0, sessionData->octetsExpected[sourceId]);
+ return PAF_FLUSH;
+ case SEC_ABORT:
+ return PAF_ABORT;
+ default:
+ assert(0);
+ return PAF_ABORT;
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
--- /dev/null
+/****************************************************************************
+ *
+** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved.
+ * Copyright (C) 2003-2013 Sourcefire, Inc.
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+//
+// @author Tom Peters <thopeter@cisco.com>
+//
+// @brief NHTTP Stream Splitter class
+//
+
+#ifndef NHTTP_STREAM_SPLITTER_H
+#define NHTTP_STREAM_SPLITTER_H
+
+#include "stream/stream_splitter.h"
+#include "nhttp_flow_data.h"
+
+class NHttpStreamSplitter : public StreamSplitter {
+public:
+ NHttpStreamSplitter(bool isClientToServer) : StreamSplitter(isClientToServer) {};
+ PAF_Status scan(Flow* flow, const uint8_t* data, uint32_t length, uint32_t flags, uint32_t* flushOffset);
+ bool is_paf() { return true; };
+ uint32_t max() { return pafMax; };
+private:
+ void prepareFlush(NHttpFlowData* sessionData, uint32_t* flushOffset, NHttpEnums::SourceId sourceId, NHttpEnums::SectionType sectionType, bool tcpClose,
+ uint64_t infractions, uint32_t numOctets);
+
+ int64_t octetsSeen;
+ int numCrlf;
+ uint32_t pafMax = 63780;
+};
+
+#endif
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
//
// @author Tom Peters <thopeter@cisco.com>
//
-// @brief Static constant tables for converting protocol strings to enum codes. Members of HttpMsgHeader.
+// @brief Static constant tables for various conversions and normalizations
+//
+// Note: protocol correctness is not the decisive criterion for inclusion in the following tables. Just because something is "wrong" per RFC does
+// not mean that it cannot appear here. The goal is to recognize and inspect things that actually happen or might happen regardless of
+// whether they should happen.
//
-
#include <string.h>
#include <sys/types.h>
#include "snort.h"
-#include "flow/flow.h"
+#include "framework/module.h"
#include "nhttp_enum.h"
-#include "nhttp_scratchpad.h"
-#include "nhttp_strtocode.h"
-#include "nhttp_headnorm.h"
-#include "nhttp_flowdata.h"
-#include "nhttp_msgheader.h"
+#include "nhttp_str_to_code.h"
+#include "nhttp_head_norm.h"
+#include "nhttp_msg_head.h"
+#include "nhttp_module.h"
using namespace NHttpEnums;
{ METH_UPDATEREDIRECTREF, "UPDATEREDIRECTREF"},
{ 0, nullptr} };
-const StrCode NHttpMsgHeader::headerList[] =
+const StrCode NHttpMsgSharedHead::headerList[] =
{{ HEAD_CACHE_CONTROL, "cache-control"},
{ HEAD_CONNECTION, "connection"},
{ HEAD_DATE, "date"},
{ HEAD_LAST_MODIFIED, "last-modified"},
{ 0, nullptr} };
-const StrCode NHttpMsgHeader::transCodeList[] =
+const StrCode NHttpMsgSharedHead::transCodeList[] =
{{ TRANSCODE_CHUNKED, "chunked"},
{ TRANSCODE_IDENTITY, "identity"},
{ TRANSCODE_GZIP, "gzip"},
{ TRANSCODE_DEFLATE, "deflate"},
{ 0, nullptr} };
-const HeaderNormalizer NHttpMsgHeader::NORMALIZER_NIL {NORM_NULL, false, false, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr};
-const HeaderNormalizer NHttpMsgHeader::NORMALIZER_BASIC {NORM_FIELD, false, false, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr};
-const HeaderNormalizer NHttpMsgHeader::NORMALIZER_CAT {NORM_FIELD, true, false, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr};
-const HeaderNormalizer NHttpMsgHeader::NORMALIZER_NOREPEAT {NORM_FIELD, false, true, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr};
-const HeaderNormalizer NHttpMsgHeader::NORMALIZER_DECIMAL {NORM_INTEGER, false, true, normDecimalInteger, nullptr, nullptr, nullptr, nullptr, nullptr};
-const HeaderNormalizer NHttpMsgHeader::NORMALIZER_TRANSCODE {NORM_INTEGER, true, false, normSeqStrCode, NHttpMsgHeader::transCodeList, nullptr, nullptr, nullptr, nullptr};
+const HeaderNormalizer NHttpMsgSharedHead::NORMALIZER_NIL {NORM_NULL, false, false, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr};
+const HeaderNormalizer NHttpMsgSharedHead::NORMALIZER_BASIC {NORM_FIELD, false, false, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr};
+const HeaderNormalizer NHttpMsgSharedHead::NORMALIZER_CAT {NORM_FIELD, true, false, normRemoveLws, nullptr, nullptr, nullptr, nullptr, nullptr};
+const HeaderNormalizer NHttpMsgSharedHead::NORMALIZER_NOREPEAT {NORM_FIELD, false, true, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr};
+const HeaderNormalizer NHttpMsgSharedHead::NORMALIZER_DECIMAL {NORM_INT64, false, true, normDecimalInteger, nullptr, nullptr, nullptr, nullptr, nullptr};
+const HeaderNormalizer NHttpMsgSharedHead::NORMALIZER_TRANSCODE {NORM_ENUM64, true, false, normRemoveLws, nullptr, norm2Lower, nullptr, normSeqStrCode, NHttpMsgSharedHead::transCodeList};
-const HeaderNormalizer* const NHttpMsgHeader::headerNorms[HEAD__MAXVALUE] = { [0] = &NORMALIZER_NIL,
+const HeaderNormalizer* const NHttpMsgSharedHead::headerNorms[HEAD__MAXVALUE] = { [0] = &NORMALIZER_NIL,
[HEAD__OTHER] = &NORMALIZER_BASIC,
[HEAD_CACHE_CONTROL] = &NORMALIZER_BASIC,
[HEAD_CONNECTION] = &NORMALIZER_BASIC,
[HEAD_WARNING] = &NORMALIZER_BASIC,
[HEAD_ACCEPT] = &NORMALIZER_BASIC,
[HEAD_ACCEPT_CHARSET] = &NORMALIZER_BASIC,
- [HEAD_ACCEPT_ENCODING] = &NORMALIZER_BASIC,
- [HEAD_ACCEPT_LANGUAGE] = &NORMALIZER_BASIC,
+ [HEAD_ACCEPT_ENCODING] = &NORMALIZER_CAT,
+ [HEAD_ACCEPT_LANGUAGE] = &NORMALIZER_CAT,
[HEAD_AUTHORIZATION] = &NORMALIZER_BASIC,
[HEAD_EXPECT] = &NORMALIZER_BASIC,
[HEAD_FROM] = &NORMALIZER_BASIC,
[HEAD_LAST_MODIFIED] = &NORMALIZER_BASIC
};
- const int32_t NHttpMsgHeader::numNorms = HEAD__MAXVALUE-1;
+const int32_t NHttpMsgSharedHead::numNorms = HEAD__MAXVALUE-1;
+const RuleMap NHttpModule::nhttpEvents[] =
+{
+ { EVENT_ASCII, "(nhttp_inspect) ascii encoding" },
+ { EVENT_DOUBLE_DECODE, "(nhttp_inspect) double decoding attack" },
+ { EVENT_U_ENCODE, "(nhttp_inspect) u encoding" },
+ { EVENT_BARE_BYTE, "(nhttp_inspect) bare byte unicode encoding" },
+ { EVENT_OBSOLETE_1, "(nhttp_inspect) obsolete event--should not appear" },
+ { EVENT_UTF_8, "(nhttp_inspect) utf-8 encoding" },
+ { EVENT_IIS_UNICODE, "(nhttp_inspect) iis unicode codepoint encoding" },
+ { EVENT_MULTI_SLASH, "(nhttp_inspect) multi_slash encoding" },
+ { EVENT_IIS_BACKSLASH, "(nhttp_inspect) iis backslash evasion" },
+ { EVENT_SELF_DIR_TRAV, "(nhttp_inspect) self directory traversal" },
+ { EVENT_DIR_TRAV, "(nhttp_inspect) directory traversal" },
+ { EVENT_APACHE_WS, "(nhttp_inspect) apache whitespace (tab)" },
+ { EVENT_IIS_DELIMITER, "(nhttp_inspect) non-rfc http delimiter" },
+ { EVENT_NON_RFC_CHAR, "(nhttp_inspect) non-rfc defined char" },
+ { EVENT_OVERSIZE_DIR, "(nhttp_inspect) oversize request-uri directory" },
+ { EVENT_LARGE_CHUNK, "(nhttp_inspect) oversize chunk encoding" },
+ { EVENT_PROXY_USE, "(nhttp_inspect) unauthorized proxy use detected" },
+ { EVENT_WEBROOT_DIR, "(nhttp_inspect) webroot directory traversal" },
+ { EVENT_LONG_HDR, "(nhttp_inspect) long header" },
+ { EVENT_MAX_HEADERS, "(nhttp_inspect) max header fields" },
+ { EVENT_MULTIPLE_CONTLEN, "(nhttp_inspect) multiple content length" },
+ { EVENT_CHUNK_SIZE_MISMATCH, "(nhttp_inspect) chunk size mismatch detected" },
+ { EVENT_INVALID_TRUEIP, "(nhttp_inspect) invalid ip in true-client-ip/xff header" },
+ { EVENT_MULTIPLE_HOST_HDRS, "(nhttp_inspect) multiple host hdrs detected" },
+ { EVENT_LONG_HOSTNAME, "(nhttp_inspect) hostname exceeds 255 characters" },
+ { EVENT_EXCEEDS_SPACES, "(nhttp_inspect) header parsing space saturation" },
+ { EVENT_CONSECUTIVE_SMALL_CHUNKS, "(nhttp_inspect) client consecutive small chunk sizes" },
+ { EVENT_UNBOUNDED_POST, "(nhttp_inspect) post w/o content-length or chunks" },
+ { EVENT_MULTIPLE_TRUEIP_IN_SESSION, "(nhttp_inspect) multiple true ips in a session" },
+ { EVENT_BOTH_TRUEIP_XFF_HDRS, "(nhttp_inspect) both true_client_ip and xff hdrs present" },
+ { EVENT_UNKNOWN_METHOD, "(nhttp_inspect) unknown method" },
+ { EVENT_SIMPLE_REQUEST, "(nhttp_inspect) simple request" },
+ { EVENT_UNESCAPED_SPACE_URI, "(nhttp_inspect) unescaped space in http uri" },
+ { EVENT_PIPELINE_MAX, "(nhttp_inspect) too many pipelined requests" },
+ { EVENT_ANOM_SERVER, "(nhttp_inspect) anomalous http server on undefined http port" },
+ { EVENT_INVALID_STATCODE, "(nhttp_inspect) invalid status code in http response" },
+ { EVENT_NO_CONTLEN, "(nhttp_inspect) no content-length or transfer-encoding in http response" },
+ { EVENT_UTF_NORM_FAIL, "(nhttp_inspect) http response has utf charset which failed to normalize" },
+ { EVENT_UTF7, "(nhttp_inspect) http response has utf-7 charset" },
+ { EVENT_DECOMPR_FAILED, "(nhttp_inspect) http response gzip decompression failed" },
+ { EVENT_CONSECUTIVE_SMALL_CHUNKS_S, "(nhttp_inspect) server consecutive small chunk sizes" },
+ { EVENT_MSG_SIZE_EXCEPTION, "(nhttp_inspect) invalid content-length or chunk size" },
+ { EVENT_JS_OBFUSCATION_EXCD, "(nhttp_inspect) javascript obfuscation levels exceeds 1" },
+ { EVENT_JS_EXCESS_WS, "(nhttp_inspect) javascript whitespaces exceeds max allowed" },
+ { EVENT_MIXED_ENCODINGS, "(nhttp_inspect) multiple encodings within javascript obfuscated data" },
+ { 0, nullptr }
+};
--- /dev/null
+/****************************************************************************
+ *
+** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved.
+ * Copyright (C) 2003-2013 Sourcefire, Inc.
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+//
+// @author Tom Peters <thopeter@cisco.com>
+//
+// @brief Interface to file of test messages
+//
+
+
+#include <assert.h>
+#include <string.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdexcept>
+
+#include "nhttp_enum.h"
+#include "nhttp_test_input.h"
+
+using namespace NHttpEnums;
+
+bool NHttpTestInput::test_mode = false;
+NHttpTestInput *NHttpTestInput::testInput = nullptr;
+
+NHttpTestInput::NHttpTestInput(const char *fileName) {
+ if ((testDataFile = fopen(fileName, "r")) == nullptr) throw std::runtime_error("Cannot open test input file");
+}
+
+NHttpTestInput::~NHttpTestInput() {
+ fclose(testDataFile);
+}
+
+// Read from the test data file and present to PAF.
+// In the process we may need to skip comments, execute simple commands, and handle escape sequences.
+// The best way to understand this function is to read the comments at the top of the file of test cases.
+void NHttpTestInput::toPaf(uint8_t*& data, uint32_t &length, SourceId &sourceId, bool &tcpClose, bool &needBreak) {
+ // No new data presented to PAF while the last section is still being flushed.
+ if (flushed) {
+ length = 0;
+ return;
+ }
+
+ sourceId = lastSourceId;
+ tcpClose = false;
+ needBreak = false;
+
+ if (justFlushed) {
+ // PAF just flushed. There may or may not be leftover data in our buffer.
+ justFlushed = false;
+ data = msgBuf;
+ length = endOffset - flushOffset; // this is the leftover data
+ previousOffset = 0;
+ endOffset = length;
+ if (length > 0) {
+ // Must present unflushed leftovers to PAF again.
+ // If we don't take this opportunity to left justify our data in the buffer we may "walk" to the right until we run out of buffer space
+ memmove(msgBuf, msgBuf+flushOffset, length);
+ tcpClose = tcpAlreadyClosed;
+ return;
+ }
+ // If we reach here then PAF has already flushed all the data we have read so far.
+ tcpAlreadyClosed = false;
+ }
+ else {
+ // The data we gave PAF last time was not flushed
+ length = 0;
+ previousOffset = endOffset;
+ data = msgBuf + previousOffset;
+ }
+
+ // Now we need to move forward by reading more data from the file
+ int newChar;
+ typedef enum { WAITING, COMMENT, COMMAND, SECTION, ESCAPE, HEXVAL, FILLNUM, BRIDGE } State;
+ State state = WAITING;
+ bool ending;
+ int commandLength;
+ const int MaxCommand = 100;
+ char commandValue[MaxCommand];
+ uint8_t hexVal;
+ int numDigits;
+ uint32_t fillLength;
+
+ while ((newChar = getc(testDataFile)) != EOF) {
+ switch (state) {
+ case WAITING:
+ if (newChar == '#') state = COMMENT;
+ else if (newChar == '@') {
+ state = COMMAND;
+ commandLength = 0;
+ }
+ else if (newChar == '\\') {
+ state = ESCAPE;
+ ending = false;
+ }
+ else if (newChar != '\n') {
+ state = SECTION;
+ ending = false;
+ data[length++] = (uint8_t) newChar;
+ }
+ break;
+ case COMMENT:
+ if (newChar == '\n') state = WAITING;
+ break;
+ case COMMAND:
+ if (newChar == '\n') {
+ state = WAITING;
+ if ((commandLength == strlen("request")) && !memcmp(commandValue, "request", strlen("request"))) sourceId = lastSourceId = SRC_CLIENT;
+ else if ((commandLength == strlen("response")) && !memcmp(commandValue, "response", strlen("response"))) sourceId = lastSourceId = SRC_SERVER;
+ else if ((commandLength == strlen("break")) && !memcmp(commandValue, "break", strlen("break"))) needBreak = true;
+ else if ((commandLength == strlen("bodyend")) && !memcmp(commandValue, "bodyend", strlen("bodyend"))) {
+ termBytes[0] = 'x';
+ termBytes[1] = 'y';
+ }
+ else if ((commandLength == strlen("chunkend")) && !memcmp(commandValue, "chunkend", strlen("chunkend"))) {
+ termBytes[0] = '\r';
+ termBytes[1] = '\n';
+ }
+ else if (commandLength > 0) {
+ // Look for a test number
+ bool isNumber = true;
+ for (int k=0; (k < commandLength) && isNumber; k++) {
+ isNumber = (commandValue[k] >= '0') && (commandValue[k] <= '9');
+ }
+ if (isNumber) {
+ testNumber = 0;
+ for (int j=0; j < commandLength; j++) {
+ testNumber = testNumber * 10 + (commandValue[j] - '0');
+ }
+ }
+ }
+ }
+ else {
+ if (commandLength < MaxCommand) commandValue[commandLength++] = newChar;
+ else assert(0);
+ }
+ break;
+ case SECTION:
+ if (newChar == '\\') {
+ state = ESCAPE;
+ ending = false;
+ }
+ else if (newChar == '\n') {
+ if (ending) {
+ // Found the blank line that ends the section.
+ endOffset = previousOffset + length;
+ return;
+ }
+ ending = true;
+ }
+ else {
+ ending = false;
+ data[length++] = (uint8_t) newChar;
+ }
+ break;
+ case ESCAPE:
+ switch (newChar) {
+ case 'n': state = SECTION; data[length++] = '\n'; break;
+ case 'r': state = SECTION; data[length++] = '\r'; break;
+ case 't': state = SECTION; data[length++] = '\t'; break;
+ case 'B': state = BRIDGE; break;
+ case 'C': endOffset = previousOffset + length; return;
+ case 'T': tcpClose = tcpAlreadyClosed = true; endOffset = previousOffset + length; return;
+ case '#': state = SECTION; data[length++] = '#'; break;
+ case '@': state = SECTION; data[length++] = '@'; break;
+ case '\\': state = SECTION; data[length++] = '\\'; break;
+ case 'x':
+ case 'X': state = HEXVAL; hexVal = 0; numDigits = 0; break;
+ case '/': state = FILLNUM; fillLength = 0; break;
+ default: assert(0); state = SECTION; break;
+ }
+ break;
+ case BRIDGE:
+ if (newChar != '\n') {
+ state = SECTION;
+ data[length++] = (uint8_t) newChar;
+ }
+ break;
+ case HEXVAL:
+ if ((newChar >= '0') && (newChar <= '9')) hexVal = hexVal * 16 + (newChar - '0');
+ else if ((newChar >= 'a') && (newChar <= 'f')) hexVal = hexVal * 16 + 10 + (newChar - 'a');
+ else if ((newChar >= 'A') && (newChar <= 'F')) hexVal = hexVal * 16 + 10 + (newChar - 'A');
+ else assert(0);
+ if (++numDigits == 2) {
+ data[length++] = hexVal;
+ state = SECTION;
+ }
+ break;
+ case FILLNUM:
+ if (newChar != '/') {
+ assert((newChar >= '0') && (newChar <= '9'));
+ fillLength = fillLength * 10 + (newChar - '0');
+ assert(fillLength <= sizeof(msgBuf));
+ break;
+ }
+ else {
+ bodyData = true;
+ // Add the specified number of fill characters to the buffer and cut.
+ // Simulates body data at the end of a header segment or the first segment containing body data
+ // Don't allow a buffer overrun.
+ if (previousOffset + length + fillLength > sizeof(msgBuf)) assert(0);
+ for (uint32_t k=0; k < fillLength; k++) {
+ data[length++] = 'x';
+ }
+ endOffset = previousOffset + length;
+ return;
+ }
+ }
+ // If we have reached the configured maximum segment size automatically cut the data.
+ if (length >= mssLength) {
+ endOffset = previousOffset + length;
+ return;
+ }
+ // Don't allow a buffer overrun.
+ if (previousOffset + length >= sizeof(msgBuf)) assert(0);
+ }
+ // End-of-file. Return everything we have so far.
+ endOffset = previousOffset + length;
+ return;
+}
+
+void NHttpTestInput::pafFlush(uint32_t length) {
+ assert(!flushed);
+ flushed = true;
+ if (bodyData && (previousOffset + length >= endOffset)) {
+ fillOctets = length;
+ bodyData = false;
+ previousOffset = 0;
+ endOffset = 0;
+ flushOffset = 0;
+ }
+ else {
+ flushOffset = previousOffset + length;
+ }
+}
+
+
+uint16_t NHttpTestInput::toEval(uint8_t **buffer, int64_t &testNumber_) {
+ if (!flushed) return 0;
+ testNumber_ = testNumber;
+ *buffer = msgBuf;
+ if (fillOctets > 0) {
+ uint32_t fillOut = (fillOctets <= 16384) ? fillOctets : 16384;
+ for (uint32_t k = 0; k < fillOut; k++) {
+ msgBuf[k] = 'A' + k % 26;
+ }
+ fillOctets -= fillOut;
+ if (fillOctets == 0) {
+ if (fillOut > 1) msgBuf[fillOut-2] = termBytes[0];
+ msgBuf[fillOut-1] = termBytes[1];
+ flushed = false;
+ justFlushed = true;
+ }
+ else if (fillOctets == 1) {
+ msgBuf[fillOut-1] = termBytes[0];
+ }
+ return (uint16_t)fillOut;
+ }
+ flushed = false;
+ justFlushed = true;
+ return (uint16_t)flushOffset;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
--- /dev/null
+/****************************************************************************
+ *
+** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved.
+ * Copyright (C) 2003-2013 Sourcefire, Inc.
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+//
+// @author Tom Peters <thopeter@cisco.com>
+//
+// @brief Read test file and present test input to PAF
+//
+
+#ifndef NHTTP_TEST_INPUT_H
+#define NHTTP_TEST_INPUT_H
+
+class NHttpTestInput {
+public:
+ NHttpTestInput(const char *fileName);
+ ~NHttpTestInput();
+ void toPaf(uint8_t*& data, uint32_t &length, NHttpEnums::SourceId &sourceId, bool &tcpClose, bool &needBreak);
+ void pafFlush(uint32_t length);
+ uint16_t toEval(uint8_t **buffer, int64_t &testNumber);
+
+ // Hard for NHttpInspect and PAF to share these without making them "global". This is as good a place as any for them to live.
+ static bool test_mode;
+ static NHttpTestInput *testInput;
+private:
+ FILE *testDataFile;
+ uint8_t msgBuf[2 * NHttpEnums::MAXOCTETS];
+ bool flushed = false; // verifies alternation between PAF section flushing and eval() section processing
+ bool justFlushed = true; // toPaf() needs to do special post-flush processing before it resumes reading the file
+ bool tcpAlreadyClosed = false; // so we can keep presenting a TCP close to PAF until all the remaining octets are consumed and flushed
+ uint32_t flushOffset = 0; // last character in buffer that has been flushed by PAF and must go to eval().
+ uint32_t previousOffset = 0; // last character in the buffer shown to PAF but not flushed yet
+ uint32_t endOffset = 0; // last read character in the buffer
+ bool bodyData = false; // pending output of fill data
+ uint32_t fillOctets = 0; // remaining fill data
+ int64_t testNumber = 0; // for numbering test output files
+ uint32_t mssLength = 1460; // Maximum Segment Size. Needs to be enhanced to be set through a command.
+ NHttpEnums::SourceId lastSourceId = NHttpEnums::SRC_CLIENT; // current direction of traffic flow. Toggled by commands in file.
+ uint8_t termBytes[2] = { 'x', 'y' };
+};
+
+#endif
+
--- /dev/null
+# Enter HTTP test message text as you want it to be presented to PAF.
+#
+# The easiest way to format is to put a blank line between message sections so that each message section is its own "paragraph".
+# Within a paragraph the placement of new lines does not have any effect. Format a paragraph any way you are comfortable. Extra blank lines
+# between paragraphs also do not have any effect.
+#
+# Lines beginning with # are comments. Lines beginning with @ are commands. This does not apply to lines in the middle of a paragraph.
+#
+# Command lines are left justified, lower case, with no whitespace:
+# @break resets HTTP Inspect data structures and begins a new test. Use it liberally to prevent unrelated tests from interfering with each other.
+# @request and @response set the message direction. Applies to subsequent sections until changed.
+# @bodyend and @chunkend set the final two characters of a body or chunk to "xy" or "\r\n".
+# @<decimal number> sets the test number and hence the test output file name. Applies to subsequent sections until changed. Don't reuse numbers.
+#
+# Escape sequences begin with '\'. They may be used within a paragraph or to begin a paragraph.
+# \r - carriage return
+# \n - linefeed
+# \t - tab
+# \\ - backslash
+# \# - #
+# \@ - @
+# \xnn or \Xnn - where nn is a two-digit hexadecimal number. Insert an arbitrary 8-bit number as the next character. a-f and A-F are both acceptable.
+# \/nnn/ - where nnn is a variable-length decimal number. Insert nnn octets of fill data. The fill characters are 'A' - 'Z' with the final two bytes
+# "xy" for a normal body and "\r\n" for a chunk.
+# \B, \C, and \T - bridge, cut, and TCP close. See below.
+
+# The default procedure for separating data into segments for presentation to PAF is 1) whenever a paragraph ends (blank line) and 2) every MSS octets,
+# where MSS is currently a source code constant set to 1460. The \C escape forces an immediate separation point even in the middle of a paragraph. It is
+# useful for testing PAF where message segments often arrive in pieces. \B causes prevents the immediately following blank line(s) from causing a
+# separation point. Data resumes with the next non-newline character as if there was no gap. This allows PAF to be tested with the end of a section and
+# the beginning of the next section or message in the same packet. Commands and comments following \B are not allowed. Data must resume immediately
+# following the blank lines.
+#
+# \T functions as \C but causes the TCP connection closed indication to be set. It only makes sense at the end of a paragraph. \C\T simulates a data
+# packet subsequently followed by a close with no more data.
+
+
+# ***********************************************************************************************
+# Valid response start lines
+@1001
+@break
+@response
+HTTP/1.1 200 OK\r\n\r\n
+
+@1002
+@break
+@response
+HTTP/1.0 315 An example reason phrase with spaces\r\n\r\n
+
+@1003
+@break
+@response
+HTTP/2.0 100 minimum valid status code\r\n\r\n
+
+@1004
+@break
+@response
+HTTP/1.1 599 MAX\r\n\r\n
+
+@1005
+@break
+@response
+HTTP/1.1 502 I'm very long and contain punctuationabcdefghijklmnopqrstuvwxyz!@#$%^&*()-_=+`~![]{}1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghij
+|\\;:'",<.>/?klmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstu vwxyz123 4567890abcdefghijklmnopqrstuvwxyz1234567890abcd
+efghijklmnopqrstuvwxyz1234567890\r\n\r\n
+
+@1006
+@break
+@response
+HTTP/1.0 200 1\r\n\r\n
+
+@1007
+@break
+@response
+HTTP/1.1 301 \r\n\r\n
+
+@1008
+@break
+@response
+HTTP/1.1 560 \r\n\r\n
+
+@1010
+@break
+@response
+HTTP/1.1 111 \r\n\r\n
+
+@1011
+@break
+@response
+HTTP/1.1 234 qwertyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZ qwertyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZqwertyuiopasdfghjklzxcvbnmABCDE{}[]
+FGHIJKLMNOPQRSTUVWXYZ qwertyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZ qwertyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZ qwertyuiopasdfghjklzxcv
+bnmABCDEFGHIJKLMNOPQRSTUVWXYZqwertyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZqwertyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZqwertyuiopasdfghjklz
+xcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZqwertyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZqwertyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZqwertyuiopasdfghj
+klzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZqwertyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZqwertyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZqwertyuiopasdf
+ghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZ,.;'<>:"qwertyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZqwertyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZqwe
+rtyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZqwertyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZqwertyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZ
+\r\n\r\n
+
+
+# ***********************************************************************************************
+# Invalid response start lines
+
+@2001
+@break
+@response
+HTTP/2.0 099 status-code too small\r\n\r\n
+
+@2002
+@break
+@response
+HTTP/1.1 600 status-code too large\r\n\r\n
+
+@2003
+@break
+@response
+HTTP/1.1 879 x\r\n\r\n
+
+@2004
+@break
+@response
+HTTP/1.1 000 x----------------------------------------------------------s\r\n\r\n
+
+@2005
+@break
+@response
+HTTP/1.1 999 \r\n\r\n
+
+@2006
+@break
+@response
+HTTP/0.9 401 aaa\r\n\r\n
+
+@2007
+@break
+@response
+HTTP/1.2 344 \r\n\r\n
+
+@2008
+@break
+@response
+HTTP/2.1 200 400\r\n\r\n
+
+@2009
+@break
+@response
+HTTP/8.0 500 HTTP/1.1\r\n\r\n
+
+@2010
+@break
+@response
+HTOP/1.1 378 :"<>;'m,.\r\n\r\n
+
+@2011
+@break
+@response
+hTTP/1.1 400 OK\r\n\r\n
+
+@2012
+@break
+@response
+HTT5/1.1 200 OK\r\n\r\n
+
+@2013
+@break
+@response
+H TP/1.1 588 OK\r\n\r\n
+
+@2014
+@break
+@response
+HTTP\\1.1 101 OK\r\n\r\n
+
+@2015
+@break
+@response
+HTTP/1.A 700 OK\r\n\r\n
+
+@2016
+@break
+@response
+HTTP/1.a 303 OK \r\n\r\n
+
+@2017
+@break
+@response
+HTTP/k.1 400 OK\tok\r\n\r\n
+
+@2018
+@break
+@response
+HTTP/1;0 400 O K\r\n\r\n
+
+@2019
+@break
+@response
+HTTP/1.0 444 O\rK\r\n\r\n
+
+@2020
+@break
+@response
+HTTP/1.1 444 O\n K1234567890\r\n\r\n
+
+@2021
+@break
+@response
+HTTP/1.1 46 status code too short\r\n\r\n
+
+@2022
+@break
+@response
+HTTP/1.1 2001 status code too \x31 long\r\n\r\n
+
+@2023
+@break
+@response
+HTTP/1.1 +88 bad character in status code I\r\n\r\n
+
+@2024
+@break
+@response
+HTTP/1.1 5E8 bad character in status code\x20II\r\n\r\n
+
+@2025
+@break
+@response
+HTTP/1.1 56a bad character in status code III\r\n\r\n
+
+@2026
+@break
+@response
+HTTP/1.0 401 illegal nontext char\x00acter in reason phrase null\r\n\r\n
+
+@2027
+@break
+@response
+HTTP/1.0 401 illegal nontext character in reason\xffphrase delete\r\n\r\n
+
+@2028
+@break
+@response
+HTTP/1.0 401 \x08illegal nontext character in reason phrase backspace\r\n\r\n
+
+
+# ***********************************************************************************************
+# Valid request start lines
+@3001
+@break
+@request
+MKREDIRECTREF / HTTP/2.0\r\n\r\n
+
+@3002
+@break
+@request
+BIND 1234567890?abcdef HTTP/1.1\r\n\r\n
+
+@3003
+@break
+@request
+GET /test/hi-there.txt HTTP/1.0\r\n\r\n
+
+
+
+# ***********************************************************************************************
+# Invalid request start lines
+@4001
+@break
+@request
+
+
+# ***********************************************************************************************
+# Valid headers without body
+@5001
+@break
+@request
+GET /test/hi-there.htm HTTP/1.1\r\nAccept: text/*\r\nAccept-Language: en,fr\r\n\r\n
+
+# Base example followed by whitespace variations before and after header field values.
+# Varying whitespace in comma-separated fields.
+@5002
+@break
+@request
+GET / HTTP/1.1\r\nAccept: */*\r\nAccept-Language: en,en-us,de,is\r\nAccept-Encoding: gzip, deflate\r\nUser-Agent: Mozilla/4.0\r\n\t(compatible: MSIE 5.5;
+ Windows NT 5.0)\r\nHost: www.ft.com\r\nConnection: Keep-Alive\r\n\r\n
+
+@5003
+# Whitespace variations before header values
+@break
+@request
+GET / HTTP/1.1\r\nAccept:*/*\r\nAccept-Language: en, en-us,\tde, is\r\nAccept-Encoding:\tgzip,deflate\r\nUser-Agent:\t\t\t\t\tMozilla/4.0\r\n (compatible: MSIE 5.5;
+ Windows NT 5.0)\r\nHost:\t \t \t \t\twww.ft.com\r\nConnection: \t Keep-Alive\r\n\r\n
+
+@5004
+# Whitespace variations after header values
+@break
+@request
+GET / HTTP/1.1\r\nAccept: */* \r\nAccept-Language: en,\t\ten-us,\t de,\r\n is\t\r\nAccept-Encoding: gzip,\tdeflate \r\nUser-Agent: Mozilla/4.0\r\n\t \r\n\t(compatible: MSIE 5.5;
+ Windows NT 5.0)\t\t\t \t\t\r\nHost: www.ft.com \t\r\n\t\t\r\nConnection: Keep-Alive \t \t \t\r\n\r\n
+
+@5005
+@break
+@response
+HTTP/2.0 200 OK\r\nContent-type: text/plain\r\nTransfer-Encoding: gzip,identity,compress,deflate,foo,chunked\r\n\r\n
+
+@5006
+@break
+@response
+HTTP/2.0 200 OK\r\nContent-type: text/plain\r\nTransfer-Encoding: gzip, identity, compress, deflate, foo, chunked\r\n\r\n
+
+@5007
+@break
+@response
+HTTP/2.0 200 OK\r\nContent-type: text/plain\r\nTransfer-Encoding: gzip\r\nTransfer-Encoding: identity\r\nTransfer-Encoding: compress\r\nTransfer-Encoding: deflate
+\r\nTransfer-Encoding: foo\r\nTransfer-Encoding: chunked\r\n\r\n
+
+
+# ***********************************************************************************************
+# Invalid headers without body
+@6001
+@break
+@request
+
+
+# ***********************************************************************************************
+# Valid Content-Length and body
+@7001
+@break
+@bodyend
+@response
+HTTP/1.1 200 OK\r\nContent-type: \ttext/plain\t\r\nContent-LENGTH: 19\r\n\r\n
+\/19/
+
+@7002
+@break
+@bodyend
+@response
+HTTP/1.1 200 OK\r\nContent-type: \ttext/plain\t\r\nContent-LENGTH: 19\r\n\r\n
+\/10/
+
+@7003
+@break
+@bodyend
+@response
+HTTP/1.1 200 OK\r\nContent-type: \ttext/plain\t\r\nContent-LENGTH: 19\r\n\r\n
+
+\/10/
+
+@7004
+@break
+@bodyend
+@response
+HTTP/1.1 200 OK\r\nContent-type: \ttext/plain\t\r\nContent-LENGTH: 19\r\n\r\n
+
+\/19/
+
+@7005
+@break
+@bodyend
+@response
+HTTP/1.1 200 OK\r\n
+Transfer-Encoding: identity\r\n
+CoNtEnT-lEnGtH:16382\r\n
+Content-type: text/plain\r\n
+\r\n
+\/800/
+
+@7006
+@break
+@bodyend
+@request
+POST /body/in/a/request/ HTTP/1.1\r\n
+Content-Length:16383\r\n
+\r\n
+
+\/1300/
+
+@7007
+@break
+@bodyend
+@request
+POST /body/in/a/request/ HTTP/1.1\r\n
+Content-Length:16384\r\n
+\r\n
+\/1/
+
+@7008
+@break
+@bodyend
+@response
+HTTP/1.0 408 barely too big for one section\r\n
+Content-Length:16385\r\n
+\r\n
+
+\/1/
+
+@7009
+@break
+@bodyend
+@response
+HTTP/1.1 408 barely too big for two sections\r\n
+Content-Length:32772\r\n
+\r\n
+\/2/
+
+@7010
+@break
+@bodyend
+@response
+HTTP/1.1 408 more than eight sections\r\n
+Content-Length:133072\r\n
+\r\n
+
+\/2/
+
+# ***********************************************************************************************
+# Invalid Content-Length
+@8001
+@break
+@response
+HTTP/2.0 200 Illegal zero length\r\nContent-type: \ttext/plain\t\r\nContent-LENGTH: 0\r\n\r\n
+
+@8002
+@break
+@response
+HTTP/1.1 200 Silly gigantic length\r\nContent-type: \ttext/plain\t\r\nContent-LENGTH: 12345678901
+23456789\r\n\r\n
+
+
+# ***********************************************************************************************
+# Valid chunks
+# Remember chunk lengths are required to be specified in hex
+@9001
+@break
+@chunkend
+@response
+HTTP/1.1 208 Example with chunks\r\nTransfer-Encoding: chunked\r\n\r\n
+
+64\r\n
+\/100/
+
+8A\r\n
+\/60/
+
+e6\r\n
+
+\/1/
+
+0\r\n
+
+\r\n
+
+@9002
+@break
+@chunkend
+@request
+POST /request/with/chunks HTTP/1.1\r\n
+Content-Type: text/plain\r\n
+Transfer-Encoding: compress\r\n
+Via: 1.1 proxy3.company.com\r\n
+tRaNsFeR-eNcO\CdInG: cHuNkEd \r\n
+Accept: *\r\n
+\r\n
+
+1C320\r\n
+\/1400/
+
+1F40; testing-the-extension-feature\r\n
+\/2/
+
+13FFE; \r\n
+
+\/1460/
+
+4000;\r\n
+\/1460/
+
+3FFF;\r\n
+
+\/2/
+
+3FFE\r\n
+\/1454/
+
+4001;\r\n
+\/1/
+
+00000;chunkextension=3\r\n
+
+date: Sun, 01 Oct 2000 23:25:17 GMT\r\n
+X-madeupdate: Mon, 02 Oct 2000 23:25:17 GMT\r\n
+Accept-Language: en, de\r\n
+Accept-Language: is\r\n
+\r\n
+
+# ***********************************************************************************************
+# Invalid chunks
+@10001
+@break
+@request
+
+
+
+# ***********************************************************************************************
+# Valid trailers
+@11001
+@break
+@request
+
+
+
+# ***********************************************************************************************
+# Invalid trailers
+@12001
+@break
+@request
+
+
+
+# ***********************************************************************************************
+# Valid request-response pairs
+@13001
+@break
+@request
+
+
+
+
+
+
+
+