* --lua can only be used in addition to, not in place of, a -c config.
Ideally, --lua could be used in lieu of -c.
+* Rule line numbers provided with syntax error messages are off by one. The first rule is
+ unnumbered, the second rule is one, etc. See nhttp_inspect/detection_buffers/bad_rules/expected
+ for an example.
+
==== Rules
}
#define SEARCH_BUFFER(ibt, pmt, cnt) \
- if ( gadget->get_buf(ibt, p, buf) ) \
+ if ( gadget->get_fp_buf(ibt, p, buf) ) \
{ \
if ( Mpse* so = port_group->mpse[pmt] ) \
SEARCH_DATA(buf.data, buf.len, cnt) \
{
InspectionBuffer buf;
+ // FIXIT-M should this be converted to get_fp_buf()?
if ( p->flow and p->flow->gadget and
p->flow->gadget->get_buf(buf.IBT_ALT, p, buf) )
{
virtual bool get_buf(unsigned /*id*/, Packet*, InspectionBuffer&)
{ return false; }
+ virtual bool get_fp_buf(InspectionBuffer::Type ibt, Packet* p, InspectionBuffer& bf)
+ { return get_buf(ibt, p, bf); }
+
// IT_SERVICE only
virtual class StreamSplitter* get_splitter(bool to_server);
#include "pp_raw_buffer_iface.h"
#include "pp_stream_splitter_iface.h"
+// FIXIT-H needs to be updated for addition of get_fp_buf()
template<typename T>
static inline bool get_buf(
Inspector& i, T v, Packet& p, std::string& rb)
{
para_list.reset();
sub_id = 0;
+ form = 0;
switch (buffer_index)
{
case NHTTP_BUFFER_URI:
case NHTTP_BUFFER_STAT_MSG:
case NHTTP_BUFFER_VERSION:
case NHTTP_BUFFER_METHOD:
- inspect_section = IS_START;
- break;
case NHTTP_BUFFER_HEADER:
case NHTTP_BUFFER_RAW_HEADER:
case NHTTP_BUFFER_COOKIE:
case NHTTP_BUFFER_RAW_COOKIE:
- inspect_section = IS_HEADER;
+ case NHTTP_BUFFER_RAW_REQUEST:
+ case NHTTP_BUFFER_RAW_STATUS:
+ inspect_section = IS_DETECTION;
break;
case NHTTP_BUFFER_CLIENT_BODY:
inspect_section = IS_BODY;
if (sub_id == STAT_OTHER)
ParseError("Unrecognized header field name");
}
+ else if (v.is("request"))
+ {
+ para_list.request = true;
+ form |= FORM_REQUEST;
+ }
else if (v.is("with_header"))
{
para_list.with_header = true;
- inspect_section = IS_HEADER;
+ inspect_section = IS_DETECTION;
}
else if (v.is("with_body"))
{
// Check for option conflicts
if (para_list.with_header + para_list.with_body + para_list.with_trailer > 1)
ParseError("Only specify one with_ option. Use the one that happens last.");
+ if (((buffer_index == NHTTP_BUFFER_TRAILER) || (buffer_index == NHTTP_BUFFER_RAW_TRAILER)) &&
+ (para_list.with_header || para_list.with_body) &&
+ !para_list.request)
+ ParseError("Trailers with with_ option must also specify request");
if (para_list.scheme + para_list.host + para_list.port + para_list.path + para_list.query +
para_list.fragment > 1)
ParseError("Only specify one part of the URI");
void NHttpCursorModule::NHttpRuleParaList::reset()
{
field.clear();
+ request = false;
with_header = false;
with_body = false;
with_trailer = false;
return DETECTION_OPTION_NO_MATCH;
if (NHttpInspect::get_latest_is() != inspect_section)
- return DETECTION_OPTION_NO_MATCH;
+ {
+ // It is OK to provide a body buffer during the detection section. If there actually is
+ // a body buffer available then the detection section must also be the first body section.
+ if (! ((inspect_section == IS_BODY) && (NHttpInspect::get_latest_is() == IS_DETECTION)) )
+ return DETECTION_OPTION_NO_MATCH;
+ }
InspectionBuffer hb;
- if (! ((NHttpInspect*)(p->flow->gadget))->get_buf((unsigned)buffer_index, sub_id, nullptr, hb))
+ if (! ((NHttpInspect*)(p->flow->gadget))->
+ get_buf((unsigned)buffer_index, sub_id, form, nullptr, hb))
return DETECTION_OPTION_NO_MATCH;
c.set(key, hb.data, hb.len);
static const Parameter http_uri_params[] =
{
- { "with_header", Parameter::PT_IMPLIED, nullptr, nullptr,
- "Parts of this rule examine HTTP message headers" },
{ "with_body", Parameter::PT_IMPLIED, nullptr, nullptr,
"Parts of this rule examine HTTP message body" },
{ "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr,
static const Parameter http_method_params[] =
{
- { "with_header", Parameter::PT_IMPLIED, nullptr, nullptr,
- "Parts of this rule examine HTTP message headers" },
{ "with_body", Parameter::PT_IMPLIED, nullptr, nullptr,
"Parts of this rule examine HTTP message body" },
{ "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr,
static const Parameter http_cookie_params[] =
{
+ { "request", Parameter::PT_IMPLIED, nullptr, nullptr,
+ "Match against the cookie from the request message even when examining the response" },
{ "with_body", Parameter::PT_IMPLIED, nullptr, nullptr,
"Parts of this rule examine HTTP message body" },
{ "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr,
static const Parameter http_stat_code_params[] =
{
- { "with_header", Parameter::PT_IMPLIED, nullptr, nullptr,
- "Parts of this rule examine HTTP message headers" },
{ "with_body", Parameter::PT_IMPLIED, nullptr, nullptr,
"Parts of this rule examine HTTP message body" },
{ "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr,
static const Parameter http_stat_msg_params[] =
{
- { "with_header", Parameter::PT_IMPLIED, nullptr, nullptr,
- "Parts of this rule examine HTTP message headers" },
{ "with_body", Parameter::PT_IMPLIED, nullptr, nullptr,
"Parts of this rule examine HTTP message body" },
{ "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr,
static const Parameter http_raw_uri_params[] =
{
- { "with_header", Parameter::PT_IMPLIED, nullptr, nullptr,
- "Parts of this rule examine HTTP message headers" },
{ "with_body", Parameter::PT_IMPLIED, nullptr, nullptr,
"Parts of this rule examine HTTP message body" },
{ "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr,
static const Parameter http_raw_header_params[] =
{
+ { "request", Parameter::PT_IMPLIED, nullptr, nullptr,
+ "Match against the headers from the request message even when examining the response" },
{ "with_body", Parameter::PT_IMPLIED, nullptr, nullptr,
"Parts of this rule examine HTTP message body" },
{ "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr,
static const Parameter http_raw_cookie_params[] =
{
+ { "request", Parameter::PT_IMPLIED, nullptr, nullptr,
+ "Match against the cookie from the request message even when examining the response" },
{ "with_body", Parameter::PT_IMPLIED, nullptr, nullptr,
"Parts of this rule examine HTTP message body" },
{ "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr,
static const Parameter http_version_params[] =
{
- { "with_header", Parameter::PT_IMPLIED, nullptr, nullptr,
- "Parts of this rule examine HTTP message headers" },
+ { "request", Parameter::PT_IMPLIED, nullptr, nullptr,
+ "Match against the version from the request message even when examining the response" },
{ "with_body", Parameter::PT_IMPLIED, nullptr, nullptr,
"Parts of this rule examine HTTP message body" },
{ "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr,
static const Parameter http_header_params[] =
{
+ { "field", Parameter::PT_STRING, nullptr, nullptr,
+ "Restrict to given header. Header name is case insensitive." },
+ { "request", Parameter::PT_IMPLIED, nullptr, nullptr,
+ "Match against the headers from the request message even when examining the response" },
{ "with_body", Parameter::PT_IMPLIED, nullptr, nullptr,
"Parts of this rule examine HTTP message body" },
{ "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr,
"Parts of this rule examine HTTP message trailers" },
- { "field", Parameter::PT_STRING, nullptr, nullptr,
- "Restrict to given header. Header name is case insensitive." },
{ nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
};
static const Parameter http_trailer_params[] =
{
{ "field", Parameter::PT_STRING, nullptr, nullptr, "restrict to given trailer" },
+ { "request", Parameter::PT_IMPLIED, nullptr, nullptr,
+ "Match against the trailers from the request message even when examining the response" },
+ { "with_header", Parameter::PT_IMPLIED, nullptr, nullptr,
+ "Parts of this rule examine HTTP response message headers (must be combined with request)"
+ },
+ { "with_body", Parameter::PT_IMPLIED, nullptr, nullptr,
+ "Parts of this rule examine HTTP message body (must be combined with request)" },
{ nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
};
static Module* trailer_mod_ctor()
{
- return new NHttpCursorModule(IPS_OPT, IPS_HELP, NHTTP_BUFFER_TRAILER, CAT_SET_OTHER,
+ return new NHttpCursorModule(IPS_OPT, IPS_HELP, NHTTP_BUFFER_TRAILER, CAT_SET_HEADER,
PSI_TRAILER, http_trailer_params);
}
// http_raw_trailer
//-------------------------------------------------------------------------
+static const Parameter http_raw_trailer_params[] =
+{
+ { "request", Parameter::PT_IMPLIED, nullptr, nullptr,
+ "Match against the trailers from the request message even when examining the response" },
+ { "with_header", Parameter::PT_IMPLIED, nullptr, nullptr,
+ "Parts of this rule examine HTTP response message headers (must be combined with request)"
+ },
+ { "with_body", Parameter::PT_IMPLIED, nullptr, nullptr,
+ "Parts of this rule examine HTTP response message body (must be combined with request)" },
+ { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
+};
+
#undef IPS_OPT
#define IPS_OPT "http_raw_trailer"
#undef IPS_HELP
static Module* raw_trailer_mod_ctor()
{
return new NHttpCursorModule(IPS_OPT, IPS_HELP, NHTTP_BUFFER_RAW_TRAILER, CAT_SET_OTHER,
- PSI_RAW_TRAILER);
+ PSI_RAW_TRAILER, http_raw_trailer_params);
}
static const IpsApi raw_trailer_api =
nullptr
};
+//-------------------------------------------------------------------------
+// http_raw_request
+//-------------------------------------------------------------------------
+
+static const Parameter http_raw_request_params[] =
+{
+ { "with_body", Parameter::PT_IMPLIED, nullptr, nullptr,
+ "Parts of this rule examine HTTP message body" },
+ { "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr,
+ "Parts of this rule examine HTTP message trailers" },
+ { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
+};
+
+#undef IPS_OPT
+#define IPS_OPT "http_raw_request"
+#undef IPS_HELP
+#define IPS_HELP "rule option to set the detection cursor to the unnormalized request line"
+
+static Module* raw_request_mod_ctor()
+{
+ return new NHttpCursorModule(IPS_OPT, IPS_HELP, NHTTP_BUFFER_RAW_REQUEST, CAT_SET_OTHER,
+ PSI_RAW_REQUEST, http_raw_request_params);
+}
+
+static const IpsApi raw_request_api =
+{
+ {
+ PT_IPS_OPTION,
+ sizeof(IpsApi),
+ IPSAPI_VERSION,
+ 1,
+ API_RESERVED,
+ API_OPTIONS,
+ IPS_OPT,
+ IPS_HELP,
+ raw_request_mod_ctor,
+ NHttpCursorModule::mod_dtor
+ },
+ OPT_TYPE_DETECTION,
+ 0, PROTO_BIT__TCP,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ NHttpIpsOption::opt_ctor,
+ NHttpIpsOption::opt_dtor,
+ nullptr
+};
+
+//-------------------------------------------------------------------------
+// http_raw_status
+//-------------------------------------------------------------------------
+
+static const Parameter http_raw_status_params[] =
+{
+ { "with_body", Parameter::PT_IMPLIED, nullptr, nullptr,
+ "Parts of this rule examine HTTP message body" },
+ { "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr,
+ "Parts of this rule examine HTTP message trailers" },
+ { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
+};
+
+#undef IPS_OPT
+#define IPS_OPT "http_raw_status"
+#undef IPS_HELP
+#define IPS_HELP "rule option to set the detection cursor to the unnormalized status line"
+
+static Module* raw_status_mod_ctor()
+{
+ return new NHttpCursorModule(IPS_OPT, IPS_HELP, NHTTP_BUFFER_RAW_STATUS, CAT_SET_OTHER,
+ PSI_RAW_STATUS, http_raw_status_params);
+}
+
+static const IpsApi raw_status_api =
+{
+ {
+ PT_IPS_OPTION,
+ sizeof(IpsApi),
+ IPSAPI_VERSION,
+ 1,
+ API_RESERVED,
+ API_OPTIONS,
+ IPS_OPT,
+ IPS_HELP,
+ raw_status_mod_ctor,
+ NHttpCursorModule::mod_dtor
+ },
+ OPT_TYPE_DETECTION,
+ 0, PROTO_BIT__TCP,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ NHttpIpsOption::opt_ctor,
+ NHttpIpsOption::opt_dtor,
+ nullptr
+};
+
//-------------------------------------------------------------------------
// plugins
//-------------------------------------------------------------------------
&header_api.base,
&trailer_api.base,
&raw_trailer_api.base,
+ &raw_request_api.base,
+ &raw_status_api.base,
nullptr
};
enum PsIdx { PSI_URI, PSI_CLIENT_BODY, PSI_METHOD, PSI_COOKIE, PSI_STAT_CODE, PSI_STAT_MSG,
PSI_RAW_URI, PSI_RAW_HEADER, PSI_RAW_COOKIE, PSI_HEADER, PSI_VERSION, PSI_TRAILER,
- PSI_RAW_TRAILER, PSI_MAX };
+ PSI_RAW_TRAILER, PSI_RAW_REQUEST, PSI_RAW_STATUS, PSI_MAX };
class NHttpCursorModule : public Module
{
{
public:
std::string field; // provide buffer containing specific header field
+ bool request; // provide buffer from request not response
bool with_header; // provide buffer with a later section than it appears in
bool with_body;
bool with_trailer;
NHttpRuleParaList para_list;
NHttpEnums::InspectSection inspect_section;
- unsigned sub_id;
+ uint64_t sub_id;
+ uint64_t form;
};
class NHttpIpsOption : public IpsOption
public:
NHttpIpsOption(const NHttpCursorModule* cm) : IpsOption(cm->key), key(cm->key),
buffer_index(cm->buffer_index), cat(cm->cat), psi(cm->psi),
- inspect_section(cm->inspect_section), sub_id(cm->sub_id) {}
+ inspect_section(cm->inspect_section), sub_id(cm->sub_id), form(cm->form) {}
CursorActionType get_cursor_type() const override { return cat; }
int eval(Cursor&, Packet*) override;
static IpsOption* opt_ctor(Module* m, OptTreeNode*)
const CursorActionType cat;
const PsIdx psi;
const NHttpEnums::InspectSection inspect_section;
- const unsigned sub_id;
+ const uint64_t sub_id;
+ const uint64_t form;
};
#endif
return new NHttpInspect(nhttp_mod->get_params());
}
-const char* NHttpApi::legacy_buffers[] =
+const char* NHttpApi::classic_buffers[] =
{
"http_client_body",
"http_cookie",
"http_version",
"http_trailer",
"http_raw_trailer",
+ "http_raw_request",
+ "http_raw_status",
nullptr
};
},
IT_SERVICE,
(uint16_t)PktType::PDU,
- legacy_buffers,
+ classic_buffers,
"http",
NHttpApi::nhttp_init,
NHttpApi::nhttp_term,
{
public:
static const InspectApi nhttp_api;
- static const char* legacy_buffers[];
+ static const char* classic_buffers[];
private:
NHttpApi() = delete;
static const int FINAL_GZIP_BLOCK_SIZE = 2304; // compromise value, too big causes gzip overruns
// too small leaves too many little end sections
static const uint32_t NHTTP_GID = 219;
-static const int GZIP_WINDOWBITS = 31;
-static const int DEFLATE_WINDOWBITS = 15;
+static const int GZIP_WINDOW_BITS = 31;
+static const int DEFLATE_WINDOW_BITS = 15;
static const int MAX_FIELD_NAME_LENGTH = 100;
+// This can grow into a bitmap for the get_buf() form parameter
+static const uint64_t FORM_REQUEST = 0x1;
+
// Field status codes for when no valid value is present in length or integer value. Positive
// values are actual length or field value.
-enum StatusCode { STAT_NOSOURCE=-16, STAT_NOTCONFIGURED=-15, STAT_NOTCOMPUTE=-14,
- STAT_INSUFMEMORY=-13, STAT_PROBLEMATIC=-12, STAT_NOTPRESENT=-11, STAT_EMPTYSTRING=0,
+enum StatusCode { STAT_NO_SOURCE=-16, STAT_NOT_CONFIGURED=-15, STAT_NOT_COMPUTE=-14,
+ STAT_INSUF_MEMORY=-13, STAT_PROBLEMATIC=-12, STAT_NOT_PRESENT=-11, STAT_EMPTY_STRING=0,
STAT_OTHER=1 };
// Message originator--client or server
-enum SourceId { SRC__NOTCOMPUTE=-14, SRC_CLIENT=0, SRC_SERVER=1 };
+enum SourceId { SRC__NOT_COMPUTE=-14, SRC_CLIENT=0, SRC_SERVER=1 };
// Type of message section
-enum SectionType { SEC_DISCARD = -19, SEC_ABORT = -18, SEC__NOTCOMPUTE=-14, SEC__NOTPRESENT=-11,
+enum SectionType { SEC_DISCARD = -19, SEC_ABORT = -18, SEC__NOT_COMPUTE=-14, SEC__NOT_PRESENT=-11,
SEC_REQUEST = 2, SEC_STATUS, SEC_HEADER, SEC_BODY_CL, SEC_BODY_CHUNK, SEC_TRAILER,
SEC_BODY_OLD };
// Message buffers available to clients
-// This enum must remain synchronized with legacy_buffers[]
+// This enum must remain synchronized with classic_buffers[]
enum NHTTP_BUFFER { NHTTP_BUFFER_CLIENT_BODY = 1, NHTTP_BUFFER_COOKIE, NHTTP_BUFFER_HEADER,
NHTTP_BUFFER_METHOD, NHTTP_BUFFER_RAW_COOKIE, NHTTP_BUFFER_RAW_HEADER, NHTTP_BUFFER_RAW_URI,
NHTTP_BUFFER_STAT_CODE, NHTTP_BUFFER_STAT_MSG, NHTTP_BUFFER_URI, NHTTP_BUFFER_VERSION,
- NHTTP_BUFFER_TRAILER, NHTTP_BUFFER_RAW_TRAILER, NHTTP_BUFFER_MAX };
+ NHTTP_BUFFER_TRAILER, NHTTP_BUFFER_RAW_TRAILER, NHTTP_BUFFER_RAW_REQUEST,
+ NHTTP_BUFFER_RAW_STATUS, NHTTP_BUFFER_MAX };
// Result of scanning by splitter
enum ScanResult { SCAN_NOTFOUND, SCAN_FOUND, SCAN_FOUND_PIECE, SCAN_DISCARD, SCAN_DISCARD_PIECE,
// 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"
-enum VersionId { VERS__NOSOURCE=-16, VERS__NOTCOMPUTE=-14, VERS__PROBLEMATIC=-12,
- VERS__NOTPRESENT=-11, VERS__OTHER=1, VERS_1_0, VERS_1_1, VERS_2_0 };
+enum VersionId { VERS__NO_SOURCE=-16, VERS__NOT_COMPUTE=-14, VERS__PROBLEMATIC=-12,
+ VERS__NOT_PRESENT=-11, VERS__OTHER=1, VERS_1_0, VERS_1_1, VERS_2_0 };
// Every request method we have ever heard of
-enum MethodId { METH__NOSOURCE=-16, METH__NOTCOMPUTE=-14, METH__INSUFMEMORY=-13,
- METH__PROBLEMATIC=-12, METH__NOTPRESENT=-11, METH__OTHER=1, METH_OPTIONS, METH_GET, METH_HEAD,
+enum MethodId { METH__NO_SOURCE=-16, METH__NOT_COMPUTE=-14, METH__INSUF_MEMORY=-13,
+ METH__PROBLEMATIC=-12, METH__NOT_PRESENT=-11, METH__OTHER=1, METH_OPTIONS, METH_GET, METH_HEAD,
METH_POST, METH_PUT, METH_DELETE, METH_TRACE, METH_CONNECT, METH_PROPFIND, METH_PROPPATCH,
METH_MKCOL, METH_COPY, METH_MOVE, METH_LOCK, METH_UNLOCK, METH_VERSION_CONTROL, METH_REPORT,
METH_CHECKOUT, METH_CHECKIN, METH_UNCHECKOUT, METH_MKWORKSPACE, METH_UPDATE, METH_LABEL,
METH_UPDATEREDIRECTREF };
// URI formats
-enum UriType { URI__NOTCOMPUTE=-14, URI__PROBLEMATIC=-12, URI_ASTERISK = 2, URI_AUTHORITY,
+enum UriType { URI__NOT_COMPUTE=-14, URI__PROBLEMATIC=-12, URI_ASTERISK = 2, URI_AUTHORITY,
URI_ABSPATH, URI_ABSOLUTE };
-// URI schemes
-enum SchemeId { SCH__NOSOURCE=-16, SCH__NOTCOMPUTE=-14, SCH__INSUFMEMORY=-13, SCH__NOTPRESENT=-11,
- SCH_OTHER = 1, SCH_HTTP, SCH_HTTPS, SCH_FTP, SCH_GOPHER, SCH_FILE };
-
// Body compression tpyes
enum CompressId { CMP_NONE=2, CMP_GZIP, CMP_DEFLATE };
// Message section in which an IPS option provides the buffer
-enum InspectSection { IS_NONE, IS_START, IS_HEADER, IS_BODY, IS_TRAILER };
+enum InspectSection { IS_NONE, IS_DETECTION, IS_BODY, IS_TRAILER };
// Part of the URI to be provided
enum UriComponent { UC_SCHEME = 1, UC_HOST, UC_PORT, UC_PATH, UC_QUERY, UC_FRAGMENT };
// Every header we have ever heard of
-enum HeaderId { HEAD__NOTCOMPUTE=-14, HEAD__INSUFMEMORY=-13, HEAD__PROBLEMATIC=-12,
- HEAD__NOTPRESENT=-11, HEAD__OTHER=1, HEAD_CACHE_CONTROL, HEAD_CONNECTION, HEAD_DATE,
+enum HeaderId { HEAD__NOT_COMPUTE=-14, HEAD__INSUF_MEMORY=-13, HEAD__PROBLEMATIC=-12,
+ HEAD__NOT_PRESENT=-11, HEAD__OTHER=1, HEAD_CACHE_CONTROL, HEAD_CONNECTION, HEAD_DATE,
HEAD_PRAGMA, HEAD_TRAILER, HEAD_COOKIE, HEAD_SET_COOKIE, HEAD_TRANSFER_ENCODING, HEAD_UPGRADE,
HEAD_VIA, HEAD_WARNING, HEAD_ACCEPT, HEAD_ACCEPT_CHARSET, HEAD_ACCEPT_ENCODING,
HEAD_ACCEPT_LANGUAGE, HEAD_AUTHORIZATION, HEAD_EXPECT, HEAD_FROM, HEAD_HOST, HEAD_IF_MATCH,
INF_BAD_CHUNK_SIZE,
INF_BAD_PHRASE,
INF_BAD_URI,
- INF_BAD_PORT,
+ INF_UNUSED,
INF_URI_NEED_NORM,
INF_URI_PERCENT_NORMAL,
INF_URI_PERCENT_ASCII,
EVENT_BAD_HEADER,
EVENT_CHUNK_OPTIONS,
EVENT_URI_BAD_FORMAT,
- EVENT_URI_BAD_PORT,
+ EVENT_UNUSED,
EVENT_BROKEN_CHUNK,
EVENT_CHUNK_WHITESPACE,
EVENT_HEAD_NAME_WHITESPACE,
using namespace NHttpEnums;
-const Field Field::FIELD_NULL { STAT_NOSOURCE };
+const Field Field::FIELD_NULL { STAT_NO_SOURCE };
#ifdef REG_TEST
void Field::print(FILE* output, const char* name) const
{
- if ((length == STAT_NOTPRESENT) || (length == STAT_NOTCOMPUTE) || (length == STAT_NOSOURCE))
+ if ((length == STAT_NOT_PRESENT) || (length == STAT_NOT_COMPUTE) || (length == STAT_NO_SOURCE))
{
return;
}
class Field
{
public:
- int32_t length = NHttpEnums::STAT_NOTCOMPUTE;
+ int32_t length = NHttpEnums::STAT_NOT_COMPUTE;
const uint8_t* start = nullptr;
static const Field FIELD_NULL;
{
assert((source_id == SRC_CLIENT) || (source_id == SRC_SERVER));
- version_id[source_id] = VERS__NOTPRESENT;
- data_length[source_id] = STAT_NOTPRESENT;
- body_octets[source_id] = STAT_NOTPRESENT;
+ version_id[source_id] = VERS__NOT_PRESENT;
+ data_length[source_id] = STAT_NOT_PRESENT;
+ body_octets[source_id] = STAT_NOT_PRESENT;
section_size_target[source_id] = 0;
section_size_max[source_id] = 0;
- file_depth_remaining[source_id] = STAT_NOTPRESENT;
- detect_depth_remaining[source_id] = STAT_NOTPRESENT;
+ file_depth_remaining[source_id] = STAT_NOT_PRESENT;
+ detect_depth_remaining[source_id] = STAT_NOT_PRESENT;
compression[source_id] = CMP_NONE;
if (compress_stream[source_id] != nullptr)
{
if (source_id == SRC_CLIENT)
{
- method_id = METH__NOTPRESENT;
+ method_id = METH__NOT_PRESENT;
if (mime_state != nullptr)
{
delete mime_state;
}
else
{
- status_code_num = STAT_NOTPRESENT;
+ status_code_num = STAT_NOT_PRESENT;
}
}
uint32_t num_good_chunks[2] = { 0, 0 };
// *** StreamSplitter => Inspector (facts about the most recent message section)
- NHttpEnums::SectionType section_type[2] = { NHttpEnums::SEC__NOTCOMPUTE,
- NHttpEnums::SEC__NOTCOMPUTE };
+ NHttpEnums::SectionType section_type[2] = { NHttpEnums::SEC__NOT_COMPUTE,
+ NHttpEnums::SEC__NOT_COMPUTE };
bool tcp_close[2] = { false, false };
NHttpInfractions infractions[2];
NHttpEventGen events[2];
- int32_t num_head_lines[2] = { NHttpEnums::STAT_NOTPRESENT, NHttpEnums::STAT_NOTPRESENT };
+ int32_t num_head_lines[2] = { NHttpEnums::STAT_NOT_PRESENT, NHttpEnums::STAT_NOT_PRESENT };
// *** Inspector => StreamSplitter (facts about the message section that is coming next)
NHttpEnums::SectionType type_expected[2] = { NHttpEnums::SEC_REQUEST, NHttpEnums::SEC_STATUS };
// length of the data from Content-Length field
- int64_t data_length[2] = { NHttpEnums::STAT_NOTPRESENT, NHttpEnums::STAT_NOTPRESENT };
+ int64_t data_length[2] = { NHttpEnums::STAT_NOT_PRESENT, NHttpEnums::STAT_NOT_PRESENT };
uint32_t section_size_target[2] = { 0, 0 };
uint32_t section_size_max[2] = { 0, 0 };
NHttpEnums::CompressId compression[2] = { NHttpEnums::CMP_NONE, NHttpEnums::CMP_NONE };
z_stream* compress_stream[2] = { nullptr, nullptr };
// *** Inspector's internal data about the current message
- NHttpEnums::VersionId version_id[2] = { NHttpEnums::VERS__NOTPRESENT,
- NHttpEnums::VERS__NOTPRESENT };
- NHttpEnums::MethodId method_id = NHttpEnums::METH__NOTPRESENT;
- int32_t status_code_num = NHttpEnums::STAT_NOTPRESENT;
- int64_t file_depth_remaining[2] = { NHttpEnums::STAT_NOTPRESENT, NHttpEnums::STAT_NOTPRESENT };
- int64_t detect_depth_remaining[2] = { NHttpEnums::STAT_NOTPRESENT,
- NHttpEnums::STAT_NOTPRESENT };
+ NHttpEnums::VersionId version_id[2] = { NHttpEnums::VERS__NOT_PRESENT,
+ NHttpEnums::VERS__NOT_PRESENT };
+ NHttpEnums::MethodId method_id = NHttpEnums::METH__NOT_PRESENT;
+ int32_t status_code_num = NHttpEnums::STAT_NOT_PRESENT;
+ int64_t file_depth_remaining[2] = { NHttpEnums::STAT_NOT_PRESENT,
+ NHttpEnums::STAT_NOT_PRESENT };
+ int64_t detect_depth_remaining[2] = { NHttpEnums::STAT_NOT_PRESENT,
+ NHttpEnums::STAT_NOT_PRESENT };
MimeSession* mime_state = nullptr; // SRC_CLIENT only
// number of user data octets seen so far (regular body or chunks)
- int64_t body_octets[2] = { NHttpEnums::STAT_NOTPRESENT, NHttpEnums::STAT_NOTPRESENT };
+ int64_t body_octets[2] = { NHttpEnums::STAT_NOT_PRESENT, NHttpEnums::STAT_NOT_PRESENT };
// Transaction management including pipelining
// FIXIT-L pipeline deserves to be its own class
NHttpInfractions& infractions, NHttpEventGen& events, const HeaderId header_name_id[],
const Field header_value[], const int32_t num_headers, Field& result_field) const
{
- if (result_field.length != STAT_NOTCOMPUTE)
+ if (result_field.length != STAT_NOT_COMPUTE)
{
return;
}
uint8_t* const scratch = scratch_pad.request(2*buffer_length);
if (scratch == nullptr)
{
- result_field.length = STAT_INSUFMEMORY;
+ result_field.length = STAT_INSUF_MEMORY;
return;
}
switch (ibt)
{
case InspectionBuffer::IBT_KEY:
- return get_buf(NHTTP_BUFFER_URI, 0, nullptr, b);
+ return get_buf(NHTTP_BUFFER_URI, 0, 0, nullptr, b);
case InspectionBuffer::IBT_HEADER:
- return get_buf(NHTTP_BUFFER_HEADER, 0, nullptr, b);
+ if (get_latest_is() == IS_TRAILER)
+ return get_buf(NHTTP_BUFFER_TRAILER, 0, 0, nullptr, b);
+ else
+ return get_buf(NHTTP_BUFFER_HEADER, 0, 0, nullptr, b);
case InspectionBuffer::IBT_BODY:
- return get_buf(NHTTP_BUFFER_CLIENT_BODY, 0, nullptr, b);
+ return get_buf(NHTTP_BUFFER_CLIENT_BODY, 0, 0, nullptr, b);
default:
return false;
}
}
-bool NHttpInspect::get_buf(unsigned id, Packet*, InspectionBuffer& b)
+SO_PUBLIC bool NHttpInspect::get_buf(unsigned id, uint64_t sub_id, uint64_t form, Packet*,
+ InspectionBuffer& b)
{
- return get_buf(id, 0, nullptr, b);
-}
-
-SO_PUBLIC bool NHttpInspect::get_buf(unsigned id, unsigned sub_id, Packet*, InspectionBuffer& b)
-{
- // FIXIT-L some day we should add support for accessing the request headers, trailers, and
- // version from the response side of the transaction.
if (latest_section == nullptr)
return false;
- const Field& buffer = latest_section->get_classic_buffer(id, sub_id);
+ const Field& buffer = latest_section->get_classic_buffer(id, sub_id, form);
if (buffer.length <= 0)
return false;
return true;
}
+bool NHttpInspect::get_fp_buf(InspectionBuffer::Type ibt, Packet*, InspectionBuffer& b)
+{
+ // Fast pattern buffers only supplied at specific times
+ switch (ibt)
+ {
+ case InspectionBuffer::IBT_KEY:
+ if ((get_latest_is() != IS_DETECTION) || (get_latest_src() != SRC_CLIENT))
+ return false;
+ break;
+ case InspectionBuffer::IBT_HEADER:
+ if ((get_latest_is() != IS_DETECTION) && (get_latest_is() != IS_TRAILER))
+ return false;
+ break;
+ case InspectionBuffer::IBT_BODY:
+ if ((get_latest_is() != IS_DETECTION) && (get_latest_is() != IS_BODY))
+ return false;
+ break;
+ default:
+ return false;
+ }
+ return get_buf(ibt, nullptr, b);
+}
+
const Field& NHttpInspect::process(const uint8_t* data, const uint16_t dsize, Flow* const flow,
SourceId source_id, bool buf_owner) const
{
NHttpInspect(NHttpParaList params_);
bool get_buf(InspectionBuffer::Type ibt, Packet*, InspectionBuffer& b) override;
- bool get_buf(unsigned id, Packet*, InspectionBuffer& b) override;
- bool get_buf(unsigned id, unsigned sub_id, Packet*, InspectionBuffer& b);
+ bool get_buf(unsigned id, uint64_t sub_id, uint64_t form, Packet*, InspectionBuffer& b);
+ bool get_fp_buf(InspectionBuffer::Type ibt, Packet*, InspectionBuffer& b) override;
bool configure(SnortConfig*) override { return true; }
void show(SnortConfig*) override { LogMessage("NHttpInspect\n"); }
void eval(Packet*) override { }
void clear(Packet* p) override;
- void clear(NHttpFlowData* session_data, NHttpEnums::SourceId source_id);
void tinit() override { }
void tterm() override { }
NHttpStreamSplitter* get_splitter(bool is_client_to_server) override
const Field& process(const uint8_t* data, const uint16_t dsize, Flow* const flow,
NHttpEnums::SourceId source_id_, bool buf_owner) const;
+ void clear(NHttpFlowData* session_data, NHttpEnums::SourceId source_id);
+ static NHttpEnums::SourceId get_latest_src() { return (latest_section != nullptr) ?
+ latest_section->get_source_id() : NHttpEnums::SRC__NOT_COMPUTE; }
static THREAD_LOCAL NHttpMsgSection* latest_section;
long request_depth;
long response_depth;
bool unzip;
-
+#ifdef REG_TEST
bool test_input;
bool test_output;
long print_amount;
+#endif
};
class NHttpModule : public Module
NHttpFlowData* session_data_, SourceId source_id_, bool buf_owner, Flow* flow_,
const NHttpParaList* params_) :
NHttpMsgSection(buffer, buf_size, session_data_, source_id_, buf_owner, flow_, params_),
- body_octets(session_data->body_octets[source_id])
+ body_octets(session_data->body_octets[source_id]),
+ detection_section((body_octets == 0) && (session_data->detect_depth_remaining[source_id] > 0))
{
transaction->set_body(this);
}
NHttpMsgRequest* request = transaction->get_request();
if (request != nullptr)
{
- const Field& tranaction_uri = request->get_uri_norm_legacy();
+ const Field& tranaction_uri = request->get_uri_norm_classic();
if (tranaction_uri.length > 0)
{
file_flows->set_file_name(tranaction_uri.start, tranaction_uri.length);
void NHttpMsgBody::print_body_section(FILE* output)
{
detect_data.print(output, "Detect data");
- get_classic_buffer(NHTTP_BUFFER_CLIENT_BODY, 0).print(output,
- NHttpApi::legacy_buffers[NHTTP_BUFFER_CLIENT_BODY-1]);
+ get_classic_buffer(NHTTP_BUFFER_CLIENT_BODY, 0, 0).print(output,
+ NHttpApi::classic_buffers[NHTTP_BUFFER_CLIENT_BODY-1]);
if (g_file_data.len > 0)
{
Field(g_file_data.len, g_file_data.data).print(output, "file_data");
void analyze() override;
const Field& get_detect_buf() const override { return detect_data; }
NHttpEnums::InspectSection get_inspection_section() const override
- { return NHttpEnums::IS_BODY; }
+ { return detection_section ? NHttpEnums::IS_DETECTION : NHttpEnums::IS_BODY; }
protected:
NHttpMsgBody(const uint8_t* buffer, const uint16_t buf_size, NHttpFlowData* session_data_,
int64_t body_octets;
Field detect_data;
Field file_data;
+ const bool detection_section;
#ifdef REG_TEST
void print_body_section(FILE* output);
session_data->infractions[source_id] = infractions;
session_data->events[source_id] = events;
}
- session_data->section_type[source_id] = SEC__NOTCOMPUTE;
+ session_data->section_type[source_id] = SEC__NOT_COMPUTE;
}
#ifdef REG_TEST
SEC_STATUS;
session_data->half_reset(source_id);
}
- session_data->section_type[source_id] = SEC__NOTCOMPUTE;
+ session_data->section_type[source_id] = SEC__NOT_COMPUTE;
}
#ifdef REG_TEST
update_depth();
session_data->infractions[source_id] = infractions;
session_data->events[source_id] = events;
- session_data->section_type[source_id] = SEC__NOTCOMPUTE;
+ session_data->section_type[source_id] = SEC__NOT_COMPUTE;
}
#ifdef REG_TEST
if ((lower_name = scratch_pad.request(length)) == nullptr)
{
infractions += INF_NO_SCRATCH;
- header_name_id[index] = HEAD__INSUFMEMORY;
+ header_name_id[index] = HEAD__INSUF_MEMORY;
return;
}
for (int32_t k=0; k < length; k++)
void NHttpMsgHeadShared::print_headers(FILE* output)
{
char title_buf[100];
- if (num_headers != STAT_NOSOURCE)
+ if (num_headers != STAT_NO_SOURCE)
fprintf(output, "Number of headers: %d\n", num_headers);
for (int j=0; j < num_headers; j++)
{
}
for (int k=1; k <= HEAD__MAX_VALUE-1; k++)
{
- if (get_header_value_norm((HeaderId)k).length != STAT_NOSOURCE)
+ if (get_header_value_norm((HeaderId)k).length != STAT_NO_SOURCE)
{
snprintf(title_buf, sizeof(title_buf), "Normalized header %d", k);
get_header_value_norm((HeaderId)k).print(output, title_buf);
// All of these are indexed by the relative position of the header field in the message
static const int MAX_HEADERS = 200; // I'm an arbitrary number. FIXIT-L
static const int MAX_HEADER_LENGTH = 4096; // Based on max cookie size of some browsers
- int32_t num_headers = NHttpEnums::STAT_NOTCOMPUTE;
+ int32_t num_headers = NHttpEnums::STAT_NOT_COMPUTE;
Field* header_line = nullptr;
Field* header_name = nullptr;
NHttpEnums::HeaderId* header_name_id = nullptr;
void NHttpMsgHeader::update_flow()
{
- session_data->section_type[source_id] = SEC__NOTCOMPUTE;
+ session_data->section_type[source_id] = SEC__NOT_COMPUTE;
// FIXIT-L put this test here for now. May want to integrate into the following code and
// do more careful checks for inappropriate Content-Length.
// The following logic to determine body type is by no means the last word on this topic.
// FIXIT-H need to distinguish methods such as POST that should have a body from those that
// should not.
+ if (tcp_close)
+ {
+ session_data->type_expected[source_id] = SEC_ABORT;
+ session_data->half_reset(source_id);
+ return;
+ }
+
if ((source_id == SRC_SERVER) && ((status_code_num <= 199) || (status_code_num == 204) ||
(status_code_num == 304)))
{
const int64_t& depth = (source_id == SRC_CLIENT) ? params->request_depth :
params->response_depth;
session_data->detect_depth_remaining[source_id] = (depth != -1) ? depth : INT64_MAX;
+ if (session_data->detect_depth_remaining[source_id] > 0)
+ {
+ // Depth must be positive because first body section must actually go to detection in order
+ // to be the detection section
+ detection_section = false;
+ }
setup_file_processing();
setup_decompression();
update_depth();
session_data->compress_stream[source_id]->zfree = Z_NULL;
session_data->compress_stream[source_id]->next_in = Z_NULL;
session_data->compress_stream[source_id]->avail_in = 0;
- const int window_bits = (compression == CMP_GZIP) ? GZIP_WINDOWBITS : DEFLATE_WINDOWBITS;
+ const int window_bits = (compression == CMP_GZIP) ? GZIP_WINDOW_BITS : DEFLATE_WINDOW_BITS;
if (inflateInit2(session_data->compress_stream[source_id], window_bits) != Z_OK)
{
session_data->compression[source_id] = CMP_NONE;
{
NHttpMsgSection::print_message_title(output, "header");
NHttpMsgHeadShared::print_headers(output);
- get_classic_buffer(NHTTP_BUFFER_COOKIE, 0).print(output,
- NHttpApi::legacy_buffers[NHTTP_BUFFER_COOKIE-1]);
- get_classic_buffer(NHTTP_BUFFER_HEADER, 0).print(output,
- NHttpApi::legacy_buffers[NHTTP_BUFFER_HEADER-1]);
- get_classic_buffer(NHTTP_BUFFER_RAW_COOKIE, 0).print(output,
- NHttpApi::legacy_buffers[NHTTP_BUFFER_RAW_COOKIE-1]);
- get_classic_buffer(NHTTP_BUFFER_RAW_HEADER, 0).print(output,
- NHttpApi::legacy_buffers[NHTTP_BUFFER_RAW_HEADER-1]);
+ get_classic_buffer(NHTTP_BUFFER_COOKIE, 0, 0).print(output,
+ NHttpApi::classic_buffers[NHTTP_BUFFER_COOKIE-1]);
+ get_classic_buffer(NHTTP_BUFFER_HEADER, 0, 0).print(output,
+ NHttpApi::classic_buffers[NHTTP_BUFFER_HEADER-1]);
+ get_classic_buffer(NHTTP_BUFFER_RAW_COOKIE, 0, 0).print(output,
+ NHttpApi::classic_buffers[NHTTP_BUFFER_RAW_COOKIE-1]);
+ get_classic_buffer(NHTTP_BUFFER_RAW_HEADER, 0, 0).print(output,
+ NHttpApi::classic_buffers[NHTTP_BUFFER_RAW_HEADER-1]);
NHttpMsgSection::print_message_wrapup(output);
}
#endif
NHttpEnums::SourceId source_id_, bool buf_owner, Flow* flow_,
const NHttpParaList* params_);
NHttpEnums::InspectSection get_inspection_section() const override
- { return NHttpEnums::IS_HEADER; }
+ { return detection_section ? NHttpEnums::IS_DETECTION : NHttpEnums::IS_NONE; }
void update_flow() override;
private:
// Dummy configurations to support MIME processing
void setup_file_processing();
void setup_decompression();
+ bool detection_section = true;
+
#ifdef REG_TEST
void print_section(FILE* output) override;
#endif
return Field::FIELD_NULL;
}
-const Field& NHttpMsgRequest::get_uri_norm_legacy()
+const Field& NHttpMsgRequest::get_uri_norm_classic()
{
if (uri != nullptr)
{
- return uri->get_norm_legacy();
+ return uri->get_norm_classic();
}
return Field::FIELD_NULL;
}
session_data->infractions[source_id].reset();
session_data->events[source_id].reset();
}
- session_data->section_type[source_id] = SEC__NOTCOMPUTE;
+ session_data->section_type[source_id] = SEC__NOT_COMPUTE;
}
#ifdef REG_TEST
uri->get_uri().print(output, "URI");
fprintf(output, "URI Type: %d\n", uri->get_uri_type());
uri->get_scheme().print(output, "Scheme");
- if (uri->get_scheme_id() != SCH__NOSOURCE)
- fprintf(output, "Scheme Id: %d\n", uri->get_scheme_id());
uri->get_authority().print(output, "Authority");
uri->get_host().print(output, "Host Name");
uri->get_norm_host().print(output, "Normalized Host Name");
uri->get_port().print(output, "Port");
- if (uri->get_port_value() != STAT_NOSOURCE)
- fprintf(output, "Port Value: %d\n", uri->get_port_value());
uri->get_abs_path().print(output, "Absolute Path");
uri->get_path().print(output, "Path");
uri->get_norm_path().print(output, "Normalized Path");
uri->get_fragment().print(output, "Fragment");
uri->get_norm_fragment().print(output, "Normalized Fragment");
}
- get_classic_buffer(NHTTP_BUFFER_METHOD, 0).print(output,
- NHttpApi::legacy_buffers[NHTTP_BUFFER_METHOD-1]);
- get_classic_buffer(NHTTP_BUFFER_RAW_URI, 0).print(output,
- NHttpApi::legacy_buffers[NHTTP_BUFFER_RAW_URI-1]);
- get_classic_buffer(NHTTP_BUFFER_URI, 0).print(output,
- NHttpApi::legacy_buffers[NHTTP_BUFFER_URI-1]);
- get_classic_buffer(NHTTP_BUFFER_VERSION, 0).print(output,
- NHttpApi::legacy_buffers[NHTTP_BUFFER_VERSION-1]);
+ get_classic_buffer(NHTTP_BUFFER_METHOD, 0, 0).print(output,
+ NHttpApi::classic_buffers[NHTTP_BUFFER_METHOD-1]);
+ get_classic_buffer(NHTTP_BUFFER_RAW_URI, 0, 0).print(output,
+ NHttpApi::classic_buffers[NHTTP_BUFFER_RAW_URI-1]);
+ get_classic_buffer(NHTTP_BUFFER_URI, 0, 0).print(output,
+ NHttpApi::classic_buffers[NHTTP_BUFFER_URI-1]);
+ get_classic_buffer(NHTTP_BUFFER_VERSION, 0, 0).print(output,
+ NHttpApi::classic_buffers[NHTTP_BUFFER_VERSION-1]);
+ get_classic_buffer(NHTTP_BUFFER_RAW_REQUEST, 0, 0).print(output,
+ NHttpApi::classic_buffers[NHTTP_BUFFER_RAW_REQUEST-1]);
NHttpMsgSection::print_message_wrapup(output);
}
void update_flow() override;
const Field& get_method() { return method; }
const Field& get_uri();
- const Field& get_uri_norm_legacy();
+ const Field& get_uri_norm_classic();
NHttpUri* get_nhttp_uri() { return uri; }
#ifdef REG_TEST
infractions(session_data->infractions[source_id]),
events(session_data->events[source_id]),
version_id(session_data->version_id[source_id]),
- method_id((source_id == SRC_CLIENT) ? session_data->method_id : METH__NOTPRESENT),
- status_code_num((source_id == SRC_SERVER) ? session_data->status_code_num : STAT_NOTPRESENT),
+ method_id((source_id == SRC_CLIENT) ? session_data->method_id : METH__NOT_PRESENT),
+ status_code_num((source_id == SRC_SERVER) ? session_data->status_code_num : STAT_NOT_PRESENT),
delete_msg_on_destruct(buf_owner)
{ }
}
}
-const Field& NHttpMsgSection::get_classic_buffer(unsigned id, unsigned sub_id)
+const Field& NHttpMsgSection::get_classic_buffer(unsigned id, uint64_t sub_id, uint64_t form)
{
+ // Only use with buffers that support the request option
+ const SourceId buffer_side = (form & FORM_REQUEST) ? SRC_CLIENT : source_id;
+
switch (id)
{
case NHTTP_BUFFER_CLIENT_BODY:
// Currently "normalization" is aggregation of multiple cookies. That is correct for raw
// cookies and all there is for normalized cookies.
{
- NHttpMsgHeader* header = transaction->get_header(source_id);
+ NHttpMsgHeader* header = transaction->get_header(buffer_side);
if (header == nullptr)
return Field::FIELD_NULL;
- HeaderId cookie_head = (source_id == SRC_CLIENT) ? HEAD_COOKIE : HEAD_SET_COOKIE;
+ HeaderId cookie_head = (buffer_side == SRC_CLIENT) ? HEAD_COOKIE : HEAD_SET_COOKIE;
return header->get_header_value_norm(cookie_head);
}
case NHTTP_BUFFER_HEADER:
{
// FIXIT-L Someday want to be able to return field name or raw field value
NHttpMsgHeadShared* const header = (id == NHTTP_BUFFER_HEADER) ?
- (NHttpMsgHeadShared*)transaction->get_header(source_id) :
- (NHttpMsgHeadShared*)transaction->get_trailer(source_id);
+ (NHttpMsgHeadShared*)transaction->get_header(buffer_side) :
+ (NHttpMsgHeadShared*)transaction->get_trailer(buffer_side);
if (header == nullptr)
return Field::FIELD_NULL;
if (sub_id == 0)
}
case NHTTP_BUFFER_RAW_HEADER:
{
- NHttpMsgHeader* header = transaction->get_header(source_id);
+ NHttpMsgHeader* header = transaction->get_header(buffer_side);
return (header != nullptr) ? header->get_headers() : Field::FIELD_NULL;
}
case NHTTP_BUFFER_STAT_CODE:
if (request == nullptr)
return Field::FIELD_NULL;
if (sub_id == 0)
- return raw ? request->get_uri() : request->get_uri_norm_legacy();
+ return raw ? request->get_uri() : request->get_uri_norm_classic();
NHttpUri* const uri = request->get_nhttp_uri();
if (uri == nullptr)
return Field::FIELD_NULL;
}
case NHTTP_BUFFER_VERSION:
{
- NHttpMsgStart* start = (source_id == SRC_CLIENT) ?
+ NHttpMsgStart* start = (buffer_side == SRC_CLIENT) ?
(NHttpMsgStart*)transaction->get_request() : (NHttpMsgStart*)transaction->get_status();
return (start != nullptr) ? start->get_version() : Field::FIELD_NULL;
}
+ case NHTTP_BUFFER_RAW_REQUEST:
+ {
+ NHttpMsgRequest* request = transaction->get_request();
+ return (request != nullptr) ? request->get_detect_buf() : Field::FIELD_NULL;
+ }
+ case NHTTP_BUFFER_RAW_STATUS:
+ {
+ NHttpMsgStatus* status = transaction->get_status();
+ return (status != nullptr) ? status->get_detect_buf() : Field::FIELD_NULL;
+ }
case NHTTP_BUFFER_RAW_TRAILER:
{
- NHttpMsgTrailer* trailer = transaction->get_trailer(source_id);
+ NHttpMsgTrailer* trailer = transaction->get_trailer(buffer_side);
return (trailer != nullptr) ? trailer->get_headers() : Field::FIELD_NULL;
}
default:
{
public:
virtual ~NHttpMsgSection() { if (delete_msg_on_destruct) delete[] msg_text.start; }
- virtual NHttpEnums::InspectSection get_inspection_section() const = 0;
+ virtual NHttpEnums::InspectSection get_inspection_section() const
+ { return NHttpEnums::IS_NONE; }
+ NHttpEnums::SourceId get_source_id() { return source_id; }
// Minimum necessary processing for every message
virtual void analyze() = 0;
// Manages the splitter and communication between message sections
virtual void update_flow() = 0;
- const Field& get_classic_buffer(unsigned id, unsigned sub_id);
+ const Field& get_classic_buffer(unsigned id, uint64_t sub_id, uint64_t form);
// Provide buffer to be sent to detection
virtual const Field& get_detect_buf() const { return msg_text; }
public:
void analyze() override;
const Field& get_version() const { return version; }
- NHttpEnums::InspectSection get_inspection_section() const override
- { return NHttpEnums::IS_START; }
protected:
NHttpMsgStart(const uint8_t* buffer, const uint16_t buf_size, NHttpFlowData* session_data_,
: NHttpMsgSection(buffer, buf_size, session_data_, source_id_, buf_owner, flow_, params_)
{ }
virtual void parse_start_line() = 0;
- virtual void gen_events() {};
+ virtual void gen_events() = 0;
void derive_version_id();
Field start_line;
session_data->infractions[source_id].reset();
session_data->events[source_id].reset();
}
- session_data->section_type[source_id] = SEC__NOTCOMPUTE;
+ session_data->section_type[source_id] = SEC__NOT_COMPUTE;
}
#ifdef REG_TEST
fprintf(output, "Version Id: %d\n", version_id);
fprintf(output, "Status Code Num: %d\n", status_code_num);
reason_phrase.print(output, "Reason Phrase");
- get_classic_buffer(NHTTP_BUFFER_STAT_CODE, 0).print(output,
- NHttpApi::legacy_buffers[NHTTP_BUFFER_STAT_CODE-1]);
- get_classic_buffer(NHTTP_BUFFER_STAT_MSG, 0).print(output,
- NHttpApi::legacy_buffers[NHTTP_BUFFER_STAT_MSG-1]);
- get_classic_buffer(NHTTP_BUFFER_VERSION, 0).print(output,
- NHttpApi::legacy_buffers[NHTTP_BUFFER_VERSION-1]);
+ get_classic_buffer(NHTTP_BUFFER_STAT_CODE, 0, 0).print(output,
+ NHttpApi::classic_buffers[NHTTP_BUFFER_STAT_CODE-1]);
+ get_classic_buffer(NHTTP_BUFFER_STAT_MSG, 0, 0).print(output,
+ NHttpApi::classic_buffers[NHTTP_BUFFER_STAT_MSG-1]);
+ get_classic_buffer(NHTTP_BUFFER_VERSION, 0, 0).print(output,
+ NHttpApi::classic_buffers[NHTTP_BUFFER_VERSION-1]);
+ get_classic_buffer(NHTTP_BUFFER_RAW_STATUS, 0, 0).print(output,
+ NHttpApi::classic_buffers[NHTTP_BUFFER_RAW_STATUS-1]);
NHttpMsgSection::print_message_wrapup(output);
}
#endif
session_data->type_expected[source_id] =
(source_id == SRC_CLIENT) ? SEC_REQUEST : SEC_STATUS;
session_data->half_reset(source_id);
- session_data->section_type[source_id] = SEC__NOTCOMPUTE;
+ session_data->section_type[source_id] = SEC__NOT_COMPUTE;
}
#ifdef REG_TEST
{
NHttpMsgSection::print_message_title(output, "trailer");
NHttpMsgHeadShared::print_headers(output);
- get_classic_buffer(NHTTP_BUFFER_TRAILER, 0).print(output,
- NHttpApi::legacy_buffers[NHTTP_BUFFER_TRAILER-1]);
- get_classic_buffer(NHTTP_BUFFER_RAW_TRAILER, 0).print(output,
- NHttpApi::legacy_buffers[NHTTP_BUFFER_RAW_TRAILER-1]);
+ get_classic_buffer(NHTTP_BUFFER_TRAILER, 0, 0).print(output,
+ NHttpApi::classic_buffers[NHTTP_BUFFER_TRAILER-1]);
+ get_classic_buffer(NHTTP_BUFFER_RAW_TRAILER, 0, 0).print(output,
+ NHttpApi::classic_buffers[NHTTP_BUFFER_RAW_TRAILER-1]);
NHttpMsgSection::print_message_wrapup(output);
}
#endif
{
for (int32_t k=0; k < in_length; k++)
{
+ // FIXIT-P tolower() might perform better but must be sure <locale> cannot be pulled in
out_buf[k] = ((in_buf[k] < 'A') || (in_buf[k] > 'Z')) ? in_buf[k] : in_buf[k] - ('A' -
'a');
}
copied = len;
- // FIXIT-M (b042cf28c49)
- // assert(total <= MAX_OCTETS) was broke after changes in stream
- // to accommodate asymmetric TCP connections.
- // See "FIXIT-M (b042cf28c49)" in "src/tcp/tcp_session.c".
- if (total > MAX_OCTETS)
- total = MAX_OCTETS;
+ assert(total <= MAX_OCTETS);
NHttpFlowData* session_data = (NHttpFlowData*)flow->get_application_data(
NHttpFlowData::nhttp_flow_id);
}
#endif
- if (session_data->section_type[source_id] == SEC__NOTCOMPUTE)
+ if (session_data->section_type[source_id] == SEC__NOT_COMPUTE)
{ // FIXIT-M In theory this check should not be necessary
return nullptr;
}
#endif
if (flags & PKT_PDU_TAIL)
{
- session_data->section_type[source_id] = SEC__NOTCOMPUTE;
+ session_data->section_type[source_id] = SEC__NOT_COMPUTE;
// When we are skipping through a message body beyond flow depth this is the end of
// the line. Here we do the message section's normal job of updating the flow for the
session_data->section_offset[source_id] = 0;
// Buffers are reset to nullptr without delete[] because NHttpMsgSection holds the pointer
- // and is responsible
- if (send_to_detection.length > 0)
+ // and is responsible.
+ // The detection section of a message is the first body section, unless there is no body
+ // section in which case it is the headers. The detection section is always returned to the
+ // framework and forwarded to detection even if it is empty. Other body sections and the
+ // trailer section are only forwarded if nonempty. The start line section and header
+ // sections other than the detection section are never forwarded.
+ if (((send_to_detection.length > 0) && (NHttpInspect::get_latest_is() != IS_NONE)) ||
+ ((send_to_detection.length == 0) && (NHttpInspect::get_latest_is() == IS_DETECTION)))
{
- nhttp_buf.data = send_to_detection.start;
- nhttp_buf.length = send_to_detection.length;
- assert((nhttp_buf.length <= MAX_OCTETS) && (nhttp_buf.length != 0));
+ // FIXIT-M kludge until we work out issues with returning an empty buffer
+ if (send_to_detection.length > 0)
+ {
+ nhttp_buf.data = send_to_detection.start;
+ nhttp_buf.length = send_to_detection.length;
+ }
+ else
+ {
+ nhttp_buf.data = (const uint8_t*)"";
+ nhttp_buf.length = 1;
+ }
buffer = nullptr;
#ifdef REG_TEST
if (NHttpTestManager::use_test_output())
// If there is leftover data for which we returned PAF_SEARCH and never flushed, we need to set
// up to process because it is about to go to reassemble(). But we don't support partial start
// lines.
- if ((session_data->section_type[source_id] == SEC__NOTCOMPUTE) &&
+ if ((session_data->section_type[source_id] == SEC__NOT_COMPUTE) &&
(session_data->cutter[source_id] != nullptr) &&
(session_data->cutter[source_id]->get_octets_seen() > 0))
{
}
// If there is no more data to process we need to wrap up file processing right now
- if ((session_data->section_type[source_id] == SEC__NOTCOMPUTE) &&
+ if ((session_data->section_type[source_id] == SEC__NOT_COMPUTE) &&
(session_data->file_depth_remaining[source_id] > 0) &&
(session_data->cutter[source_id] != nullptr) &&
(session_data->cutter[source_id]->get_octets_seen() == 0))
FileFlows* file_flows = FileFlows::get_file_flows(flow);
file_flows->file_process(nullptr, 0, SNORT_FILE_END, false);
}
- else
+ else if (session_data->mime_state != nullptr)
{
session_data->mime_state->process_mime_data(flow, nullptr, 0, true,
SNORT_FILE_END);
{ 0, nullptr }
};
-const StrCode NHttpUri::scheme_list[] =
-{
- { SCH_HTTP, "http" },
- { SCH_HTTPS, "https" },
- { SCH_FTP, "ftp" },
- { SCH_GOPHER, "gopher" },
- { SCH_FILE, "file" },
- { 0, nullptr }
-};
-
SO_PUBLIC const StrCode NHttpMsgHeadShared::header_list[] =
{
{ HEAD_CACHE_CONTROL, "cache-control" },
{ EVENT_BAD_HEADER, "Format error in HTTP header" },
{ EVENT_CHUNK_OPTIONS, "Chunk header options present" },
{ EVENT_URI_BAD_FORMAT, "URI badly formatted" },
- { EVENT_URI_BAD_PORT, "URI bad port number" },
+ { EVENT_UNUSED, "Unused" },
{ EVENT_BROKEN_CHUNK, "HTTP chunk misformatted" },
{ EVENT_CHUNK_WHITESPACE, "White space following chunk length" },
{ EVENT_GZIP_OVERRUN, "Excessive gzip compression" },
// Read from the test data file and present to StreamSplitter. 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.
+// function is to read dev_notes.txt.
void NHttpTestInput::scan(uint8_t*& data, uint32_t& length, SourceId source_id, uint64_t seq_num)
{
bool skip_to_break = false;
void NHttpUri::parse_uri()
{
- if (uri_type != URI__NOTCOMPUTE)
- {
- return;
- }
-
// Four basic types of HTTP URI
// "*" means request does not apply to any specific resource
if ((uri.length == 1) && (uri.start[0] == '*'))
{
uri_type = URI_ASTERISK;
- scheme.length = STAT_NOTPRESENT;
- authority.length = STAT_NOTPRESENT;
- abs_path.length = STAT_NOTPRESENT;
+ scheme.length = STAT_NOT_PRESENT;
+ authority.length = STAT_NOT_PRESENT;
+ abs_path.length = STAT_NOT_PRESENT;
}
// CONNECT method uses an authority
else if (method_id == METH_CONNECT)
{
uri_type = URI_AUTHORITY;
- scheme.length = STAT_NOTPRESENT;
+ scheme.length = STAT_NOT_PRESENT;
authority.length = uri.length;
authority.start = uri.start;
- abs_path.length = STAT_NOTPRESENT;
+ abs_path.length = STAT_NOT_PRESENT;
}
// Absolute path is a path but no scheme or authority
else if (uri.start[0] == '/')
{
uri_type = URI_ABSPATH;
- scheme.length = STAT_NOTPRESENT;
- authority.length = STAT_NOTPRESENT;
+ scheme.length = STAT_NOT_PRESENT;
+ authority.length = STAT_NOT_PRESENT;
abs_path.length = uri.length;
abs_path.start = uri.start;
}
}
}
-SchemeId NHttpUri::get_scheme_id()
-{
- if (scheme_id != SCH__NOTCOMPUTE)
- {
- return scheme_id;
- }
- if (get_scheme().length <= 0)
- {
- scheme_id = SCH__NOSOURCE;
- return scheme_id;
- }
-
- // Normalize scheme name to lower case for matching purposes
- uint8_t* lower_scheme;
- if ((lower_scheme = scratch_pad.request(scheme.length)) == nullptr)
- {
- infractions += INF_NO_SCRATCH;
- scheme_id = SCH__INSUFMEMORY;
- return scheme_id;
- }
- norm_to_lower(scheme.start, scheme.length, lower_scheme, infractions, events);
- scheme_id = (SchemeId)str_to_code(lower_scheme, scheme.length, scheme_list);
- return scheme_id;
-}
-
-const Field& NHttpUri::get_norm_host()
-{
- if (host_norm.length != STAT_NOTCOMPUTE)
- {
- return host_norm;
- }
- if (get_host().length < 0)
- {
- host_norm.length = STAT_NOSOURCE;
- return host_norm;
- }
- UriNormalizer::normalize(host, host_norm, false, scratch_pad, infractions, events);
- return host_norm;
-}
-
-const Field& NHttpUri::get_norm_path()
-{
- if (path_norm.length != STAT_NOTCOMPUTE)
- {
- return path_norm;
- }
- if (get_path().length < 0)
- {
- path_norm.length = STAT_NOSOURCE;
- return path_norm;
- }
- UriNormalizer::normalize(path, path_norm, true, scratch_pad, infractions, events);
- return path_norm;
-}
-
-const Field& NHttpUri::get_norm_query()
-{
- if (query_norm.length != STAT_NOTCOMPUTE)
- {
- return query_norm;
- }
- if (get_query().length < 0)
- {
- query_norm.length = STAT_NOSOURCE;
- return query_norm;
- }
- UriNormalizer::normalize(query, query_norm, false, scratch_pad, infractions, events);
- return query_norm;
-}
-
-const Field& NHttpUri::get_norm_fragment()
-{
- if (fragment_norm.length != STAT_NOTCOMPUTE)
- {
- return fragment_norm;
- }
- if (get_fragment().length < 0)
- {
- fragment_norm.length = STAT_NOSOURCE;
- return fragment_norm;
- }
- UriNormalizer::normalize(fragment, fragment_norm, false, scratch_pad, infractions, events);
- return fragment_norm;
-}
-
-int32_t NHttpUri::get_port_value()
-{
- if (port_value != STAT_NOTCOMPUTE)
- {
- return port_value;
- }
- if (get_port().length <= 0)
- {
- port_value = STAT_NOSOURCE;
- return port_value;
- }
- port_value = 0;
- for (int k = 0; k < port.length; k++)
- {
- port_value = port_value * 10 + (port.start[k] - '0');
- if ((port.start[k] < '0') || (port.start[k] > '9') || (port_value > MAX_PORT_VALUE))
- {
- infractions += INF_BAD_PORT;
- events.create_event(EVENT_URI_BAD_PORT);
- port_value = STAT_PROBLEMATIC;
- break;
- }
- }
- return port_value;
-}
-
void NHttpUri::parse_authority()
{
- if (host.length != STAT_NOTCOMPUTE)
+ if (authority.length <= 0)
{
- return;
- }
- if (get_authority().length <= 0)
- {
- host.length = STAT_NOSOURCE;
- port.length = STAT_NOSOURCE;
+ host.length = STAT_NO_SOURCE;
+ port.length = STAT_NO_SOURCE;
return;
}
host.start = authority.start;
for (host.length = 0; (authority.start[host.length] != ':') && (host.length <
- authority.length); host.length++)
- ;
+ authority.length); host.length++);
if (host.length < authority.length)
{
port.length = authority.length - host.length - 1;
port.start = authority.start + host.length + 1;
}
else
- port.length = STAT_NOTPRESENT;
+ port.length = STAT_NOT_PRESENT;
}
void NHttpUri::parse_abs_path()
{
- if (path.length != STAT_NOTCOMPUTE)
- return;
- if (get_abs_path().length <= 0)
+ // path?query#fragment
+ // path is always present in absolute path, while query and fragment are optional
+ if (abs_path.length <= 0)
{
- path.length = STAT_NOSOURCE;
- query.length = STAT_NOSOURCE;
- fragment.length = STAT_NOSOURCE;
+ path.length = STAT_NO_SOURCE;
+ query.length = STAT_NO_SOURCE;
+ fragment.length = STAT_NO_SOURCE;
return;
}
path.start = abs_path.start;
for (path.length = 0; (abs_path.start[path.length] != '?') && (abs_path.start[path.length] !=
- '#') && (path.length < abs_path.length); path.length++)
- ;
+ '#') && (path.length < abs_path.length); path.length++);
if (path.length == abs_path.length)
{
- query.length = STAT_NOTPRESENT;
- fragment.length = STAT_NOTPRESENT;
+ query.length = STAT_NOT_PRESENT;
+ fragment.length = STAT_NOT_PRESENT;
return;
}
if (abs_path.start[path.length] == '?')
{
query.start = abs_path.start + path.length + 1;
for (query.length = 0; (query.start[query.length] != '#') && (query.length <
- abs_path.length - path.length - 1); query.length++)
- ;
+ abs_path.length - path.length - 1); query.length++);
fragment.start = query.start + query.length + 1;
fragment.length = abs_path.length - path.length - 1 - query.length - 1;
}
else
{
- query.length = STAT_NOTPRESENT;
+ query.length = STAT_NOT_PRESENT;
fragment.start = abs_path.start + path.length + 1;
fragment.length = abs_path.length - path.length - 1;
}
}
-// Glue normalized URI fields back together
-const Field& NHttpUri::get_norm_legacy()
+void NHttpUri::normalize()
{
- if (legacy_norm.length != STAT_NOTCOMPUTE)
- {
- return legacy_norm;
- }
- if (get_path().length >= 0)
+ // FIXIT-P generating the normalized URI components directly into the normalized classic buffer
+ // would save a lot of memory and some copying.
+
+ // Divide the URI up into its six components: scheme, host, port, path, query, and fragment
+ parse_uri();
+ parse_authority();
+ parse_abs_path();
+
+ // Normalize the individual components. We don't do anything with scheme or port.
+ if (path.length >= 0)
{
UriNormalizer::normalize(path, path_norm, true, scratch_pad, infractions, events);
}
- if (get_host().length >= 0)
+ if (host.length >= 0)
{
UriNormalizer::normalize(host, host_norm, false, scratch_pad, infractions, events);
}
- if (get_query().length >= 0)
+ if (query.length >= 0)
{
UriNormalizer::normalize(query, query_norm, false, scratch_pad, infractions, events);
}
- if (get_fragment().length >= 0)
+ if (fragment.length >= 0)
{
UriNormalizer::normalize(fragment, fragment_norm, false, scratch_pad,
infractions, events);
// We can reuse the raw URI for the normalized URI if no normalization is required
if (!(infractions & INF_URI_NEED_NORM))
{
- legacy_norm.start = uri.start;
- legacy_norm.length = uri.length;
- return legacy_norm;
+ classic_norm.start = uri.start;
+ classic_norm.length = uri.length;
+ return;
}
// Glue normalized URI pieces back together
}
assert(total_length == current - scratch);
scratch_pad.commit(current - scratch);
- legacy_norm.start = scratch;
- legacy_norm.length = current - scratch;
+ classic_norm.start = scratch;
+ classic_norm.length = current - scratch;
}
else
- legacy_norm.length = STAT_INSUFMEMORY;
- return legacy_norm;
+ classic_norm.length = STAT_INSUF_MEMORY;
}
NHttpUri(const uint8_t* start, int32_t length, NHttpEnums::MethodId method,
NHttpInfractions& infractions_, NHttpEventGen& events_) :
uri(length, start), method_id(method), infractions(infractions_), events(events_),
- scratch_pad(2*length+200) { }
+ scratch_pad(2*length+200) { normalize(); }
const Field& get_uri() const { return uri; }
- NHttpEnums::UriType get_uri_type() { parse_uri(); return uri_type; }
- const Field& get_scheme() { parse_uri(); return scheme; }
- const Field& get_authority() { parse_uri(); return authority; }
- const Field& get_host() { parse_authority(); return host; }
- const Field& get_port() { parse_authority(); return port; }
- const Field& get_abs_path() { parse_uri(); return abs_path; }
- const Field& get_path() { parse_abs_path(); return path; }
- const Field& get_query() { parse_abs_path(); return query; }
- const Field& get_fragment() { parse_abs_path(); return fragment; }
+ NHttpEnums::UriType get_uri_type() { return uri_type; }
+ const Field& get_scheme() { return scheme; }
+ const Field& get_authority() { return authority; }
+ const Field& get_host() { return host; }
+ const Field& get_port() { return port; }
+ const Field& get_abs_path() { return abs_path; }
+ const Field& get_path() { return path; }
+ const Field& get_query() { return query; }
+ const Field& get_fragment() { return fragment; }
- NHttpEnums::SchemeId get_scheme_id();
- const Field& get_norm_host();
- int32_t get_port_value();
- const Field& get_norm_path();
- const Field& get_norm_query();
- const Field& get_norm_fragment();
- const Field& get_norm_legacy();
+ const Field& get_norm_host() { return host_norm; }
+ const Field& get_norm_path() { return path_norm; }
+ const Field& get_norm_query() { return query_norm; }
+ const Field& get_norm_fragment() { return fragment_norm; }
+ const Field& get_norm_classic() { return classic_norm; }
private:
- static const StrCode scheme_list[];
- static const int MAX_PORT_VALUE = 65535;
-
const Field uri;
const NHttpEnums::MethodId method_id;
NHttpInfractions& infractions;
Field query;
Field fragment;
- NHttpEnums::UriType uri_type = NHttpEnums::URI__NOTCOMPUTE;
- NHttpEnums::SchemeId scheme_id = NHttpEnums::SCH__NOTCOMPUTE;
+ NHttpEnums::UriType uri_type = NHttpEnums::URI__NOT_COMPUTE;
Field host_norm;
- int32_t port_value = NHttpEnums::STAT_NOTCOMPUTE;
Field path_norm;
Field query_norm;
Field fragment_norm;
- Field legacy_norm;
+ Field classic_norm;
+ void normalize();
void parse_uri();
void parse_authority();
void parse_abs_path();
+ // FIXIT-P there is an enormous memory waste that this is always allocated. It is only needed
+ // when the URI requires normalization. Most of the time the raw URI is already in normal form.
ScratchPad scratch_pad;
};
void UriNormalizer::normalize(const Field& input, Field& result, bool do_path,
ScratchPad& scratch_pad, NHttpInfractions& infractions, NHttpEventGen& events)
{
- if (result.length != STAT_NOTCOMPUTE)
- return;
- assert (input.length >= 0);
-
// Almost all HTTP requests are honest and rarely need expensive normalization processing. We
// do a quick scan for red flags and only perform normalization if something comes up.
// Otherwise we set the normalized field to point at the raw value.
uint8_t* const scratch = scratch_pad.request(2 * buffer_length);
if (scratch == nullptr)
{
- result.length = STAT_INSUFMEMORY;
+ result.length = STAT_INSUF_MEMORY;
return;
}
uint8_t* const front_half = scratch;