diff options
author | Ashlee Young <ashlee@onosfw.com> | 2015-09-09 22:21:41 -0700 |
---|---|---|
committer | Ashlee Young <ashlee@onosfw.com> | 2015-09-09 22:21:41 -0700 |
commit | 8879b125d26e8db1a5633de5a9c692eb2d1c4f83 (patch) | |
tree | c7259d85a991b83dfa85ab2e339360669fc1f58e /framework/src/suricata/src/util-file.c | |
parent | 13d05bc8458758ee39cb829098241e89616717ee (diff) |
suricata checkin based on commit id a4bce14770beee46a537eda3c3f6e8e8565d5d0a
Change-Id: I9a214fa0ee95e58fc640e50bd604dac7f42db48f
Diffstat (limited to 'framework/src/suricata/src/util-file.c')
-rw-r--r-- | framework/src/suricata/src/util-file.c | 932 |
1 files changed, 932 insertions, 0 deletions
diff --git a/framework/src/suricata/src/util-file.c b/framework/src/suricata/src/util-file.c new file mode 100644 index 00000000..bfb68e1e --- /dev/null +++ b/framework/src/suricata/src/util-file.c @@ -0,0 +1,932 @@ +/* Copyright (C) 2007-2012 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 Victor Julien <victor@inliniac.net> + * \author Pablo Rincon <pablo.rincon.crespo@gmail.com> + * + */ + +#include "suricata-common.h" +#include "suricata.h" +#include "debug.h" +#include "flow.h" +#include "stream.h" +#include "runmodes.h" +#include "util-hash.h" +#include "util-debug.h" +#include "util-memcmp.h" +#include "util-print.h" +#include "app-layer-parser.h" +#include "util-validate.h" + +/** \brief switch to force magic checks on all files + * regardless of the rules. + */ +static int g_file_force_magic = 0; + +/** \brief switch to force md5 calculation on all files + * regardless of the rules. + */ +static int g_file_force_md5 = 0; + +/** \brief switch to force tracking off all files + * regardless of the rules. + */ +static int g_file_force_tracking = 0; + +/* prototypes */ +static void FileFree(File *); +static void FileDataFree(FileData *); + +void FileForceMagicEnable(void) +{ + g_file_force_magic = 1; +} + +void FileForceMd5Enable(void) +{ + g_file_force_md5 = 1; +} + +int FileForceMagic(void) +{ + return g_file_force_magic; +} + +int FileForceMd5(void) +{ + return g_file_force_md5; +} + +void FileForceTrackingEnable(void) +{ + g_file_force_tracking = 1; +} + +int FileMagicSize(void) +{ + /** \todo make this size configurable */ + return 512; +} + +static int FileAppendFileDataFilePtr(File *ff, FileData *ffd) +{ + SCEnter(); + + if (ff == NULL) { + SCReturnInt(-1); + } + + if (ff->chunks_tail == NULL) { + ff->chunks_head = ffd; + ff->chunks_tail = ffd; + ff->content_len_so_far = ffd->len; + } else { + ff->chunks_tail->next = ffd; + ff->chunks_tail = ffd; + ff->content_len_so_far += ffd->len; + } + +#ifdef DEBUG + ff->chunks_cnt++; + if (ff->chunks_cnt > ff->chunks_cnt_max) + ff->chunks_cnt_max = ff->chunks_cnt; +#endif + +#ifdef HAVE_NSS + if (ff->md5_ctx) + HASH_Update(ff->md5_ctx, ffd->data, ffd->len); +#endif + SCReturnInt(0); +} + +static int FileAppendFileData(FileContainer *ffc, FileData *ffd) +{ + SCEnter(); + + if (ffc == NULL) { + SCReturnInt(-1); + } + + if (FileAppendFileDataFilePtr(ffc->tail, ffd) == -1) + { + SCReturnInt(-1); + } + + SCReturnInt(0); +} + + + +static int FilePruneFile(File *file) +{ + SCEnter(); + + SCLogDebug("file %p, file->chunks_cnt %"PRIu64, file, file->chunks_cnt); + + if (!(file->flags & FILE_NOMAGIC)) { + /* need magic but haven't set it yet, bail out */ + if (file->magic == NULL) + SCReturnInt(0); + else + SCLogDebug("file->magic %s", file->magic); + } else { + SCLogDebug("file->flags & FILE_NOMAGIC == true"); + } + + /* okay, we now know we can prune */ + FileData *fd = file->chunks_head; + + while (fd != NULL) { + SCLogDebug("fd %p", fd); + + if (file->flags & FILE_NOSTORE || fd->stored == 1) { + file->chunks_head = fd->next; + if (file->chunks_tail == fd) + file->chunks_tail = fd->next; + + FileDataFree(fd); + + fd = file->chunks_head; +#ifdef DEBUG + file->chunks_cnt--; + SCLogDebug("file->chunks_cnt %"PRIu64, file->chunks_cnt); +#endif + } else if (fd->stored == 0) { + fd = NULL; + SCReturnInt(0); + break; + } + } + + /* file is done when state is closed+, logging/storing is done (if any) */ + if (file->state >= FILE_STATE_CLOSED && + (!RunModeOutputFileEnabled() || (file->flags & FILE_LOGGED)) && + (!RunModeOutputFiledataEnabled() || (file->flags & FILE_STORED))) + { + SCReturnInt(1); + } else { + SCReturnInt(0); + } +} + +void FilePrune(FileContainer *ffc) +{ + File *file = ffc->head; + + while (file) { + if (FilePruneFile(file) == 0) + break; + + BUG_ON(file != ffc->head); + + File *file_next = file->next; + + /* update head and tail */ + ffc->head = file_next; + if (file == ffc->tail) + ffc->tail = NULL; + + FileFree(file); + file = file_next; + } +} + +/** + * \brief allocate a FileContainer + * + * \retval new newly allocated FileContainer + * \retval NULL error + */ +FileContainer *FileContainerAlloc(void) +{ + FileContainer *new = SCMalloc(sizeof(FileContainer)); + if (unlikely(new == NULL)) { + SCLogError(SC_ERR_MEM_ALLOC, "Error allocating mem"); + return NULL; + } + memset(new, 0, sizeof(FileContainer)); + new->head = new->tail = NULL; + return new; +} + +/** + * \brief Recycle a FileContainer + * + * \param ffc FileContainer + */ +void FileContainerRecycle(FileContainer *ffc) +{ + if (ffc == NULL) + return; + + File *cur = ffc->head; + File *next = NULL; + for (;cur != NULL; cur = next) { + next = cur->next; + FileFree(cur); + } + ffc->head = ffc->tail = NULL; +} + +/** + * \brief Free a FileContainer + * + * \param ffc FileContainer + */ +void FileContainerFree(FileContainer *ffc) +{ + if (ffc == NULL) + return; + + File *ptr = ffc->head; + File *next = NULL; + for (;ptr != NULL; ptr = next) { + next = ptr->next; + FileFree(ptr); + } + ffc->head = ffc->tail = NULL; + SCFree(ffc); +} + +/** + * \internal + * + * \brief allocate a FileData chunk and set it up + * + * \param data data chunk to store in the FileData + * \param data_len lenght of the data + * + * \retval new FileData object + */ +static FileData *FileDataAlloc(uint8_t *data, uint32_t data_len) +{ + FileData *new = SCMalloc(sizeof(FileData)); + if (unlikely(new == NULL)) { + return NULL; + } + memset(new, 0, sizeof(FileData)); + + new->data = SCMalloc(data_len); + if (new->data == NULL) { + SCFree(new); + return NULL; + } + + new->len = data_len; + memcpy(new->data, data, data_len); + + new->next = NULL; + return new; +} + +/** + * \internal + * + * \brief free a FileData object + * + * \param ffd the flow file data object to free + */ +static void FileDataFree(FileData *ffd) +{ + if (ffd == NULL) + return; + + if (ffd->data != NULL) { + SCFree(ffd->data); + } + + SCFree(ffd); +} + +/** + * \brief Alloc a new File + * + * \param name character array containing the name (not a string) + * \param name_len length in bytes of the name + * + * \retval new File object or NULL on error + */ +static File *FileAlloc(uint8_t *name, uint16_t name_len) +{ + File *new = SCMalloc(sizeof(File)); + if (unlikely(new == NULL)) { + SCLogError(SC_ERR_MEM_ALLOC, "Error allocating mem"); + return NULL; + } + memset(new, 0, sizeof(File)); + + new->name = SCMalloc(name_len); + if (new->name == NULL) { + SCFree(new); + return NULL; + } + + new->name_len = name_len; + memcpy(new->name, name, name_len); + + return new; +} + +static void FileFree(File *ff) +{ + if (ff == NULL) + return; + + if (ff->name != NULL) + SCFree(ff->name); + + /* magic returned by libmagic is strdup'd by MagicLookup. */ + if (ff->magic != NULL) + SCFree(ff->magic); + + if (ff->chunks_head != NULL) { + FileData *ffd = ff->chunks_head; + + while (ffd != NULL) { + FileData *next_ffd = ffd->next; + FileDataFree(ffd); + ffd = next_ffd; + } + } + +#ifdef HAVE_NSS + if (ff->md5_ctx) + HASH_Destroy(ff->md5_ctx); +#endif + SCLogDebug("ff chunks_cnt %"PRIu64", chunks_cnt_max %"PRIu64, + ff->chunks_cnt, ff->chunks_cnt_max); + SCFree(ff); +} + +void FileContainerAdd(FileContainer *ffc, File *ff) +{ + if (ffc->head == NULL || ffc->tail == NULL) { + ffc->head = ffc->tail = ff; + } else { + ffc->tail->next = ff; + ffc->tail = ff; + } +} + +/** + * \brief Tag a file for storing + * + * \param ff The file to store + */ +int FileStore(File *ff) +{ + ff->flags |= FILE_STORE; + SCReturnInt(0); +} + +/** + * \brief Set the TX id for a file + * + * \param ff The file to store + * \param txid the tx id + */ +int FileSetTx(File *ff, uint64_t txid) +{ + SCLogDebug("ff %p txid %"PRIu64, ff, txid); + if (ff != NULL) + ff->txid = txid; + SCReturnInt(0); +} + +/** + * \brief check if we have stored enough + * + * \param ff file + * + * \retval 0 limit not reached yet + * \retval 1 limit reached + */ +static int FileStoreNoStoreCheck(File *ff) +{ + SCEnter(); + + if (ff == NULL) { + SCReturnInt(0); + } + + if (ff->flags & FILE_NOSTORE) { + if (ff->state == FILE_STATE_OPENED && + ff->size >= (uint64_t)FileMagicSize()) + { + SCReturnInt(1); + } + } + + SCReturnInt(0); +} + +/** + * \brief Store a chunk of file data in the flow. The open "flowfile" + * will be used. + * + * \param ffc the container + * \param data data chunk + * \param data_len data chunk len + * + * \retval 0 ok + * \retval -1 error + * \retval -2 no store for this file + */ +int FileAppendData(FileContainer *ffc, uint8_t *data, uint32_t data_len) +{ + SCEnter(); + + if (ffc == NULL || ffc->tail == NULL || data == NULL || data_len == 0) { + SCReturnInt(-1); + } + + if (ffc->tail->state != FILE_STATE_OPENED) { + if (ffc->tail->flags & FILE_NOSTORE) { + SCReturnInt(-2); + } + SCReturnInt(-1); + } + + ffc->tail->size += data_len; + SCLogDebug("file size is now %"PRIu64, ffc->tail->size); + + if (FileStoreNoStoreCheck(ffc->tail) == 1) { +#ifdef HAVE_NSS + /* no storage but forced md5 */ + if (ffc->tail->md5_ctx) { + if (ffc->tail->md5_ctx) + HASH_Update(ffc->tail->md5_ctx, data, data_len); + + SCReturnInt(0); + } +#endif + if (g_file_force_tracking || (!(ffc->tail->flags & FILE_NOTRACK))) + SCReturnInt(0); + + ffc->tail->state = FILE_STATE_TRUNCATED; + SCLogDebug("flowfile state transitioned to FILE_STATE_TRUNCATED"); + SCReturnInt(-2); + } + + SCLogDebug("appending %"PRIu32" bytes", data_len); + + FileData *ffd = FileDataAlloc(data, data_len); + if (ffd == NULL) { + ffc->tail->state = FILE_STATE_ERROR; + SCReturnInt(-1); + } + + if (ffc->tail->chunks_head == NULL) + ffd->stream_offset = 0; + else + ffd->stream_offset = ffc->tail->size; + + /* append the data */ + if (FileAppendFileData(ffc, ffd) < 0) { + ffc->tail->state = FILE_STATE_ERROR; + FileDataFree(ffd); + SCReturnInt(-1); + } + SCReturnInt(0); +} + +/** + * \brief Open a new File + * + * \param ffc flow container + * \param name filename character array + * \param name_len filename len + * \param data initial data + * \param data_len initial data len + * \param flags open flags + * + * \retval ff flowfile object + * + * \note filename is not a string, so it's not nul terminated. + */ +File *FileOpenFile(FileContainer *ffc, uint8_t *name, + uint16_t name_len, uint8_t *data, uint32_t data_len, uint8_t flags) +{ + SCEnter(); + + //PrintRawDataFp(stdout, name, name_len); + + File *ff = FileAlloc(name, name_len); + if (ff == NULL) { + SCReturnPtr(NULL, "File"); + } + + if (flags & FILE_STORE) { + ff->flags |= FILE_STORE; + } else if (flags & FILE_NOSTORE) { + SCLogDebug("not storing this file"); + ff->flags |= FILE_NOSTORE; + } + if (flags & FILE_NOMAGIC) { + SCLogDebug("not doing magic for this file"); + ff->flags |= FILE_NOMAGIC; + } + if (flags & FILE_NOMD5) { + SCLogDebug("not doing md5 for this file"); + ff->flags |= FILE_NOMD5; + } + +#ifdef HAVE_NSS + if (!(ff->flags & FILE_NOMD5) || g_file_force_md5) { + ff->md5_ctx = HASH_Create(HASH_AlgMD5); + if (ff->md5_ctx != NULL) { + HASH_Begin(ff->md5_ctx); + } + } +#endif + + ff->state = FILE_STATE_OPENED; + SCLogDebug("flowfile state transitioned to FILE_STATE_OPENED"); + + FileContainerAdd(ffc, ff); + + if (data != NULL) { + //PrintRawDataFp(stdout, data, data_len); + ff->size += data_len; + SCLogDebug("file size is now %"PRIu64, ff->size); + + FileData *ffd = FileDataAlloc(data, data_len); + if (ffd == NULL) { + ff->state = FILE_STATE_ERROR; + SCReturnPtr(NULL, "File"); + } + + /* append the data */ + if (FileAppendFileData(ffc, ffd) < 0) { + ff->state = FILE_STATE_ERROR; + FileDataFree(ffd); + SCReturnPtr(NULL, "File"); + } + } + + SCReturnPtr(ff, "File"); +} + +static int FileCloseFilePtr(File *ff, uint8_t *data, + uint32_t data_len, uint8_t flags) +{ + SCEnter(); + + if (ff == NULL) { + SCReturnInt(-1); + } + + if (ff->state != FILE_STATE_OPENED) { + SCReturnInt(-1); + } + + ff->size += data_len; + SCLogDebug("file size is now %"PRIu64, ff->size); + + if (data != NULL) { + //PrintRawDataFp(stdout, data, data_len); + + if (ff->flags & FILE_NOSTORE) { +#ifdef HAVE_NSS + /* no storage but md5 */ + if (ff->md5_ctx) + HASH_Update(ff->md5_ctx, data, data_len); +#endif + } else { + FileData *ffd = FileDataAlloc(data, data_len); + if (ffd == NULL) { + ff->state = FILE_STATE_ERROR; + SCReturnInt(-1); + } + + /* append the data */ + if (FileAppendFileDataFilePtr(ff, ffd) < 0) { + ff->state = FILE_STATE_ERROR; + FileDataFree(ffd); + SCReturnInt(-1); + } + } + } + + if (flags & FILE_TRUNCATED) { + ff->state = FILE_STATE_TRUNCATED; + SCLogDebug("flowfile state transitioned to FILE_STATE_TRUNCATED"); + + if (flags & FILE_NOSTORE) { + SCLogDebug("not storing this file"); + ff->flags |= FILE_NOSTORE; + } + } else { + ff->state = FILE_STATE_CLOSED; + SCLogDebug("flowfile state transitioned to FILE_STATE_CLOSED"); + +#ifdef HAVE_NSS + if (ff->md5_ctx) { + unsigned int len = 0; + HASH_End(ff->md5_ctx, ff->md5, &len, sizeof(ff->md5)); + ff->flags |= FILE_MD5; + } +#endif + } + + SCReturnInt(0); +} + +/** + * \brief Close a File + * + * \param ffc the container + * \param data final data if any + * \param data_len data len if any + * \param flags flags + * + * \retval 0 ok + * \retval -1 error + */ +int FileCloseFile(FileContainer *ffc, uint8_t *data, + uint32_t data_len, uint8_t flags) +{ + SCEnter(); + + if (ffc == NULL || ffc->tail == NULL) { + SCReturnInt(-1); + } + + if (FileCloseFilePtr(ffc->tail, data, data_len, flags) == -1) { + SCReturnInt(-1); + } + + SCReturnInt(0); +} + +/** + * \brief disable file storage for a flow + * + * \param f *LOCKED* flow + * \param direction flow direction + */ +void FileDisableStoring(Flow *f, uint8_t direction) +{ + File *ptr = NULL; + + SCEnter(); + + DEBUG_ASSERT_FLOW_LOCKED(f); + + if (direction == STREAM_TOSERVER) + f->flags |= FLOW_FILE_NO_STORE_TS; + else + f->flags |= FLOW_FILE_NO_STORE_TC; + + FileContainer *ffc = AppLayerParserGetFiles(f->proto, f->alproto, f->alstate, direction); + if (ffc != NULL) { + for (ptr = ffc->head; ptr != NULL; ptr = ptr->next) { + /* if we're already storing, we'll continue */ + if (!(ptr->flags & FILE_STORE)) { + SCLogDebug("not storing this file"); + ptr->flags |= FILE_NOSTORE; + } + } + } + SCReturn; +} + +/** + * \brief disable file magic lookups for this flow + * + * \param f *LOCKED* flow + * \param direction flow direction + */ +void FileDisableMagic(Flow *f, uint8_t direction) +{ + File *ptr = NULL; + + SCEnter(); + + DEBUG_ASSERT_FLOW_LOCKED(f); + + if (direction == STREAM_TOSERVER) + f->flags |= FLOW_FILE_NO_MAGIC_TS; + else + f->flags |= FLOW_FILE_NO_MAGIC_TC; + + FileContainer *ffc = AppLayerParserGetFiles(f->proto, f->alproto, f->alstate, direction); + if (ffc != NULL) { + for (ptr = ffc->head; ptr != NULL; ptr = ptr->next) { + SCLogDebug("disabling magic for file %p from direction %s", + ptr, direction == STREAM_TOSERVER ? "toserver":"toclient"); + ptr->flags |= FILE_NOMAGIC; + } + } + + SCReturn; +} + +/** + * \brief disable file md5 calc for this flow + * + * \param f *LOCKED* flow + * \param direction flow direction + */ +void FileDisableMd5(Flow *f, uint8_t direction) +{ + File *ptr = NULL; + + SCEnter(); + + DEBUG_ASSERT_FLOW_LOCKED(f); + + if (direction == STREAM_TOSERVER) + f->flags |= FLOW_FILE_NO_MD5_TS; + else + f->flags |= FLOW_FILE_NO_MD5_TC; + + FileContainer *ffc = AppLayerParserGetFiles(f->proto, f->alproto, f->alstate, direction); + if (ffc != NULL) { + for (ptr = ffc->head; ptr != NULL; ptr = ptr->next) { + SCLogDebug("disabling md5 for file %p from direction %s", + ptr, direction == STREAM_TOSERVER ? "toserver":"toclient"); + ptr->flags |= FILE_NOMD5; + +#ifdef HAVE_NSS + /* destroy any ctx we may have so far */ + if (ptr->md5_ctx != NULL) { + HASH_Destroy(ptr->md5_ctx); + ptr->md5_ctx = NULL; + } +#endif + } + } + + SCReturn; +} + +/** + * \brief disable file size tracking for this flow + * + * \param f *LOCKED* flow + * \param direction flow direction + */ +void FileDisableFilesize(Flow *f, uint8_t direction) +{ + File *ptr = NULL; + + SCEnter(); + + DEBUG_ASSERT_FLOW_LOCKED(f); + + if (direction == STREAM_TOSERVER) + f->flags |= FLOW_FILE_NO_SIZE_TS; + else + f->flags |= FLOW_FILE_NO_SIZE_TC; + + FileContainer *ffc = AppLayerParserGetFiles(f->proto, f->alproto, f->alstate, direction); + if (ffc != NULL) { + for (ptr = ffc->head; ptr != NULL; ptr = ptr->next) { + SCLogDebug("disabling size tracking for file %p from direction %s", + ptr, direction == STREAM_TOSERVER ? "toserver":"toclient"); + ptr->flags |= FILE_NOTRACK; + } + } + + SCReturn; +} + + +/** + * \brief set no store flag, close file if needed + * + * \param ff file + */ +void FileDisableStoringForFile(File *ff) +{ + SCEnter(); + + if (ff == NULL) { + SCReturn; + } + + SCLogDebug("not storing this file"); + ff->flags |= FILE_NOSTORE; + + if (ff->state == FILE_STATE_OPENED && ff->size >= (uint64_t)FileMagicSize()) { + if (g_file_force_md5 == 0 && g_file_force_tracking == 0) { + (void)FileCloseFilePtr(ff, NULL, 0, + (FILE_TRUNCATED|FILE_NOSTORE)); + } + } +} + +/** + * \brief disable file storing for files in a transaction + * + * \param f *LOCKED* flow + * \param direction flow direction + * \param tx_id transaction id + */ +void FileDisableStoringForTransaction(Flow *f, uint8_t direction, uint64_t tx_id) +{ + File *ptr = NULL; + + DEBUG_ASSERT_FLOW_LOCKED(f); + + SCEnter(); + + FileContainer *ffc = AppLayerParserGetFiles(f->proto, f->alproto, f->alstate, direction); + if (ffc != NULL) { + for (ptr = ffc->head; ptr != NULL; ptr = ptr->next) { + if (ptr->txid == tx_id) { + if (ptr->flags & FILE_STORE) { + /* weird, already storing -- let it continue*/ + SCLogDebug("file is already being stored"); + } else { + FileDisableStoringForFile(ptr); + } + } + } + } + + SCReturn; +} + +/** + * \brief flag a file with id "file_id" to be stored. + * + * \param fc file store + * \param file_id the file's id + */ +void FileStoreFileById(FileContainer *fc, uint16_t file_id) +{ + File *ptr = NULL; + + SCEnter(); + + if (fc != NULL) { + for (ptr = fc->head; ptr != NULL; ptr = ptr->next) { + if (ptr->file_id == file_id) { + ptr->flags |= FILE_STORE; + } + } + } +} + +void FileStoreAllFilesForTx(FileContainer *fc, uint16_t tx_id) +{ + File *ptr = NULL; + + SCEnter(); + + if (fc != NULL) { + for (ptr = fc->head; ptr != NULL; ptr = ptr->next) { + if (ptr->txid == tx_id) { + ptr->flags |= FILE_STORE; + } + } + } +} + +void FileStoreAllFiles(FileContainer *fc) +{ + File *ptr = NULL; + + SCEnter(); + + if (fc != NULL) { + for (ptr = fc->head; ptr != NULL; ptr = ptr->next) { + ptr->flags |= FILE_STORE; + } + } +} + +void FileTruncateAllOpenFiles(FileContainer *fc) +{ + File *ptr = NULL; + + SCEnter(); + + if (fc != NULL) { + for (ptr = fc->head; ptr != NULL; ptr = ptr->next) { + if (ptr->state == FILE_STATE_OPENED) { + FileCloseFilePtr(ptr, NULL, 0, FILE_TRUNCATED); + } + } + } +} |