From 6d60b3a747940b6cc78be0dc5b0cd3b76b93ef09 Mon Sep 17 00:00:00 2001 From: Pablo Rincon Date: Wed, 6 Apr 2011 17:23:52 +0200 Subject: [PATCH] filename and fileext keywords --- src/Makefile.am | 3 + src/app-layer-htp.c | 241 +++++++++++++++++++++++++++++++++- src/app-layer-htp.h | 16 ++- src/detect-fileext.c | 298 +++++++++++++++++++++++++++++++++++++++++ src/detect-fileext.h | 39 ++++++ src/detect-filename.c | 299 ++++++++++++++++++++++++++++++++++++++++++ src/detect-filename.h | 38 ++++++ src/detect.c | 4 + src/detect.h | 3 + src/flow-util.h | 14 ++ src/flow.c | 1 + src/flow.h | 5 + src/suricata.c | 3 + src/util-spm-bm.h | 2 +- 14 files changed, 961 insertions(+), 5 deletions(-) create mode 100644 src/detect-fileext.c create mode 100644 src/detect-fileext.h create mode 100644 src/detect-filename.c create mode 100644 src/detect-filename.h diff --git a/src/Makefile.am b/src/Makefile.am index b7a39a76b2..d5f95167a9 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -47,6 +47,7 @@ flow-manager.c flow-manager.h \ flow-queue.c flow-queue.h \ flow-hash.c flow-hash.h \ flow-util.c flow-util.h \ +flow-file.c flow-file.h \ util-mem.h \ flow-var.c flow-var.h \ flow-bit.c flow-bit.h \ @@ -149,6 +150,8 @@ detect-detection-filter.c detect-detection-filter.h \ detect-http-client-body.c detect-http-client-body.h \ detect-http-stat-msg.c detect-http-stat-msg.h \ detect-asn1.c detect-asn1.h \ +detect-filename.c detect-filename.h \ +detect-fileext.c detect-fileext.h \ detect-http-stat-code.c detect-http-stat-code.h \ detect-ssl-version.c detect-ssl-version.h \ detect-ssl-state.c detect-ssl-state.h \ diff --git a/src/app-layer-htp.c b/src/app-layer-htp.c index ea2e3ad8f6..0ad22e1334 100644 --- a/src/app-layer-htp.c +++ b/src/app-layer-htp.c @@ -59,6 +59,7 @@ #include "util-unittest.h" #include "util-unittest-helper.h" #include "flow-util.h" +#include "flow-file.h" #include "detect-engine.h" #include "detect-engine-state.h" @@ -87,6 +88,7 @@ static uint64_t htp_state_memcnt = 0; #endif static uint8_t need_htp_request_body = 0; +static uint8_t need_htp_file_inspection = 0; #ifdef DEBUG /** @@ -283,6 +285,19 @@ void AppLayerHtpEnableRequestBodyCallback(void) SCReturn; } +/** + * \brief Sets a flag that informs the HTP app layer that some module in the + * engine needs the http request body data. + * \initonly + */ +void AppLayerHtpNeedFileInspection(void) +{ + SCEnter(); + need_htp_request_body = 1; + need_htp_file_inspection = 1; + SCReturn; +} + /** * \brief Function to handle the reassembled data from client and feed it to @@ -310,6 +325,7 @@ static int HTPHandleRequestData(Flow *f, void *htp_state, //PrintRawDataFp(stdout, input, input_len); HtpState *hstate = (HtpState *)htp_state; + hstate->f = f; /* On the first invocation, create the connection parser structure to * be used by HTP library. This is looked up via IP in the radix @@ -462,6 +478,7 @@ static int HTPHandleResponseData(Flow *f, void *htp_state, int ret = 1; HtpState *hstate = (HtpState *)htp_state; + hstate->f = f; if (hstate->connp == NULL) { SCLogError(SC_ERR_ALPARSER, "HTP state has no connp"); SCReturnInt(-1); @@ -638,7 +655,7 @@ error: */ void HtpBodyPrint(HtpBody *body) { - if (SCLogDebugEnabled()) { + if (SCLogDebugEnabled()||1) { SCEnter(); if (body->nchunks == 0) @@ -646,9 +663,12 @@ void HtpBodyPrint(HtpBody *body) HtpBodyChunk *cur = NULL; SCLogDebug("--- Start body chunks at %p ---", body); + printf("--- Start body chunks at %p ---\n", body); for (cur = body->first; cur != NULL; cur = cur->next) { SCLogDebug("Body %p; Chunk id: %"PRIu32", data %p, len %"PRIu32"\n", body, cur->id, cur->data, (uint32_t)cur->len); + printf("Body %p; Chunk id: %"PRIu32", data %p, len %"PRIu32"\n", + body, cur->id, cur->data, (uint32_t)cur->len); PrintRawDataFp(stdout, (uint8_t*)cur->data, cur->len); } SCLogDebug("--- End body chunks at %p ---", body); @@ -724,6 +744,9 @@ static int HTPCallbackRequestUriNormalize(htp_connp_t *c) int HTPCallbackRequestBodyData(htp_tx_data_t *d) { SCEnter(); + const char *GROUP_DELIM = ";\r\n"; + const char *FIELD_DELIM = ":= "; + HtpState *hstate = (HtpState *)d->tx->connp->user_data; SCLogDebug("New response body data available at %p -> %p -> %p, bodylen " "%"PRIu32"", hstate, d, d->data, (uint32_t)d->len); @@ -742,6 +765,52 @@ int HTPCallbackRequestBodyData(htp_tx_data_t *d) if (cl != NULL) htud->content_len = htp_parse_content_length(cl->value); + htp_header_t *h = (htp_header_t *)table_getc(d->tx->request_headers, + "Content-Type"); + if (h != NULL && bstr_len(h->value) > 0) { + uint8_t *content_type = (uint8_t*) SCMalloc(bstr_len(h->value)); + if (content_type == NULL) { + SCLogError(SC_ERR_MEM_ALLOC, "Could not allocate memory for content type"); + exit(EXIT_FAILURE); + } + memcpy(content_type, (uint8_t*) bstr_ptr(h->value), bstr_len(h->value)); + + char *tok = NULL; + char *ftok = NULL; + char *lctx1 = NULL; + char *lctx2 = NULL; + + tok = strtok_r((char *) content_type, GROUP_DELIM, &lctx1); + if (tok == NULL) { + SCLogWarning(SC_ERR_INVALID_VALUE, "Malformed content-type field (null)"); + } else { + htud->contenttype = SCStrdup(tok); + htud->contenttype_len = strlen(tok); + + if (strcasecmp("multipart/form-data", tok) == 0) { + /* We have a form, let's see the boundary */ + tok = strtok_r(NULL, GROUP_DELIM, &lctx1); + while (tok != NULL) { + ftok = strtok_r(tok, FIELD_DELIM, &lctx2); + if (ftok == NULL) { + continue; + } + if (strcasecmp("boundary", ftok) == 0) { + ftok = strtok_r(NULL, FIELD_DELIM, &lctx2); + if (ftok == NULL) { + continue; + } + htud->boundary = SCStrdup(ftok); + htud->boundary_len = strlen(ftok); + htud->flags |= HTP_BOUNDARY_SET; + } + tok = strtok_r(NULL, GROUP_DELIM, &lctx1); + } + } + } + SCFree(content_type); + } + /* Set the user data for handling body chunks on this transaction */ htp_tx_set_user_data(d->tx, htud); } @@ -776,11 +845,179 @@ int HTPCallbackRequestBodyData(htp_tx_data_t *d) htud->flags |= HTP_BODY_COMPLETE; } + if (htud->flags & HTP_BOUNDARY_SET) { + /* Checkout the boundaries */ + if ( !(htud->flags & HTP_BOUNDARY_OPEN)) { + HtpBodyChunk *cur = htud->body.first; + uint8_t *chunks_buffer = NULL; + int32_t chunks_buffer_len = 0; + //TODO: Now that we are concatenating chunks here, free the list + //TODO: of chunks and append only 1 big chunk + while (cur != NULL) { + chunks_buffer_len += cur->len; + if ( (chunks_buffer = SCRealloc(chunks_buffer, chunks_buffer_len)) == NULL) { + goto end; + } + + memcpy(chunks_buffer + chunks_buffer_len - cur->len, cur->data, cur->len); + cur = cur->next; + } + //PrintRawDataFp(stdout, chunks_buffer, chunks_buffer_len); + if (chunks_buffer != NULL) { + char *expected_boundary = (char*)SCMalloc(htud->boundary_len + 4); + if (expected_boundary == NULL) { + goto end; + } + expected_boundary[0]='-'; + expected_boundary[1]='-'; + strncat(expected_boundary + 2, (char *)htud->boundary, htud->boundary_len + 4); + + char *expected_boundary_end = (char*)SCMalloc(htud->boundary_len + 6); + if (expected_boundary_end == NULL) { + goto end; + } + expected_boundary_end[0]='-'; + expected_boundary_end[1]='-'; + strncat(expected_boundary_end + 2, (char *)htud->boundary, htud->boundary_len + 4); + strncat(expected_boundary_end, "--", htud->boundary_len + 6); + + uint8_t *filename = NULL; + uint8_t *filetype = NULL; + + uint8_t *header_start = Bs2bmSearch(chunks_buffer, chunks_buffer_len, (uint8_t *) expected_boundary, strlen(expected_boundary)); + uint8_t *header_end = Bs2bmSearch(chunks_buffer, chunks_buffer_len, (uint8_t *)"\r\n\r\n", strlen("\r\n\r\n")); + uint8_t *form_end = Bs2bmSearch(chunks_buffer, chunks_buffer_len, (uint8_t *) expected_boundary_end, strlen(expected_boundary_end)); + + SCLogDebug("Expected boundary: %s", expected_boundary); + SCLogDebug("Expected boundary_end: %s", expected_boundary_end); + + uint8_t *header = NULL; + + while (header_start != NULL && header_end != NULL && + (header_end != form_end) && + header_start < chunks_buffer + chunks_buffer_len && + header_end < chunks_buffer + chunks_buffer_len && header_start < header_end) { + header = SCMalloc(sizeof(header_end - header_start) + 4); + memcpy(header, header_start, header_end - header_start); + header[header_end - header_start] = '\0'; + header[1 + header_end - header_start] = '\0'; + + /* + printf("HEADER START: \n"); + PrintRawDataFp(stdout, header, header_end - header_start); + printf("HEADER END: \n"); + */ + + char *ftok2 = NULL; + char *tok2 = NULL; + char *ctx1 = NULL; + char *ctx2 = NULL; + char *field = NULL; + tok2 = strtok_r((char *) header, GROUP_DELIM, &ctx1); + if (tok2 == NULL) { + SCLogWarning(SC_ERR_INVALID_VALUE, "Expecting Boundary headers"); + } else { + SCLogDebug("Token: %s", tok2); + if (tok2 != NULL && strcasecmp(expected_boundary, tok2) == 0) { + + tok2 = strtok_r(NULL, GROUP_DELIM, &ctx1); + /* We have the boundary! Extract filename, type, and so on */ + while (tok2 != NULL && strlen(tok2) > 0) { + SCLogDebug("TOKEN: %s", tok2); + field = SCStrdup(tok2); + ftok2 = strtok_r(field, FIELD_DELIM, &ctx2); + while (ftok2 != NULL && strlen(ftok2) > 0) { + if(strcasecmp("filename", ftok2) == 0) { + ftok2 = strtok_r(NULL, FIELD_DELIM, &ctx2); + if (ftok2 != NULL) { + filename = SCStrdup(ftok2); + SCLogDebug("got filename %s", filename); + } + } else if(strcasecmp("Content-Type", ftok2) == 0) { + ftok2 = strtok_r(NULL, FIELD_DELIM, &ctx2); + if (ftok2 != NULL) { + filetype = SCStrdup(ftok2); + SCLogDebug("got proto filetype %s", filetype); + } + } else { + SCLogDebug("-> ftok2 %s len %d", ftok2, (int)strlen(ftok2)); + } + ftok2 = strtok_r(NULL, FIELD_DELIM, &ctx2); + } + //SCFree(field); + tok2 = strtok_r(NULL, GROUP_DELIM, &ctx1); + } + } + //exit(EXIT_FAILURE); + } + if (filename != NULL) { + int i = 0; + SCMutexLock(&hstate->f->files_m); + FlowFileContainer *ffc = NULL; + /* We have to add/update the file */ + SCLogDebug("Adding file entry to flow: %s", filename); + FlowFile *cur_file = NULL; + if (hstate->f->files == NULL) { + ffc = FlowFileContainerAlloc(); + if (ffc == NULL) { + SCMutexUnlock(&hstate->f->files_m); + goto end; + } + hstate->f->files = ffc; + + } else { + /* TODO: else append chunk, update things...*/ + ffc = hstate->f->files; + } + cur_file = FlowFileContainerRetrieve(ffc, filename, ALPROTO_HTTP, filetype); + if (cur_file == NULL) { + cur_file = FlowFileAlloc(); + cur_file->name = filename; + cur_file->name_len = strlen((char *)filename); + for (i = strlen((char *)filename) - 1; i >= 0 && filename[i] != '.'; i--); + if (filename[i] == '.') { + cur_file->ext = SCStrdup((char *)&filename[i]); + printf("File ext: %s\n", cur_file->ext); + } + + if (filetype != NULL) { + cur_file->proto_type = filetype; + cur_file->proto_type_len = strlen((char *)filetype); + } + cur_file->alproto = ALPROTO_HTTP; + SCLogDebug("Added"); + FlowFileContainerAdd(ffc, cur_file); + /* cur_file->ext = get extension */ + } + SCMutexUnlock(&hstate->f->files_m); + } else if (filetype != NULL) { + /* We got a filetype but not the name of file, so removing */ + SCFree(filetype); + } + filename = NULL; + filetype = NULL; + + /* Search next boundary entry after the start of body */ + if ( (form_end == NULL && header_end + 4 < chunks_buffer + chunks_buffer_len) || (form_end != NULL && header_end != NULL && header_end + 4 < form_end)) { + uint32_t cursizeread = header_end - chunks_buffer; + header_start = Bs2bmSearch(header_end + 4, chunks_buffer_len - (cursizeread + 4), (uint8_t *) expected_boundary, strlen(expected_boundary)); + header_end = Bs2bmSearch(header_end + 4, chunks_buffer_len - (cursizeread + 4), (uint8_t *) "\r\n\r\n", strlen("\r\n\r\n")); + } + } + if (header != NULL) { + SCFree(header); + header = NULL; + } + } + } + } + +end: /* set the new chunk flag */ hstate->flags |= HTP_FLAG_NEW_BODY_SET; //if (SCLogDebugEnabled()) { - // HtpBodyPrint(&htud->body); + //HtpBodyPrint(&htud->body); //} } diff --git a/src/app-layer-htp.h b/src/app-layer-htp.h index 78040b819d..824d00b48f 100644 --- a/src/app-layer-htp.h +++ b/src/app-layer-htp.h @@ -85,8 +85,11 @@ typedef struct HtpBody_ { or a response */ } HtpBody; -#define HTP_BODY_COMPLETE 0x01 /* body is complete or limit is reached, - either way, this is it. */ +#define HTP_BODY_COMPLETE 0x01 /* body is complete or limit is reached, + either way, this is it. */ +#define HTP_CONTENTTYPE_SET 0x02 /* We have the content type */ +#define HTP_BOUNDARY_SET 0x04 /* We have a boundary string */ +#define HTP_BOUNDARY_OPEN 0x08 /* We have a boundary string */ /** Now the Body Chunks will be stored per transaction, at * the tx user data */ @@ -97,6 +100,13 @@ typedef struct SCHtpTxUserData_ { uint32_t content_len; /* Holds the length of the htp request body seen so far */ uint32_t content_len_so_far; + /* Holds the boundary identificator string if any (used on multipart/form-data only) */ + uint8_t *boundary; + uint32_t boundary_len; + /* Holds the content-type */ + uint8_t *contenttype; + uint32_t contenttype_len; + uint8_t flags; } SCHtpTxUserData; @@ -104,6 +114,7 @@ typedef struct HtpState_ { htp_connp_t *connp; /**< Connection parser structure for each connection */ + Flow *f; /**< Needed to retrieve the original flow when usin HTPLib callbacks */ uint8_t flags; uint16_t transaction_cnt; uint16_t transaction_done; @@ -125,6 +136,7 @@ void AppLayerHtpRegisterExtraCallbacks(void); /* To free the state from unittests using app-layer-htp */ void HTPStateFree(void *); void AppLayerHtpEnableRequestBodyCallback(void); +void AppLayerHtpNeedFileInspection(void); void AppLayerHtpPrintStats(void); #endif /* __APP_LAYER_HTP_H__ */ diff --git a/src/detect-fileext.c b/src/detect-fileext.c new file mode 100644 index 0000000000..5568b92fc1 --- /dev/null +++ b/src/detect-fileext.c @@ -0,0 +1,298 @@ +/* Copyright (C) 2007-2010 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * 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 + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Pablo Rincon + * + */ + +#include "suricata-common.h" +#include "threads.h" +#include "debug.h" +#include "decode.h" + +#include "detect.h" +#include "detect-parse.h" + +#include "detect-engine.h" +#include "detect-engine-mpm.h" +#include "detect-engine-state.h" + +#include "flow.h" +#include "flow-var.h" +#include "flow-util.h" + +#include "util-debug.h" +#include "util-unittest.h" +#include "util-unittest-helper.h" +#include "util-spm-bm.h" + +#include "app-layer.h" + +#include "stream-tcp.h" +#include "detect-fileext.h" + +/** + * \brief Regex for parsing the fileext string + */ +#define PARSE_REGEX "^\\s*\"\\s*(.+)\\s*\"\\s*$" + +static pcre *parse_regex; +static pcre_extra *parse_regex_study; + +int DetectFileextMatch (ThreadVars *, DetectEngineThreadCtx *, Flow *, uint8_t, void *, Signature *, SigMatch *); +static int DetectFileextSetup (DetectEngineCtx *, Signature *, char *); +void DetectFileextRegisterTests(void); +void DetectFileextFree(void *); + +/** + * \brief Registration function for keyword: fileext + */ +void DetectFileextRegister(void) { + sigmatch_table[DETECT_FILEEXT].name = "fileext"; + sigmatch_table[DETECT_FILEEXT].Match = NULL; + sigmatch_table[DETECT_FILEEXT].AppLayerMatch = DetectFileextMatch; + sigmatch_table[DETECT_FILEEXT].alproto = ALPROTO_HTTP; + sigmatch_table[DETECT_FILEEXT].Setup = DetectFileextSetup; + sigmatch_table[DETECT_FILEEXT].Free = DetectFileextFree; + sigmatch_table[DETECT_FILEEXT].RegisterTests = DetectFileextRegisterTests; + + const char *eb; + int eo; + int opts = 0; + + SCLogDebug("registering fileext rule option"); + + parse_regex = pcre_compile(PARSE_REGEX, opts, &eb, &eo, NULL); + if (parse_regex == NULL) { + SCLogError(SC_ERR_PCRE_COMPILE, "Compile of \"%s\" failed at offset %" PRId32 ": %s", + PARSE_REGEX, eo, eb); + goto error; + } + + parse_regex_study = pcre_study(parse_regex, 0, &eb); + if (eb != NULL) { + SCLogError(SC_ERR_PCRE_STUDY, "pcre study failed: %s", eb); + goto error; + } + return; + +error: + return; +} + +/** + * \brief match the specified file extension + * + * \param t pointer to thread vars + * \param det_ctx pointer to the pattern matcher thread + * \param p pointer to the current packet + * \param m pointer to the sigmatch that we will cast into DetectFileextData + * + * \retval 0 no match + * \retval 1 match + */ +int DetectFileextMatch (ThreadVars *t, DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, void *state, Signature *s, SigMatch *m) +{ + SCEnter(); + int ret = 0; + + DetectFileextData *fileext = m->ctx; + + SCMutexLock(&f->files_m); + if (f->files != NULL && f->files->cnt > 0) { + FlowFile *file = f->files->start; + for (; file != NULL; file = file->next) { + if (file != NULL && file->ext != NULL && + BoyerMooreNocase(fileext->ext, fileext->len, file->ext, + file->ext_len, fileext->bm_ctx->bmGs, fileext->bm_ctx->bmBc) != NULL) + { + ret = 1; + SCLogDebug("File ext %s found", file->ext); + /* Stop searching */ + break; + } + } + } + SCMutexUnlock(&f->files_m); + + SCReturnInt(ret); +} + +/** + * \brief This function is used to parse fileet + * + * \param str Pointer to the fileext value string + * + * \retval pointer to DetectFileextData on success + * \retval NULL on failure + */ +DetectFileextData *DetectFileextParse (char *str) +{ + DetectFileextData *fileext = NULL; + #define MAX_SUBSTRINGS 30 + int ret = 0, res = 0; + int ov[MAX_SUBSTRINGS]; + + ret = pcre_exec(parse_regex, parse_regex_study, str, strlen(str), 0, 0, + ov, MAX_SUBSTRINGS); + + if (ret < 1 || ret > 3) { + SCLogError(SC_ERR_PCRE_MATCH, "invalid fileext option"); + goto error; + } + + if (ret > 1) { + const char *str_ptr; + res = pcre_get_substring((char *)str, ov, MAX_SUBSTRINGS, 1, &str_ptr); + if (res < 0) { + SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed"); + goto error; + } + fileext = SCMalloc(sizeof(DetectFileextData)); + if (fileext == NULL) + goto error; + + memset(fileext, 0x00, sizeof(DetectFileextData)); + + /* Remove quotes if any and copy the filename */ + if (str_ptr[0] == '"') { + fileext->ext = (uint8_t *)SCStrdup((char*)str_ptr + 1); + fileext->ext[strlen(str_ptr - 1)] = '\0'; + } else { + fileext->ext = (uint8_t *)SCStrdup((char*)str_ptr); + } + + if (fileext->ext == NULL) { + goto error; + } + fileext->len = strlen((char *) fileext->ext); + fileext->bm_ctx = BoyerMooreCtxInit(fileext->ext, fileext->len); + BoyerMooreCtxToNocase(fileext->bm_ctx, fileext->ext, fileext->len); + + SCLogDebug("will look for fileext %s", fileext->ext); + } + + return fileext; + +error: + if (fileext != NULL) + DetectFileextFree(fileext); + return NULL; + +} + +/** + * \brief this function is used to add the parsed "id" option + * \brief into the current signature + * + * \param de_ctx pointer to the Detection Engine Context + * \param s pointer to the Current Signature + * \param idstr pointer to the user provided "id" option + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectFileextSetup (DetectEngineCtx *de_ctx, Signature *s, char *str) +{ + DetectFileextData *fileext= NULL; + SigMatch *sm = NULL; + + fileext = DetectFileextParse(str); + if (fileext == NULL) + goto error; + + /* Okay so far so good, lets get this into a SigMatch + * and put it in the Signature. */ + sm = SigMatchAlloc(); + if (sm == NULL) + goto error; + + sm->type = DETECT_FILEEXT; + sm->ctx = (void *)fileext; + + SigMatchAppendAppLayer(s, sm); + + if (s->alproto != ALPROTO_UNKNOWN && s->alproto != ALPROTO_HTTP) { + SCLogError(SC_ERR_CONFLICTING_RULE_KEYWORDS, "rule contains conflicting keywords."); + goto error; + } + + AppLayerHtpNeedFileInspection(); + s->alproto = ALPROTO_HTTP; + return 0; + +error: + if (fileext != NULL) + DetectFileextFree(fileext); + if (sm != NULL) + SCFree(sm); + return -1; + +} + +/** + * \brief this function will free memory associated with DetectFileextData + * + * \param fileext pointer to DetectFileextData + */ +void DetectFileextFree(void *ptr) { + DetectFileextData *fileext = (DetectFileextData *)ptr; + BoyerMooreCtxDeInit(fileext->bm_ctx); + SCFree(fileext); +} + +#ifdef UNITTESTS /* UNITTESTS */ + +/** + * \test DetectFileextTestParse01 + */ +int DetectFileextTestParse01 (void) { + return 0; +} + +/** + * \test DetectFileextTestParse02 + */ +int DetectFileextTestParse02 (void) { + return 0; +} + +/** + * \test DetectFileextTestParse03 + */ +int DetectFileextTestParse03 (void) { + return 1; +} + + +#include "stream-tcp-reassemble.h" + +#endif /* UNITTESTS */ + +/** + * \brief this function registers unit tests for DetectFileext + */ +void DetectFileextRegisterTests(void) { +#ifdef UNITTESTS /* UNITTESTS */ + UtRegisterTest("DetectFileextTestParse01", DetectFileextTestParse01, 1); + UtRegisterTest("DetectFileextTestParse02", DetectFileextTestParse02, 1); + UtRegisterTest("DetectFileextTestParse03", DetectFileextTestParse03, 1); +#endif /* UNITTESTS */ +} diff --git a/src/detect-fileext.h b/src/detect-fileext.h new file mode 100644 index 0000000000..12db5f7665 --- /dev/null +++ b/src/detect-fileext.h @@ -0,0 +1,39 @@ +/* Copyright (C) 2007-2010 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * 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 + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Pablo Rincon + */ + +#ifndef __DETECT_FILEEXT_H__ +#define __DETECT_FILEEXT_H__ +#include "util-spm-bm.h" + + +typedef struct DetectFileextData_ { + uint8_t *ext; /** file extension to match */ + uint16_t len; /** length of the file */ + BmCtx *bm_ctx; + uint8_t flags; +} DetectFileextData; + +/* prototypes */ +void DetectFileextRegister (void); + +#endif /* __DETECT_FILEEXT_H__ */ diff --git a/src/detect-filename.c b/src/detect-filename.c new file mode 100644 index 0000000000..4c3df08269 --- /dev/null +++ b/src/detect-filename.c @@ -0,0 +1,299 @@ +/* Copyright (C) 2007-2010 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * 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 + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Pablo Rincon + * + */ + +#include "suricata-common.h" +#include "threads.h" +#include "debug.h" +#include "decode.h" + +#include "detect.h" +#include "detect-parse.h" + +#include "detect-engine.h" +#include "detect-engine-mpm.h" +#include "detect-engine-state.h" + +#include "flow.h" +#include "flow-var.h" +#include "flow-util.h" + +#include "util-debug.h" +#include "util-spm-bm.h" +#include "util-unittest.h" +#include "util-unittest-helper.h" + +#include "app-layer.h" + +#include "stream-tcp.h" + +#include "detect-filename.h" + +/** + * \brief Regex for parsing the protoversion string + */ +#define PARSE_REGEX "^\\s*\"\\s*(.+)\\s*\"\\s*$" + +static pcre *parse_regex; +static pcre_extra *parse_regex_study; + +int DetectFilenameMatch (ThreadVars *, DetectEngineThreadCtx *, Flow *, uint8_t, void *, Signature *, SigMatch *); +static int DetectFilenameSetup (DetectEngineCtx *, Signature *, char *); +void DetectFilenameRegisterTests(void); +void DetectFilenameFree(void *); + +/** + * \brief Registration function for keyword: filename + */ +void DetectFilenameRegister(void) { + sigmatch_table[DETECT_FILENAME].name = "filename"; + sigmatch_table[DETECT_FILENAME].Match = NULL; + sigmatch_table[DETECT_FILENAME].AppLayerMatch = DetectFilenameMatch; + sigmatch_table[DETECT_FILENAME].alproto = ALPROTO_HTTP; + sigmatch_table[DETECT_FILENAME].Setup = DetectFilenameSetup; + sigmatch_table[DETECT_FILENAME].Free = DetectFilenameFree; + sigmatch_table[DETECT_FILENAME].RegisterTests = DetectFilenameRegisterTests; + + const char *eb; + int eo; + int opts = 0; + + SCLogDebug("registering filename rule option"); + + parse_regex = pcre_compile(PARSE_REGEX, opts, &eb, &eo, NULL); + if (parse_regex == NULL) { + SCLogError(SC_ERR_PCRE_COMPILE, "Compile of \"%s\" failed at offset %" PRId32 ": %s", + PARSE_REGEX, eo, eb); + goto error; + } + + parse_regex_study = pcre_study(parse_regex, 0, &eb); + if (eb != NULL) { + SCLogError(SC_ERR_PCRE_STUDY, "pcre study failed: %s", eb); + goto error; + } + return; + +error: + return; +} + +/** + * \brief match the specified filename + * + * \param t pointer to thread vars + * \param det_ctx pointer to the pattern matcher thread + * \param p pointer to the current packet + * \param m pointer to the sigmatch that we will cast into DetectFilenameData + * + * \retval 0 no match + * \retval 1 match + */ +int DetectFilenameMatch (ThreadVars *t, DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, void *state, Signature *s, SigMatch *m) +{ + SCEnter(); + int ret = 0; + + DetectFilenameData *filename = m->ctx; + + SCMutexLock(&f->files_m); + if (f->files != NULL && f->files->cnt > 0) { + FlowFile *file = f->files->start; + for (; file != NULL; file = file->next) { + if (file != NULL && file->name != NULL && + BoyerMooreNocase(filename->name, filename->len, file->name, + file->name_len, filename->bm_ctx->bmGs, filename->bm_ctx->bmBc) != NULL) + { + ret = 1; + SCLogDebug("File %s found", file->name); + /* Stop searching */ + break; + } + } + } + SCMutexUnlock(&f->files_m); + + SCReturnInt(ret); +} + +/** + * \brief This function is used to parse IPV4 ip_id passed via keyword: "id" + * + * \param idstr Pointer to the user provided id option + * + * \retval filename pointer to DetectFilenameData on success + * \retval NULL on failure + */ +DetectFilenameData *DetectFilenameParse (char *str) +{ + DetectFilenameData *filename = NULL; + #define MAX_SUBSTRINGS 30 + int ret = 0, res = 0; + int ov[MAX_SUBSTRINGS]; + + ret = pcre_exec(parse_regex, parse_regex_study, str, strlen(str), 0, 0, + ov, MAX_SUBSTRINGS); + + if (ret < 1 || ret > 3) { + SCLogError(SC_ERR_PCRE_MATCH, "invalid filename option"); + goto error; + } + + if (ret > 1) { + const char *str_ptr; + res = pcre_get_substring((char *)str, ov, MAX_SUBSTRINGS, 1, &str_ptr); + if (res < 0) { + SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed"); + goto error; + } + + /* We have a correct filename option */ + filename = SCMalloc(sizeof(DetectFilenameData)); + if (filename == NULL) + goto error; + + memset(filename, 0x00, sizeof(DetectFilenameData)); + + if (str_ptr[0] == '"') { + filename->name = (uint8_t *)SCStrdup((char*)str_ptr + 1); + filename->name[strlen(str_ptr - 1)] = '\0'; + } else { + filename->name = (uint8_t *)SCStrdup((char*)str_ptr); + } + if (filename->name == NULL) { + goto error; + } + filename->len = strlen((char *) filename->name); + filename->bm_ctx = BoyerMooreCtxInit(filename->name, filename->len); + BoyerMooreCtxToNocase(filename->bm_ctx, filename->name, filename->len); + + SCLogDebug("will look for filename %s", filename->name); + } + + return filename; + +error: + if (filename != NULL) + DetectFilenameFree(filename); + return NULL; + +} + +/** + * \brief this function is used to parse filename options + * \brief into the current signature + * + * \param de_ctx pointer to the Detection Engine Context + * \param s pointer to the Current Signature + * \param str pointer to the user provided "filename" option + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectFilenameSetup (DetectEngineCtx *de_ctx, Signature *s, char *str) +{ + DetectFilenameData *filename = NULL; + SigMatch *sm = NULL; + + filename = DetectFilenameParse(str); + if (filename == NULL) + goto error; + + /* Okay so far so good, lets get this into a SigMatch + * and put it in the Signature. */ + sm = SigMatchAlloc(); + if (sm == NULL) + goto error; + + sm->type = DETECT_FILENAME; + sm->ctx = (void *)filename; + + SigMatchAppendAppLayer(s, sm); + + if (s->alproto != ALPROTO_UNKNOWN && s->alproto != ALPROTO_HTTP) { + SCLogError(SC_ERR_CONFLICTING_RULE_KEYWORDS, "rule contains conflicting keywords."); + goto error; + } + + AppLayerHtpNeedFileInspection(); + + s->alproto = ALPROTO_HTTP; + return 0; + +error: + if (filename != NULL) + DetectFilenameFree(filename); + if (sm != NULL) + SCFree(sm); + return -1; +} + +/** + * \brief this function will free memory associated with DetectFilenameData + * + * \param filename pointer to DetectFilenameData + */ +void DetectFilenameFree(void *ptr) { + DetectFilenameData *filename = (DetectFilenameData *)ptr; + BoyerMooreCtxDeInit(filename->bm_ctx); + SCFree(filename); +} + +#ifdef UNITTESTS /* UNITTESTS */ + +/** + * \test DetectFilenameTestParse01 + */ +int DetectFilenameTestParse01 (void) { + return 0; +} + +/** + * \test DetectFilenameTestParse02 + */ +int DetectFilenameTestParse02 (void) { + return 0; +} + +/** + * \test DetectFilenameTestParse03 + */ +int DetectFilenameTestParse03 (void) { + return 1; +} + + +#include "stream-tcp-reassemble.h" + +#endif /* UNITTESTS */ + +/** + * \brief this function registers unit tests for DetectFilename + */ +void DetectFilenameRegisterTests(void) { +#ifdef UNITTESTS /* UNITTESTS */ + UtRegisterTest("DetectFilenameTestParse01", DetectFilenameTestParse01, 1); + UtRegisterTest("DetectFilenameTestParse02", DetectFilenameTestParse02, 1); + UtRegisterTest("DetectFilenameTestParse03", DetectFilenameTestParse03, 1); +#endif /* UNITTESTS */ +} diff --git a/src/detect-filename.h b/src/detect-filename.h new file mode 100644 index 0000000000..e04bdd6636 --- /dev/null +++ b/src/detect-filename.h @@ -0,0 +1,38 @@ +/* Copyright (C) 2007-2010 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * 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 + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Pablo Rincon + */ + +#ifndef __DETECT_FILENAME_H__ +#define __DETECT_FILENAME_H__ +#include "util-spm-bm.h" + +typedef struct DetectFilenameData { + uint8_t *name; /** name of the file to match */ + BmCtx *bm_ctx; /** BM context */ + uint16_t len; /** name length */ + uint8_t flags; +} DetectFilenameData; + +/* prototypes */ +void DetectFilenameRegister (void); + +#endif /* __DETECT_FILENAME_H__ */ diff --git a/src/detect.c b/src/detect.c index f6792f5a7c..13a4b4b979 100644 --- a/src/detect.c +++ b/src/detect.c @@ -90,6 +90,8 @@ #include "detect-id.h" #include "detect-rpc.h" #include "detect-asn1.h" +#include "detect-filename.h" +#include "detect-fileext.h" #include "detect-dsize.h" #include "detect-flowvar.h" #include "detect-flowint.h" @@ -4294,6 +4296,8 @@ void SigTableSetup(void) { DetectHttpStatCodeRegister(); DetectSslVersionRegister(); DetectByteExtractRegister(); + DetectFilenameRegister(); + DetectFileextRegister(); uint8_t i = 0; for (i = 0; i < DETECT_TBLSIZE; i++) { diff --git a/src/detect.h b/src/detect.h index 3b904785b0..a5d942b5ab 100644 --- a/src/detect.h +++ b/src/detect.h @@ -1023,6 +1023,9 @@ enum { DETECT_ENGINE_EVENT, DETECT_STREAM_EVENT, + DETECT_FILENAME, + DETECT_FILEEXT, + /* make sure this stays last */ DETECT_TBLSIZE, }; diff --git a/src/flow-util.h b/src/flow-util.h index 9c5c1f0891..b3bbe410fa 100644 --- a/src/flow-util.h +++ b/src/flow-util.h @@ -62,6 +62,8 @@ (f)->lnext = NULL; \ (f)->lprev = NULL; \ RESET_COUNTERS((f)); \ + (f)->files = NULL; \ + SCMutexInit(&(f)->files_m, NULL); \ } while (0) /** \brief macro to recycle a flow before it goes into the spare queue for reuse. @@ -92,6 +94,10 @@ GenericVarFree((f)->flowvar); \ (f)->flowvar = NULL; \ RESET_COUNTERS((f)); \ + SCMutexLock(&(f)->files_m); \ + if ((f)->files != NULL) \ + FlowFileContainerRecycle((f)->files); \ + SCMutexUnlock(&(f)->files_m); \ } while(0) #define FLOW_DESTROY(f) do { \ @@ -105,6 +111,14 @@ DetectTagDataListFree((f)->tag_list); \ GenericVarFree((f)->flowvar); \ SCMutexDestroy(&(f)->de_state_m); \ + (f)->tag_list = NULL; \ + SCMutexLock(&(f)->files_m); \ + if ((f)->files != NULL) {\ + FlowFileContainerFree((f)->files); \ + SCMutexDestroy(&(f)->files_m); \ + (f)->files = NULL; \ + } \ + SCMutexUnlock(&(f)->files_m); \ } while(0) Flow *FlowAlloc(void); diff --git a/src/flow.c b/src/flow.c index 93ce1d3a8e..70980d0a86 100644 --- a/src/flow.c +++ b/src/flow.c @@ -41,6 +41,7 @@ #include "flow-queue.h" #include "flow-hash.h" #include "flow-util.h" +#include "flow-file.h" #include "flow-var.h" #include "flow-private.h" #include "flow-timeout.h" diff --git a/src/flow.h b/src/flow.h index 7253818699..27da8050c9 100644 --- a/src/flow.h +++ b/src/flow.h @@ -28,6 +28,7 @@ #include "util-var.h" #include "util-atomic.h" #include "detect-tag.h" +#include "flow-file.h" #define FLOW_QUIET TRUE #define FLOW_VERBOSE FALSE @@ -311,6 +312,10 @@ typedef struct Flow_ uint32_t tosrcpktcnt; uint64_t bytecnt; #endif + + FlowFileContainer *files; + SCMutex files_m; + } Flow; enum { diff --git a/src/suricata.c b/src/suricata.c index 9fc3c505ea..cf382e0f7b 100644 --- a/src/suricata.c +++ b/src/suricata.c @@ -1262,6 +1262,8 @@ int main(int argc, char **argv) TmModuleDecodeErfDagRegister(); TmModuleDebugList(); + AppLayerHtpNeedFileInspection(); + /** \todo we need an api for these */ AppLayerDetectProtoThreadInit(); AppLayerParsersInitPostProcess(); @@ -1279,6 +1281,7 @@ int main(int argc, char **argv) } AppLayerHtpEnableRequestBodyCallback(); + AppLayerHtpNeedFileInspection(); AppLayerHtpRegisterExtraCallbacks(); UtInitialize(); diff --git a/src/util-spm-bm.h b/src/util-spm-bm.h index 07008ddf7f..47baf000cc 100644 --- a/src/util-spm-bm.h +++ b/src/util-spm-bm.h @@ -34,7 +34,7 @@ typedef struct BmCtx_ { int32_t bmBc[ALPHABET_SIZE]; int32_t *bmGs; // = SCMalloc(sizeof(int32_t)*(needlelen + 1)); -}BmCtx; +} BmCtx; /** Prepare and return a Boyer Moore context */ BmCtx *BoyerMooreCtxInit(uint8_t *needle, uint32_t needle_len);