Browse Source

Merge branch 'source-files'

master
Alex 'AdUser' Z 8 years ago
parent
commit
bf41d09794
  1. 4
      README.md
  2. 2
      configs/conf-available/05-source-files.conf
  3. 16
      src/CMakeLists.txt
  4. 8
      src/config.c
  5. 2
      src/config.h
  6. 1
      src/daemon.c
  7. 58
      src/filelist.c
  8. 19
      src/filelist.h
  9. 2
      src/filter-test.c
  10. 13
      src/filter.h
  11. 121
      src/jail.c
  12. 7
      src/jail.h
  13. 4
      src/log.c
  14. 1
      src/log.h
  15. 84
      src/logfile.c
  16. 23
      src/logfile.h
  17. 48
      src/source-test.c
  18. 113
      src/source.c
  19. 34
      src/source.h
  20. 10
      src/sources/CMakeLists.txt
  21. 227
      src/sources/files.c
  22. 29
      src/sources/source.h
  23. 2
      t/CMakeLists.txt
  24. 58
      t/t_logfile.c

4
README.md

@ -7,8 +7,8 @@ Features:
* written in pure C
* small memory footprint
* minimum dependencies (req: libc, libdl; optional: pcre)
* pluggable filters / backends (you may write custom one)
* minimum dependencies (req: libc, libdl; optional: pcre, redis)
* pluggable sources / filters / backends (you may write custom one)
Similar software:

2
configs/conf-available/05-source-files.conf

@ -0,0 +1,2 @@
[source:files]
load = libf2b_source_files.so

16
src/CMakeLists.txt

