diff --git a/CMakeLists.txt b/CMakeLists.txt index e185287..a80be2f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ set(CNAME "f2b") -set(VERSION 0.2) +set(VERSION 0.3) project(${CNAME} C) cmake_minimum_required(VERSION 2.6) @@ -8,9 +8,9 @@ include(CTest) option(WITH_CLIENT "Simple client for configuring daemon" ON) option(WITH_CSOCKET "Unix control socket support for daemon" ON) -option(WITH_HARDENING "Enable hardening options" OFF) +option(WITH_HARDENING "Enable hardening options" ON) option(WITH_PCRE "Build pcre-compatible filter" ON) -option(WITH_REDIS "Build redis backend" ON) +option(WITH_REDIS "Build redis backend" OFF) if (NOT DEFINED CMAKE_INSTALL_PREFIX) set(CMAKE_INSTALL_PREFIX "/usr") @@ -22,7 +22,12 @@ endif () include(GNUInstallDirs) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -pedantic -std=c99") -add_definitions("-D_XOPEN_SOURCE=600") +if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") + add_definitions("-D_XOPEN_SOURCE=600") +else () + include_directories(AFTER SYSTEM "/usr/local/include") + link_directories("/usr/local/lib") +endif () if (WITH_HARDENING) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wformat -Wformat-security -Werror=format-security" ) @@ -52,8 +57,16 @@ add_subdirectory(t) set_property(DIRECTORY "t" PROPERTY COMPILE_FLAGS "-g;-ggdb;-Wall;-Wextra;-pedantic;-O0") install(DIRECTORY "filters" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/f2b") -install(DIRECTORY "configs/" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/f2b") -install(FILES "configs/f2b.conf.sample" DESTINATION "${CMAKE_INSTALL_SYSCONFDIR}/f2b/" RENAME "f2b.conf") +file(GLOB_RECURSE CONFIGS "*.conf.in") +foreach(CONFIG ${CONFIGS}) + string(REPLACE ".conf.in" ".conf" GENERATED ${CONFIG}) + configure_file(${CONFIG} ${GENERATED}) +endforeach() +install(DIRECTORY "configs/" DESTINATION "${CMAKE_INSTALL_SYSCONFDIR}/f2b" + FILES_MATCHING PATTERN "*.conf") +install(FILES "configs/f2b.conf" DESTINATION "${CMAKE_INSTALL_SYSCONFDIR}/f2b" + RENAME "f2b.conf.sample") +file(MAKE_DIRECTORY "${CMAKE_INSTALL_SYSCONFDIR}/f2b/conf-enabled") add_custom_target("dist" COMMAND "git" "archive" "--format=tar.gz" diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..f135277 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,42 @@ +# Change Log +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + +## Unreleased + +## [0.3] - 2016-09-12 +### Added + + * "jail regex stats" command + * "jail regex add" command + * apply CMAKE_INSTALL_PREFIX to configs + * added config for exec backend for ipfw + * redis backend (experimental) + * added config reload + * log file rotation + +### Changed + + * enable 'icase' for filters by default + * enable 'sharing' for backends by default + * tune configs location + * enable hardening in build opts by default + * fix ssh filter patterns + * use strl*() instead snprintf()/strncpy() in backends + * rename tests utils + * print date/time in log file + * disable buffering for logfile + * add stats() funtion to filter's api + +### Fixed + + * fix segfault in preg filter + * fix cppcheck warnings + * fix bsd build + * fix termination of daemon + +## [0.2] - 2016-08-21 + + * Initial public release diff --git a/configs/conf-available/10-backend-exec-ipfw.conf b/configs/conf-available/10-backend-exec-ipfw.conf new file mode 100644 index 0000000..6efe0a5 --- /dev/null +++ b/configs/conf-available/10-backend-exec-ipfw.conf @@ -0,0 +1,6 @@ +[backend:exec-ipfw] +load = libf2b_backend_exec.so +ban = /sbin/ipfw table add +unban = /sbin/ipfw table delete +timeout = 2 +shared = yes diff --git a/configs/conf-available/10-backend-exec-ipset.conf b/configs/conf-available/10-backend-exec-ipset.conf index 992353e..a9d57f1 100644 --- a/configs/conf-available/10-backend-exec-ipset.conf +++ b/configs/conf-available/10-backend-exec-ipset.conf @@ -8,4 +8,4 @@ ban = /sbin/ipset -! add check = /sbin/ipset -! test unban = /sbin/ipset -! del timeout = 2 -shared = no +shared = yes diff --git a/configs/conf-available/10-backend-redis.conf b/configs/conf-available/10-backend-redis.conf index 10ad869..6e21c9b 100644 --- a/configs/conf-available/10-backend-redis.conf +++ b/configs/conf-available/10-backend-redis.conf @@ -1,6 +1,6 @@ [backend:redis] load = libf2b_backend_redis.so -shared = no +shared = yes timeout = 2 host = 127.0.0.1 port = 6379 diff --git a/configs/conf-available/15-filter-pcre.conf b/configs/conf-available/15-filter-pcre.conf index d090d69..ad702e5 100644 --- a/configs/conf-available/15-filter-pcre.conf +++ b/configs/conf-available/15-filter-pcre.conf @@ -1,5 +1,5 @@ [filter:pcre] load = libf2b_filter_pcre.so -icase = no +icase = yes study = yes usejit = no diff --git a/configs/conf-available/15-filter-preg.conf b/configs/conf-available/15-filter-preg.conf index a764326..03d878d 100644 --- a/configs/conf-available/15-filter-preg.conf +++ b/configs/conf-available/15-filter-preg.conf @@ -1,3 +1,3 @@ [filter:preg] load = libf2b_filter_preg.so -icase = no +icase = yes diff --git a/configs/f2b.conf.sample b/configs/f2b.conf.in similarity index 73% rename from configs/f2b.conf.sample rename to configs/f2b.conf.in index 8f9df04..8047afe 100644 --- a/configs/f2b.conf.sample +++ b/configs/f2b.conf.in @@ -1,5 +1,5 @@ [main] -includes = /etc/f2b/conf-enabled +includes = ${CMAKE_INSTALL_FULL_SYSCONFDIR}/f2b/conf-enabled pidfile = /var/run/f2b.pid logdest = syslog loglevel = info @@ -17,9 +17,8 @@ incr_bantime = 0.0 incr_findtime = 0.0 maxretry = 5 source = files:/var/log/messages -; filter = preg:/etc/f2b/filters/$someservice.preg backend = exec-ipset:banned [jail:ssh] source = files:/var/log/auth.log -filter = preg:/etc/f2b/filters/ssh.preg +filter = preg:${CMAKE_INSTALL_FULL_DATAROOTDIR}/f2b/filters/ssh.preg diff --git a/debian/changelog b/debian/changelog index 3babdeb..ee645e1 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +f2b (0.3-1) unstable; urgency=medium + + * new version + + -- Alex 'AdUser' Z Tue, 13 Sep 2016 16:55:43 +1000 + f2b (0.2-1) unstable; urgency=low * Initial release diff --git a/filters/ssh.preg b/filters/ssh.preg index 4f2bbc2..940dafb 100644 --- a/filters/ssh.preg +++ b/filters/ssh.preg @@ -5,7 +5,8 @@ refused connect from [[:print:]]+ \(\) Received disconnect from : [0-9]*: [[:print:]]+: Auth fail Did not receive identification string from Invalid user [[:print:]]+ from -Connection closed by \[preauth\] +Connection closed by ( port [0-9]+)? \[preauth\] +Postponed keyboard-interactive for invalid user [[:print:]]+ from port [0-9]+ User [[:print:]]+ from not allowed because not listed in AllowUsers User [[:print:]]+ from not allowed because listed in DenyUsers User [[:print:]]+ from not allowed because not in any group diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2c00c86..56bc59d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,7 +8,6 @@ if (WITH_CSOCKET) endif () add_executable("f2b" ${SOURCES}) -target_link_libraries(f2b "dl") install(TARGETS "f2b" RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}) @@ -19,12 +18,18 @@ if (WITH_CLIENT) endif () set(SOURCES "strlcpy.c" "backend-test.c" "log.c" "config.c" "backend.c") -add_executable("backend-test" ${SOURCES}) -target_link_libraries("backend-test" "dl") +add_executable("f2b-backend-test" ${SOURCES}) set(SOURCES "strlcpy.c" "filter-test.c" "log.c" "config.c" "filter.c") -add_executable("filter-test" ${SOURCES}) -target_link_libraries("filter-test" "dl") +add_executable("f2b-filter-test" ${SOURCES}) + +if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") + target_link_libraries(f2b "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}) add_subdirectory("backends") add_subdirectory("filters") diff --git a/src/backends/CMakeLists.txt b/src/backends/CMakeLists.txt index 87597d2..89ee271 100644 --- a/src/backends/CMakeLists.txt +++ b/src/backends/CMakeLists.txt @@ -1,12 +1,12 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) set(BACKENDS "") -add_library("f2b_backend_exec" MODULE "exec.c") +add_library("f2b_backend_exec" MODULE "exec.c" "../strlcpy.c") list(APPEND BACKENDS "f2b_backend_exec") find_library(REDIS_FOUND "pcre") if (WITH_REDIS AND REDIS_FOUND) - add_library("f2b_backend_redis" MODULE "redis.c") + add_library("f2b_backend_redis" MODULE "redis.c" "../strlcpy.c") target_link_libraries("f2b_backend_redis" "hiredis") list(APPEND BACKENDS "f2b_backend_redis") endif () diff --git a/src/backends/exec.c b/src/backends/exec.c index 1f221ef..a0dbf3e 100644 --- a/src/backends/exec.c +++ b/src/backends/exec.c @@ -13,6 +13,8 @@ #include #include +#include "../strlcpy.h" + #include "backend.h" #include "shared.c" @@ -157,7 +159,7 @@ create(const char *id) { if ((cfg = calloc(1, sizeof(cfg_t))) == NULL) return NULL; - snprintf(cfg->name, sizeof(cfg->name), "%s", id); + strlcpy(cfg->name, id, sizeof(cfg->name)); return cfg; } @@ -268,6 +270,7 @@ check(cfg_t *cfg, const char *ip) { bool ping(cfg_t *cfg) { assert(cfg != NULL); + (void)(cfg); /* suppress warning about unused variable */ return true; } diff --git a/src/backends/redis.c b/src/backends/redis.c index ee79a27..0bc06bd 100644 --- a/src/backends/redis.c +++ b/src/backends/redis.c @@ -17,6 +17,8 @@ #include +#include "../strlcpy.h" + #include "backend.h" #include "shared.c" @@ -98,8 +100,9 @@ create(const char *id) { if ((cfg = calloc(1, sizeof(cfg_t))) == NULL) return NULL; - snprintf(cfg->name, sizeof(cfg->name), "%s", id); - snprintf(cfg->hash, sizeof(cfg->hash), "f2b-banned-%s", id); + strlcpy(cfg->name, id, sizeof(cfg->name)); + strlcpy(cfg->hash, "f2b-banned-", sizeof(cfg->hash)); + strlcat(cfg->hash, id, sizeof(cfg->hash)); return cfg; } @@ -119,7 +122,7 @@ config(cfg_t *cfg, const char *key, const char *value) { return true; } if (strcmp(key, "host") == 0) { - snprintf(cfg->host, sizeof(cfg->host), "%s", value); + strlcpy(cfg->host, value, sizeof(cfg->host)); return true; } if (strcmp(key, "port") == 0) { @@ -131,7 +134,7 @@ config(cfg_t *cfg, const char *key, const char *value) { return true; } if (strcmp(key, "password") == 0) { - snprintf(cfg->password, sizeof(cfg->password), "%s", value); + strlcpy(cfg->password, value, sizeof(cfg->password)); return true; } @@ -239,7 +242,7 @@ ping(cfg_t *cfg) { if (reply) { bool result = true; if (reply->type == REDIS_REPLY_ERROR) { - snprintf(cfg->error, sizeof(cfg->error), "%s", reply->str); + strlcpy(cfg->error, reply->str, sizeof(cfg->error)); result = false; } freeReplyObject(reply); diff --git a/src/cmsg.h b/src/cmsg.h index a646376..aa8812a 100644 --- a/src/cmsg.h +++ b/src/cmsg.h @@ -3,7 +3,7 @@ #include -#define DATA_LEN_MAX 496 /* 512 - 16 bytes of header */ +#define DATA_LEN_MAX 1476 /* 1500 - (16 bytes of cmsg header + 8 bytes of udp) */ #define DATA_ARGS_MAX 6 /* number of args in data */ #define F2B_PROTO_VER 1 @@ -21,6 +21,8 @@ enum f2b_cmsg_type { CMD_JAIL_IP_SHOW, CMD_JAIL_IP_BAN, CMD_JAIL_IP_RELEASE, + CMD_JAIL_REGEX_STATS, + CMD_JAIL_REGEX_ADD, CMD_MAX_NUMBER, }; diff --git a/src/commands.c b/src/commands.c index 9442380..bb4ac5c 100644 --- a/src/commands.c +++ b/src/commands.c @@ -12,7 +12,7 @@ struct f2b_cmd_t { const char *help; const char *tokens[CMD_TOKENS_MAX]; char *data; -} commands[] = { +} commands[CMD_MAX_NUMBER] = { [CMD_NONE] = { .tokens = { NULL }, .help = "Unspecified command" @@ -65,6 +65,14 @@ struct f2b_cmd_t { .tokens = { "jail", "", "release", "", NULL }, .help = "Forcefully release some ip in given jail", }, + [CMD_JAIL_REGEX_STATS] = { + .tokens = { "jail", "", "regex", "stats", NULL }, + .help = "Show matches stats for jail regexps", + }, + [CMD_JAIL_REGEX_ADD] = { + .tokens = { "jail", "", "regex", "add", "", NULL }, + .help = "Add new regexp to jail", + }, }; void @@ -158,6 +166,23 @@ f2b_cmd_parse(const char *src, char *buf, size_t buflen) { strlcat(buf, "\n", buflen); return CMD_JAIL_IP_RELEASE; } + if (tokenc == 4 && strcmp(tokens[2], "regex") == 0 && strcmp(tokens[3], "stats") == 0) { + return CMD_JAIL_REGEX_STATS; + } + if (tokenc >= 5 && strcmp(tokens[2], "regex") == 0 && strcmp(tokens[3], "add") == 0) { + /* TODO: rewrite, this version is very error-prone */ + char *regex = strstr(src, "add"); + regex += strlen("add"); + while (isblank(*regex)) + regex++; + if (*regex == '\0') { + /* empty regex */ + return CMD_NONE; + } + strlcat(buf, regex, buflen); + strlcat(buf, "\n", buflen); + return CMD_JAIL_REGEX_ADD; + } } return CMD_NONE; diff --git a/src/common.h b/src/common.h index 1ca8eff..7a3edfc 100644 --- a/src/common.h +++ b/src/common.h @@ -7,11 +7,11 @@ #ifndef F2B_COMMON_H_ #define F2B_COMMON_H_ -#include #include #include #include #include +#include #include #include #include diff --git a/src/daemon.c b/src/daemon.c index d7ef281..c512a46 100644 --- a/src/daemon.c +++ b/src/daemon.c @@ -85,7 +85,6 @@ f2b_cmsg_process(const f2b_cmsg_t *msg, char *res, size_t ressize) { void f2b_cmsg_process(const f2b_cmsg_t *msg, char *res, size_t ressize) { const char *args[DATA_ARGS_MAX]; - const char *fmt; f2b_jail_t *jail = NULL; f2b_ipaddr_t *addr = NULL; char line[LINE_MAX]; @@ -94,10 +93,13 @@ f2b_cmsg_process(const f2b_cmsg_t *msg, char *res, size_t ressize) { assert(res != NULL); assert(msg->type < CMD_MAX_NUMBER); + if (msg->type == CMD_NONE) + return; + memset(args, 0x0, sizeof(args)); f2b_cmsg_extract_args(msg, args); - if (msg->type >= CMD_JAIL_STATUS && msg->type <= CMD_JAIL_IP_RELEASE) { + if (msg->type >= CMD_JAIL_STATUS && msg->type <= CMD_MAX_NUMBER) { if (args[0] == NULL) { strlcpy(res, "can't find jail: no args\n", ressize); return; @@ -113,10 +115,6 @@ f2b_cmsg_process(const f2b_cmsg_t *msg, char *res, size_t ressize) { strlcpy(res, "can't find ip: no args", ressize); return; } - if ((addr = f2b_addrlist_lookup(jail->ipaddrs, args[1])) == NULL) { - snprintf(res, ressize, "can't find ip '%s' in jail '%s'\n", args[1], args[0]); - return; - } } if (msg->type == CMD_PING) { @@ -139,38 +137,47 @@ f2b_cmsg_process(const f2b_cmsg_t *msg, char *res, size_t ressize) { strlcat(res, line, ressize); } } else if (msg->type == CMD_JAIL_STATUS) { - fmt = "name: %s\n" - "enabled: %s\n" - "maxretry: %d\n" - "times:\n" - " bantime: %d\n" - " findtime: %d\n" - " expiretime: %d\n" - "incr:\n" - " bantime: %.1f\n" - " findtime: %.1f\n" - "stats:\n" - " banned: %d\n" - " matched: %d\n"; - snprintf(res, ressize, fmt, jail->name, jail->enabled ? "yes" : "no", jail->maxretry, - jail->bantime, jail->findtime, jail->expiretime, - jail->incr_bantime, jail->incr_findtime, - jail->bancount, jail->matchcount); + f2b_jail_get_status(jail, res, ressize); } else if (msg->type == CMD_JAIL_IP_SHOW) { - fmt = "ipaddr: %s\n" - "banned: %s\n" - "bancount: %d\n" - "lastseen: %d\n" - "banned_at: %d\n" - "release_at: %d\n"; - snprintf(res, ressize, fmt, addr->text, addr->banned ? "yes" : "no", - addr->bancount, addr->lastseen, addr->banned_at, addr->release_at); + if ((addr = f2b_addrlist_lookup(jail->ipaddrs, args[1])) != NULL) { + f2b_ipaddr_status(addr, res, ressize); + } else { + snprintf(res, ressize, "can't find ip '%s' in jail '%s'\n", args[1], args[0]); + } } else if (msg->type == CMD_JAIL_IP_BAN) { + if ((addr = f2b_addrlist_lookup(jail->ipaddrs, args[1])) == NULL) { + /* TODO: this is copy-paste from f2b_jail_process */ + time_t now = time(NULL); + addr = f2b_ipaddr_create(args[1], jail->maxretry); + if (!addr) { + snprintf(res, ressize, "cat't parse ip address: %s", args[1]); + return; + } + addr->lastseen = now; + f2b_matches_append(&addr->matches, now); + jail->ipaddrs = f2b_addrlist_append(jail->ipaddrs, addr); + } f2b_jail_ban(jail, addr); strlcpy(res, "ok", ressize); } else if (msg->type == CMD_JAIL_IP_RELEASE) { - f2b_jail_unban(jail, addr); + if ((addr = f2b_addrlist_lookup(jail->ipaddrs, args[1])) != NULL) { + f2b_jail_unban(jail, addr); + } else { + snprintf(res, ressize, "can't find ip '%s' in jail '%s'\n", args[1], args[0]); + } strlcpy(res, "ok", ressize); + } else if (msg->type == CMD_JAIL_REGEX_STATS) { + f2b_filter_stats(jail->filter, res, ressize); + } else if (msg->type == CMD_JAIL_REGEX_ADD) { + if (args[1] == NULL) { + strlcpy(res, "can't find regex: no args", ressize); + return; + } + if (f2b_filter_append(jail->filter, args[1])) { + strlcpy(res, "ok", ressize); + } else { + strlcpy(res, f2b_filter_error(jail->filter), ressize); + } } else { strlcpy(res, "error: unsupported command type", ressize); } @@ -264,9 +271,14 @@ jails_start(f2b_config_t *config) { void jails_stop(f2b_jail_t *jails) { - for (f2b_jail_t *jail = jails; jail != NULL; jail = jail->next) + f2b_jail_t *jail = jails; + f2b_jail_t *next = NULL; + for (; jail != NULL; ) { + next = jail->next; f2b_jail_stop(jail); - jails = NULL; + free(jail); + jail = next; + } } int main(int argc, char *argv[]) { @@ -380,8 +392,10 @@ int main(int argc, char *argv[]) { } if (state == reconfig) { state = run; + memset(&config, 0x0, sizeof(config)); if (f2b_config_load(&config, opts.config_path, true)) { jails_stop(jails); + jails = NULL; if (config.defaults) f2b_jail_set_defaults(config.defaults); jails_start(&config); @@ -395,6 +409,7 @@ int main(int argc, char *argv[]) { f2b_csocket_destroy(opts.csock, opts.csocket_path); jails_stop(jails); + jails = NULL; return EXIT_SUCCESS; } diff --git a/src/filter.c b/src/filter.c index 91efa43..a8b836b 100644 --- a/src/filter.c +++ b/src/filter.c @@ -96,6 +96,8 @@ f2b_filter_create(f2b_config_section_t *config, const char *file) { goto cleanup; if ((*(void **) (&filter->ready) = dlsym(filter->h, "ready")) == NULL) goto cleanup; + if ((*(void **) (&filter->stats) = dlsym(filter->h, "stats")) == NULL) + goto cleanup; if ((*(void **) (&filter->match) = dlsym(filter->h, "match")) == NULL) goto cleanup; if ((*(void **) (&filter->destroy) = dlsym(filter->h, "destroy")) == NULL) @@ -147,6 +149,14 @@ f2b_filter_destroy(f2b_filter_t *filter) { free(filter); } +bool +f2b_filter_append(f2b_filter_t *filter, const char *pattern) { + assert(filter != NULL); + assert(pattern != NULL); + + return filter->append(filter->cfg, pattern); +} + bool f2b_filter_match(f2b_filter_t *filter, const char *line, char *buf, size_t buf_size) { assert(filter != NULL); @@ -161,3 +171,22 @@ f2b_filter_error(f2b_filter_t *filter) { assert(filter != NULL); return filter->error(filter->cfg); } + +void +f2b_filter_stats(f2b_filter_t *filter, char *res, size_t ressize) { + assert(filter != NULL); + assert(res != NULL); + bool reset = true; + char *pattern; + int matches; + char buf[256]; + const char *fmt = + "- pattern: %s\n" + " matches: %d\n"; + res[0] = '\0'; + while (filter->stats(filter->cfg, &matches, &pattern, reset)) { + snprintf(buf, sizeof(buf), fmt, pattern, matches); + strlcat(res, buf, ressize); + reset = false; + } +} diff --git a/src/filter.h b/src/filter.h index fa4ddb4..5fa0671 100644 --- a/src/filter.h +++ b/src/filter.h @@ -18,6 +18,7 @@ typedef struct f2b_filter_t { bool (*append) (void *cfg, const char *pattern); char *(*error) (void *cfg); bool (*ready) (void *cfg); + bool (*stats) (void *cfg, int *matches, char **pattern, bool reset); bool (*match) (void *cfg, const char *line, char *buf, size_t buf_size); void (*destroy) (void *cfg); } f2b_filter_t; @@ -25,7 +26,9 @@ typedef struct 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); #endif /* F2B_FILTER_H_ */ diff --git a/src/filters/CMakeLists.txt b/src/filters/CMakeLists.txt index 6401f11..6e66da8 100644 --- a/src/filters/CMakeLists.txt +++ b/src/filters/CMakeLists.txt @@ -1,12 +1,12 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) set(FILTERS "") -add_library("f2b_filter_preg" MODULE "preg.c") +add_library("f2b_filter_preg" MODULE "preg.c" "../strlcpy.c") list(APPEND FILTERS "f2b_filter_preg") find_library(PCRE_FOUND "pcre") if (WITH_PCRE AND PCRE_FOUND) - add_library("f2b_filter_pcre" MODULE "pcre.c") + add_library("f2b_filter_pcre" MODULE "pcre.c" "../strlcpy.c") target_link_libraries("f2b_filter_pcre" "pcre") list(APPEND FILTERS "f2b_filter_pcre") endif () diff --git a/src/filters/filter.h b/src/filters/filter.h index 4504a8c..6fe16c7 100644 --- a/src/filters/filter.h +++ b/src/filters/filter.h @@ -4,8 +4,19 @@ * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ +#if defined(__linux__) +#include +#endif +#include #include +#include +#include +#include +#include "../strlcpy.h" + +#define ID_MAX 32 +#define PATTERN_MAX 256 #define HOST_TOKEN "" typedef struct _config cfg_t; @@ -15,5 +26,6 @@ extern const char *error(cfg_t *c); extern bool config(cfg_t *c, const char *key, const char *value); extern bool append(cfg_t *c, const char *pattern); extern bool ready(cfg_t *c); -extern bool match(cfg_t *c, const char *line, char *buf, size_t buf_size); +extern bool stats(cfg_t *c, int *matches, char **pattern, bool reset); +extern bool match(cfg_t *c, const char *line, char *buf, size_t bufsize); extern void destroy(cfg_t *c); diff --git a/src/filters/pcre.c b/src/filters/pcre.c index 462faf6..4239e5e 100644 --- a/src/filters/pcre.c +++ b/src/filters/pcre.c @@ -4,12 +4,6 @@ * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ -#include -#include -#include -#include -#include - #include "filter.h" #include @@ -18,18 +12,20 @@ typedef struct f2b_regex_t { struct f2b_regex_t *next; + char pattern[PATTERN_MAX]; int matches; pcre *regex; pcre_extra *data; } f2b_regex_t; struct _config { - char id[32]; + char id[ID_MAX]; char error[256]; bool icase; bool study; bool usejit; f2b_regex_t *regexps; + f2b_regex_t *statp; }; cfg_t * @@ -38,7 +34,7 @@ create(const char *id) { if ((cfg = calloc(1, sizeof(cfg_t))) == NULL) return NULL; - snprintf(cfg->id, sizeof(cfg->id), "%s", id); + strlcpy(cfg->id, id, sizeof(cfg->id)); return cfg; } @@ -89,8 +85,8 @@ append(cfg_t *cfg, const char *pattern) { memset(buf, 0x0, bufsize); memcpy(buf, pattern, token - pattern); - strcat(buf, HOST_REGEX); - strcat(buf, token + strlen(HOST_TOKEN)); + strlcat(buf, HOST_REGEX, bufsize); + strlcat(buf, token + strlen(HOST_TOKEN), bufsize); if ((regex = calloc(1, sizeof(f2b_regex_t))) == NULL) return false; @@ -115,6 +111,7 @@ append(cfg_t *cfg, const char *pattern) { regex->next = cfg->regexps; cfg->regexps = regex; + strlcpy(regex->pattern, pattern, sizeof(regex->pattern)); return true; } @@ -126,6 +123,23 @@ ready(cfg_t *cfg) { return false; } +bool +stats(cfg_t *cfg, int *matches, char **pattern, bool reset) { + assert(cfg != NULL); + + if (reset) + cfg->statp = cfg->regexps; + + if (cfg->statp) { + *matches = cfg->statp->matches; + *pattern = cfg->statp->pattern; + cfg->statp = cfg->statp->next; + return true; + } + + return false; +} + const char * error(cfg_t *cfg) { assert(cfg != NULL); diff --git a/src/filters/preg.c b/src/filters/preg.c index b6e083a..d3f3d0a 100644 --- a/src/filters/preg.c +++ b/src/filters/preg.c @@ -4,12 +4,6 @@ * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ -#include -#include -#include -#include -#include - #include "filter.h" #include @@ -19,15 +13,17 @@ typedef struct f2b_regex_t { struct f2b_regex_t *next; + char pattern[PATTERN_MAX]; int matches; regex_t regex; } f2b_regex_t; struct _config { - char id[32]; + char id[ID_MAX]; char error[256]; bool icase; f2b_regex_t *regexps; + f2b_regex_t *statp; }; cfg_t * @@ -36,7 +32,7 @@ create(const char *id) { if ((cfg = calloc(1, sizeof(cfg_t))) == NULL) return NULL; - snprintf(cfg->id, sizeof(cfg->id), "%s", id); + strlcpy(cfg->id, id, sizeof(cfg->id)); return cfg; } @@ -77,8 +73,8 @@ append(cfg_t *cfg, const char *pattern) { memset(buf, 0x0, bufsize); memcpy(buf, pattern, token - pattern); - strcat(buf, HOST_REGEX); - strcat(buf, token + strlen(HOST_TOKEN)); + strlcat(buf, HOST_REGEX, bufsize); + strlcat(buf, token + strlen(HOST_TOKEN), bufsize); if ((regex = calloc(1, sizeof(f2b_regex_t))) == NULL) return false; @@ -86,6 +82,7 @@ append(cfg_t *cfg, const char *pattern) { if (regcomp(®ex->regex, buf, flags) == 0) { regex->next = cfg->regexps; cfg->regexps = regex; + strlcpy(regex->pattern, pattern, sizeof(regex->pattern)); return true; } @@ -101,6 +98,23 @@ ready(cfg_t *cfg) { return false; } +bool +stats(cfg_t *cfg, int *matches, char **pattern, bool reset) { + assert(cfg != NULL); + + if (reset) + cfg->statp = cfg->regexps; + + if (cfg->statp) { + *matches = cfg->statp->matches; + *pattern = cfg->statp->pattern; + cfg->statp = cfg->statp->next; + return true; + } + + return false; +} + const char * error(cfg_t *cfg) { assert(cfg != NULL); @@ -127,7 +141,7 @@ match(cfg_t *cfg, const char *line, char *buf, size_t buf_size) { assert(buf_size > match_len); memcpy(buf, &line[match[1].rm_so], match_len); buf[match_len] = '\0'; - buf[buf_size] = '\0'; + buf[buf_size - 1] = '\0'; return true; } diff --git a/src/ipaddr.c b/src/ipaddr.c index 5d41a30..c46231d 100644 --- a/src/ipaddr.c +++ b/src/ipaddr.c @@ -44,6 +44,21 @@ f2b_ipaddr_destroy(f2b_ipaddr_t *ipaddr) { free(ipaddr); } +void +f2b_ipaddr_status(f2b_ipaddr_t *addr, char *res, size_t ressize) { + assert(addr != NULL); + assert(res != NULL); + const char *fmt = + "ipaddr: %s\n" + "banned: %s\n" + "bancount: %d\n" + "lastseen: %d\n" + "banned_at: %d\n" + "release_at: %d\n"; + snprintf(res, ressize, fmt, addr->text, addr->banned ? "yes" : "no", + addr->bancount, addr->lastseen, addr->banned_at, addr->release_at); +} + f2b_ipaddr_t * f2b_addrlist_append(f2b_ipaddr_t *list, f2b_ipaddr_t *ipaddr) { assert(ipaddr != NULL); diff --git a/src/ipaddr.h b/src/ipaddr.h index cc654b7..7712ba9 100644 --- a/src/ipaddr.h +++ b/src/ipaddr.h @@ -7,6 +7,8 @@ #ifndef F2B_IPADDR_H_ #define F2B_IPADDR_H_ +#include +#include #include #include "matches.h" @@ -31,6 +33,7 @@ typedef struct f2b_ipaddr_t { f2b_ipaddr_t * f2b_ipaddr_create (const char *addr, size_t max_matches); void f2b_ipaddr_destroy(f2b_ipaddr_t *ipaddr); +void f2b_ipaddr_status (f2b_ipaddr_t *ipaddr, char *res, size_t ressize); f2b_ipaddr_t * f2b_addrlist_append(f2b_ipaddr_t *list, f2b_ipaddr_t *ipaddr); f2b_ipaddr_t * f2b_addrlist_lookup(f2b_ipaddr_t *list, const char *addr); diff --git a/src/jail.c b/src/jail.c index 125b39f..5f0b841 100644 --- a/src/jail.c +++ b/src/jail.c @@ -77,7 +77,7 @@ f2b_jail_apply_config(f2b_jail_t *jail, f2b_config_section_t *section) { } if (strcmp(param->name, "maxretry") == 0) { jail->maxretry = atoi(param->value); - if (jail->maxretry <= 0) + if (jail->maxretry == 0) jail->maxretry = DEFAULT_MAXRETRY; continue; } @@ -412,3 +412,27 @@ f2b_jail_stop(f2b_jail_t *jail) { return errors; } + +void +f2b_jail_get_status(f2b_jail_t *jail, char *res, size_t ressize) { + assert(jail != NULL); + assert(res != NULL); + const char *fmt = + "name: %s\n" + "enabled: %s\n" + "maxretry: %d\n" + "times:\n" + " bantime: %d\n" + " findtime: %d\n" + " expiretime: %d\n" + "incr:\n" + " bantime: %.1f\n" + " findtime: %.1f\n" + "stats:\n" + " banned: %d\n" + " matched: %d\n"; + snprintf(res, ressize, fmt, jail->name, jail->enabled ? "yes" : "no", jail->maxretry, + jail->bantime, jail->findtime, jail->expiretime, + jail->incr_bantime, jail->incr_findtime, + jail->bancount, jail->matchcount); +} diff --git a/src/jail.h b/src/jail.h index e8f5339..426fbbd 100644 --- a/src/jail.h +++ b/src/jail.h @@ -52,4 +52,6 @@ bool f2b_jail_unban (f2b_jail_t *jail, f2b_ipaddr_t *addr); bool f2b_jail_init (f2b_jail_t *jail, f2b_config_t *config); size_t f2b_jail_process (f2b_jail_t *jail); bool f2b_jail_stop (f2b_jail_t *jail); + +void f2b_jail_get_status(f2b_jail_t *jail, char *res, size_t ressize); #endif /* F2B_JAIL_H_ */ diff --git a/src/log.c b/src/log.c index 13bbfe3..7e7411a 100644 --- a/src/log.c +++ b/src/log.c @@ -39,6 +39,8 @@ get_facility(log_msgtype_t l) { void f2b_log_msg(log_msgtype_t l, const char *fmt, ...) { va_list args; char msg[LOGLINE_MAX] = ""; + char when[64] = ""; + time_t now = time(NULL); if (l < minlevel) return; @@ -55,7 +57,8 @@ void f2b_log_msg(log_msgtype_t l, const char *fmt, ...) { case log_stderr: logfile = stderr; case log_file: - fprintf(logfile, "[%s] %s\n", loglevels[l], msg); + strftime(when, sizeof(when), "%F %H:%M:%S", localtime(&now)); + fprintf(logfile, "%s [%s] %s\n", when, loglevels[l], msg); break; } @@ -83,6 +86,7 @@ void f2b_log_to_file(const char *path) { if (path == NULL || *path == '\0') return; if ((new = fopen(path, "a")) != NULL) { + setvbuf(new, NULL , _IONBF, 0); if (logfile && logfile != stderr) fclose(logfile); dest = log_file; diff --git a/src/logfile.c b/src/logfile.c index 350ad99..bd2fd2f 100644 --- a/src/logfile.c +++ b/src/logfile.c @@ -41,8 +41,12 @@ f2b_logfile_open(f2b_logfile_t *file, const char *path) { void f2b_logfile_close(f2b_logfile_t *file) { assert(file != NULL); - fclose(file->fd); + + if (file->fd) + fclose(file->fd); + file->opened = false; + file->fd = NULL; } bool @@ -51,6 +55,9 @@ f2b_logfile_rotated(const f2b_logfile_t *file) { assert(file != NULL); + if (!file->opened) + return true; + if (stat(file->path, &st) != 0) return true; @@ -64,6 +71,12 @@ f2b_logfile_rotated(const f2b_logfile_t *file) { 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; diff --git a/t/CMakeLists.txt b/t/CMakeLists.txt index bc257e0..6fdee34 100644 --- a/t/CMakeLists.txt +++ b/t/CMakeLists.txt @@ -14,11 +14,11 @@ add_test("tests/f2b_matches_*" "t_matches") add_test("tests/f2b_ipaddr_*" "t_ipaddr") add_test("tests/f2b_config_param*" "t_config_param") -add_executable("t_filter_preg" "t_filters.c" "${SRC_DIR}/filters/preg.c") +add_executable("t_filter_preg" "t_filters.c" "${SRC_DIR}/filters/preg.c" "${SRC_DIR}/strlcpy.c") add_test("tests/filter/preg" "t_filter_preg") if (WITH_PCRE) add_test("tests/filter/pcre" "t_filter_pcre") -add_executable("t_filter_pcre" "t_filters.c" "${SRC_DIR}/filters/pcre.c") +add_executable("t_filter_pcre" "t_filters.c" "${SRC_DIR}/filters/pcre.c" "${SRC_DIR}/strlcpy.c") target_link_libraries("t_filter_pcre" "pcre") endif ()