Coverage Report

Created: 2026-03-31 07:45

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata/src/source-pcap-file-directory-helper.c
Line
Count
Source
1
/* Copyright (C) 2007-2016 Open Information Security Foundation
2
 *
3
 * You can copy, redistribute or modify this Program under the terms of
4
 * the GNU General Public License version 2 as published by the Free
5
 * Software Foundation.
6
 *
7
 * This program is distributed in the hope that it will be useful,
8
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 * GNU General Public License for more details.
11
 *
12
 * You should have received a copy of the GNU General Public License
13
 * version 2 along with this program; if not, write to the Free Software
14
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15
 * 02110-1301, USA.
16
 */
17
18
/**
19
 * \file
20
 *
21
 * \author Danny Browning <danny.browning@protectwise.com>
22
 *
23
 * Helper methods for directory based packet acquisition
24
 */
25
26
#include "source-pcap-file-directory-helper.h"
27
#include "suricata.h"
28
#include "runmode-unix-socket.h"
29
#include "util-mem.h"
30
#include "util-time.h"
31
#include "util-path.h"
32
#include "source-pcap-file.h"
33
34
static void GetTime(struct timespec *tm);
35
static void CopyTime(struct timespec *from, struct timespec *to);
36
static int CompareTimes(struct timespec *left, struct timespec *right);
37
static TmEcode PcapRunStatus(PcapFileDirectoryVars *);
38
static TmEcode PcapDirectoryFailure(PcapFileDirectoryVars *ptv);
39
static TmEcode PcapDirectoryDone(PcapFileDirectoryVars *ptv);
40
static int PcapDirectoryGetModifiedTime(char const * file, struct timespec * out);
41
static TmEcode PcapDirectoryInsertFile(PcapFileDirectoryVars *pv,
42
                                       PendingFile *file_to_add);
43
static TmEcode PcapDirectoryPopulateBuffer(PcapFileDirectoryVars *ptv,
44
                                           struct timespec * older_than);
45
static TmEcode PcapDirectoryDispatchForTimeRange(PcapFileDirectoryVars *pv,
46
                                                 struct timespec *older_than);
47
48
extern PcapFileGlobalVars pcap_g;
49
50
void GetTime(struct timespec *tm)
51
0
{
52
0
    struct timeval now;
53
0
    if(gettimeofday(&now, NULL) == 0) {
54
0
        tm->tv_sec  = now.tv_sec;
55
0
        tm->tv_nsec = now.tv_usec * 1000L;
56
0
    }
57
0
}
58
59
void CopyTime(struct timespec *from, struct timespec *to)
60
0
{
61
0
    to->tv_sec = from->tv_sec;
62
0
    to->tv_nsec = from->tv_nsec;
63
0
}
64
65
int CompareTimes(struct timespec *left, struct timespec *right)
66
0
{
67
0
    if (left->tv_sec < right->tv_sec) {
68
0
        return -1;
69
0
    } else if (left->tv_sec > right->tv_sec) {
70
0
        return 1;
71
0
    } else {
72
0
        if (left->tv_nsec < right->tv_nsec) {
73
0
            return -1;
74
0
        } else if (left->tv_nsec > right->tv_nsec) {
75
0
            return 1;
76
0
        } else {
77
0
            return 0;
78
0
        }
79
0
    }
80
0
}
81
82
/**
83
 * Pcap Folder Utilities
84
 */
