Browse Source

Merge branch 'master' into source-files

master
Alex 'AdUser' Z 8 years ago
parent
commit
990a75a2d0
  1. 25
      CMakeLists.txt
  2. 42
      ChangeLog
  3. 6
      configs/conf-available/10-backend-exec-ipfw.conf
  4. 2
      configs/conf-available/10-backend-exec-ipset.conf
  5. 2
      configs/conf-available/10-backend-redis.conf
  6. 2
      configs/conf-available/15-filter-pcre.conf
  7. 2
      configs/conf-available/15-filter-preg.conf
  8. 5
      configs/f2b.conf.in
  9. 6
      debian/changelog
  10. 3
      filters/ssh.preg
  11. 15
      src/CMakeLists.txt
  12. 4
      src/backends/CMakeLists.txt
  13. 5
      src/backends/exec.c
  14. 13
      src/backends/redis.c
  15. 4
      src/cmsg.h
  16. 27
      src/commands.c
  17. 2
      src/common.h
  18. 83
      src/daemon.c
  19. 29
      src/filter.c
  20. 3
      src/filter.h
  21. 4
      src/filters/CMakeLists.txt
  22. 14
      src/filters/filter.h
  23. 34
      src/filters/pcre.c
  24. 36
      src/filters/preg.c
  25. 15
      src/ipaddr.c
  26. 3
      src/ipaddr.h
  27. 26
      src/jail.c
  28. 2
      src/jail.h
  29. 6
      src/log.c
  30. 15
      src/logfile.c
  31. 4
      t/CMakeLists.txt

25
CMakeLists.txt