@ -1,6 +1,6 @@
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(SOURCES "daemon.c" "strlcpy.c" "logfile.c" "log.c" "matches.c" "ipaddr.c" "filelist.c" "filter.c" "config.c" "jail.c" "backend.c")
set(SOURCES "daemon.c" "strlcpy.c" "config.c" "log.c" "matches.c" "ipaddr.c" "source.c" "filter.c" "backend.c" "jail.c")
if (WITH_CSOCKET)
list(APPEND SOURCES "csocket.c" "cmsg.c")
@ -17,19 +17,25 @@ if (WITH_CLIENT)
install(TARGETS "f2bc" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
endif ()
set(SOURCES "strlcpy.c" "backend-test.c" "log.c" "config.c" "backend.c")
add_executable("f2b-backend-test" ${SOURCES})
set(SOURCES "strlcpy.c" "source-test.c" "log.c" "config.c" "source.c")
add_executable("f2b-source-test" ${SOURCES})
set(SOURCES "strlcpy.c" "filter-test.c" "log.c" "config.c" "filter.c")
add_executable("f2b-filter-test" ${SOURCES})
set(SOURCES "strlcpy.c" "backend-test.c" "log.c" "config.c" "backend.c")
add_executable("f2b-backend-test" ${SOURCES})
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
target_link_libraries(f2b "dl")
target_link_libraries("f2b-source-test" "dl")
target_link_libraries("f2b-filter-test" "dl")
target_link_libraries("f2b-backend-test" "dl")
target_link_libraries("f2b-filter-test" "dl")
endif ()
install(TARGETS "f2b-filter-test" "f2b-backend-test" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
install(TARGETS "f2b-source-test" "f2b-filter-test" "f2b-backend-test"
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
add_subdirectory("backends")
add_subdirectory("filters")
add_subdirectory("sources")

8
src/config.c

@ -132,6 +132,13 @@ f2b_config_section_create(const char *src) {
return section;
}
name = "source:";
if (strncmp(line, name, strlen(name)) == 0) {
section->type = t_source;
strlcpy(section->name, line + strlen(name), sizeof(section->name));
return section;
}
name = "backend:";
if (strncmp(line, name, strlen(name)) == 0) {
section->type = t_backend;
@ -305,6 +312,7 @@ f2b_config_section_append(f2b_config_t *config, f2b_config_section_t *section) {
switch (section->type) {
case t_main: s = &config->main; break;
case t_defaults: s = &config->defaults; break;
case t_source: s = &config->sources; break;
case t_filter: s = &config->filters; break;
case t_backend: s = &config->backends; break;
case t_jail: s = &config->jails; break;

2
src/config.h

@ -16,6 +16,7 @@ typedef enum f2b_section_type {
t_unknown = 0,
t_main,
t_defaults,
t_source,
t_filter,
t_backend,
t_jail,
@ -38,6 +39,7 @@ typedef struct f2b_config_section_t {
typedef struct f2b_config_t {
f2b_config_section_t *main;
f2b_config_section_t *defaults;
f2b_config_section_t *sources;
f2b_config_section_t *filters;
f2b_config_section_t *backends;
f2b_config_section_t *jails;

1
src/daemon.c

@ -5,7 +5,6 @@
* published by the Free Software Foundation.
*/
#include "common.h"
#include "logfile.h"
#include "ipaddr.h"
#include "config.h"
#include "jail.h"

58
src/filelist.c

@ -1,58 +0,0 @@
/* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <glob.h>
#include "common.h"
#include "logfile.h"
#include "filelist.h"
#include "log.h"
f2b_logfile_t *
f2b_filelist_append(f2b_logfile_t *list, f2b_logfile_t *file) {
assert(file != NULL);
if (list != NULL)
return file->next = list;
return file;
}
f2b_logfile_t *
f2b_filelist_from_glob(const char *pattern) {
f2b_logfile_t *file = NULL;
f2b_logfile_t *files = NULL;
glob_t globbuf;
assert(pattern != NULL);
if (glob(pattern, GLOB_MARK | GLOB_NOESCAPE, NULL, &globbuf) != 0)
return NULL;
for (size_t i = 0; i < globbuf.gl_pathc; i++) {
if ((file = calloc(1, sizeof(f2b_logfile_t))) == NULL)
continue;
if (f2b_logfile_open(file, globbuf.gl_pathv[i]) == false) {
f2b_log_msg(log_error, "can't open file: %s: %s", globbuf.gl_pathv[i], strerror(errno));
free(file);
continue;
}
files = f2b_filelist_append(files, file);
}
globfree(&globbuf);
return files;
}
void
f2b_filelist_destroy(f2b_logfile_t *list) {
f2b_logfile_t *next = NULL;
for (; list != NULL; list = next) {
next = list->next;
f2b_logfile_close(list);
free(list);
}
}

19
src/filelist.h

@ -1,19 +0,0 @@
/* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef F2B_FILELIST_H_
#define F2B_FILELIST_H_
f2b_logfile_t *
f2b_filelist_from_glob(const char *pattern);
f2b_logfile_t *
f2b_filelist_append(f2b_logfile_t *list, f2b_logfile_t *file);
void
f2b_filelist_destroy(f2b_logfile_t *list);
#endif /* F2B_FILELIST_H_ */

2
src/filter-test.c

@ -17,7 +17,7 @@ void usage() {
int main(int argc, char *argv[]) {
f2b_config_param_t param = { .name = "load", .value = "", .next = 0x0 };
f2b_config_section_t config = { .type = t_filter, .param = 0x0, .next = 0x0 };
f2b_config_section_t config = { .name = "test", .type = t_filter, .param = 0x0, .next = 0x0 };
f2b_filter_t *filter = NULL;
char match[IPADDR_MAX] = "";
char line[LOGLINE_MAX] = "";

13
src/filter.h

@ -23,12 +23,11 @@ typedef struct f2b_filter_t {
void (*destroy) (void *cfg);
} f2b_filter_t;
f2b_filter_t * f2b_filter_create (f2b_config_section_t *config, const char *id);
void f2b_filter_destroy(f2b_filter_t *b);
bool f2b_filter_append(f2b_filter_t *b, const char *pattern);
bool f2b_filter_match(f2b_filter_t *b, const char *line, char *buf, size_t buf_size);
const char * f2b_filter_error(f2b_filter_t *b);
void f2b_filter_stats (f2b_filter_t *b, char *res, size_t ressize);
f2b_filter_t * f2b_filter_create (f2b_config_section_t *config, const char *id);
const char * f2b_filter_error (f2b_filter_t *f);
bool f2b_filter_append (f2b_filter_t *f, const char *pattern);
bool f2b_filter_match (f2b_filter_t *f, const char *line, char *buf, size_t buf_size);
void f2b_filter_stats (f2b_filter_t *f, char *res, size_t ressize);
void f2b_filter_destroy (f2b_filter_t *f);
#endif /* F2B_FILTER_H_ */

121
src/jail.c

@ -202,16 +202,16 @@ f2b_jail_find(f2b_jail_t *list, const char *name) {
size_t
f2b_jail_process(f2b_jail_t *jail) {
f2b_logfile_t *file = NULL;
f2b_ipaddr_t *prev = NULL;
f2b_ipaddr_t *addr = NULL;
size_t processed = 0;
char logline[LOGLINE_MAX] = "";
char line[LOGLINE_MAX] = "";
char matchbuf[IPADDR_MAX] = "";
time_t now = time(NULL);
time_t findtime = 0;
time_t expiretime = 0;
bool remove = false;
bool reset = true; /* source reset */
assert(jail != NULL);
@ -219,55 +219,46 @@ f2b_jail_process(f2b_jail_t *jail) {
f2b_backend_ping(jail->backend);
for (file = jail->logfiles; file != NULL; file = file->next) {
if (f2b_logfile_rotated(file)) {
f2b_log_msg(log_info, "jail '%s': file changed -- %s", jail->name, file->path);
f2b_logfile_close(file);
}
if (!file->opened && !f2b_logfile_open(file, NULL)) {
f2b_log_msg(log_error, "jail '%s': can't open file -- %s", jail->name, file->path);
while (f2b_source_next(jail->source, line, sizeof(line), reset)) {
reset = false;
if (!f2b_filter_match(jail->filter, line, matchbuf, sizeof(matchbuf)))
continue;
}
while (f2b_logfile_getline(file, logline, sizeof(logline))) {
if (!f2b_filter_match(jail->filter, logline, matchbuf, sizeof(matchbuf)))
continue;
/* some regex matches the line */
jail->matchcount++;
addr = f2b_addrlist_lookup(jail->ipaddrs, matchbuf);
if (!addr) {
/* new ip */
addr = f2b_ipaddr_create(matchbuf, jail->maxretry);
addr->lastseen = now;
f2b_matches_append(&addr->matches, now);
jail->ipaddrs = f2b_addrlist_append(jail->ipaddrs, addr);
f2b_log_msg(log_info, "jail '%s': new ip found -- %s", jail->name, matchbuf);
continue;
}
/* this ip was seen before */
/* some regex matches the line */
jail->matchcount++;
addr = f2b_addrlist_lookup(jail->ipaddrs, matchbuf);
if (!addr) {
/* new ip */
addr = f2b_ipaddr_create(matchbuf, jail->maxretry);
addr->lastseen = now;
if (addr->banned) {
if (addr->banned_at != now)
f2b_log_msg(log_warn, "jail '%s': ip %s was already banned", jail->name, matchbuf);
continue;
}
if (jail->incr_findtime > 0 && addr->matches.hits > jail->maxretry) {
findtime = now - jail->findtime;
findtime -= (int) ((addr->matches.hits - jail->maxretry) *
(jail->findtime * jail->incr_findtime));
} else {
findtime = now - jail->findtime;
}
f2b_matches_expire(&addr->matches, findtime);
f2b_matches_append(&addr->matches, now);
if (addr->matches.used < jail->maxretry) {
f2b_log_msg(log_info, "jail '%s': new ip match -- %s (%zu/%zu)",
jail->name, matchbuf, addr->matches.used, addr->matches.max);
continue;
}
/* limit reached, ban ip */
f2b_jail_ban(jail, addr);
} /* while(lines) */
} /* for(files) */
jail->ipaddrs = f2b_addrlist_append(jail->ipaddrs, addr);
f2b_log_msg(log_info, "jail '%s': new ip found -- %s", jail->name, matchbuf);
continue;
}
/* this ip was seen before */
addr->lastseen = now;
if (addr->banned) {
if (addr->banned_at != now)
f2b_log_msg(log_warn, "jail '%s': ip %s was already banned", jail->name, matchbuf);
continue;
}
if (jail->incr_findtime > 0 && addr->matches.hits > jail->maxretry) {
findtime = now - jail->findtime;
findtime -= (int) ((addr->matches.hits - jail->maxretry) *
(jail->findtime * jail->incr_findtime));
} else {
findtime = now - jail->findtime;
}
f2b_matches_expire(&addr->matches, findtime);
f2b_matches_append(&addr->matches, now);
if (addr->matches.used < jail->maxretry) {
f2b_log_msg(log_info, "jail '%s': new ip match -- %s (%zu/%zu)",
jail->name, matchbuf, addr->matches.used, addr->matches.max);
continue;
}
/* limit reached, ban ip */
f2b_jail_ban(jail, addr);
} /* while(1) */
for (addr = jail->ipaddrs, prev = NULL; addr != NULL; ) {
remove = false;
@ -308,6 +299,7 @@ f2b_jail_process(f2b_jail_t *jail) {
bool
f2b_jail_init(f2b_jail_t *jail, f2b_config_t *config) {
f2b_config_section_t * s_section = NULL;
f2b_config_section_t * b_section = NULL;
f2b_config_section_t * f_section = NULL;
@ -319,13 +311,8 @@ f2b_jail_init(f2b_jail_t *jail, f2b_config_t *config) {
f2b_log_msg(log_error, "jail '%s': missing 'source' parameter", jail->name);
return false;
}
/* TODO: temp stub */
if (strcmp(jail->source_name, "files") != 0) {
f2b_log_msg(log_error, "jail '%s': 'source' supports only 'files' for now", jail->name);
return false;
}
if (jail->source_init[0] == '\0') {
f2b_log_msg(log_error, "jail '%s': 'source' requires file or files pattern", jail->name);
if ((s_section = f2b_config_section_find(config->sources, jail->source_name)) == NULL) {
f2b_log_msg(log_error, "jail '%s': no source with name '%s'", jail->name, jail->source_name);
return false;
}
@ -350,8 +337,13 @@ f2b_jail_init(f2b_jail_t *jail, f2b_config_t *config) {
}
/* init all */
if ((jail->logfiles = f2b_filelist_from_glob(jail->source_init)) == NULL) {
f2b_log_msg(log_error, "jail '%s': no files matching '%s' pattern", jail->name, jail->source_init);
if ((jail->source = f2b_source_create(s_section, jail->source_init, f2b_log_error_cb)) == NULL) {
f2b_log_msg(log_error, "jail '%s': no regexps loaded from '%s'", jail->name, jail->source_init);
goto cleanup;
}
if (!f2b_source_start(jail->source)) {
f2b_log_msg(log_warn, "jail '%s': source action 'start' failed -- %s",
jail->name, f2b_source_error(jail->source));
goto cleanup;
}
@ -368,6 +360,7 @@ f2b_jail_init(f2b_jail_t *jail, f2b_config_t *config) {
if (!f2b_backend_start(jail->backend)) {
f2b_log_msg(log_warn, "jail '%s': backend action 'start' failed -- %s",
jail->name, f2b_backend_error(jail->backend));
goto cleanup;
}
f2b_log_msg(log_info, "jail '%s': started", jail->name);
@ -375,8 +368,8 @@ f2b_jail_init(f2b_jail_t *jail, f2b_config_t *config) {
return true;
cleanup:
if (jail->logfiles)
f2b_filelist_destroy(jail->logfiles);
if (jail->source)
f2b_source_destroy(jail->source);
if (jail->filter)
f2b_filter_destroy(jail->filter);
if (jail->backend)
@ -392,7 +385,13 @@ f2b_jail_stop(f2b_jail_t *jail) {
f2b_log_msg(log_info, "jail '%s': gracefull shutdown", jail->name);
f2b_filelist_destroy(jail->logfiles);
if (!f2b_source_stop(jail->source)) {
f2b_log_msg(log_error, "jail '%s': action 'stop' for source failed: %s",
jail->name, f2b_source_error(jail->source));
errors = true;
}
f2b_source_destroy(jail->source);
f2b_filter_destroy(jail->filter);
for (f2b_ipaddr_t *addr = jail->ipaddrs; addr != NULL; addr = addr->next) {
@ -405,7 +404,7 @@ f2b_jail_stop(f2b_jail_t *jail) {
f2b_addrlist_destroy(jail->ipaddrs);
if (!f2b_backend_stop(jail->backend)) {
f2b_log_msg(log_error, "jail '%s': action 'stop' failed: %s",
f2b_log_msg(log_error, "jail '%s': action 'stop' for backend failed: %s",
jail->name, f2b_backend_error(jail->backend));
errors = true;
}

7
src/jail.h

@ -8,12 +8,11 @@
#define F2B_JAIL_H_
#include "log.h"
#include "logfile.h"
#include "ipaddr.h"
#include "config.h"
#include "source.h"
#include "filter.h"
#include "backend.h"
#include "filelist.h"
typedef struct f2b_jail_t {
struct f2b_jail_t *next;
@ -34,10 +33,10 @@ typedef struct f2b_jail_t {
char filter_init[CONFIG_VAL_MAX];
char source_name[CONFIG_KEY_MAX];
char source_init[CONFIG_VAL_MAX];
f2b_logfile_t *logfiles;
f2b_source_t *source;
f2b_filter_t *filter;
f2b_ipaddr_t *ipaddrs;
f2b_backend_t *backend;
f2b_ipaddr_t *ipaddrs;
} f2b_jail_t;
void f2b_jail_parse_compound_value(const char *value, char *name, char *init);

4
src/log.c

@ -65,6 +65,10 @@ void f2b_log_msg(log_msgtype_t l, const char *fmt, ...) {
return;
}
void f2b_log_error_cb(const char *errstr) {
f2b_log_msg(log_error, "%s", errstr);
}
void f2b_log_set_level(const char *level) {
if (strcmp(level, "debug") == 0) { minlevel = log_debug; return; }
if (strcmp(level, "info") == 0) { minlevel = log_info; return; }

1
src/log.h

@ -20,6 +20,7 @@ typedef enum {
void f2b_log_msg(log_msgtype_t l, const char *fmt, ...)
__attribute__ ((format (printf, 2, 3)));
void f2b_log_error_cb(const char *errstr);
void f2b_log_set_level(const char *level);
void f2b_log_to_file (const char *path);
void f2b_log_to_stderr();

84
src/logfile.c

@ -1,84 +0,0 @@
/* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include "common.h"
#include "logfile.h"
bool
f2b_logfile_open(f2b_logfile_t *file, const char *path) {
struct stat st;
char buf[PATH_MAX] = "";
assert(file != NULL);
assert(path != NULL || file->path[0] != '\0');
strlcpy(buf, path ? path : file->path, sizeof(buf));
memset(file, 0x0, sizeof(f2b_logfile_t));
if (stat(buf, &st) != 0)
return false;
if (!(S_ISREG(st.st_mode) || S_ISFIFO(st.st_mode)))
return false;
if ((file->fd = fopen(buf, "r")) == NULL)
return false;
if (S_ISREG(st.st_mode) && fseek(file->fd, 0, SEEK_END) < 0)
return false;
memcpy(&file->st, &st, sizeof(st));
strlcpy(file->path, buf, sizeof(file->path));
file->opened = true;
return true;
}
void
f2b_logfile_close(f2b_logfile_t *file) {
assert(file != NULL);
if (file->fd)
fclose(file->fd);
file->opened = false;
file->fd = NULL;
}
bool
f2b_logfile_rotated(const f2b_logfile_t *file) {
struct stat st;
assert(file != NULL);
if (!file->opened)
return true;
if (stat(file->path, &st) != 0)
return true;
if (file->st.st_dev != st.st_dev ||
file->st.st_ino != st.st_ino ||
file->st.st_size > st.st_size)
return true;
return false;
}
bool
f2b_logfile_getline(const f2b_logfile_t *file, char *buf, size_t bufsize) {
assert(file != NULL);
assert(buf != NULL);
if (feof(file->fd))
clearerr(file->fd);
/* fread()+EOF set is implementation defined */
if (fgets(buf, bufsize, file->fd) != NULL)
return true;
return false;
}

23
src/logfile.h

@ -1,23 +0,0 @@
/* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef F2B_LOGFILE_H_
#define F2B_LOGFILE_H_
typedef struct f2b_logfile_t {
struct f2b_logfile_t *next;
bool opened;
char path[PATH_MAX];
FILE *fd;
struct stat st;
} f2b_logfile_t;
bool f2b_logfile_open(f2b_logfile_t *file, const char *path);
void f2b_logfile_close(f2b_logfile_t *file);
bool f2b_logfile_rotated(const f2b_logfile_t *file);
bool f2b_logfile_getline(const f2b_logfile_t *file, char *buf, size_t bufsize);
#endif /* F2B_LOGFILE_H_ */

48
src/source-test.c

@ -0,0 +1,48 @@
/* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include "common.h"
#include "log.h"
#include "config.h"
#include "source.h"
void usage() {
fprintf(stderr, "Usage: source-test <library.so> <init string>\n");
exit(EXIT_FAILURE);
}
int main(int argc, char *argv[]) {
f2b_config_param_t param = { .name = "load", .value = "", .next = 0x0 };
f2b_config_section_t config = { .name = "test", .type = t_source, .param = 0x0, .next = 0x0 };
f2b_source_t *source = NULL;
char buf[1024] = "";
bool reset;
if (argc < 2)
usage();
config.param = &param;
snprintf(param.value, sizeof(param.value), "%s", argv[1]);
if ((source = f2b_source_create(&config, argv[2], f2b_log_error_cb)) == false)
usage();
if (f2b_source_start(source) == false) {
f2b_log_msg(log_error, "source start error: %s", f2b_source_error(source));
exit(EXIT_FAILURE);
}
while (1) {
reset = true;
while (f2b_source_next(source, buf, sizeof(buf), reset)) {
reset = false;
fputs(buf, stderr);
}
sleep(1);
}
return EXIT_SUCCESS;
}

113
src/source.c

@ -0,0 +1,113 @@
/* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include "common.h"
#include "source.h"
#define SOURCE_LIBRARY_PARAM "load"
f2b_source_t *
f2b_source_create(f2b_config_section_t *config, const char *init, void (*errcb)(const char *)) {
f2b_config_param_t *param = NULL;
f2b_source_t *source = NULL;
int flags = RTLD_NOW | RTLD_LOCAL;
const char *dlerr = NULL;
assert(config != NULL);
assert(config->type == t_source);
param = f2b_config_param_find(config->param, SOURCE_LIBRARY_PARAM);
if (!param) {
f2b_log_msg(log_error, "can't find '%s' param in source config", SOURCE_LIBRARY_PARAM);
return NULL;
}
if ((source = calloc(1, sizeof(f2b_source_t))) == NULL)
return NULL;
if ((source->h = dlopen(param->value, flags)) == NULL)
goto cleanup;
if ((*(void **) (&source->create) = dlsym(source->h, "create")) == NULL)
goto cleanup;
if ((*(void **) (&source->config) = dlsym(source->h, "config")) == NULL)
goto cleanup;
if ((*(void **) (&source->ready) = dlsym(source->h, "ready")) == NULL)
goto cleanup;
if ((*(void **) (&source->error) = dlsym(source->h, "error")) == NULL)
goto cleanup;
if ((*(void **) (&source->errcb) = dlsym(source->h, "errcb")) == NULL)
goto cleanup;
if ((*(void **) (&source->start) = dlsym(source->h, "start")) == NULL)
goto cleanup;
if ((*(void **) (&source->next) = dlsym(source->h, "next")) == NULL)
goto cleanup;
if ((*(void **) (&source->stop) = dlsym(source->h, "stop")) == NULL)
goto cleanup;
if ((*(void **) (&source->destroy) = dlsym(source->h, "destroy")) == NULL)
goto cleanup;
if ((source->cfg = source->create(init)) == NULL) {
f2b_log_msg(log_error, "source create config failed");
goto cleanup;
}
/* try init */
for (param = config->param; param != NULL; param = param->next) {
if (strcmp(param->name, SOURCE_LIBRARY_PARAM) == 0)
continue;
if (source->config(source->cfg, param->name, param->value))
continue;
f2b_log_msg(log_warn, "param pair not accepted by source '%s': %s=%s",
config->name, param->name, param->value);
}
if (errcb)
source->errcb(source->cfg, errcb);
if (source->ready(source->cfg))
return source;
/* still not ready */
f2b_log_msg(log_error, "source '%s' not fully configured", config->name);
cleanup:
dlerr = dlerror();
if (dlerr)
f2b_log_msg(log_error, "source load error: %s", dlerr);
if (source->h) {
if (source->cfg && source->destroy)
source->destroy(source->cfg);
dlclose(source->h);
}
free(source);
return NULL;
}
void
f2b_source_destroy(f2b_source_t *source) {
assert(source != NULL);
source->destroy(source->cfg);
dlclose(source->h);
free(source);
}
bool
f2b_source_next(f2b_source_t *source, char *buf, size_t bufsize, bool reset) {
assert(source != NULL);
return source->next(source->cfg, buf, bufsize, reset);
}
#define SOURCE_CMD_ARG0(CMD, RETURNS) \
RETURNS \
f2b_source_ ## CMD(f2b_source_t *source) { \
assert(source != NULL); \
return source->CMD(source->cfg); \
}
SOURCE_CMD_ARG0(error, const char *)
SOURCE_CMD_ARG0(start, bool)
SOURCE_CMD_ARG0(stop, bool)
SOURCE_CMD_ARG0(ready, bool)

34
src/source.h

@ -0,0 +1,34 @@
/* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef F2B_SOURCE_H_
#define F2B_SOURCE_H_
#include "config.h"
#include "log.h"
typedef struct f2b_source_t {
void *h;
void *cfg;
void *(*create) (const char *init);
bool (*config) (void *cfg, const char *key, const char *value);
bool (*ready) (void *cfg);
char *(*error) (void *cfg);
void (*errcb) (void *cfg, void (*cb)(const char *errstr));
bool (*start) (void *cfg);
bool (*next) (void *cfg, char *buf, size_t bufsize, bool reset);
bool (*stop) (void *cfg);
void (*destroy) (void *cfg);
} f2b_source_t;
f2b_source_t * f2b_source_create (f2b_config_section_t *config, const char *init, void (*errcb)(const char *));
void f2b_source_destroy (f2b_source_t *s);
bool f2b_source_start (f2b_source_t *s);
bool f2b_source_next (f2b_source_t *s, char *buf, size_t bufsize, bool reset);
bool f2b_source_stop (f2b_source_t *s);
const char * f2b_source_error (f2b_source_t *s);
#endif /* F2B_SOURCE_H_ */

10
src/sources/CMakeLists.txt

@ -0,0 +1,10 @@
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(SOURCES "")
add_library("f2b_source_files" MODULE "files.c" "../strlcpy.c")
list(APPEND SOURCES "f2b_source_files")
message(STATUS "- Sources : ${SOURCES}")
install(TARGETS ${SOURCES}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})

227
src/sources/files.c

@ -0,0 +1,227 @@
/* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include "source.h"
#include <limits.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <glob.h>
typedef struct f2b_file_t {
struct f2b_file_t *next;
bool opened;
char path[PATH_MAX];
FILE *fd;
struct stat st;
} f2b_file_t;
struct _config {
char path[256];
char error[256];
void (*errcb)(char *errstr);
f2b_file_t *files;
f2b_file_t *current;
};
static bool
file_open(f2b_file_t *file, const char *path) {
struct stat st;
char buf[PATH_MAX] = "";
assert(file != NULL);
assert(path != NULL || file->path[0] != '\0');
strlcpy(buf, path ? path : file->path, sizeof(buf));
memset(file, 0x0, sizeof(f2b_file_t));
if (stat(buf, &st) != 0)
return false;
if (!(S_ISREG(st.st_mode) || S_ISFIFO(st.st_mode)))
return false;
if ((file->fd = fopen(buf, "r")) == NULL)
return false;
if (S_ISREG(st.st_mode) && fseek(file->fd, 0, SEEK_END) < 0)
return false;
memcpy(&file->st, &st, sizeof(st));
strlcpy(file->path, buf, sizeof(file->path));
file->opened = true;
return true;
}
static void
file_close(f2b_file_t *file) {
assert(file != NULL);
if (file->fd)
fclose(file->fd);
file->opened = false;
file->fd = NULL;
}
static bool
file_rotated(const f2b_file_t *file) {
struct stat st;
assert(file != NULL);
if (!file->opened)
return true;
if (stat(file->path, &st) != 0)
return true;
if (file->st.st_dev != st.st_dev ||
file->st.st_ino != st.st_ino ||
file->st.st_size > st.st_size)
return true;
return false;
}
static bool
file_getline(const f2b_file_t *file, char *buf, size_t bufsize) {
assert(file != NULL);
assert(buf != NULL);
if (feof(file->fd))
clearerr(file->fd);
/* fread()+EOF set is implementation defined */
if (fgets(buf, bufsize, file->fd) != NULL)
return true;
return false;
}
cfg_t *
create(const char *init) {
cfg_t *cfg = NULL;
assert(init != NULL);
if ((cfg = calloc(1, sizeof(cfg_t))) == NULL)
return NULL;
strlcpy(cfg->path, init, sizeof(cfg->path));
return cfg;
}
bool
config(cfg_t *cfg, const char *key, const char *value) {
assert(cfg != NULL);
assert(key != NULL);
assert(value != NULL);
/* no options */
return false;
}
bool
ready(cfg_t *cfg) {
assert(cfg != NULL);
if (cfg->path[0] == '\0')
return false;
return true;
}
char *
error(cfg_t *cfg) {
assert(cfg != NULL);
return cfg->error;
}
void
errcb(cfg_t *cfg, void (*cb)(char *errstr)) {
assert(cfg != NULL);
assert(cb != NULL);
cfg->errcb = cb;
}
bool
start(cfg_t *cfg) {
f2b_file_t *file = NULL;
glob_t globbuf;
assert(cfg != NULL);
if (glob(cfg->path, GLOB_MARK | GLOB_NOESCAPE, NULL, &globbuf) != 0)
return NULL;
for (size_t i = 0; i < globbuf.gl_pathc; i++) {
if ((file = calloc(1, sizeof(f2b_file_t))) == NULL)
continue;
if (file_open(file, globbuf.gl_pathv[i]) == false) {
if (cfg->errcb) {
snprintf(cfg->error, sizeof(cfg->error), "can't open file: %s -- %s",
globbuf.gl_pathv[i], strerror(errno));
cfg->errcb(cfg->error);
}
free(file);
continue;
}
if (cfg->files == NULL) {
cfg->files = file;
} else {
file->next = cfg->files;
cfg->files = file;
}
}
globfree(&globbuf);
return true;
}
bool
stop(cfg_t *cfg) {
f2b_file_t *next = NULL;
for (; cfg->files != NULL; cfg->files = next) {
next = cfg->files->next;
file_close(cfg->files);
free(cfg->files);
}
return true;
}
bool
next(cfg_t *cfg, char *buf, size_t bufsize, bool reset) {
assert(cfg != NULL);
assert(buf != NULL);
assert(bufsize > 0);
if (reset || cfg->current == NULL)
cfg->current = cfg->files;
for (f2b_file_t *file = cfg->current; file != NULL; file = file->next) {
if (file_rotated(file))
file_close(file);
if (!file->opened && !file_open(file, NULL)) {
if (cfg->errcb) {
snprintf(cfg->error, sizeof(cfg->error), "can't open file -- %s", file->path);
cfg->errcb(cfg->error);
}
continue;
}
if (file_getline(file, buf, bufsize))
return true;
}
return false;
}
void
destroy(cfg_t *cfg) {
assert(cfg != NULL);
free(cfg);
}

29
src/sources/source.h

@ -0,0 +1,29 @@
/* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "../strlcpy.h"
#define INIT_MAX 256
typedef struct _config cfg_t;
extern cfg_t *create(const char *init);
extern bool config(cfg_t *c, const char *key, const char *value);
extern bool ready(cfg_t *c);
extern char *error(cfg_t *c);
extern void errcb(cfg_t *c, void (*cb)(char *errstr));
extern bool start(cfg_t *c);
extern bool next(cfg_t *c, char *buf, size_t bufsize, bool reset);
extern bool stop(cfg_t *c);
extern void destroy(cfg_t *c);

2
t/CMakeLists.txt

@ -3,14 +3,12 @@ enable_testing()
set(SRC_DIR "../src")
add_executable("t_cmsg" "t_cmsg.c" "${SRC_DIR}/strlcpy.c" "${SRC_DIR}/cmsg.c")
add_executable("t_logfile" "t_logfile.c" "${SRC_DIR}/strlcpy.c" "${SRC_DIR}/logfile.c")
add_executable("t_matches" "t_matches.c" "${SRC_DIR}/strlcpy.c" "${SRC_DIR}/matches.c")
add_executable("t_ipaddr" "t_ipaddr.c" "${SRC_DIR}/strlcpy.c" "${SRC_DIR}/matches.c" "${SRC_DIR}/ipaddr.c")
add_executable("t_config_param" "t_config_param.c" "${SRC_DIR}/strlcpy.c" "${SRC_DIR}/config.c" "${SRC_DIR}/log.c")
add_executable("t_backend_usage" "t_backend_usage.c" "${SRC_DIR}/strlcpy.c")
add_test("tests/f2b_cmsg_*" "t_cmsg")
add_test("tests/f2b_logfile_*" "t_logfile")
add_test("tests/f2b_matches_*" "t_matches")
add_test("tests/f2b_ipaddr_*" "t_ipaddr")
add_test("tests/f2b_config_param*" "t_config_param")

58
t/t_logfile.c

@ -1,58 +0,0 @@
#include "../src/common.h"
#include "../src/logfile.h"
int main() {
f2b_logfile_t file;
char filename[] = "/tmp/f2b-test.XXXXXX";
int fd = 0;
int ret;
bool res;
FILE *wfd = NULL;
char buf[2048];
UNUSED(res);
UNUSED(ret);
fd = mkstemp(filename);
assert(fd > 0);
wfd = fdopen(fd, "a");
assert(wfd != NULL);
ret = fputs("test1\n", wfd);
assert(ret > 0);
ret = fflush(wfd);
assert(ret == 0);
res = f2b_logfile_open(&file, filename);
assert(res == true);
res = f2b_logfile_getline(&file, buf, sizeof(buf));
assert(res == false);
ret = fputs("test2\n", wfd);
assert(ret > 0);
ret = fflush(wfd);
assert(ret == 0);
res = f2b_logfile_getline(&file, buf, sizeof(buf));
assert(res == true);
ret = strncmp(buf, "test2\n", sizeof(buf));
assert(ret == 0);
res = f2b_logfile_getline(&file, buf, sizeof(buf));
assert(res == false);
fclose(wfd);
close(fd);
unlink(filename);
wfd = fopen(filename, "a");
assert(wfd != NULL);
res = f2b_logfile_rotated(&file);
assert(res == true);
f2b_logfile_close(&file);
fclose(wfd);
unlink(filename);
exit(0);
}
Loading…
Cancel
Save