85
TmEcode PcapRunStatus(PcapFileDirectoryVars *ptv)
86
0
{
87
0
    if (RunModeUnixSocketIsActive()) {
88
0
        TmEcode done = UnixSocketPcapFile(TM_ECODE_OK, &ptv->shared->last_processed);
89
0
        if ( (suricata_ctl_flags & SURICATA_STOP) || done != TM_ECODE_OK) {
90
0
            SCReturnInt(TM_ECODE_DONE);
91
0
        }
92
0
    } else {
93
0
        if (suricata_ctl_flags & SURICATA_STOP) {
94
0
            SCReturnInt(TM_ECODE_DONE);
95
0
        }
96
0
    }
97
0
    SCReturnInt(TM_ECODE_OK);
98
0
}
99
100
0
void CleanupPendingFile(PendingFile *pending) {
101
0
    if (pending != NULL) {
102
0
        if (pending->filename != NULL) {
103
0
            SCFree(pending->filename);
104
0
        }
105
0
        SCFree(pending);
106
0
    }
107
0
}
108
109
void CleanupPcapFileDirectoryVars(PcapFileDirectoryVars *ptv)
110
0
{
111
0
    if (ptv != NULL) {
112
0
        if (ptv->current_file != NULL) {
113
0
            CleanupPcapFileFileVars(ptv->current_file);
114
0
            ptv->current_file = NULL;
115
0
        }
116
0
        if (ptv->directory != NULL) {
117
0
            closedir(ptv->directory);
118
0
            ptv->directory = NULL;
119
0
        }
120
0
        if (ptv->filename != NULL) {
121
0
            SCFree(ptv->filename);
122
0
        }
123
0
        ptv->shared = NULL;
124
0
        PendingFile *current_file = NULL;
125
0
        while (!TAILQ_EMPTY(&ptv->directory_content)) {
126
0
            current_file = TAILQ_FIRST(&ptv->directory_content);
127
0
            TAILQ_REMOVE(&ptv->directory_content, current_file, next);
128
0
            CleanupPendingFile(current_file);
129
0
        }
130
0
        SCFree(ptv);
131
0
    }
132
0
}
133
134
TmEcode PcapDirectoryFailure(PcapFileDirectoryVars *ptv)
135
0
{
136
0
    TmEcode status = TM_ECODE_FAILED;
137
138
0
    if (unlikely(ptv == NULL)) {
139
0
        SCLogError("Directory vars was null");
140
0
        SCReturnInt(TM_ECODE_FAILED);
141
0
    }
142
0
    if (unlikely(ptv->shared == NULL)) {
143
0
        SCLogError("Directory shared vars was null");
144
0
        SCReturnInt(TM_ECODE_FAILED);
145
0
    }
146
147
0
    if (RunModeUnixSocketIsActive()) {
148
0
        status = UnixSocketPcapFile(status, &ptv->shared->last_processed);
149
0
    }
150
151
0
    SCReturnInt(status);
152
0
}
153
154
TmEcode PcapDirectoryDone(PcapFileDirectoryVars *ptv)
155
0
{
156
0
    TmEcode status = TM_ECODE_DONE;
157
158
0
    if (unlikely(ptv == NULL)) {
159
0
        SCLogError("Directory vars was null");
160
0
        SCReturnInt(TM_ECODE_FAILED);
161
0
    }
162
0
    if (unlikely(ptv->shared == NULL)) {
163
0
        SCLogError("Directory shared vars was null");
164
0
        SCReturnInt(TM_ECODE_FAILED);
165
0
    }
166
167
0
    if (RunModeUnixSocketIsActive()) {
168
0
        status = UnixSocketPcapFile(status, &ptv->shared->last_processed);
169
0
    }
170
171
0
    SCReturnInt(status);
172
0
}
173
174
TmEcode PcapDetermineDirectoryOrFile(char *filename, DIR **directory)
175
24.7k
{
176
24.7k
    DIR *temp_dir = NULL;
177
24.7k
    TmEcode return_code = TM_ECODE_FAILED;
178
179
24.7k
    temp_dir = opendir(filename);
180
181
24.7k
    if (temp_dir == NULL) {//if null, our filename may just be a normal file
182
24.7k
        switch (errno) {
183
0
            case EACCES:
184
0
                SCLogError("%s: Permission denied", filename);
185
0
                break;
186
187
0
            case EBADF:
188
0
                SCLogError("%s: Not a valid file descriptor opened for reading", filename);
189
0
                break;
190
191
0
            case EMFILE:
192
0
                SCLogError("%s: Per process open file descriptor limit reached", filename);
193
0
                break;
194
195
0
            case ENFILE:
196
0
                SCLogError("%s: System wide open file descriptor limit reached", filename);
197
0
                break;
198
199
200
            case ENOENT:
200
200
                SCLogError("%s: Does not exist, or name is an empty string", filename);
201
200
                break;
202
0
            case ENOMEM:
203
0
                SCLogError("%s: Insufficient memory to complete the operation", filename);
204
0
                break;
205
206
24.5k
            case ENOTDIR: //no error checking the directory, just is a plain file
207
24.5k
                SCLogDebug("%s: plain file, not a directory", filename);
208
24.5k
                return_code = TM_ECODE_OK;
209
24.5k
                break;
210
211
0
            default:
212
0
                SCLogError("%s: %" PRId32, filename, errno);
213
24.7k
        }
214
24.7k
    } else {
215
        //no error, filename references a directory
216
0
        *directory = temp_dir;
217
0
        return_code = TM_ECODE_OK;
218
0
    }
219
220
24.7k
    return return_code;
221
24.7k
}
222
223
int PcapDirectoryGetModifiedTime(char const *file, struct timespec *out)
224
0
{
225
0
    SCStat buf;
226
0
    int ret;
227
228
0
    if (file == NULL)
229
0
        return -1;
230
231
0
    if ((ret = SCStatFn(file, &buf)) != 0)
232
0
        return ret;
233
234
#ifdef OS_DARWIN
235
    out->tv_sec = buf.st_mtimespec.tv_sec;
236
    out->tv_nsec = buf.st_mtimespec.tv_nsec;
237
#elif OS_WIN32
238
    out->tv_sec = buf.st_mtime;
239
#else
240
0
    out->tv_sec = buf.st_mtim.tv_sec;
241
0
    out->tv_nsec = buf.st_mtim.tv_nsec;
242
0
#endif
243
244
0
    return ret;
245
0
}
246
247
TmEcode PcapDirectoryInsertFile(PcapFileDirectoryVars *pv,
248
                                PendingFile *file_to_add
249
0
) {
250
0
    PendingFile *file_to_compare = NULL;
251
0
    PendingFile *next_file_to_compare = NULL;
252
253
0
    if (unlikely(pv == NULL)) {
254
0
        SCLogError("No directory vars passed");
255
0
        SCReturnInt(TM_ECODE_FAILED);
256
0
    }
257
258
0
    if (unlikely(file_to_add == NULL)) {
259
0
        SCLogError("File passed was null");
260
0
        SCReturnInt(TM_ECODE_FAILED);
261
0
    }
262
263
0
    if (unlikely(file_to_add->filename == NULL)) {
264
0
        SCLogError("File was passed with null filename");
265
0
        SCReturnInt(TM_ECODE_FAILED);
266
0
    }
267
268
0
    SCLogDebug("Inserting %s into directory buffer", file_to_add->filename);
269
270
0
    if (TAILQ_EMPTY(&pv->directory_content)) {
271
0
        TAILQ_INSERT_TAIL(&pv->directory_content, file_to_add, next);
272
0
    } else {
273
0
        file_to_compare = TAILQ_FIRST(&pv->directory_content);
274
0
        while(file_to_compare != NULL) {
275
0
            if (CompareTimes(&file_to_add->modified_time, &file_to_compare->modified_time) < 0) {
276
0
                TAILQ_INSERT_BEFORE(file_to_compare, file_to_add, next);
277
0
                file_to_compare = NULL;
278
0
            } else {
279
0
                next_file_to_compare = TAILQ_NEXT(file_to_compare, next);
280
0
                if (next_file_to_compare == NULL) {
281
0
                    TAILQ_INSERT_AFTER(&pv->directory_content, file_to_compare,
282
0
                                       file_to_add, next);
283
0
                }
284
0
                file_to_compare = next_file_to_compare;
285
0
            }
286
0
        }
287
0
    }
288
289
0
    SCReturnInt(TM_ECODE_OK);
290
0
}
291
292
TmEcode PcapDirectoryPopulateBuffer(PcapFileDirectoryVars *pv,
293
                                    struct timespec *older_than
294
0
) {
295
0
    if (unlikely(pv == NULL)) {
296
0
        SCLogError("No directory vars passed");
297
0
        SCReturnInt(TM_ECODE_FAILED);
298
0
    }
299
0
    if (unlikely(pv->filename == NULL)) {
300
0
        SCLogError("No directory filename was passed");
301
0
        SCReturnInt(TM_ECODE_FAILED);
302
0
    }
303
0
    struct dirent * dir = NULL;
304
0
    PendingFile *file_to_add = NULL;
305
306
0
    while ((dir = readdir(pv->directory)) != NULL) {
307
0
#ifndef OS_WIN32
308
0
        if (dir->d_type != DT_REG) {
309
0
            continue;
310
0
        }
311
0
#endif
312
0
        if (strcmp(dir->d_name, ".") == 0 ||
313
0
            strcmp(dir->d_name, "..") == 0) {
314
0
            continue;
315
0
        }
316
317
0
        char pathbuff[PATH_MAX] = {0};
318
319
0
        int written = 0;
320
321
0
        written = snprintf(pathbuff, PATH_MAX, "%s/%s", pv->filename, dir->d_name);
322
323
0
        if (written <= 0 || written >= PATH_MAX) {
324
0
            SCLogError("Could not write path");
325
326
0
            SCReturnInt(TM_ECODE_FAILED);
327
0
        } else {
328
0
            struct timespec temp_time;
329
0
            memset(&temp_time, 0, sizeof(struct timespec));
330
331
0
            if (PcapDirectoryGetModifiedTime(pathbuff, &temp_time) == 0) {
332
0
                SCLogDebug("%" PRIuMAX " < %" PRIuMAX "(%s) < %" PRIuMAX ")",
333
0
                           (uintmax_t)SCTimespecAsEpochMillis(&pv->shared->last_processed),
334
0
                           (uintmax_t)SCTimespecAsEpochMillis(&temp_time),
335
0
                           pathbuff,
336
0
                           (uintmax_t)SCTimespecAsEpochMillis(older_than));
337
338
                // Skip files outside of our time range
339
0
                if (CompareTimes(&temp_time, &pv->shared->last_processed) <= 0) {
340
0
                    SCLogDebug("Skipping old file %s", pathbuff);
341
0
                    continue;
342
0
                }
343
0
                else if (CompareTimes(&temp_time, older_than) >= 0) {
344
0
                    SCLogDebug("Skipping new file %s", pathbuff);
345
0
                    continue;
346
0
                }
347
0
            } else {
348
0
                SCLogDebug("Unable to get modified time on %s, skipping", pathbuff);
349
0
                continue;
350
0
            }
351
352
0
            file_to_add = SCCalloc(1, sizeof(PendingFile));
353
0
            if (unlikely(file_to_add == NULL)) {
354
0
                SCLogError("Failed to allocate pending file");
355
356
0
                SCReturnInt(TM_ECODE_FAILED);
357
0
            }
358
359
0
            file_to_add->filename = SCStrdup(pathbuff);
360
0
            if (unlikely(file_to_add->filename == NULL)) {
361
0
                SCLogError("Failed to copy filename");
362
0
                CleanupPendingFile(file_to_add);
363
364
0
                SCReturnInt(TM_ECODE_FAILED);
365
0
            }
366
367
0
            memset(&file_to_add->modified_time, 0, sizeof(struct timespec));
368
0
            CopyTime(&temp_time, &file_to_add->modified_time);
369
370
0
            SCLogInfo("Found \"%s\" at %" PRIuMAX, file_to_add->filename,
371
0
                       (uintmax_t)SCTimespecAsEpochMillis(&file_to_add->modified_time));
372
373
0
            if (PcapDirectoryInsertFile(pv, file_to_add) == TM_ECODE_FAILED) {
374
0
                SCLogError("Failed to add file");
375
0
                CleanupPendingFile(file_to_add);
376
377
0
                SCReturnInt(TM_ECODE_FAILED);
378
0
            }
379
0
        }
380
0
    }
381
382
0
    SCReturnInt(TM_ECODE_OK);
383
0
}
384
385
386
TmEcode PcapDirectoryDispatchForTimeRange(PcapFileDirectoryVars *pv,
387
                                          struct timespec *older_than)