@ -1,5 +1,5 @@
set(CNAME "f2b") set(CNAME "f2b")
set(VERSION 0.2) set(VERSION 0.3)
project(${CNAME} C) project(${CNAME} C)
cmake_minimum_required(VERSION 2.6) cmake_minimum_required(VERSION 2.6)
@ -8,9 +8,9 @@ include(CTest)
option(WITH_CLIENT "Simple client for configuring daemon" ON) option(WITH_CLIENT "Simple client for configuring daemon" ON)
option(WITH_CSOCKET "Unix control socket support for 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_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) if (NOT DEFINED CMAKE_INSTALL_PREFIX)
set(CMAKE_INSTALL_PREFIX "/usr") set(CMAKE_INSTALL_PREFIX "/usr")
@ -22,7 +22,12 @@ endif ()
include(GNUInstallDirs) include(GNUInstallDirs)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -pedantic -std=c99") 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) if (WITH_HARDENING)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wformat -Wformat-security -Werror=format-security" ) 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") set_property(DIRECTORY "t" PROPERTY COMPILE_FLAGS "-g;-ggdb;-Wall;-Wextra;-pedantic;-O0")
install(DIRECTORY "filters" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/f2b") install(DIRECTORY "filters" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/f2b")
install(DIRECTORY "configs/" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/f2b") file(GLOB_RECURSE CONFIGS "*.conf.in")
install(FILES "configs/f2b.conf.sample" DESTINATION "${CMAKE_INSTALL_SYSCONFDIR}/f2b/" RENAME "f2b.conf") 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 add_custom_target("dist" COMMAND
"git" "archive" "--format=tar.gz" "git" "archive" "--format=tar.gz"

42
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 <jail> regex stats" command
* "jail <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

6
configs/conf-available/10-backend-exec-ipfw.conf

@ -0,0 +1,6 @@
[backend:exec-ipfw]
load = libf2b_backend_exec.so
ban = /sbin/ipfw table <ID> add <IP>
unban = /sbin/ipfw table <ID> delete <IP>
timeout = 2
shared = yes

2
configs/conf-available/10-backend-exec-ipset.conf

@ -8,4 +8,4 @@ ban = /sbin/ipset -! add <ID> <IP>
check = /sbin/ipset -! test <ID> <IP> check = /sbin/ipset -! test <ID> <IP>
unban = /sbin/ipset -! del <ID> <IP> unban = /sbin/ipset -! del <ID> <IP>
timeout = 2 timeout = 2
shared = no shared = yes

2
configs/conf-available/10-backend-redis.conf

@ -1,6 +1,6 @@
[backend:redis] [backend:redis]
load = libf2b_backend_redis.so load = libf2b_backend_redis.so
shared = no shared = yes
timeout = 2 timeout = 2
host = 127.0.0.1 host = 127.0.0.1
port = 6379 port = 6379

2
configs/conf-available/15-filter-pcre.conf

@ -1,5 +1,5 @@
[filter:pcre] [filter:pcre]
load = libf2b_filter_pcre.so load = libf2b_filter_pcre.so
icase = no icase = yes
study = yes study = yes
usejit = no usejit = no

2
configs/conf-available/15-filter-preg.conf

@ -1,3 +1,3 @@
[filter:preg] [filter:preg]
load = libf2b_filter_preg.so load = libf2b_filter_preg.so
icase = no icase = yes

5
configs/f2b.conf.sample → configs/f2b.conf.in

@ -1,5 +1,5 @@
[main] [main]
includes = /etc/f2b/conf-enabled includes = ${CMAKE_INSTALL_FULL_SYSCONFDIR}/f2b/conf-enabled
pidfile = /var/run/f2b.pid pidfile = /var/run/f2b.pid
logdest = syslog logdest = syslog
loglevel = info loglevel = info
@ -17,9 +17,8 @@ incr_bantime = 0.0
incr_findtime = 0.0 incr_findtime = 0.0
maxretry = 5 maxretry = 5
source = files:/var/log/messages source = files:/var/log/messages
; filter = preg:/etc/f2b/filters/$someservice.preg
backend = exec-ipset:banned backend = exec-ipset:banned
[jail:ssh] [jail:ssh]
source = files:/var/log/auth.log source = files:/var/log/auth.log
filter = preg:/etc/f2b/filters/ssh.preg filter = preg:${CMAKE_INSTALL_FULL_DATAROOTDIR}/f2b/filters/ssh.preg

6
debian/changelog vendored

@ -1,3 +1,9 @@
f2b (0.3-1) unstable; urgency=medium
* new version
-- Alex 'AdUser' Z <ad_user@runbox.com> Tue, 13 Sep 2016 16:55:43 +1000
f2b (0.2-1) unstable; urgency=low f2b (0.2-1) unstable; urgency=low
* Initial release * Initial release

3
filters/ssh.preg

@ -5,7 +5,8 @@ refused connect from [[:print:]]+ \(<HOST>\)
Received disconnect from <HOST>: [0-9]*: [[:print:]]+: Auth fail Received disconnect from <HOST>: [0-9]*: [[:print:]]+: Auth fail
Did not receive identification string from <HOST> Did not receive identification string from <HOST>
Invalid user [[:print:]]+ from <HOST> Invalid user [[:print:]]+ from <HOST>
Connection closed by <HOST> \[preauth\] Connection closed by <HOST>( port [0-9]+)? \[preauth\]
Postponed keyboard-interactive for invalid user [[:print:]]+ from <HOST> port [0-9]+
User [[:print:]]+ from <HOST> not allowed because not listed in AllowUsers User [[:print:]]+ from <HOST> not allowed because not listed in AllowUsers
User [[:print:]]+ from <HOST> not allowed because listed in DenyUsers User [[:print:]]+ from <HOST> not allowed because listed in DenyUsers
User [[:print:]]+ from <HOST> not allowed because not in any group User [[:print:]]+ from <HOST> not allowed because not in any group

15
src/CMakeLists.txt

@ -8,7 +8,6 @@ if (WITH_CSOCKET)
endif () endif ()
add_executable("f2b" ${SOURCES}) add_executable("f2b" ${SOURCES})
target_link_libraries(f2b "dl")
install(TARGETS "f2b" RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}) install(TARGETS "f2b" RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR})
@ -19,12 +18,18 @@ if (WITH_CLIENT)
endif () endif ()
set(SOURCES "strlcpy.c" "backend-test.c" "log.c" "config.c" "backend.c") set(SOURCES "strlcpy.c" "backend-test.c" "log.c" "config.c" "backend.c")
add_executable("backend-test" ${SOURCES}) add_executable("f2b-backend-test" ${SOURCES})
target_link_libraries("backend-test" "dl")
set(SOURCES "strlcpy.c" "filter-test.c" "log.c" "config.c" "filter.c") set(SOURCES "strlcpy.c" "filter-test.c" "log.c" "config.c" "filter.c")
add_executable("filter-test" ${SOURCES}) add_executable("f2b-filter-test" ${SOURCES})
target_link_libraries("filter-test" "dl")
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("backends")
add_subdirectory("filters") add_subdirectory("filters")

