/* Copyright (C) 2007-2016 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 Danny Browning * * Helper methods for directory based packet acquisition */ #include "source-pcap-file-directory-helper.h" #include "suricata.h" #include "runmode-unix-socket.h" #include "util-mem.h" #include "util-time.h" #include "util-path.h" #include "source-pcap-file.h" static void GetTime(struct timespec *tm); static void CopyTime(struct timespec *from, struct timespec *to); static int CompareTimes(struct timespec *left, struct timespec *right); static TmEcode PcapRunStatus(PcapFileDirectoryVars *); static TmEcode PcapDirectoryFailure(PcapFileDirectoryVars *ptv); static TmEcode PcapDirectoryDone(PcapFileDirectoryVars *ptv); static int PcapDirectoryGetModifiedTime(char const * file, struct timespec * out); static TmEcode PcapDirectoryInsertFile(PcapFileDirectoryVars *pv, PendingFile *file_to_add); static TmEcode PcapDirectoryPopulateBuffer(PcapFileDirectoryVars *ptv, struct timespec * older_than); static TmEcode PcapDirectoryDispatchForTimeRange(PcapFileDirectoryVars *pv, struct timespec *older_than); void GetTime(struct timespec *tm) { struct timeval now; if(gettimeofday(&now, NULL) == 0) { tm->tv_sec = now.tv_sec; tm->tv_nsec = now.tv_usec * 1000L; } } void CopyTime(struct timespec *from, struct timespec *to) { to->tv_sec = from->tv_sec; to->tv_nsec = from->tv_nsec; } int CompareTimes(struct timespec *left, struct timespec *right) { if (left->tv_sec < right->tv_sec) { return -1; } else if (left->tv_sec > right->tv_sec) { return 1; } else { if (left->tv_nsec < right->tv_nsec) { return -1; } else if (left->tv_nsec > right->tv_nsec) { return 1; } else { return 0; } } } /** * Pcap Folder Utilities */ TmEcode PcapRunStatus(PcapFileDirectoryVars *ptv) { if (RunModeUnixSocketIsActive()) { TmEcode done = UnixSocketPcapFile(TM_ECODE_OK, &ptv->shared->last_processed); if ( (suricata_ctl_flags & SURICATA_STOP) || done != TM_ECODE_OK) { SCReturnInt(TM_ECODE_DONE); } } else { if (suricata_ctl_flags & SURICATA_STOP) { SCReturnInt(TM_ECODE_DONE); } } SCReturnInt(TM_ECODE_OK); } void CleanupPendingFile(PendingFile *pending) { if (pending != NULL) { if (pending->filename != NULL) { SCFree(pending->filename); } SCFree(pending); } } void CleanupPcapFileDirectoryVars(PcapFileDirectoryVars *ptv) { if (ptv != NULL) { if (ptv->current_file != NULL) { CleanupPcapFileFileVars(ptv->current_file); ptv->current_file = NULL; } if (ptv->directory != NULL) { closedir(ptv->directory); ptv->directory = NULL; } if (ptv->filename != NULL) { SCFree(ptv->filename); } ptv->shared = NULL; PendingFile *current_file = NULL; while (!TAILQ_EMPTY(&ptv->directory_content)) { current_file = TAILQ_FIRST(&ptv->directory_content); TAILQ_REMOVE(&ptv->directory_content, current_file, next); CleanupPendingFile(current_file); } SCFree(ptv); } } TmEcode PcapDirectoryFailure(PcapFileDirectoryVars *ptv) { TmEcode status = TM_ECODE_FAILED; if (unlikely(ptv == NULL)) { SCLogError("Directory vars was null"); SCReturnInt(TM_ECODE_FAILED); } if (unlikely(ptv->shared == NULL)) { SCLogError("Directory shared vars was null"); SCReturnInt(TM_ECODE_FAILED); } if (RunModeUnixSocketIsActive()) { status = UnixSocketPcapFile(status, &ptv->shared->last_processed); } SCReturnInt(status); } TmEcode PcapDirectoryDone(PcapFileDirectoryVars *ptv) { TmEcode status = TM_ECODE_DONE; if (unlikely(ptv == NULL)) { SCLogError("Directory vars was null"); SCReturnInt(TM_ECODE_FAILED); } if (unlikely(ptv->shared == NULL)) { SCLogError("Directory shared vars was null"); SCReturnInt(TM_ECODE_FAILED); } if (RunModeUnixSocketIsActive()) { status = UnixSocketPcapFile(status, &ptv->shared->last_processed); } SCReturnInt(status); } TmEcode PcapDetermineDirectoryOrFile(char *filename, DIR **directory) { DIR *temp_dir = NULL; TmEcode return_code = TM_ECODE_FAILED; temp_dir = opendir(filename); if (temp_dir == NULL) {//if null, our filename may just be a normal file switch (errno) { case EACCES: SCLogError("%s: Permission denied", filename); break; case EBADF: SCLogError("%s: Not a valid file descriptor opened for reading", filename); break; case EMFILE: SCLogError("%s: Per process open file descriptor limit reached", filename); break; case ENFILE: SCLogError("%s: System wide open file descriptor limit reached", filename); break; case ENOENT: SCLogError("%s: Does not exist, or name is an empty string", filename); break; case ENOMEM: SCLogError("%s: Insufficient memory to complete the operation", filename); break; case ENOTDIR: //no error checking the directory, just is a plain file SCLogDebug("%s: plain file, not a directory", filename); return_code = TM_ECODE_OK; break; default: SCLogError("%s: %" PRId32, filename, errno); } } else { //no error, filename references a directory *directory = temp_dir; return_code = TM_ECODE_OK; } return return_code; } int PcapDirectoryGetModifiedTime(char const *file, struct timespec *out) { SCStat buf; int ret; if (file == NULL) return -1; if ((ret = SCStatFn(file, &buf)) != 0) return ret; #ifdef OS_DARWIN out->tv_sec = buf.st_mtimespec.tv_sec; out->tv_nsec = buf.st_mtimespec.tv_nsec; #elif OS_WIN32 out->tv_sec = buf.st_mtime; #else out->tv_sec = buf.st_mtim.tv_sec; out->tv_nsec = buf.st_mtim.tv_nsec; #endif return ret; } TmEcode PcapDirectoryInsertFile(PcapFileDirectoryVars *pv, PendingFile *file_to_add ) { PendingFile *file_to_compare = NULL; PendingFile *next_file_to_compare = NULL; if (unlikely(pv == NULL)) { SCLogError("No directory vars passed"); SCReturnInt(TM_ECODE_FAILED); } if (unlikely(file_to_add == NULL)) { SCLogError("File passed was null"); SCReturnInt(TM_ECODE_FAILED); } if (unlikely(file_to_add->filename == NULL)) { SCLogError("File was passed with null filename"); SCReturnInt(TM_ECODE_FAILED); } SCLogDebug("Inserting %s into directory buffer", file_to_add->filename); if (TAILQ_EMPTY(&pv->directory_content)) { TAILQ_INSERT_TAIL(&pv->directory_content, file_to_add, next); } else { file_to_compare = TAILQ_FIRST(&pv->directory_content); while(file_to_compare != NULL) { if (CompareTimes(&file_to_add->modified_time, &file_to_compare->modified_time) < 0) { TAILQ_INSERT_BEFORE(file_to_compare, file_to_add, next); file_to_compare = NULL; } else { next_file_to_compare = TAILQ_NEXT(file_to_compare, next); if (next_file_to_compare == NULL) { TAILQ_INSERT_AFTER(&pv->directory_content, file_to_compare, file_to_add, next); } file_to_compare = next_file_to_compare; } } } SCReturnInt(TM_ECODE_OK); } TmEcode PcapDirectoryPopulateBuffer(PcapFileDirectoryVars *pv, struct timespec *older_than ) { if (unlikely(pv == NULL)) { SCLogError("No directory vars passed"); SCReturnInt(TM_ECODE_FAILED); } if (unlikely(pv->filename == NULL)) { SCLogError("No directory filename was passed"); SCReturnInt(TM_ECODE_FAILED); } struct dirent * dir = NULL; PendingFile *file_to_add = NULL; while ((dir = readdir(pv->directory)) != NULL) { #ifndef OS_WIN32 if (dir->d_type != DT_REG) { continue; } #endif if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) { continue; } char pathbuff[PATH_MAX] = {0}; int written = 0; written = snprintf(pathbuff, PATH_MAX, "%s/%s", pv->filename, dir->d_name); if (written <= 0 || written >= PATH_MAX) { SCLogError("Could not write path"); SCReturnInt(TM_ECODE_FAILED); } else { struct timespec temp_time; memset(&temp_time, 0, sizeof(struct timespec)); if (PcapDirectoryGetModifiedTime(pathbuff, &temp_time) == 0) { SCLogDebug("%" PRIuMAX " < %" PRIuMAX "(%s) < %" PRIuMAX ")", (uintmax_t)SCTimespecAsEpochMillis(&pv->shared->last_processed), (uintmax_t)SCTimespecAsEpochMillis(&temp_time), pathbuff, (uintmax_t)SCTimespecAsEpochMillis(older_than)); // Skip files outside of our time range if (CompareTimes(&temp_time, &pv->shared->last_processed) <= 0) { SCLogDebug("Skipping old file %s", pathbuff); continue; } else if (CompareTimes(&temp_time, older_than) >= 0) { SCLogDebug("Skipping new file %s", pathbuff); continue; } } else { SCLogDebug("Unable to get modified time on %s, skipping", pathbuff); continue; } file_to_add = SCCalloc(1, sizeof(PendingFile)); if (unlikely(file_to_add == NULL)) { SCLogError("Failed to allocate pending file"); SCReturnInt(TM_ECODE_FAILED); } file_to_add->filename = SCStrdup(pathbuff); if (unlikely(file_to_add->filename == NULL)) { SCLogError("Failed to copy filename"); CleanupPendingFile(file_to_add); SCReturnInt(TM_ECODE_FAILED); } memset(&file_to_add->modified_time, 0, sizeof(struct timespec)); CopyTime(&temp_time, &file_to_add->modified_time); SCLogInfo("Found \"%s\" at %" PRIuMAX, file_to_add->filename, (uintmax_t)SCTimespecAsEpochMillis(&file_to_add->modified_time)); if (PcapDirectoryInsertFile(pv, file_to_add) == TM_ECODE_FAILED) { SCLogError("Failed to add file"); CleanupPendingFile(file_to_add); SCReturnInt(TM_ECODE_FAILED); } } } SCReturnInt(TM_ECODE_OK); } TmEcode PcapDirectoryDispatchForTimeRange(PcapFileDirectoryVars *pv, struct timespec *older_than) { if (PcapDirectoryPopulateBuffer(pv, older_than) == TM_ECODE_FAILED) { SCLogError("Failed to populate directory buffer"); SCReturnInt(TM_ECODE_FAILED); } TmEcode status = TM_ECODE_OK; if (TAILQ_EMPTY(&pv->directory_content)) { SCLogDebug("Directory %s has no files to process", pv->filename); GetTime(older_than); older_than->tv_sec = older_than->tv_sec - pv->delay; rewinddir(pv->directory); status = TM_ECODE_OK; } else { PendingFile *current_file = NULL; struct timespec last_time_seen; memset(&last_time_seen, 0, sizeof(struct timespec)); while (status == TM_ECODE_OK && !TAILQ_EMPTY(&pv->directory_content)) { current_file = TAILQ_FIRST(&pv->directory_content); TAILQ_REMOVE(&pv->directory_content, current_file, next); if (unlikely(current_file == NULL)) { SCLogWarning("Current file was null"); } else if (unlikely(current_file->filename == NULL)) { SCLogWarning("Current file filename was null"); } else { SCLogDebug("Processing file %s", current_file->filename); PcapFileFileVars *pftv = SCMalloc(sizeof(PcapFileFileVars)); if (unlikely(pftv == NULL)) { SCLogError("Failed to allocate PcapFileFileVars"); SCReturnInt(TM_ECODE_FAILED); } memset(pftv, 0, sizeof(PcapFileFileVars)); pftv->filename = SCStrdup(current_file->filename); if (unlikely(pftv->filename == NULL)) { SCLogError("Failed to allocate filename"); CleanupPcapFileFileVars(pftv); SCReturnInt(TM_ECODE_FAILED); } pftv->shared = pv->shared; if (InitPcapFile(pftv) == TM_ECODE_FAILED) { SCLogWarning("Failed to init pcap file %s, skipping", current_file->filename); CleanupPendingFile(current_file); CleanupPcapFileFileVars(pftv); status = TM_ECODE_OK; } else { pv->current_file = pftv; status = PcapFileDispatch(pftv); CleanupPcapFileFileVars(pftv); if (status == TM_ECODE_FAILED) { CleanupPendingFile(current_file); SCReturnInt(status); } SCLogInfo("Processed file %s, processed up to %" PRIuMAX, current_file->filename, (uintmax_t)SCTimespecAsEpochMillis(¤t_file->modified_time)); if(CompareTimes(¤t_file->modified_time, &last_time_seen) > 0) { CopyTime(¤t_file->modified_time, &last_time_seen); } CleanupPendingFile(current_file); pv->current_file = NULL; status = PcapRunStatus(pv); } } } if(CompareTimes(&last_time_seen, &pv->shared->last_processed) > 0) { SCLogInfo("Updating processed to %" PRIuMAX, (uintmax_t)SCTimespecAsEpochMillis(&last_time_seen)); CopyTime(&last_time_seen, &pv->shared->last_processed); status = PcapRunStatus(pv); } } GetTime(older_than); older_than->tv_sec = older_than->tv_sec - pv->delay; SCReturnInt(status); } TmEcode PcapDirectoryDispatch(PcapFileDirectoryVars *ptv) { SCEnter(); DIR *directory_check = NULL; struct timespec older_than; memset(&older_than, 0, sizeof(struct timespec)); older_than.tv_sec = LONG_MAX; uint32_t poll_seconds = (uint32_t)localtime(&ptv->poll_interval)->tm_sec; if (ptv->should_loop) { GetTime(&older_than); older_than.tv_sec = older_than.tv_sec - ptv->delay; } TmEcode status = TM_ECODE_OK; while (status == TM_ECODE_OK) { //loop while directory is ok SCLogInfo("Processing pcaps directory %s, files must be newer than %" PRIuMAX " and older than %" PRIuMAX, ptv->filename, (uintmax_t)SCTimespecAsEpochMillis(&ptv->shared->last_processed), (uintmax_t)SCTimespecAsEpochMillis(&older_than)); status = PcapDirectoryDispatchForTimeRange(ptv, &older_than); if (ptv->should_loop && status == TM_ECODE_OK) { sleep(poll_seconds); //update our status based on suricata control flags or unix command socket status = PcapRunStatus(ptv); if (status == TM_ECODE_OK) { SCLogDebug("Checking if directory %s still exists", ptv->filename); //check directory if (PcapDetermineDirectoryOrFile(ptv->filename, &directory_check) == TM_ECODE_FAILED) { SCLogInfo("Directory %s no longer exists, stopping", ptv->filename); status = TM_ECODE_DONE; } else if(directory_check != NULL) { closedir(directory_check); directory_check = NULL; } } } else if (status == TM_ECODE_OK) { //not looping, mark done SCLogDebug("Not looping, stopping directory mode"); status = TM_ECODE_DONE; } } StatsSyncCountersIfSignalled(ptv->shared->tv); if (status == TM_ECODE_FAILED) { SCLogError("Directory %s run mode failed", ptv->filename); status = PcapDirectoryFailure(ptv); } else { SCLogInfo("Directory run mode complete"); status = PcapDirectoryDone(ptv); } SCReturnInt(status); } /* eof */