388
0
{
389
0
    if (PcapDirectoryPopulateBuffer(pv, older_than) == TM_ECODE_FAILED) {
390
0
        SCLogError("Failed to populate directory buffer");
391
0
        SCReturnInt(TM_ECODE_FAILED);
392
0
    }
393
394
0
    TmEcode status = TM_ECODE_OK;
395
396
0
    if (TAILQ_EMPTY(&pv->directory_content)) {
397
0
        SCLogDebug("Directory %s has no files to process", pv->filename);
398
0
        GetTime(older_than);
399
0
        older_than->tv_sec = older_than->tv_sec - pv->delay;
400
0
        rewinddir(pv->directory);
401
0
        status = TM_ECODE_OK;
402
0
    } else {
403
0
        PendingFile *current_file = NULL;
404
405
0
        struct timespec last_time_seen;
406
0
        memset(&last_time_seen, 0, sizeof(struct timespec));
407
408
0
        while (status == TM_ECODE_OK && !TAILQ_EMPTY(&pv->directory_content)) {
409
0
            current_file = TAILQ_FIRST(&pv->directory_content);
410
0
            TAILQ_REMOVE(&pv->directory_content, current_file, next);
411
412
0
            if (unlikely(current_file == NULL)) {
413
0
                SCLogWarning("Current file was null");
414
0
            } else if (unlikely(current_file->filename == NULL)) {
415
0
                SCLogWarning("Current file filename was null");
416
0
            } else {
417
0
                SCLogDebug("Processing file %s", current_file->filename);
418
419
0
                const size_t toalloc = sizeof(PcapFileFileVars) + pcap_g.read_buffer_size;
420
0
                PcapFileFileVars *pftv = SCCalloc(1, toalloc);
421
0
                if (unlikely(pftv == NULL)) {
422
0
                    SCLogError("Failed to allocate PcapFileFileVars");
423
0
                    SCReturnInt(TM_ECODE_FAILED);
424
0
                }
425
426
0
                pftv->filename = SCStrdup(current_file->filename);
427
0
                if (unlikely(pftv->filename == NULL)) {
428
0
                    SCLogError("Failed to allocate filename");
429
0
                    CleanupPcapFileFileVars(pftv);
430
0
                    SCReturnInt(TM_ECODE_FAILED);
431
0
                }
432
0
                pftv->shared = pv->shared;
433
434
0
                if (InitPcapFile(pftv) == TM_ECODE_FAILED) {
435
0
                    SCLogWarning("Failed to init pcap file %s, skipping", current_file->filename);
436
0
                    CleanupPendingFile(current_file);
437
0
                    CleanupPcapFileFileVars(pftv);
438
0
                    status = TM_ECODE_OK;
439
0
                } else {
440
0
                    pv->current_file = pftv;
441
442
0
                    status = PcapFileDispatch(pftv);
443
444
0
                    CleanupPcapFileFileVars(pftv);
445
446
0
                    if (status == TM_ECODE_FAILED) {
447
0
                        CleanupPendingFile(current_file);
448
0
                        SCReturnInt(status);
449
0
                    }
450
451
0
                    SCLogInfo("Processed file %s, processed up to %" PRIuMAX,
452
0
                               current_file->filename,
453
0
                               (uintmax_t)SCTimespecAsEpochMillis(&current_file->modified_time));
454
455
0
                    if(CompareTimes(&current_file->modified_time, &last_time_seen) > 0) {
456
0
                        CopyTime(&current_file->modified_time, &last_time_seen);
457
0
                    }
458
459
0
                    CleanupPendingFile(current_file);
460
0
                    pv->current_file = NULL;
461
462
0
                    status = PcapRunStatus(pv);
463
0
                }
464
0
            }
465
0
        }
466
467
0
        if(CompareTimes(&last_time_seen, &pv->shared->last_processed) > 0) {
468
0
            SCLogInfo("Updating processed to %" PRIuMAX,
469
0
                      (uintmax_t)SCTimespecAsEpochMillis(&last_time_seen));
470
0
            CopyTime(&last_time_seen, &pv->shared->last_processed);
471
0
            status = PcapRunStatus(pv);
472
0
        }
473
0
    }
474
0
    GetTime(older_than);
475
0
    older_than->tv_sec = older_than->tv_sec - pv->delay;
476
477
0
    SCReturnInt(status);
478
0
}
479
480
TmEcode PcapDirectoryDispatch(PcapFileDirectoryVars *ptv)
481
0
{
482
0
    SCEnter();
483
484
0
    DIR *directory_check = NULL;
485
486
0
    struct timespec older_than;
487
0
    memset(&older_than, 0, sizeof(struct timespec));
488
0
    older_than.tv_sec = LONG_MAX;
489
0
    uint32_t poll_seconds;
490
0
#ifndef OS_WIN32
491
0
    struct tm safe_tm;
492
0
    memset(&safe_tm, 0, sizeof(safe_tm));
493
0
    poll_seconds = (uint32_t)localtime_r(&ptv->poll_interval, &safe_tm)->tm_sec;
494
#else
495
    /* windows localtime is threadsafe */
496
    poll_seconds = (uint32_t)localtime(&ptv->poll_interval)->tm_sec;
497
#endif
498
499
0
    if (ptv->should_loop) {
500
0
        GetTime(&older_than);
501
0
        older_than.tv_sec = older_than.tv_sec - ptv->delay;
502
0
    }
503
0
    TmEcode status = TM_ECODE_OK;
504
505
0
    while (status == TM_ECODE_OK) {
506
        //loop while directory is ok
507
0
        SCLogInfo("Processing pcaps directory %s, files must be newer than %" PRIuMAX " and older than %" PRIuMAX,
508
0
                  ptv->filename, (uintmax_t)SCTimespecAsEpochMillis(&ptv->shared->last_processed),
509
0
                  (uintmax_t)SCTimespecAsEpochMillis(&older_than));
510
0
        status = PcapDirectoryDispatchForTimeRange(ptv, &older_than);
511
0
        if (ptv->should_loop && status == TM_ECODE_OK) {
512
0
            sleep(poll_seconds);
513
            //update our status based on suricata control flags or unix command socket
514
0
            status = PcapRunStatus(ptv);
515
0
            if (status == TM_ECODE_OK) {
516
0
                SCLogDebug("Checking if directory %s still exists", ptv->filename);
517
                //check directory
518
0
                if (PcapDetermineDirectoryOrFile(ptv->filename,
519
0
                                                 &directory_check) == TM_ECODE_FAILED) {
520
0
                    SCLogInfo("Directory %s no longer exists, stopping",
521
0
                              ptv->filename);
522
0
                    status = TM_ECODE_DONE;
523
0
                } else if(directory_check != NULL) {
524
0
                    closedir(directory_check);
525
0
                    directory_check = NULL;
526
0
                }
527
0
            }
528
0
        } else if (status == TM_ECODE_OK) { //not looping, mark done
529
0
            SCLogDebug("Not looping, stopping directory mode");
530
0
            status = TM_ECODE_DONE;
531
0
        }
532
0
    }
533
534
0
    StatsSyncCountersIfSignalled(&ptv->shared->tv->stats);
535
536
0
    if (status == TM_ECODE_FAILED) {
537
0
        SCLogError("Directory %s run mode failed", ptv->filename);
538
0
        status = PcapDirectoryFailure(ptv);
539
0
    } else {
540
0
        SCLogInfo("Directory run mode complete");
541
0
        status = PcapDirectoryDone(ptv);
542
0
    }
543
544
0
    SCReturnInt(status);
545
0
}
546
547
/* eof */