4
src/backends/CMakeLists.txt

@ -1,12 +1,12 @@
set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(BACKENDS "") 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") list(APPEND BACKENDS "f2b_backend_exec")
find_library(REDIS_FOUND "pcre") find_library(REDIS_FOUND "pcre")
if (WITH_REDIS AND REDIS_FOUND) 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") target_link_libraries("f2b_backend_redis" "hiredis")
list(APPEND BACKENDS "f2b_backend_redis") list(APPEND BACKENDS "f2b_backend_redis")
endif () endif ()

5
src/backends/exec.c

@ -13,6 +13,8 @@
#include <sys/wait.h> #include <sys/wait.h>
#include <unistd.h> #include <unistd.h>
#include "../strlcpy.h"
#include "backend.h" #include "backend.h"
#include "shared.c" #include "shared.c"
@ -157,7 +159,7 @@ create(const char *id) {
if ((cfg = calloc(1, sizeof(cfg_t))) == NULL) if ((cfg = calloc(1, sizeof(cfg_t))) == NULL)
return NULL; return NULL;
snprintf(cfg->name, sizeof(cfg->name), "%s", id); strlcpy(cfg->name, id, sizeof(cfg->name));
return cfg; return cfg;
} }
@ -268,6 +270,7 @@ check(cfg_t *cfg, const char *ip) {
bool bool
ping(cfg_t *cfg) { ping(cfg_t *cfg) {
assert(cfg != NULL); assert(cfg != NULL);
(void)(cfg); /* suppress warning about unused variable */
return true; return true;
} }

13
src/backends/redis.c

@ -17,6 +17,8 @@
#include <hiredis/hiredis.h> #include <hiredis/hiredis.h>
#include "../strlcpy.h"
#include "backend.h" #include "backend.h"
#include "shared.c" #include "shared.c"
@ -98,8 +100,9 @@ create(const char *id) {
if ((cfg = calloc(1, sizeof(cfg_t))) == NULL) if ((cfg = calloc(1, sizeof(cfg_t))) == NULL)
return NULL; return NULL;
snprintf(cfg->name, sizeof(cfg->name), "%s", id); strlcpy(cfg->name, id, sizeof(cfg->name));
snprintf(cfg->hash, sizeof(cfg->hash), "f2b-banned-%s", id); strlcpy(cfg->hash, "f2b-banned-", sizeof(cfg->hash));
strlcat(cfg->hash, id, sizeof(cfg->hash));
return cfg; return cfg;
} }
@ -119,7 +122,7 @@ config(cfg_t *cfg, const char *key, const char *value) {
return true; return true;
} }
if (strcmp(key, "host") == 0) { if (strcmp(key, "host") == 0) {
snprintf(cfg->host, sizeof(cfg->host), "%s", value); strlcpy(cfg->host, value, sizeof(cfg->host));
return true; return true;
} }
if (strcmp(key, "port") == 0) { if (strcmp(key, "port") == 0) {
@ -131,7 +134,7 @@ config(cfg_t *cfg, const char *key, const char *value) {
return true; return true;
} }
if (strcmp(key, "password") == 0) { if (strcmp(key, "password") == 0) {
snprintf(cfg->password, sizeof(cfg->password), "%s", value); strlcpy(cfg->password, value, sizeof(cfg->password));
return true; return true;
} }
@ -239,7 +242,7 @@ ping(cfg_t *cfg) {
if (reply) { if (reply) {
bool result = true; bool result = true;
if (reply->type == REDIS_REPLY_ERROR) { 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; result = false;
} }
freeReplyObject(reply); freeReplyObject(reply);

4
src/cmsg.h

@ -3,7 +3,7 @@
#include <sys/uio.h> #include <sys/uio.h>
#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 DATA_ARGS_MAX 6 /* number of args in data */
#define F2B_PROTO_VER 1 #define F2B_PROTO_VER 1
@ -21,6 +21,8 @@ enum f2b_cmsg_type {
CMD_JAIL_IP_SHOW, CMD_JAIL_IP_SHOW,
CMD_JAIL_IP_BAN, CMD_JAIL_IP_BAN,
CMD_JAIL_IP_RELEASE, CMD_JAIL_IP_RELEASE,
CMD_JAIL_REGEX_STATS,
CMD_JAIL_REGEX_ADD,
CMD_MAX_NUMBER, CMD_MAX_NUMBER,
}; };

27
src/commands.c

@ -12,7 +12,7 @@ struct f2b_cmd_t {
const char *help; const char *help;
const char *tokens[CMD_TOKENS_MAX]; const char *tokens[CMD_TOKENS_MAX];
char *data; char *data;
} commands[] = { } commands[CMD_MAX_NUMBER] = {
[CMD_NONE] = { [CMD_NONE] = {
.tokens = { NULL }, .tokens = { NULL },
.help = "Unspecified command" .help = "Unspecified command"
@ -65,6 +65,14 @@ struct f2b_cmd_t {
.tokens = { "jail", "<jailname>", "release", "<ip>", NULL }, .tokens = { "jail", "<jailname>", "release", "<ip>", NULL },
.help = "Forcefully release some ip in given jail", .help = "Forcefully release some ip in given jail",
}, },
[CMD_JAIL_REGEX_STATS] = {
.tokens = { "jail", "<jailname>", "regex", "stats", NULL },
.help = "Show matches stats for jail regexps",
},
[CMD_JAIL_REGEX_ADD] = {
.tokens = { "jail", "<jailname>", "regex", "add", "<regex>", NULL },
.help = "Add new regexp to jail",
},
}; };
void void
@ -158,6 +166,23 @@ f2b_cmd_parse(const char *src, char *buf, size_t buflen) {
strlcat(buf, "\n", buflen); strlcat(buf, "\n", buflen);
return CMD_JAIL_IP_RELEASE; 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; return CMD_NONE;

2
src/common.h

@ -7,11 +7,11 @@
#ifndef F2B_COMMON_H_ #ifndef F2B_COMMON_H_
#define F2B_COMMON_H_ #define F2B_COMMON_H_
#include <alloca.h>
#include <assert.h> #include <assert.h>
#include <ctype.h> #include <ctype.h>
#include <dlfcn.h> #include <dlfcn.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h>
#include <limits.h> #include <limits.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdbool.h> #include <stdbool.h>

83
src/daemon.c

@ -85,7 +85,6 @@ f2b_cmsg_process(const f2b_cmsg_t *msg, char *res, size_t ressize) {
void void
f2b_cmsg_process(const f2b_cmsg_t *msg, char *res, size_t ressize) { f2b_cmsg_process(const f2b_cmsg_t *msg, char *res, size_t ressize) {
const char *args[DATA_ARGS_MAX]; const char *args[DATA_ARGS_MAX];
const char *fmt;
f2b_jail_t *jail = NULL; f2b_jail_t *jail = NULL;
f2b_ipaddr_t *addr = NULL; f2b_ipaddr_t *addr = NULL;
char line[LINE_MAX]; 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(res != NULL);
assert(msg->type < CMD_MAX_NUMBER); assert(msg->type < CMD_MAX_NUMBER);
if (msg->type == CMD_NONE)
return;
memset(args, 0x0, sizeof(args)); memset(args, 0x0, sizeof(args));
f2b_cmsg_extract_args(msg, 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) { if (args[0] == NULL) {
strlcpy(res, "can't find jail: no args\n", ressize); strlcpy(res, "can't find jail: no args\n", ressize);
return; 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); strlcpy(res, "can't find ip: no args", ressize);
return; 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) { 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); strlcat(res, line, ressize);
} }
} else if (msg->type == CMD_JAIL_STATUS) { } else if (msg->type == CMD_JAIL_STATUS) {
fmt = "name: %s\n" f2b_jail_get_status(jail, res, ressize);
"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);
} else if (msg->type == CMD_JAIL_IP_SHOW) { } else if (msg->type == CMD_JAIL_IP_SHOW) {
fmt = "ipaddr: %s\n" if ((addr = f2b_addrlist_lookup(jail->ipaddrs, args[1])) != NULL) {
"banned: %s\n" f2b_ipaddr_status(addr, res, ressize);
"bancount: %d\n" } else {
"lastseen: %d\n" snprintf(res, ressize, "can't find ip '%s' in jail '%s'\n", args[1], args[0]);
"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);
} else if (msg->type == CMD_JAIL_IP_BAN) { } 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); f2b_jail_ban(jail, addr);
strlcpy(res, "ok", ressize); strlcpy(res, "ok", ressize);
} else if (msg->type == CMD_JAIL_IP_RELEASE) { } 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); 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 { } else {
strlcpy(res, "error: unsupported command type", ressize); strlcpy(res, "error: unsupported command type", ressize);
} }
@ -264,9 +271,14 @@ jails_start(f2b_config_t *config) {
void void
jails_stop(f2b_jail_t *jails) { 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); f2b_jail_stop(jail);
jails = NULL; free(jail);
jail = next;
}
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
@ -380,8 +392,10 @@ int main(int argc, char *argv[]) {
} }
if (state == reconfig) { if (state == reconfig) {
state = run; state = run;
memset(&config, 0x0, sizeof(config));
if (f2b_config_load(&config, opts.config_path, true)) { if (f2b_config_load(&config, opts.config_path, true)) {
jails_stop(jails); jails_stop(jails);
jails = NULL;
if (config.defaults) if (config.defaults)
f2b_jail_set_defaults(config.defaults); f2b_jail_set_defaults(config.defaults);
jails_start(&config); jails_start(&config);
@ -395,6 +409,7 @@ int main(int argc, char *argv[]) {
f2b_csocket_destroy(opts.csock, opts.csocket_path); f2b_csocket_destroy(opts.csock, opts.csocket_path);
jails_stop(jails); jails_stop(jails);
jails = NULL;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

29
src/filter.c

@ -96,6 +96,8 @@ f2b_filter_create(f2b_config_section_t *config, const char *file) {
goto cleanup; goto cleanup;
if ((*(void **) (&filter->ready) = dlsym(filter->h, "ready")) == NULL) if ((*(void **) (&filter->ready) = dlsym(filter->h, "ready")) == NULL)
goto cleanup; goto cleanup;
if ((*(void **) (&filter->stats) = dlsym(filter->h, "stats")) == NULL)
goto cleanup;
if ((*(void **) (&filter->match) = dlsym(filter->h, "match")) == NULL) if ((*(void **) (&filter->match) = dlsym(filter->h, "match")) == NULL)
goto cleanup; goto cleanup;
if ((*(void **) (&filter->destroy) = dlsym(filter->h, "destroy")) == NULL) if ((*(void **) (&filter->destroy) = dlsym(filter->h, "destroy")) == NULL)
@ -147,6 +149,14 @@ f2b_filter_destroy(f2b_filter_t *filter) {
free(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 bool
f2b_filter_match(f2b_filter_t *filter, const char *line, char *buf, size_t buf_size) { f2b_filter_match(f2b_filter_t *filter, const char *line, char *buf, size_t buf_size) {
assert(filter != NULL); assert(filter != NULL);
@ -161,3 +171,22 @@ f2b_filter_error(f2b_filter_t *filter) {
assert(filter != NULL); assert(filter != NULL);
return filter->error(filter->cfg); 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;
}
}

3
src/filter.h

@ -18,6 +18,7 @@ typedef struct f2b_filter_t {
bool (*append) (void *cfg, const char *pattern); bool (*append) (void *cfg, const char *pattern);
char *(*error) (void *cfg); char *(*error) (void *cfg);
bool (*ready) (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); bool (*match) (void *cfg, const char *line, char *buf, size_t buf_size);
void (*destroy) (void *cfg); void (*destroy) (void *cfg);
} f2b_filter_t; } 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); f2b_filter_t * f2b_filter_create (f2b_config_section_t *config, const char *id);
void f2b_filter_destroy(f2b_filter_t *b); 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); 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); 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_ */ #endif /* F2B_FILTER_H_ */

4
src/filters/CMakeLists.txt

@ -1,12 +1,12 @@
set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(FILTERS "") 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") list(APPEND FILTERS "f2b_filter_preg")
find_library(PCRE_FOUND "pcre") find_library(PCRE_FOUND "pcre")
if (WITH_PCRE AND PCRE_FOUND) 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") target_link_libraries("f2b_filter_pcre" "pcre")
list(APPEND FILTERS "f2b_filter_pcre") list(APPEND FILTERS "f2b_filter_pcre")
endif () endif ()

14
src/filters/filter.h

@ -4,8 +4,19 @@
* it under the terms of the GNU General Public License version 2 as * it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation. * published by the Free Software Foundation.
*/ */
#if defined(__linux__)
#include <alloca.h>
#endif
#include <assert.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../strlcpy.h"
#define ID_MAX 32
#define PATTERN_MAX 256
#define HOST_TOKEN "<HOST>" #define HOST_TOKEN "<HOST>"
typedef struct _config cfg_t; 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 config(cfg_t *c, const char *key, const char *value);
extern bool append(cfg_t *c, const char *pattern); extern bool append(cfg_t *c, const char *pattern);
extern bool ready(cfg_t *c); 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); extern void destroy(cfg_t *c);

34
src/filters/pcre.c

@ -4,12 +4,6 @@
* it under the terms of the GNU General Public License version 2 as * it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation. * published by the Free Software Foundation.
*/ */
#include <alloca.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "filter.h" #include "filter.h"
#include <pcre.h> #include <pcre.h>
@ -18,18 +12,20 @@
typedef struct f2b_regex_t { typedef struct f2b_regex_t {
struct f2b_regex_t *next; struct f2b_regex_t *next;
char pattern[PATTERN_MAX];
int matches; int matches;
pcre *regex; pcre *regex;
pcre_extra *data; pcre_extra *data;
} f2b_regex_t; } f2b_regex_t;
struct _config { struct _config {
char id[32]; char id[ID_MAX];
char error[256]; char error[256];
bool icase; bool icase;
bool study; bool study;
bool usejit; bool usejit;
f2b_regex_t *regexps; f2b_regex_t *regexps;
f2b_regex_t *statp;
}; };
cfg_t * cfg_t *
@ -38,7 +34,7 @@ create(const char *id) {
if ((cfg = calloc(1, sizeof(cfg_t))) == NULL) if ((cfg = calloc(1, sizeof(cfg_t))) == NULL)
return NULL; return NULL;
snprintf(cfg->id, sizeof(cfg->id), "%s", id); strlcpy(cfg->id, id, sizeof(cfg->id));
return cfg; return cfg;
} }
@ -89,8 +85,8 @@ append(cfg_t *cfg, const char *pattern) {
memset(buf, 0x0, bufsize); memset(buf, 0x0, bufsize);
memcpy(buf, pattern, token - pattern); memcpy(buf, pattern, token - pattern);
strcat(buf, HOST_REGEX); strlcat(buf, HOST_REGEX, bufsize);
strcat(buf, token + strlen(HOST_TOKEN)); strlcat(buf, token + strlen(HOST_TOKEN), bufsize);
if ((regex = calloc(1, sizeof(f2b_regex_t))) == NULL) if ((regex = calloc(1, sizeof(f2b_regex_t))) == NULL)
return false; return false;
@ -115,6 +111,7 @@ append(cfg_t *cfg, const char *pattern) {
regex->next = cfg->regexps; regex->next = cfg->regexps;
cfg->regexps = regex; cfg->regexps = regex;
strlcpy(regex->pattern, pattern, sizeof(regex->pattern));
return true; return true;
} }
@ -126,6 +123,23 @@ ready(cfg_t *cfg) {
return false; 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 * const char *
error(cfg_t *cfg) { error(cfg_t *cfg) {
assert(cfg != NULL); assert(cfg != NULL);

36
src/filters/preg.c

@ -4,12 +4,6 @@
* it under the terms of the GNU General Public License version 2 as * it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation. * published by the Free Software Foundation.
*/ */
#include <alloca.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "filter.h" #include "filter.h"
#include <regex.h> #include <regex.h>
@ -19,15 +13,17 @@
typedef struct f2b_regex_t { typedef struct f2b_regex_t {
struct f2b_regex_t *next; struct f2b_regex_t *next;
char pattern[PATTERN_MAX];
int matches; int matches;
regex_t regex; regex_t regex;
} f2b_regex_t; } f2b_regex_t;
struct _config { struct _config {
char id[32]; char id[ID_MAX];
char error[256]; char error[256];
bool icase; bool icase;
f2b_regex_t *regexps; f2b_regex_t *regexps;
f2b_regex_t *statp;
}; };
cfg_t * cfg_t *
@ -36,7 +32,7 @@ create(const char *id) {
if ((cfg = calloc(1, sizeof(cfg_t))) == NULL) if ((cfg = calloc(1, sizeof(cfg_t))) == NULL)
return NULL; return NULL;
snprintf(cfg->id, sizeof(cfg->id), "%s", id); strlcpy(cfg->id, id, sizeof(cfg->id));
return cfg; return cfg;
} }
@ -77,8 +73,8 @@ append(cfg_t *cfg, const char *pattern) {
memset(buf, 0x0, bufsize); memset(buf, 0x0, bufsize);
memcpy(buf, pattern, token - pattern); memcpy(buf, pattern, token - pattern);
strcat(buf, HOST_REGEX); strlcat(buf, HOST_REGEX, bufsize);
strcat(buf, token + strlen(HOST_TOKEN)); strlcat(buf, token + strlen(HOST_TOKEN), bufsize);
if ((regex = calloc(1, sizeof(f2b_regex_t))) == NULL) if ((regex = calloc(1, sizeof(f2b_regex_t))) == NULL)
return false; return false;
@ -86,6 +82,7 @@ append(cfg_t *cfg, const char *pattern) {
if (regcomp(&regex->regex, buf, flags) == 0) { if (regcomp(&regex->regex, buf, flags) == 0) {
regex->next = cfg->regexps; regex->next = cfg->regexps;
cfg->regexps = regex; cfg->regexps = regex;
strlcpy(regex->pattern, pattern, sizeof(regex->pattern));
return true; return true;
} }
@ -101,6 +98,23 @@ ready(cfg_t *cfg) {
return false; 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 * const char *
error(cfg_t *cfg) { error(cfg_t *cfg) {
assert(cfg != NULL); 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); assert(buf_size > match_len);
memcpy(buf, &line[match[1].rm_so], match_len); memcpy(buf, &line[match[1].rm_so], match_len);
buf[match_len] = '\0'; buf[match_len] = '\0';
buf[buf_size] = '\0'; buf[buf_size - 1] = '\0';
return true; return true;
} }

15
src/ipaddr.c

@ -44,6 +44,21 @@ f2b_ipaddr_destroy(f2b_ipaddr_t *ipaddr) {
free(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_ipaddr_t *
f2b_addrlist_append(f2b_ipaddr_t *list, f2b_ipaddr_t *ipaddr) { f2b_addrlist_append(f2b_ipaddr_t *list, f2b_ipaddr_t *ipaddr) {
assert(ipaddr != NULL); assert(ipaddr != NULL);

3
src/ipaddr.h

@ -7,6 +7,8 @@
#ifndef F2B_IPADDR_H_ #ifndef F2B_IPADDR_H_
#define F2B_IPADDR_H_ #define F2B_IPADDR_H_
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include "matches.h" #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); f2b_ipaddr_t * f2b_ipaddr_create (const char *addr, size_t max_matches);
void f2b_ipaddr_destroy(f2b_ipaddr_t *ipaddr); 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_append(f2b_ipaddr_t *list, f2b_ipaddr_t *ipaddr);
f2b_ipaddr_t * f2b_addrlist_lookup(f2b_ipaddr_t *list, const char *addr); f2b_ipaddr_t * f2b_addrlist_lookup(f2b_ipaddr_t *list, const char *addr);

26
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) { if (strcmp(param->name, "maxretry") == 0) {
jail->maxretry = atoi(param->value); jail->maxretry = atoi(param->value);
if (jail->maxretry <= 0) if (jail->maxretry == 0)
jail->maxretry = DEFAULT_MAXRETRY; jail->maxretry = DEFAULT_MAXRETRY;
continue; continue;
} }
@ -412,3 +412,27 @@ f2b_jail_stop(f2b_jail_t *jail) {
return errors; 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);
}

2
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); bool f2b_jail_init (f2b_jail_t *jail, f2b_config_t *config);
size_t f2b_jail_process (f2b_jail_t *jail); size_t f2b_jail_process (f2b_jail_t *jail);
bool f2b_jail_stop (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_ */ #endif /* F2B_JAIL_H_ */

6
src/log.c

@ -39,6 +39,8 @@ get_facility(log_msgtype_t l) {
void f2b_log_msg(log_msgtype_t l, const char *fmt, ...) { void f2b_log_msg(log_msgtype_t l, const char *fmt, ...) {
va_list args; va_list args;
char msg[LOGLINE_MAX] = ""; char msg[LOGLINE_MAX] = "";
char when[64] = "";
time_t now = time(NULL);
if (l < minlevel) if (l < minlevel)
return; return;
@ -55,7 +57,8 @@ void f2b_log_msg(log_msgtype_t l, const char *fmt, ...) {
case log_stderr: case log_stderr:
logfile = stderr; logfile = stderr;
case log_file: 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; break;
} }
@ -83,6 +86,7 @@ void f2b_log_to_file(const char *path) {
if (path == NULL || *path == '\0') if (path == NULL || *path == '\0')
return; return;
if ((new = fopen(path, "a")) != NULL) { if ((new = fopen(path, "a")) != NULL) {
setvbuf(new, NULL , _IONBF, 0);
if (logfile && logfile != stderr) if (logfile && logfile != stderr)
fclose(logfile); fclose(logfile);
dest = log_file; dest = log_file;

15
src/logfile.c

@ -41,8 +41,12 @@ f2b_logfile_open(f2b_logfile_t *file, const char *path) {
void void
f2b_logfile_close(f2b_logfile_t *file) { f2b_logfile_close(f2b_logfile_t *file) {
assert(file != NULL); assert(file != NULL);
fclose(file->fd);
if (file->fd)
fclose(file->fd);
file->opened = false; file->opened = false;
file->fd = NULL;
} }
bool bool
@ -51,6 +55,9 @@ f2b_logfile_rotated(const f2b_logfile_t *file) {
assert(file != NULL); assert(file != NULL);
if (!file->opened)
return true;
if (stat(file->path, &st) != 0) if (stat(file->path, &st) != 0)
return true; return true;
@ -64,6 +71,12 @@ f2b_logfile_rotated(const f2b_logfile_t *file) {
bool bool
f2b_logfile_getline(const f2b_logfile_t *file, char *buf, size_t bufsize) { 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) if (fgets(buf, bufsize, file->fd) != NULL)
return true; return true;

4
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_ipaddr_*" "t_ipaddr")
add_test("tests/f2b_config_param*" "t_config_param") 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") add_test("tests/filter/preg" "t_filter_preg")
if (WITH_PCRE) if (WITH_PCRE)
add_test("tests/filter/pcre" "t_filter_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") target_link_libraries("t_filter_pcre" "pcre")
endif () endif ()

Loading…
Cancel
Save