├── .gitignore ├── CMakeLists.txt ├── ChangeLog ├── Doxyfile ├── LICENSE ├── README.md ├── configs ├── conf-available │ ├── 05-source-files.conf │ ├── 05-source-portknock.conf │ ├── 05-source-redis.conf │ ├── 10-backend-exec-ipfw.conf │ ├── 10-backend-exec-ipset.conf │ ├── 10-backend-ipset.conf │ ├── 10-backend-redis.conf │ ├── 15-filter-pcre.conf │ ├── 15-filter-preg.conf │ ├── 20-jail-dovecot.conf.in │ ├── 20-jail-global.conf │ ├── 20-jail-nginx.conf.in │ ├── 20-jail-postfix.conf.in │ └── 20-jail-ssh.conf.in ├── conf-enabled │ └── README.txt └── f2b.conf.in ├── contrib └── init.openrc ├── debian ├── changelog ├── compat ├── control ├── copyright ├── f2b-mod-ipset.install ├── f2b-mod-pcre3.install ├── f2b-mod-redis.install ├── f2b.default ├── f2b.dirs ├── f2b.init ├── f2b.install ├── f2b.links ├── f2b.logrotate ├── rules ├── source │ └── format └── watch ├── docs ├── configuration.md └── install.md ├── filters └── README.txt ├── src ├── CMakeLists.txt ├── appconfig.c ├── appconfig.h ├── backend-test.c ├── backend.c ├── backend.h ├── backends │ ├── CMakeLists.txt │ ├── backend.c │ ├── backend.h │ ├── exec.c │ ├── ipset6.c │ ├── ipset7.c │ └── redis.c ├── buf.c ├── buf.h ├── client.c ├── commands.c ├── commands.h ├── common.h ├── config.c ├── config.h ├── csocket-test.c ├── csocket.c ├── csocket.h ├── daemon.c ├── event.c ├── event.h ├── filter-test.c ├── filter.c ├── filter.h ├── filters │ ├── CMakeLists.txt │ ├── filter.c │ ├── filter.h │ ├── pcre.c │ └── preg.c ├── fnv.h ├── fnv32a.c ├── ipaddr.c ├── ipaddr.h ├── jail.c ├── jail.h ├── log.c ├── log.h ├── matches.c ├── matches.h ├── md5.c ├── md5.h ├── mod-api.h ├── mod-defs.h ├── source-test.c ├── source.c ├── source.h ├── sources │ ├── CMakeLists.txt │ ├── files.c │ ├── portknock.c │ ├── redis.c │ ├── source.c │ └── source.h ├── statefile.c ├── statefile.h ├── strlcpy.c └── strlcpy.h └── t ├── CMakeLists.txt ├── t_buf.c ├── t_cmd.c ├── t_config_param.c ├── t_filters.c ├── t_ipaddr.c ├── t_matches.c ├── t_md5.c └── t_statefile.c /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeCache.txt 2 | CMakeFiles/ 3 | *.cmake 4 | # generated configs: 5 | configs/f2b.conf 6 | configs/conf-available/20-jail-*.conf 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.4) 2 | 3 | set(CNAME "f2b") 4 | set(VERSION "0.6.1") 5 | 6 | project(${CNAME} C) 7 | 8 | include(CTest) 9 | 10 | find_package(PkgConfig REQUIRED) 11 | 12 | option(WITH_CLIENT "Simple client for configuring daemon" ON) 13 | option(WITH_CSOCKET "Unix control socket support for daemon" ON) 14 | option(WITH_HARDENING "Enable hardening options" ON) 15 | option(WITH_READLINE "Use readline library for client" ON) 16 | option(WITH_PCRE "Build pcre-compatible filter" ON) 17 | option(WITH_REDIS "Build redis source/backend" OFF) 18 | option(WITH_IPSET "Build native ipset backend" OFF) 19 | 20 | set(INIT_SCRIPT "OFF") # valid variants: "off", "sysvinit", "openrc", "systemd" 21 | 22 | if (NOT DEFINED CMAKE_INSTALL_PREFIX) 23 | set(CMAKE_INSTALL_PREFIX "/usr") 24 | endif () 25 | include(GNUInstallDirs) 26 | 27 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -pedantic -std=c99") 28 | if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") 29 | add_definitions("-D_GNU_SOURCE") 30 | else () 31 | include_directories(AFTER SYSTEM "/usr/local/include") 32 | link_directories("/usr/local/lib") 33 | endif () 34 | 35 | if (WITH_HARDENING) 36 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wformat -Wformat-security -Werror=format-security" ) 37 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fstack-protector --param ssp-buffer-size=4" ) 38 | add_definitions("-D_FORTIFY_SOURCE=2") 39 | endif () 40 | 41 | pkg_check_modules(READLINE "readline" REQUIRED) 42 | 43 | message(STATUS "----------------------------------------") 44 | message(STATUS "Compiler : ${CMAKE_C_COMPILER} (${CMAKE_C_COMPILER_ID} ${CMAKE_C_COMPILER_VERSION})") 45 | message(STATUS "- CFLAGS : ${CMAKE_C_FLAGS}") 46 | message(STATUS "Paths:") 47 | message(STATUS "- prefix : ${CMAKE_INSTALL_PREFIX}") 48 | message(STATUS "- configs : ${CMAKE_INSTALL_FULL_SYSCONFDIR}") 49 | message(STATUS "- binary : ${CMAKE_INSTALL_FULL_BINDIR}") 50 | message(STATUS "- binary : ${CMAKE_INSTALL_FULL_SBINDIR}") 51 | message(STATUS "- plugins : ${CMAKE_INSTALL_FULL_LIBDIR}/${CNAME}") 52 | message(STATUS "- data : ${CMAKE_INSTALL_FULL_DATAROOTDIR}") 53 | message(STATUS "- state : ${CMAKE_INSTALL_FULL_LOCALSTATEDIR}") 54 | message(STATUS "Build type : ${CMAKE_BUILD_TYPE}") 55 | message(STATUS "Options:") 56 | message(STATUS "- WITH_CLIENT : ${WITH_CLIENT}") 57 | message(STATUS "- WITH_CSOCKET : ${WITH_CSOCKET}") 58 | message(STATUS "- WITH_HARDENING : ${WITH_HARDENING}") 59 | message(STATUS "- WITH_PCRE : ${WITH_PCRE}") 60 | message(STATUS "- WITH_REDIS : ${WITH_REDIS}") 61 | message(STATUS "- WITH_IPSET : ${WITH_IPSET}") 62 | message(STATUS "- WITH_READLINE : ${WITH_READLINE}") 63 | message(STATUS "Components:") 64 | 65 | add_subdirectory(src) 66 | add_subdirectory(t) 67 | set_property(DIRECTORY "t" PROPERTY COMPILE_FLAGS "-g;-ggdb;-Wall;-Wextra;-pedantic;-O0") 68 | 69 | file(GLOB_RECURSE CONFIGS "*.conf.in") 70 | foreach(CONFIG ${CONFIGS}) 71 | string(REPLACE ".conf.in" ".conf" GENERATED ${CONFIG}) 72 | configure_file(${CONFIG} ${GENERATED}) 73 | endforeach() 74 | install(DIRECTORY "configs/" DESTINATION "${CMAKE_INSTALL_SYSCONFDIR}/${CNAME}" 75 | FILES_MATCHING PATTERN "*.conf") 76 | install(DIRECTORY "configs/" DESTINATION "${CMAKE_INSTALL_SYSCONFDIR}/${CNAME}" 77 | FILES_MATCHING PATTERN "README.txt") 78 | install(FILES "configs/f2b.conf" DESTINATION "${CMAKE_INSTALL_SYSCONFDIR}/${CNAME}" 79 | RENAME "f2b.conf.sample") 80 | 81 | if (INIT_SCRIPT STREQUAL "openrc") 82 | install(FILES "contrib/init.openrc" DESTINATION "${CMAKE_INSTALL_SYSCONFDIR}/init.d" RENAME "f2b") 83 | elseif (INIT_SCRIPT STREQUAL "systemd") 84 | install(FILES "contrib/f2b.service" DESTINATION "${CMAKE_INSTALL_FULL_LIBDIR}/systemd/system") 85 | endif () 86 | 87 | add_custom_target("dist" COMMAND 88 | "git" "archive" "--format=tar.gz" 89 | "--output=${CNAME}_v${VERSION}.tar.gz" 90 | "--prefix=${CNAME}_v${VERSION}/" "v${VERSION}") 91 | 92 | add_custom_target("dist-latest" COMMAND 93 | "git" "archive" "--format=tar.gz" 94 | "--output=${CNAME}_latest.tar.gz" 95 | "--prefix=${CNAME}_latest/" "master") 96 | 97 | add_custom_target("lint" COMMAND "cppcheck" "src/") 98 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/). 6 | 7 | ## Unreleased 8 | 9 | ## [0.6] - 2023-02-07 10 | ### Added 11 | 12 | * add 'log level ' command 13 | * add 'log events' command 14 | * support for libipset > 7.X 15 | * readline support in f2bc 16 | * add log rotation to debian package 17 | * new options for daemon -- "coredumps" && "nice" 18 | * allow jails without filter 19 | * replace simple "match count" with advanced "scored matches" 20 | * add source/filter match tags in stats 21 | * show daemon uptime in status 22 | 23 | ### Changed 24 | 25 | * filters collection now in separate repository 26 | * change modules naming & location 27 | * change 'rotate' command to 'log rotate' for consistency 28 | * client and control socket fully refactored to use plain tcp 29 | * allow redis source/backend fail on start (no network yet) 30 | * filter-test now uses config instead direct library load 31 | * match count now not limited to last 5 32 | * jail's "maxcount" parameter replaced with "maxscore" (need config fix) 33 | * if missing password for control socket in config it will be set random (and send to logfile) 34 | * build system now relies on pkg-config instead cmake modules 35 | 36 | ### Removed 37 | 38 | * multicast source/backend (replaced with f2bcd) 39 | 40 | ### Fixed 41 | 42 | * don't hard depend on mountall 43 | * fix setting jail 'state' param 44 | * SO_PEERCRED is linux-specific now 45 | 46 | ## [0.5] - 2017-01-19 47 | ### Added 48 | 49 | + added 'fatal' log facility 50 | + added empty filter for use with sources that providing bare ip address 51 | + added doxygen comments to all sources 52 | + added some documentation: see docs/install.md and docs/configuration.md 53 | + added ability to save and restore banned addresses on reload/restart 54 | + added 'portknock' source 55 | + added 'mcast' source/backend (not well tested yet) 56 | + added handler for 'jail set' command 57 | + added interactive mode for backend test 58 | 59 | ### Changed 60 | 61 | * refactoring of f2b_cmsg_*(), f2b_csocket_*() 62 | 63 | ### Fixed 64 | 65 | * better error handling in 'redis' source & backend 66 | * fix freopen() calls: std{in,out,err} may be read-only 67 | * fix setting uid/git & daemon options 68 | * fix compatibility with old pcre (< 8.20) in filter/pcre 69 | * fixed f2b-backend-test cmdline parse 70 | * fix setting uid/git & daemon options 71 | * fix errcb in 'redis' source 72 | * fix SIGUSR1 handler 73 | 74 | ## [0.4] - 2016-10-07 75 | ### Added 76 | 77 | * make source(s) also a module. now available: 78 | * files source 79 | * redis source 80 | * f2b-source-test utility 81 | * SIGUSR1 handler for logfile reopening 82 | * timeout in client 83 | * filters/nginx-bots.pcre 84 | 85 | ### Changed 86 | 87 | * f2b-filter-test now show per-pattern match stats 88 | * install short readme file in conf-enabled dir 89 | * tested & fixed redis backend 90 | * f2b-backend-test : simplify usage 91 | * chg jail commands 'show', 'ban' & 'release' : add expicit 'ip' prefix 92 | * rename commands: regex stats -> filter stats, regex add -> filter reload 93 | 94 | ### Fixed 95 | 96 | * inversion of 'shared' option for 'exec' backend 97 | * correctly write pidfile 98 | * bans with maxretry = 1 99 | * redis detection in cmake 100 | * double free in filter's flush() 101 | 102 | ## [0.3] - 2016-09-12 103 | ### Added 104 | 105 | * "jail regex stats" command 106 | * "jail regex add" command 107 | * apply CMAKE_INSTALL_PREFIX to configs 108 | * added config for exec backend for ipfw 109 | * redis backend (experimental) 110 | * added config reload 111 | * log file rotation 112 | 113 | ### Changed 114 | 115 | * enable 'icase' for filters by default 116 | * enable 'sharing' for backends by default 117 | * tune configs location 118 | * enable hardening in build opts by default 119 | * fix ssh filter patterns 120 | * use strl*() instead snprintf()/strncpy() in backends 121 | * rename tests utils 122 | * print date/time in log file 123 | * disable buffering for logfile 124 | * add stats() funtion to filter's api 125 | 126 | ### Fixed 127 | 128 | * fix segfault in preg filter 129 | * fix cppcheck warnings 130 | * fix bsd build 131 | * fix termination of daemon 132 | 133 | ## [0.2] - 2016-08-21 134 | 135 | * Initial public release 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | f2b is lightweight automatic anti-bot turret for your public serivces. 5 | 6 | Features: 7 | 8 | * written in pure C 9 | * small memory footprint 10 | * minimum dependencies (required: libc, libdl; optional: readline, pcre, redis, ipset) 11 | * fully modular: pluggable sources/filters/backends (you may easy write custom one) 12 | * support for distributed installs (teamwork) 13 | * stateful (can save/restore banned ips on restart) 14 | * adapting to bots (automatically adjust bantime/findtime on rare but steady events from one source) 15 | * can use not only logfiles, but anything that can give malicious ip: accept(), recv(), pubsub event, pipe 16 | * may work as honeypot (emulating open tcp ports) 17 | 18 | Docs: 19 | 20 | * [Installation](docs/install.md) -- generic installation instructions 21 | * [Quickstart](docs/configuration.md) -- config file description and configuration notes 22 | 23 | Similar software: 24 | 25 | * [fail2ban](http://www.fail2ban.org) 26 | * [sshguard](http://sshguard.sourceforge.net) 27 | 28 | License: GPL2+ 29 | -------------------------------------------------------------------------------- /configs/conf-available/05-source-files.conf: -------------------------------------------------------------------------------- 1 | [source:files] 2 | load = source_files.so 3 | -------------------------------------------------------------------------------- /configs/conf-available/05-source-portknock.conf: -------------------------------------------------------------------------------- 1 | [source:portknock] 2 | load = source_portknock.so 3 | ; listen = 0.0.0.0:21 # ftp 4 | ; listen = 0.0.0.0:23 # telnet 5 | ; listen = 0.0.0.0:25 # smtp 6 | ; listen = 0.0.0.0:110 # pop3 7 | ; listen = 0.0.0.0:143 # imap 8 | ; listen = 0.0.0.0:1080 # socks 9 | ; listen = 0.0.0.0:3128 # proxy 10 | ; listen = 0.0.0.0:3389 # rdp 11 | ; listen = 0.0.0.0:5060 # sip 12 | ; listen = 0.0.0.0:6667 # irc 13 | ; listen = 0.0.0.0:7547 # TR-069 14 | ; listen = 0.0.0.0:8080 # proxy 15 | -------------------------------------------------------------------------------- /configs/conf-available/05-source-redis.conf: -------------------------------------------------------------------------------- 1 | [source:redis] 2 | load = source_redis.so 3 | timeout = 2 4 | host = 127.0.0.1 5 | port = 6379 6 | database = 0 7 | ; password = some 8 | -------------------------------------------------------------------------------- /configs/conf-available/10-backend-exec-ipfw.conf: -------------------------------------------------------------------------------- 1 | # -q option stops a ipfw table add or delete from failing if the entry 2 | # already exists or is not present. 3 | [backend:exec-ipfw] 4 | load = backend_exec.so 5 | ban = /sbin/ipfw -q table add 6 | unban = /sbin/ipfw -q table delete 7 | timeout = 2 8 | shared = yes 9 | -------------------------------------------------------------------------------- /configs/conf-available/10-backend-exec-ipset.conf: -------------------------------------------------------------------------------- 1 | [backend:exec-ipset] 2 | load = backend_exec.so 3 | start = /sbin/ipset -! create hash:ip 4 | start = /sbin/iptables -I INPUT -m set --match-set src -j DROP 5 | stop = /sbin/iptables -D INPUT -m set --match-set src -j DROP 6 | stop = /sbin/ipset -! destroy 7 | ban = /sbin/ipset -! add 8 | check = /sbin/ipset -! test 9 | unban = /sbin/ipset -! del 10 | timeout = 2 11 | shared = yes 12 | -------------------------------------------------------------------------------- /configs/conf-available/10-backend-ipset.conf: -------------------------------------------------------------------------------- 1 | [backend:ipset] 2 | load = backend_ipset.so 3 | -------------------------------------------------------------------------------- /configs/conf-available/10-backend-redis.conf: -------------------------------------------------------------------------------- 1 | [backend:redis] 2 | load = backend_redis.so 3 | shared = yes 4 | timeout = 2 5 | ping = 5 6 | host = 127.0.0.1 7 | port = 6379 8 | database = 0 9 | ; password = some 10 | -------------------------------------------------------------------------------- /configs/conf-available/15-filter-pcre.conf: -------------------------------------------------------------------------------- 1 | [filter:pcre] 2 | load = filter_pcre.so 3 | icase = yes 4 | study = yes 5 | usejit = no 6 | -------------------------------------------------------------------------------- /configs/conf-available/15-filter-preg.conf: -------------------------------------------------------------------------------- 1 | [filter:preg] 2 | load = filter_preg.so 3 | icase = yes 4 | -------------------------------------------------------------------------------- /configs/conf-available/20-jail-dovecot.conf.in: -------------------------------------------------------------------------------- 1 | [jail:dovecot] 2 | source = files:/var/log/mail.log 3 | filter = preg:${CMAKE_INSTALL_FULL_DATAROOTDIR}/f2b/filters/dovecot.preg 4 | -------------------------------------------------------------------------------- /configs/conf-available/20-jail-global.conf: -------------------------------------------------------------------------------- 1 | [jail:global] 2 | ; this jail used by remote controller 3 | ; no source/filter needed here 4 | state = yes 5 | enabled = yes 6 | bantime = 86400 ; will be redefined at runtime 7 | expiretime = 14400 8 | bantime_extend = 0 9 | findtime_extend = 0 10 | -------------------------------------------------------------------------------- /configs/conf-available/20-jail-nginx.conf.in: -------------------------------------------------------------------------------- 1 | [jail:nginx] 2 | source = files:/var/log/nginx/*access*.log 3 | filter = pcre:${CMAKE_INSTALL_FULL_DATAROOTDIR}/f2b/filters/nginx-bots.pcre 4 | -------------------------------------------------------------------------------- /configs/conf-available/20-jail-postfix.conf.in: -------------------------------------------------------------------------------- 1 | [jail:postfix] 2 | source = files:/var/log/mail.log 3 | filter = preg:${CMAKE_INSTALL_FULL_DATAROOTDIR}/f2b/filters/postfix.preg 4 | -------------------------------------------------------------------------------- /configs/conf-available/20-jail-ssh.conf.in: -------------------------------------------------------------------------------- 1 | [jail:ssh] 2 | source = files:/var/log/auth.log 3 | filter = preg:${CMAKE_INSTALL_FULL_DATAROOTDIR}/f2b/filters/ssh.preg 4 | -------------------------------------------------------------------------------- /configs/conf-enabled/README.txt: -------------------------------------------------------------------------------- 1 | You may enable various source/filter/backend types by symlinking config parts here 2 | from conf-available/ and creating your own settings in separate file(s). 3 | Config files will be sorted by name before processing. 4 | 5 | !!!IMPORTANT!!! 6 | 7 | Every config file should ends with '.conf', all other will be ignored. 8 | -------------------------------------------------------------------------------- /configs/f2b.conf.in: -------------------------------------------------------------------------------- 1 | [main] 2 | includes = ${CMAKE_INSTALL_FULL_SYSCONFDIR}/f2b/conf-enabled 3 | pidfile = /var/run/f2b.pid 4 | statedir = ${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/lib/f2b 5 | ; valid destinations: stdout, stderr, syslog, file (logfile option should be set) 6 | logdest = syslog 7 | loglevel = info 8 | logfile = /var/log/f2b.log 9 | user = root 10 | group = root 11 | daemon = yes 12 | coredumps = no 13 | nice = 0 14 | 15 | [defaults] 16 | state = no 17 | enabled = yes 18 | bantime = 3600 19 | findtime = 300 20 | expiretime = 14400 21 | bantime_extend = 0.2 22 | findtime_extend = 0.07 23 | banscore = 50 24 | backend = exec-ipset:banned 25 | 26 | [csocket] 27 | listen = unix:/var/run/f2b.sock 28 | ;listen = inet:localhost 29 | ;listen = inet:192.168.1.1 30 | ;listen = inet:[::1]:12345 31 | ; auth used only for 'inet' connections 32 | ; if password not set - it will be generated on each restart, see logs 33 | ;password = 34 | -------------------------------------------------------------------------------- /contrib/init.openrc: -------------------------------------------------------------------------------- 1 | #!/sbin/openrc-run 2 | 3 | name="f2b" 4 | command="/usr/sbin/f2b" 5 | command_args="-d" 6 | description="lightweight blocker of network attacks" 7 | pidfile="/var/run/f2b.pid" 8 | 9 | required_dirs="/var/run" 10 | required_files="/etc/f2b/f2b.conf" 11 | 12 | depend() { 13 | use mountall net 14 | after bootmisc 15 | config "/etc/f2b/f2b.conf" 16 | } 17 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | f2b (0.6.1-1) unstable; urgency=medium 2 | 3 | * bugfix version 4 | 5 | -- Alex 'AdUser' Z Tue, 04 Jun 2024 09:48:18 +1000 6 | 7 | f2b (0.6-1) unstable; urgency=medium 8 | 9 | * new version 10 | 11 | -- Alex 'AdUser' Z Tue, 07 Feb 2023 14:00:06 +1000 12 | 13 | f2b (0.5-1) unstable; urgency=medium 14 | 15 | * new version 16 | 17 | -- Alex 'AdUser' Z Thu, 20 Apr 2017 13:11:37 +1000 18 | 19 | f2b (0.4-1) unstable; urgency=medium 20 | 21 | * new version 22 | 23 | -- Alex 'AdUser' Z Fri, 07 Oct 2016 17:44:32 +1000 24 | 25 | f2b (0.3-1) unstable; urgency=medium 26 | 27 | * new version 28 | 29 | -- Alex 'AdUser' Z Tue, 13 Sep 2016 16:55:43 +1000 30 | 31 | f2b (0.2-1) unstable; urgency=low 32 | 33 | * Initial release 34 | 35 | -- Alex 'AdUser' Z Sun, 21 Aug 2016 15:56:52 +1000 36 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 11 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: f2b 2 | Section: net 3 | Priority: optional 4 | Maintainer: Alex 'AdUser' Z 5 | Build-Depends: debhelper (>= 9), cmake, libpcre3-dev, libhiredis-dev, libipset-dev, libreadline-dev, pkg-config 6 | Standards-Version: 3.9.5 7 | Homepage: https://github.com/AdUser/f2b 8 | Vcs-Git: https://github.com/AdUser/f2b.git 9 | Vcs-Browser: https://github.com/AdUser/f2b 10 | 11 | Package: f2b 12 | Architecture: any 13 | Depends: ${shlibs:Depends}, ${misc:Depends}, f2b-filters 14 | Recommends: netfilter-persistent 15 | Description: lightweight automatic anti-bot turret for your public serivces 16 | Features: 17 | . 18 | * written in pure C 19 | * small memory footprint 20 | * minimum dependencies (required: libc, libdl; optional: readline, pcre, redis, ipset) 21 | * fully modular: pluggable sources/filters/backends (you may easy write custom one) 22 | * support for distributed installs (teamwork) 23 | * stateful (can save/restore banned ips on restart) 24 | * adapting to bots (automatically adjust bantime/findtime on rare but steady events from one source) 25 | * can use not only logfiles, but anything that can give malicious ip: accept(), recv(), pubsub event, pipe 26 | * may work as honeypot (emulating open tcp ports) 27 | . 28 | This package contains daemon, client and basic modules 29 | 30 | Package: f2b-mod-pcre3 31 | Architecture: any 32 | Depends: f2b (= ${binary:Version}), ${shlibs:Depends}, ${misc:Depends} 33 | Description: lightweight automatic anti-bot turret for your public serivces 34 | This package contains filter module that uses pcre3 library 35 | 36 | Package: f2b-mod-redis 37 | Architecture: any 38 | Depends: f2b (= ${binary:Version}), ${shlibs:Depends}, ${misc:Depends} 39 | Suggests: redis-server 40 | Description: lightweight automatic anti-bot turret for your public serivces 41 | This package contains source and backend modules working with redis-server 42 | 43 | Package: f2b-mod-ipset 44 | Architecture: any 45 | Depends: f2b (= ${binary:Version}), ${shlibs:Depends}, ${misc:Depends} 46 | Recommends: ipset, ipset-persistent 47 | Description: lightweight automatic anti-bot turret for your public serivces 48 | This package contains native ipset manage backend 49 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: f2b 3 | Source: https://github.com/AdUser/f2b 4 | 5 | Files: * 6 | Copyright: 2015-2016 Alex 'AdUser' Z 7 | License: GPL-2.0+ 8 | 9 | Files: debian/* 10 | Copyright: 2016 Alex 'AdUser' Z 11 | License: GPL-2.0+ 12 | 13 | License: GPL-2.0+ 14 | This package is free software; you can redistribute it and/or modify 15 | it under the terms of the GNU General Public License as published by 16 | the Free Software Foundation; either version 2 of the License, or 17 | (at your option) any later version. 18 | . 19 | This package is distributed in the hope that it will be useful, 20 | but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | GNU General Public License for more details. 23 | . 24 | You should have received a copy of the GNU General Public License 25 | along with this program. If not, see 26 | . 27 | On Debian systems, the complete text of the GNU General 28 | Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". 29 | -------------------------------------------------------------------------------- /debian/f2b-mod-ipset.install: -------------------------------------------------------------------------------- 1 | etc/f2b/conf-available/*-backend-ipset.conf 2 | usr/lib/*/f2b/*_ipset.so 3 | -------------------------------------------------------------------------------- /debian/f2b-mod-pcre3.install: -------------------------------------------------------------------------------- 1 | etc/f2b/conf-available/*-*-pcre.conf 2 | usr/lib/*/f2b/*_pcre.so 3 | -------------------------------------------------------------------------------- /debian/f2b-mod-redis.install: -------------------------------------------------------------------------------- 1 | etc/f2b/conf-available/*-*-redis.conf 2 | usr/lib/*/f2b/*_redis.so 3 | -------------------------------------------------------------------------------- /debian/f2b.default: -------------------------------------------------------------------------------- 1 | # This is a POSIX shell fragment 2 | 3 | # Additional options that are passed to the Daemon. 4 | DAEMON_OPTS="" 5 | -------------------------------------------------------------------------------- /debian/f2b.dirs: -------------------------------------------------------------------------------- 1 | usr/share/f2b/filters 2 | var/lib/f2b 3 | -------------------------------------------------------------------------------- /debian/f2b.init: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ### BEGIN INIT INFO 3 | # Provides: f2b 4 | # Required-Start: $remote_fs $syslog 5 | # Required-Stop: $remote_fs $syslog 6 | # Default-Start: 2 3 4 5 7 | # Default-Stop: 0 1 6 8 | # Short-Description: lightweight blocker of network attacks 9 | # Description: lightweight blocker of network attacks 10 | ### END INIT INFO 11 | 12 | # Author: Alex 'AdUser' Z 13 | 14 | # Do NOT "set -e" 15 | 16 | # PATH should only include /usr/* if it runs after the mountnfs.sh script 17 | PATH=/sbin:/usr/sbin:/bin:/usr/bin 18 | DESC="lightweight blocker of network attacks" 19 | NAME=f2b 20 | DAEMON=/usr/sbin/$NAME 21 | DAEMON_ARGS="-d" 22 | PIDFILE=/var/run/$NAME.pid 23 | SCRIPTNAME=/etc/init.d/$NAME 24 | 25 | # Exit if the package is not installed 26 | [ -x "$DAEMON" ] || exit 0 27 | 28 | # Read configuration variable file if it is present 29 | [ -r /etc/default/$NAME ] && . /etc/default/$NAME 30 | 31 | # Load the VERBOSE setting and other rcS variables 32 | . /lib/init/vars.sh 33 | 34 | # Define LSB log_* functions. 35 | # Depend on lsb-base (>= 3.2-14) to ensure that this file is present 36 | # and status_of_proc is working. 37 | . /lib/lsb/init-functions 38 | 39 | # Function that starts the daemon/service 40 | do_start() 41 | { 42 | # Return 43 | # 0 if daemon has been started 44 | # 1 if daemon was already running 45 | # 2 if daemon could not be started 46 | start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ 47 | || return 1 48 | start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \ 49 | $DAEMON_ARGS \ 50 | || return 2 51 | # Add code here, if necessary, that waits for the process to be ready 52 | # to handle requests from services started subsequently which depend 53 | # on this one. As a last resort, sleep for some time. 54 | } 55 | 56 | # Function that stops the daemon/service 57 | do_stop() 58 | { 59 | # Return 60 | # 0 if daemon has been stopped 61 | # 1 if daemon was already stopped 62 | # 2 if daemon could not be stopped 63 | # other if a failure occurred 64 | start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME 65 | RETVAL="$?" 66 | [ "$RETVAL" = 2 ] && return 2 67 | # Wait for children to finish too if this is a daemon that forks 68 | # and if the daemon is only ever run from this initscript. 69 | # If the above conditions are not satisfied then add some other code 70 | # that waits for the process to drop all resources that could be 71 | # needed by services started subsequently. A last resort is to 72 | # sleep for some time. 73 | start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON 74 | [ "$?" = 2 ] && return 2 75 | # Many daemons don't delete their pidfiles when they exit. 76 | rm -f $PIDFILE 77 | return "$RETVAL" 78 | } 79 | 80 | # 81 | # Rotate log files 82 | # 83 | do_rotate() { 84 | start-stop-daemon --stop --signal USR1 --quiet --pidfile $PIDFILE --name $NAME 85 | return 0 86 | } 87 | 88 | case "$1" in 89 | start) 90 | [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" 91 | do_start 92 | case "$?" in 93 | 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 94 | 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; 95 | esac 96 | ;; 97 | stop) 98 | [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" 99 | do_stop 100 | case "$?" in 101 | 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 102 | 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; 103 | esac 104 | ;; 105 | status) 106 | status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? 107 | ;; 108 | restart|force-reload) 109 | log_daemon_msg "Restarting $DESC" "$NAME" 110 | do_stop 111 | case "$?" in 112 | 0|1) 113 | do_start 114 | case "$?" in 115 | 0) log_end_msg 0 ;; 116 | 1) log_end_msg 1 ;; # Old process is still running 117 | *) log_end_msg 1 ;; # Failed to start 118 | esac 119 | ;; 120 | *) 121 | # Failed to stop 122 | log_end_msg 1 123 | ;; 124 | esac 125 | ;; 126 | rotate) 127 | log_daemon_msg "Re-opening $DESC log files" "$NAME" 128 | do_rotate 129 | log_end_msg $? 130 | ;; 131 | *) 132 | echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload|rotate}" >&2 133 | exit 3 134 | ;; 135 | esac 136 | 137 | : 138 | -------------------------------------------------------------------------------- /debian/f2b.install: -------------------------------------------------------------------------------- 1 | etc/f2b/conf-available/*-source-files.conf 2 | etc/f2b/conf-available/*-source-portknock.conf 3 | etc/f2b/conf-available/*-filter-preg.conf 4 | etc/f2b/conf-available/*-backend-exec-*.conf 5 | etc/f2b/conf-available/*-jail-*.conf 6 | etc/f2b/conf-enabled 7 | etc/f2b/f2b.conf 8 | usr/bin/f2b-*-test 9 | usr/bin/f2bc 10 | usr/sbin/f2b 11 | usr/lib/*/f2b/source_files.so 12 | usr/lib/*/f2b/source_portknock.so 13 | usr/lib/*/f2b/filter_preg.so 14 | usr/lib/*/f2b/backend_exec.so 15 | -------------------------------------------------------------------------------- /debian/f2b.links: -------------------------------------------------------------------------------- 1 | usr/share/f2b/filters etc/f2b/filters 2 | -------------------------------------------------------------------------------- /debian/f2b.logrotate: -------------------------------------------------------------------------------- 1 | /var/log/f2b.log { 2 | rotate 7 3 | daily 4 | missingok 5 | notifempty 6 | delaycompress 7 | compress 8 | postrotate 9 | [ -e /var/run/f2b.pid ] && invoke-rc.d f2b rotate > /dev/null 10 | endscript 11 | } 12 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # See debhelper(7) (uncomment to enable) 3 | # output every command that modifies files on the build system. 4 | #DH_VERBOSE = 1 5 | 6 | DPKG_EXPORT_BUILDFLAGS = 1 7 | include /usr/share/dpkg/default.mk 8 | 9 | export DEB_BUILD_MAINT_OPTIONS = hardening=+all 10 | 11 | export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic 12 | export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed 13 | 14 | %: 15 | dh $@ 16 | 17 | # debmake generated override targets 18 | # This is example for Cmake (See http://bugs.debian.org/641051 ) 19 | override_dh_auto_configure: 20 | dh_auto_configure -- \ 21 | -DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH) \ 22 | -DWITH_REDIS=ON \ 23 | -DWITH_IPSET=ON \ 24 | -DWITH_HARDENING=ON 25 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /debian/watch: -------------------------------------------------------------------------------- 1 | version=3 2 | opts=filenamemangle=s/.+\/v?(\d\S*)\.tar\.gz/f2b-$1\.tar\.gz/ \ 3 | https://github.com/AdUser/f2b/releases .*/v?(\d\S*)\.tar\.gz 4 | -------------------------------------------------------------------------------- /docs/install.md: -------------------------------------------------------------------------------- 1 | Build process is quite simple: 2 | 3 | cmake . 4 | make 5 | make test 6 | 7 | Then, if everything builds fine, you may install files in proper places. 8 | 9 | sudo make install 10 | 11 | NOTE: This is old good "slackware-way" of installing programs. 12 | You may want to use [checkinstall](http://checkinstall.izto.org) or something like this. 13 | 14 | Actual list of configurable options for first line can be found at top of `CMakeLists.txt`. 15 | It looks like this: 16 | 17 | option(SYMBOL "Option description" DEFAULT_VALUE) 18 | 19 | Option can be (re)defined with `-DSYMBOL=ON` or `-DSYMBOL=OFF` on cmake command line: 20 | 21 | cmake -D CMAKE_BUILD_TYPE=Release -DWIITH_HARDENING=ON . 22 | 23 | Other noticeable options are: 24 | 25 | * `CMAKE_BUILD_TYPE` (Debug, Release or unset) -- sets compiler optimization level and debugging info. Set to "Release" for production code. 26 | * `CMAKE_C_COMPILER` -- allows specify another compiler 27 | * `CMAKE_INSTALL_PREFIX` -- set root of install dir ($DESTDIR also will be prefixed if set). 28 | * `INIT_SCRIPT` -- install system init script (values: off/openrc/systemd, default: off) 29 | 30 | After building you may type `sudo make install` to install compiled binaries and other files. 31 | Default install layout is: 32 | 33 | /etc/f2b <- configs location 34 | /etc/f2b/conf-available <- all available config parts 35 | /etc/f2b/conf-enabled <- enabled config parts 36 | /usr/bin <- non-root binaries 37 | /usr/sbin <- root binaries 38 | /usr/lib <- loadable modules 39 | /usr/share/f2b <- patterns collection 40 | /var/lib/f2b <- files with saved states of jails 41 | 42 | After install you need additional steps before configuring f2b. 43 | 44 | cd /etc/f2b 45 | # make symlink to patterns collection 46 | ln -s /usr/share/f2b/filters filters 47 | # enable some config parts 48 | # you need at least one backend, filter and source 49 | cd /etc/f2b/conf-enabled 50 | ln -s ../conf-available/05-source-files.conf ./ 51 | ls -n ../conf-available/10-backend-exec-ipset.conf ./ 52 | ln -s ../conf-available/15-filter-preg.conf ./ 53 | # make config part for local settings 54 | touch 99-local.conf 55 | 56 | Now you may proceed for configuration and first launch. 57 | -------------------------------------------------------------------------------- /filters/README.txt: -------------------------------------------------------------------------------- 1 | - Where have all filters gone? 2 | - Now it located in their own repository, for convinient maintenance 3 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 2 | 3 | set(SOURCES "daemon.c" "strlcpy.c" "config.c" "buf.c" "log.c" "matches.c" "ipaddr.c" 4 | "appconfig.c" "statefile.c" "event.c" "source.c" "filter.c" "backend.c" "jail.c") 5 | 6 | if (WITH_CSOCKET) 7 | list(APPEND SOURCES "md5.c" "commands.c" "csocket.c") 8 | add_definitions("-DWITH_CSOCKET") 9 | endif () 10 | 11 | add_executable("f2b" ${SOURCES}) 12 | install(TARGETS "f2b" RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}) 13 | 14 | if (WITH_CLIENT) 15 | set(SOURCES "strlcpy.c" "client.c") 16 | add_executable("f2bc" ${SOURCES}) 17 | if (WITH_READLINE) 18 | add_definitions("-DWITH_READLINE") 19 | target_link_libraries("f2bc" "readline") 20 | endif () 21 | install(TARGETS "f2bc" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) 22 | endif () 23 | 24 | set(SOURCES "strlcpy.c" "source-test.c" "log.c" "config.c" "source.c") 25 | add_executable("f2b-source-test" ${SOURCES}) 26 | 27 | set(SOURCES "strlcpy.c" "filter-test.c" "log.c" "config.c" "filter.c") 28 | add_executable("f2b-filter-test" ${SOURCES}) 29 | 30 | set(SOURCES "strlcpy.c" "backend-test.c" "log.c" "config.c" "backend.c") 31 | add_executable("f2b-backend-test" ${SOURCES}) 32 | 33 | set(SOURCES "strlcpy.c" "csocket-test.c" "log.c" "buf.c" "config.c" "commands.c" "csocket.c" "md5.c") 34 | add_executable("f2b-csocket-test" ${SOURCES}) 35 | 36 | if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") 37 | target_link_libraries("f2b" "dl") 38 | target_link_libraries("f2b-source-test" "dl") 39 | target_link_libraries("f2b-filter-test" "dl") 40 | target_link_libraries("f2b-backend-test" "dl") 41 | endif () 42 | 43 | set_target_properties("f2b" "f2b-source-test" "f2b-filter-test" "f2b-backend-test" 44 | PROPERTIES INSTALL_RPATH "${CMAKE_INSTALL_FULL_LIBDIR}/${CNAME}") 45 | 46 | install(TARGETS "f2b-source-test" "f2b-filter-test" "f2b-backend-test" 47 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) 48 | 49 | set(PLUGINS_PATH "${CMAKE_INSTALL_LIBDIR}/${CNAME}") 50 | add_subdirectory("backends") 51 | add_subdirectory("filters") 52 | add_subdirectory("sources") 53 | -------------------------------------------------------------------------------- /src/appconfig.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #include "common.h" 8 | #include "log.h" 9 | #include "config.h" 10 | #include "appconfig.h" 11 | 12 | #include 13 | #include 14 | 15 | f2b_appconfig_t appconfig = { 16 | .coredumps = false, 17 | .daemon = false, 18 | .uid = 0, 19 | .gid = 0, 20 | .nice = 0, 21 | .logdest = "file", 22 | .config_path = "/etc/f2b/f2b.conf", 23 | .logfile_path = "/var/log/f2b.log", 24 | .pidfile_path = DEFAULT_PIDFILE_PATH, 25 | .statedir_path = DEFAULT_STATEDIR_PATH, 26 | }; 27 | 28 | void 29 | f2b_appconfig_update(f2b_config_section_t *section) { 30 | f2b_config_param_t *pa, *pb; 31 | 32 | if (!section) 33 | return; 34 | 35 | if ((pa = f2b_config_param_find(section->param, "user")) != NULL) { 36 | struct passwd *pw; 37 | if ((pw = getpwnam(pa->value)) != NULL) 38 | appconfig.uid = pw->pw_uid, appconfig.gid = pw->pw_gid; 39 | } 40 | if ((pa = f2b_config_param_find(section->param, "group")) != NULL) { 41 | struct group *grp; 42 | if ((grp = getgrnam(pa->value)) != NULL) 43 | appconfig.gid = grp->gr_gid; 44 | } 45 | if ((pa = f2b_config_param_find(section->param, "nice")) != NULL) { 46 | appconfig.nice = atoi(pa->value); 47 | } 48 | 49 | if ((pa = f2b_config_param_find(section->param, "daemon")) != NULL) { 50 | appconfig.daemon = (strcmp(pa->value, "yes") == 0) ? true : false; 51 | } 52 | 53 | if ((pa = f2b_config_param_find(section->param, "coredumps")) != NULL) { 54 | appconfig.coredumps = (strcmp(pa->value, "yes") == 0) ? true : false; 55 | } 56 | 57 | if ((pa = f2b_config_param_find(section->param, "pidfile")) != NULL) 58 | strlcpy(appconfig.pidfile_path, pa->value, sizeof(appconfig.pidfile_path)); 59 | 60 | if ((pa = f2b_config_param_find(section->param, "statedir")) != NULL) 61 | strlcpy(appconfig.statedir_path, pa->value, sizeof(appconfig.statedir_path)); 62 | 63 | /* setup logging */ 64 | if ((pa = f2b_config_param_find(section->param, "loglevel")) != NULL) 65 | f2b_log_set_level(pa->value); 66 | 67 | pa = f2b_config_param_find(section->param, "logdest"); 68 | pb = f2b_config_param_find(section->param, "logfile"); 69 | if (pa) { 70 | strlcpy(appconfig.logdest, pa->value, sizeof(appconfig.logdest)); 71 | if (!appconfig.daemon && strcmp(pa->value, "stderr") == 0) { 72 | f2b_log_to_stderr(); 73 | } else if (strcmp(pa->value, "file") == 0) { 74 | if (pb && *pb->value != '\0') { 75 | strlcpy(appconfig.logfile_path, pb->value, sizeof(appconfig.logfile_path)); 76 | f2b_log_to_file(appconfig.logfile_path); 77 | } else { 78 | f2b_log_msg(log_warn, "you must set 'logfile' option with 'logdest = file'"); 79 | f2b_log_to_syslog(); 80 | } 81 | } else { 82 | f2b_log_to_syslog(); 83 | } 84 | } 85 | 86 | /* TODO: */ 87 | } 88 | -------------------------------------------------------------------------------- /src/appconfig.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #ifndef F2B_APPCONFIG_H_ 8 | #define F2B_APPCONFIG_H_ 9 | 10 | typedef struct f2b_appconfig_t { 11 | bool daemon; 12 | bool coredumps; 13 | int nice; 14 | uid_t uid; 15 | gid_t gid; 16 | char logdest[CONFIG_KEY_MAX]; 17 | char config_path[PATH_MAX]; 18 | char logfile_path[PATH_MAX]; 19 | char pidfile_path[PATH_MAX]; 20 | char statedir_path[PATH_MAX]; 21 | } f2b_appconfig_t; 22 | 23 | extern f2b_appconfig_t appconfig; 24 | 25 | void f2b_appconfig_update(f2b_config_section_t *section); 26 | 27 | #endif /* F2B_APPCONFIG_H_ */ 28 | -------------------------------------------------------------------------------- /src/backend-test.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #include "common.h" 8 | #include "config.h" 9 | #include "log.h" 10 | #include "backend.h" 11 | 12 | void usage() { 13 | fprintf(stderr, "Usage: backend-test \n"); 14 | exit(EXIT_FAILURE); 15 | } 16 | 17 | /* returns -1 on error, 0 for 'ban', 1 for 'check' and 3 for 'unban' 18 | * also fills buf with second arg after command name 19 | */ 20 | int 21 | parse_input(char *line, char *buf, size_t bufsize) { 22 | const char *ops[] = { "ban", "check", "unban", NULL }; 23 | int code = -1; 24 | 25 | assert(line != NULL); 26 | assert(buf != NULL); 27 | 28 | while (isspace(*line)) 29 | line++; 30 | if (*line == '\0') 31 | return -1; /* empty line */ 32 | 33 | for (size_t i = 0; ops[i] != NULL; i++) { 34 | size_t len = strlen(ops[i]); 35 | if (strncmp(line, ops[i], len) != 0) 36 | continue; 37 | code = i; 38 | line += len; 39 | break; 40 | } 41 | 42 | while (isspace(*line)) 43 | line++; 44 | if (*line == '\0') 45 | return -1; /* missing second arg */ 46 | 47 | for (char *p = line; *p != '\0'; p++) { 48 | if ((*p >= 'a' && *p <= 'f') || *p == '.' || *p == ':' || 49 | (*p >= 'A' && *p <= 'F') || isdigit(*p)) 50 | continue; /* valid ipaddr char */ 51 | /* everything else */ 52 | *p = '\0'; 53 | break; 54 | } 55 | 56 | if (*line == '\0') 57 | return -1; /* garbled second arg */ 58 | 59 | strlcpy(buf, line, bufsize); 60 | return code; 61 | } 62 | 63 | int main(int argc, char *argv[]) { 64 | f2b_config_t config; 65 | f2b_config_section_t *section = NULL; 66 | f2b_backend_t *backend = NULL; 67 | bool (*handler)(f2b_backend_t *, const char *) = NULL; 68 | char line[256]; 69 | char addr[64]; 70 | 71 | if (argc < 3) 72 | usage(); 73 | 74 | memset(&config, 0x0, sizeof(config)); 75 | if (f2b_config_load(&config, argv[1], false) != true) { 76 | f2b_log_msg(log_fatal, "can't load config"); 77 | return EXIT_FAILURE; 78 | } 79 | 80 | if (config.backends == NULL) { 81 | f2b_log_msg(log_fatal, "no backends found in config"); 82 | return EXIT_FAILURE; 83 | } else { 84 | section = config.backends; 85 | } 86 | 87 | if ((backend = f2b_backend_create(section->name, argv[2])) == NULL) { 88 | f2b_log_msg(log_fatal, "can't create backend '%s'", section->name); 89 | return EXIT_FAILURE; 90 | } 91 | 92 | if (!f2b_backend_init(backend, section)) { 93 | f2b_log_msg(log_fatal, "can't init backend '%s'", backend->name); 94 | return EXIT_FAILURE; 95 | } 96 | 97 | if (!f2b_backend_start(backend)) { 98 | f2b_log_msg(log_error, "action 'ban' failed"); 99 | goto cleanup; 100 | } 101 | 102 | fputs("usage: \n", stdout); 103 | fputs(" available commands: ban, unban, check\n", stdout); 104 | fputs("press Ctrl-D for exit\n", stdout); 105 | while (1) { 106 | fputs("test >> ", stdout); 107 | if (!fgets(line, sizeof(line) - 1, stdin)) { 108 | if (feof(stdin)) { 109 | fputc('\n', stdout); 110 | } else { 111 | fputs("read error\n", stdout); 112 | } 113 | break; 114 | } 115 | if (line[0] == '\n') 116 | continue; 117 | switch (parse_input(line, addr, sizeof(addr))) { 118 | case 0 : handler = f2b_backend_ban; break; 119 | case 1 : handler = f2b_backend_check; break; 120 | case 2 : handler = f2b_backend_unban; break; 121 | default : 122 | f2b_log_msg(log_error, "can't parse input"); 123 | continue; 124 | break; 125 | } /* switch */ 126 | if (handler(backend, addr)) { 127 | fputs("ok\n", stdout); 128 | } else { 129 | fputs("failure\n", stdout); 130 | } 131 | } /* while */ 132 | 133 | cleanup: 134 | f2b_backend_stop(backend); 135 | f2b_backend_destroy(backend); 136 | f2b_config_free(&config); 137 | 138 | return EXIT_SUCCESS; 139 | } 140 | -------------------------------------------------------------------------------- /src/backend.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #include "common.h" 8 | #include "config.h" 9 | #include "log.h" 10 | #include "mod-defs.h" 11 | #include "backend.h" 12 | 13 | #include 14 | 15 | #define BACKEND_LIBRARY_PARAM "load" 16 | 17 | f2b_backend_t * 18 | f2b_backend_create(const char *name, const char *init) { 19 | f2b_backend_t *backend = NULL; 20 | 21 | if ((backend = calloc(1, sizeof(f2b_backend_t))) == NULL) 22 | return NULL; 23 | 24 | strlcpy(backend->name, name, sizeof(backend->name)); 25 | strlcpy(backend->init, init, sizeof(backend->init)); 26 | 27 | return backend; 28 | } 29 | 30 | bool 31 | f2b_backend_init(f2b_backend_t *backend, f2b_config_section_t *config) { 32 | f2b_config_param_t *param = NULL; 33 | int flags = RTLD_NOW | RTLD_LOCAL; 34 | const char *dlerr = NULL; 35 | 36 | assert(config != NULL); 37 | assert(config->type == t_backend); 38 | 39 | param = f2b_config_param_find(config->param, BACKEND_LIBRARY_PARAM); 40 | if (!param) { 41 | f2b_log_msg(log_error, "can't find '%s' param in backend config", BACKEND_LIBRARY_PARAM); 42 | return false; 43 | } 44 | 45 | if ((backend->h = dlopen(param->value, flags)) == NULL) 46 | goto cleanup; 47 | if ((*(void **) (&backend->create) = dlsym(backend->h, "create")) == NULL) 48 | goto cleanup; 49 | if ((*(void **) (&backend->config) = dlsym(backend->h, "config")) == NULL) 50 | goto cleanup; 51 | if ((*(void **) (&backend->state) = dlsym(backend->h, "state")) == NULL) 52 | goto cleanup; 53 | if ((*(void **) (&backend->logcb) = dlsym(backend->h, "logcb")) == NULL) 54 | goto cleanup; 55 | if ((*(void **) (&backend->start) = dlsym(backend->h, "start")) == NULL) 56 | goto cleanup; 57 | if ((*(void **) (&backend->stop) = dlsym(backend->h, "stop")) == NULL) 58 | goto cleanup; 59 | if ((*(void **) (&backend->ping) = dlsym(backend->h, "ping")) == NULL) 60 | goto cleanup; 61 | if ((*(void **) (&backend->ban) = dlsym(backend->h, "ban")) == NULL) 62 | goto cleanup; 63 | if ((*(void **) (&backend->check) = dlsym(backend->h, "check")) == NULL) 64 | goto cleanup; 65 | if ((*(void **) (&backend->unban) = dlsym(backend->h, "unban")) == NULL) 66 | goto cleanup; 67 | if ((*(void **) (&backend->destroy) = dlsym(backend->h, "destroy")) == NULL) 68 | goto cleanup; 69 | 70 | if ((backend->cfg = backend->create(backend->init)) == NULL) { 71 | f2b_log_msg(log_error, "backend create config failed"); 72 | goto cleanup; 73 | } 74 | 75 | if ((backend->state(backend->cfg) & MOD_TYPE_BACKEND) == 0) { 76 | f2b_log_msg(log_error, "loaded module is not backend type"); 77 | goto cleanup; 78 | } 79 | 80 | backend->logcb(backend->cfg, f2b_log_mod_cb); 81 | 82 | /* try init */ 83 | for (param = config->param; param != NULL; param = param->next) { 84 | if (strcmp(param->name, BACKEND_LIBRARY_PARAM) == 0) 85 | continue; 86 | if (backend->config(backend->cfg, param->name, param->value)) 87 | continue; 88 | f2b_log_msg(log_warn, "param pair not accepted by backend '%s': %s=%s", 89 | config->name, param->name, param->value); 90 | } 91 | 92 | if ((backend->flags = backend->state(backend->cfg)) < 0) { 93 | f2b_log_msg(log_error, "can't get module state"); 94 | goto cleanup; 95 | } 96 | 97 | if (backend->flags & MOD_WRONG_API) { 98 | f2b_log_msg(log_error, "module reports wrong api version"); 99 | goto cleanup; 100 | } 101 | 102 | if (backend->flags & MOD_IS_READY) 103 | return true; 104 | 105 | /* still not ready */ 106 | f2b_log_msg(log_error, "backend '%s' not fully configured", config->name); 107 | 108 | cleanup: 109 | dlerr = dlerror(); 110 | if (dlerr) 111 | f2b_log_msg(log_error, "backend load error: %s", dlerr); 112 | if (backend->h) { 113 | if (backend->cfg && backend->destroy) { 114 | backend->destroy(backend->cfg); 115 | backend->cfg = NULL; 116 | } 117 | dlclose(backend->h); 118 | backend->create = NULL; 119 | backend->config = NULL; 120 | backend->state = NULL; 121 | backend->logcb = NULL; 122 | backend->start = NULL; 123 | backend->stop = NULL; 124 | backend->ping = NULL; 125 | backend->ban = NULL; 126 | backend->unban = NULL; 127 | backend->check = NULL; 128 | backend->destroy = NULL; 129 | backend->h = NULL; 130 | } 131 | return false; 132 | } 133 | 134 | void 135 | f2b_backend_destroy(f2b_backend_t *backend) { 136 | if (!backend) return; 137 | if (backend->h) { 138 | if (backend->cfg) 139 | backend->destroy(backend->cfg); 140 | dlclose(backend->h); 141 | backend->h = NULL; 142 | } 143 | free(backend); 144 | } 145 | 146 | #define BACKEND_CMD_ARG0(CMD, RETURNS) \ 147 | RETURNS \ 148 | f2b_backend_ ## CMD(f2b_backend_t *backend) { \ 149 | assert(backend != NULL); \ 150 | return backend->CMD(backend->cfg); \ 151 | } 152 | 153 | #define BACKEND_CMD_ARG1(CMD, RETURNS) \ 154 | RETURNS \ 155 | f2b_backend_ ## CMD(f2b_backend_t *backend, const char *ip) { \ 156 | assert(backend != NULL); \ 157 | return backend->CMD(backend->cfg, ip); \ 158 | } 159 | 160 | BACKEND_CMD_ARG0(start, bool) 161 | BACKEND_CMD_ARG0(stop, bool) 162 | BACKEND_CMD_ARG0(ping, bool) 163 | 164 | BACKEND_CMD_ARG1(check, bool) 165 | BACKEND_CMD_ARG1(ban, bool) 166 | BACKEND_CMD_ARG1(unban, bool) 167 | -------------------------------------------------------------------------------- /src/backend.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #ifndef F2B_BACKEND_H_ 8 | #define F2B_BACKEND_H_ 9 | 10 | /** 11 | * @file 12 | * This header describes backend module definition and related routines 13 | */ 14 | 15 | /** backend module definition */ 16 | typedef struct f2b_backend_t { 17 | void *h; /**< dlopen handler */ 18 | void *cfg; /**< opaque pointer of module config */ 19 | int flags; /**< module flags (update with state() call) */ 20 | /* handlers */ 21 | /** dlsym pointer to handler of @a create command */ 22 | void *(*create) (const char *id); 23 | /** dlsym pointer to handler of @a config command */ 24 | bool (*config) (void *cfg, const char *key, const char *value); 25 | /** dlsym pointer to handler of @a state command */ 26 | int (*state) (void *cfg); 27 | /** dlsym pointer to handler of @a error command */ 28 | void (*logcb) (void *cfg, void (*cb)(log_msgtype_t lvl, const char *msg)); 29 | /** dlsym pointer to handler of @a start command */ 30 | bool (*start) (void *cfg); 31 | /** dlsym pointer to handler of @a stop command */ 32 | bool (*stop) (void *cfg); 33 | /** dlsym pointer to handler of @a ping command */ 34 | bool (*ping) (void *cfg); 35 | /** dlsym pointer to handler of @a ban command */ 36 | bool (*ban) (void *cfg, const char *ip); 37 | /** dlsym pointer to handler of @a check command */ 38 | bool (*check) (void *cfg, const char *ip); 39 | /** dlsym pointer to handler of @a unban command */ 40 | bool (*unban) (void *cfg, const char *ip); 41 | /** dlsym pointer to handler of @a destroy command */ 42 | void (*destroy) (void *cfg); 43 | /* config variables */ 44 | char name[CONFIG_KEY_MAX]; /**< backend name from config (eg [backend:$NAME] section) */ 45 | char init[CONFIG_VAL_MAX]; /**< backend init string (eg `backend = NAME:$INIT_STRING` line from jail section) */ 46 | } f2b_backend_t; 47 | 48 | /** 49 | * @brief Allocate new backend struct and fill name/init fields 50 | * @param name Module name 51 | * @param init Module init string 52 | * @returns Pointer to allocated module struct or NULL on error 53 | */ 54 | f2b_backend_t * f2b_backend_create (const char *name, const char *init); 55 | 56 | /** 57 | * @brief Initialize and configure backend 58 | * @param config Pointer to config section with module description 59 | * @return true on success, false on error 60 | */ 61 | bool f2b_backend_init(f2b_backend_t *backend, f2b_config_section_t *config); 62 | 63 | /** 64 | * @brief Free module metadata 65 | * @param b Pointer to module struct 66 | */ 67 | void f2b_backend_destroy(f2b_backend_t *b); 68 | 69 | /* helpers */ 70 | /** 71 | * @brief Start given backend 72 | * @param b Pointer to backend struct 73 | * @returns true on success, false on error with setting last error 74 | */ 75 | bool f2b_backend_start (f2b_backend_t *b); 76 | /** 77 | * @brief Stop given backend 78 | * @param b Pointer to backend struct 79 | * @returns true on success, false on error with setting last error 80 | */ 81 | bool f2b_backend_stop (f2b_backend_t *b); 82 | /** 83 | * @brief Perform maintenance of given backend 84 | * @param b Pointer to backend struct 85 | * @returns true on success, false on error with setting last error 86 | */ 87 | bool f2b_backend_ping (f2b_backend_t *b); 88 | 89 | /** 90 | * @brief Send command to ban given ip 91 | * @param b Pointer to backend struct 92 | * @param ip IP address 93 | * @returns true on success, false on error with setting last error 94 | */ 95 | bool f2b_backend_ban (f2b_backend_t *b, const char *ip); 96 | /** 97 | * @brief Check is given ip already banned by backend 98 | * @param b Pointer to backend struct 99 | * @param ip IP address 100 | * @returns true on success, false on error with setting last error 101 | * @note Not all backends support this command 102 | */ 103 | bool f2b_backend_check (f2b_backend_t *b, const char *ip); 104 | /** 105 | * @brief Send command to release given ip 106 | * @param b Pointer to backend struct 107 | * @param ip IP address 108 | * @returns true on success, false on error with setting last error 109 | */ 110 | bool f2b_backend_unban (f2b_backend_t *b, const char *ip); 111 | 112 | #endif /* F2B_BACKEND_H_ */ 113 | -------------------------------------------------------------------------------- /src/backends/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 2 | unset(CMAKE_SHARED_MODULE_PREFIX) 3 | set(BACKENDS "") 4 | 5 | add_library("b_exec" MODULE "exec.c" "../strlcpy.c") 6 | list(APPEND BACKENDS "exec") 7 | 8 | if (WITH_REDIS) 9 | pkg_check_modules(REDIS "hiredis" REQUIRED) 10 | add_library("b_redis" MODULE "redis.c" "../strlcpy.c") 11 | target_link_libraries("b_redis" "hiredis") 12 | list(APPEND BACKENDS "redis") 13 | endif () 14 | 15 | if (WITH_IPSET) 16 | pkg_check_modules(IPSET "libipset" REQUIRED) 17 | if (IPSET_VERSION VERSION_LESS 7) 18 | add_library("b_ipset" MODULE "ipset6.c" "../strlcpy.c") 19 | else () 20 | add_library("b_ipset" MODULE "ipset7.c" "../strlcpy.c") 21 | endif () 22 | target_link_libraries("b_ipset" "ipset") 23 | list(APPEND BACKENDS "ipset") 24 | endif () 25 | 26 | foreach (BACKEND IN LISTS BACKENDS) 27 | set_target_properties("b_${BACKEND}" PROPERTIES OUTPUT_NAME "backend_${BACKEND}") 28 | install(TARGETS "b_${BACKEND}" LIBRARY DESTINATION ${PLUGINS_PATH}) 29 | endforeach () 30 | 31 | message(STATUS "- Backends : ${BACKENDS}") 32 | -------------------------------------------------------------------------------- /src/backends/backend.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | 8 | typedef struct cfg_id_t { 9 | struct cfg_id_t *next; 10 | char name[ID_MAX + 1]; 11 | size_t count; 12 | } cfg_id_t; 13 | 14 | /* this list needed for tracking backend usage with `shared = yes` */ 15 | cfg_id_t *ids_usage = NULL; 16 | 17 | static size_t 18 | usage_inc(const char *id) { 19 | cfg_id_t *e = NULL; 20 | 21 | assert(id != NULL); 22 | 23 | for (e = ids_usage; e != NULL; e = e->next) { 24 | if (strcmp(e->name, id) != 0) 25 | continue; 26 | /* found */ 27 | e->count++; 28 | return e->count; 29 | } 30 | /* not found or list is empty */ 31 | e = calloc(1, sizeof(cfg_id_t)); 32 | strlcpy(e->name, id, sizeof(e->name)); 33 | e->count++; 34 | e->next = ids_usage; 35 | ids_usage = e; 36 | return e->count; 37 | } 38 | 39 | static size_t 40 | usage_dec(const char *id) { 41 | cfg_id_t *e = NULL; 42 | 43 | assert(id != NULL); 44 | 45 | for (e = ids_usage; e != NULL; e = e->next) { 46 | if (strcmp(e->name, id) != 0) 47 | continue; 48 | /* found */ 49 | if (e->count > 0) 50 | e->count--; 51 | return e->count; 52 | } 53 | 54 | /* not found or list is empty */ 55 | return 0; 56 | } 57 | 58 | static void 59 | logcb_stub(enum loglevel lvl, const char *str) { 60 | assert(str != NULL); 61 | (void)(lvl); 62 | (void)(str); 63 | } 64 | 65 | __attribute__ ((format (printf, 3, 4))) 66 | static void 67 | log_msg(const cfg_t *cfg, enum loglevel lvl, const char *format, ...) { 68 | char buf[4096] = ""; 69 | va_list args; 70 | size_t len; 71 | 72 | len = snprintf(buf, sizeof(buf), "backend/%s: ", MODNAME); 73 | va_start(args, format); 74 | vsnprintf(buf + len, sizeof(buf) - len, format, args); 75 | va_end(args); 76 | 77 | cfg->logcb(lvl, buf); 78 | } 79 | 80 | void 81 | logcb(cfg_t *cfg, void (*cb)(enum loglevel lvl, const char *msg)) { 82 | assert(cfg != NULL); 83 | assert(cb != NULL); 84 | 85 | cfg->logcb = cb; 86 | } 87 | 88 | int 89 | state(cfg_t *cfg) { 90 | assert(cfg != NULL); 91 | return cfg->flags; 92 | } 93 | -------------------------------------------------------------------------------- /src/backends/backend.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #include 8 | #include 9 | 10 | #include "../mod-defs.h" 11 | #include "../mod-api.h" 12 | 13 | /** 14 | * @file 15 | * This header describes module API of type 'backend' 16 | * 17 | * Sample workflow of module usage: 18 | * 19 | * @msc 20 | * f2b, backend; 21 | * f2b => backend [label="create(init)"]; 22 | * f2b << backend [label="module handler, cfg_t *cfg"]; 23 | * |||; 24 | * f2b => backend [label="config(cfg, param, value)"]; 25 | * f2b << backend [label="true"]; 26 | * f2b => backend [label="config(cfg, param, value)"]; 27 | * f2b << backend [label="true"]; 28 | * f2b => backend [label="config(cfg, param, value)"]; 29 | * f2b <<= backend [label="logcb(level, char *msg)"]; 30 | * f2b << backend [label="false"]; 31 | * |||; 32 | * f2b => backend [label="state(cfg)"]; 33 | * f2b << backend [label="int"]; 34 | * --- [label="check for MOD_IS_READY flag"]; 35 | * f2b => backend [label="start()"]; 36 | * f2b << backend [label="true"]; 37 | * --- [label="backend is ready to use"]; 38 | * f2b => backend [label="ping(cfg)"]; 39 | * f2b << backend [label="true"]; 40 | * f2b => backend [label="ping(cfg)"]; 41 | * f2b << backend [label="true"]; 42 | * ... [label="time passed"]; 43 | * f2b => backend [label="ban(cfg, ip)"]; 44 | * f2b << backend [label="true"]; 45 | * f2b => backend [label="ban(cfg, ip)"]; 46 | * f2b <<= backend [label="logcb(level, char *msg)"]; 47 | * f2b << backend [label="false"]; 48 | * ... [label="time passed"]; 49 | * f2b => backend [label="ping(cfg)"]; 50 | * f2b << backend [label="true"]; 51 | * ... [label="time passed"]; 52 | * f2b => backend [label="unban(cfg, ip)"]; 53 | * f2b << backend [label="true"]; 54 | * ... [label="time passed"]; 55 | * f2b => backend [label="stop(cfg)"]; 56 | * f2b << backend [label="true"]; 57 | * --- [label="now you may config(), start() or destroy() backend"]; 58 | * f2b => backend [label="destroy(cfg)"]; 59 | * @endmsc 60 | */ 61 | 62 | /** 63 | * @def TOKEN_ID 64 | * Only 'exec' backend: placeholder for module id, in ban(), check() and unban() actions 65 | */ 66 | #define TOKEN_ID "" 67 | /** 68 | * @def TOKEN_IP 69 | * Only 'exec' backend: placeholder for ip in ban(), check() and unban() actions 70 | */ 71 | #define TOKEN_IP "" 72 | /** 73 | * @def ID_MAX 74 | * maximum lenth of id from @a create() 75 | */ 76 | #define ID_MAX 32 77 | 78 | /** @note config() parameters are module-specific, but each module 79 | * at least should handle 'shared' option */ 80 | 81 | /** type-specific module exportable routines */ 82 | 83 | /** 84 | * @brief Allocate resources and start processing 85 | * @param cfg Module handler 86 | * @returns true on success, false on error 87 | */ 88 | extern bool start(cfg_t *cfg); 89 | /** 90 | * @brief Deallocate resources, prepare for module destroy 91 | * @param cfg Module handler 92 | * @returns true on success 93 | */ 94 | extern bool stop(cfg_t *cfg); 95 | /** 96 | * @brief Module maintenance routine 97 | * @param cfg Module handler 98 | * @returns true on success 99 | */ 100 | extern bool ping(cfg_t *cfg); 101 | /** 102 | * @brief Make a rabbit disappear 103 | * @param cfg Module handler 104 | * @param ip IP address 105 | * @returns true on success, false on error 106 | */ 107 | extern bool ban(cfg_t *cfg, const char *ip); 108 | /** 109 | * @brief Check is some ip already banned 110 | * @param cfg Module handler 111 | * @param ip IP address 112 | * @returns true on success, false on error 113 | * @note If this action is meaningless for backend it should return true 114 | */ 115 | extern bool check(cfg_t *cfg, const char *ip); 116 | /** 117 | * @brief Make a rabbit appear again 118 | * @param cfg Module handler 119 | * @param ip IP address 120 | * @returns true on success, false on error 121 | * @note If this action is meaningless for backend it should return true 122 | */ 123 | extern bool unban(cfg_t *cfg, const char *ip); 124 | -------------------------------------------------------------------------------- /src/backends/exec.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "../strlcpy.h" 17 | 18 | #include "backend.h" 19 | #define MODNAME "exec" 20 | 21 | typedef struct cmd_t { 22 | struct cmd_t *next; 23 | char *orig; /**< stores original command, used in log messages */ 24 | char *args; /**< stores path of cmd & args, delimited by '\0' */ 25 | char **argv; /**< stores array of pointers to args + NULL */ 26 | size_t argc; /**< args count */ 27 | size_t pos_ip; /**< index+1 in argv[] where to insert IP address (zero means "no placeholder") */ 28 | size_t pos_id; /**< index+1 in argv[] where to insert IP address (zero means "no placeholder") */ 29 | } cmd_t; 30 | 31 | struct _config { 32 | char name[ID_MAX + 1]; 33 | void (*logcb)(enum loglevel lvl, const char *msg); 34 | time_t timeout; 35 | int flags; 36 | bool shared; 37 | cmd_t *start; 38 | cmd_t *stop; 39 | cmd_t *ban; 40 | cmd_t *unban; 41 | cmd_t *check; 42 | }; 43 | 44 | #include "backend.c" 45 | 46 | static cmd_t * 47 | cmd_from_str(const char *str) { 48 | cmd_t *cmd = NULL; 49 | const char *delim = " \t"; 50 | char *token, *saveptr, **argv; 51 | 52 | assert(str != NULL); 53 | 54 | if ((cmd = calloc(1, sizeof(cmd_t))) == NULL) 55 | return NULL; 56 | 57 | if ((cmd->orig = strdup(str)) == NULL) 58 | goto cleanup; 59 | if ((cmd->args = strdup(str)) == NULL) 60 | goto cleanup; 61 | 62 | cmd->argc = 1; 63 | if ((cmd->argv = calloc(2, sizeof(cmd->argv))) == NULL) 64 | goto cleanup; 65 | cmd->argv[cmd->argc] = NULL; 66 | 67 | strtok_r(cmd->args, delim, &saveptr); 68 | cmd->argv[0] = cmd->args; 69 | 70 | while ((token = strtok_r(NULL, delim, &saveptr)) != NULL) { 71 | if ((argv = realloc(cmd->argv, sizeof(cmd->argv) * (cmd->argc + 2))) == NULL) 72 | goto cleanup; 73 | cmd->argv = argv; 74 | if (strcmp(token, TOKEN_ID) == 0) 75 | cmd->pos_id = cmd->argc; 76 | if (strcmp(token, TOKEN_IP) == 0) 77 | cmd->pos_ip = cmd->argc; 78 | cmd->argv[cmd->argc] = token; 79 | cmd->argc++; 80 | } 81 | cmd->argv[cmd->argc] = NULL; 82 | 83 | return cmd; 84 | 85 | cleanup: 86 | free(cmd->orig); 87 | free(cmd->args); 88 | free(cmd->argv); 89 | free(cmd); 90 | return NULL; 91 | } 92 | 93 | static cmd_t * 94 | cmd_list_append(cmd_t *list, cmd_t *cmd) { 95 | cmd_t *c = NULL; 96 | 97 | assert(cmd != NULL); 98 | 99 | if (!list) 100 | return cmd; 101 | 102 | for (c = list; c->next != NULL; c = c->next); 103 | 104 | c->next = cmd; 105 | return list; 106 | } 107 | 108 | static void 109 | cmd_list_destroy(cmd_t *list) { 110 | cmd_t *next = NULL; 111 | 112 | for (; list != NULL; list = next) { 113 | next = list->next; 114 | free(list->orig); 115 | free(list->args); 116 | free(list->argv); 117 | free(list); 118 | } 119 | } 120 | 121 | static bool 122 | cmd_list_exec(cfg_t *cfg, cmd_t *list, const char *ip) { 123 | int status = 0; 124 | pid_t pid; 125 | 126 | for (cmd_t *cmd = list; cmd != NULL; cmd = cmd->next) { 127 | pid = fork(); 128 | if (pid == 0) { 129 | /* child */ 130 | if (cmd->pos_ip && ip) 131 | cmd->argv[cmd->pos_ip] = strdup(ip); 132 | if (cmd->pos_id) 133 | cmd->argv[cmd->pos_id] = strdup(cfg->name); 134 | if (cfg->timeout) 135 | alarm(cfg->timeout); 136 | execv(cmd->args, cmd->argv); 137 | } else if (pid > 0) { 138 | /* parent */ 139 | waitpid(pid, &status, 0); 140 | if (WIFEXITED(status) && WEXITSTATUS(status) == 0) 141 | continue; 142 | if (WIFEXITED(status)) { 143 | log_msg(cfg, error, "cmd '%s' terminated with code %d", cmd->orig, WEXITSTATUS(status)); 144 | } else if (WIFSIGNALED(status)) { 145 | log_msg(cfg, error, "cmd '%s' terminated by signal %d", cmd->orig, WTERMSIG(status)); 146 | } 147 | return false; 148 | } else { 149 | /* parent, fork error */ 150 | log_msg(cfg, error, "can't fork(): %s", strerror(errno)); 151 | return false; 152 | } 153 | } 154 | 155 | return true; 156 | } 157 | 158 | /* handlers */ 159 | cfg_t * 160 | create(const char *id) { 161 | cfg_t *cfg = NULL; 162 | 163 | assert(id != NULL); 164 | 165 | if ((cfg = calloc(1, sizeof(cfg_t))) == NULL) 166 | return NULL; 167 | strlcpy(cfg->name, id, sizeof(cfg->name)); 168 | 169 | cfg->logcb = &logcb_stub; 170 | cfg->flags |= MOD_IS_READY; 171 | cfg->flags |= MOD_TYPE_BACKEND; 172 | return cfg; 173 | } 174 | 175 | #define CREATE_CMD(TYPE) \ 176 | if (strcmp(key, #TYPE) == 0) { \ 177 | cmd = cmd_from_str(value); \ 178 | if (cmd) { \ 179 | cfg->TYPE = cmd_list_append(cfg->TYPE, cmd); \ 180 | return true; \ 181 | } \ 182 | return false; \ 183 | } 184 | 185 | bool 186 | config(cfg_t *cfg, const char *key, const char *value) { 187 | cmd_t *cmd = NULL; 188 | 189 | assert(cfg != NULL); 190 | assert(key != NULL); 191 | assert(value != NULL); 192 | 193 | if (strcmp(key, "timeout") == 0) { 194 | cfg->timeout = atoi(value); 195 | return true; 196 | } 197 | if (strcmp(key, "shared") == 0) { 198 | cfg->shared = (strcmp(value, "yes") == 0) ? true : false; 199 | return true; 200 | } 201 | 202 | CREATE_CMD(start) 203 | CREATE_CMD(stop) 204 | CREATE_CMD(ban) 205 | CREATE_CMD(unban) 206 | CREATE_CMD(check) 207 | 208 | return false; 209 | } 210 | 211 | bool 212 | start(cfg_t *cfg) { 213 | assert(cfg != NULL); 214 | 215 | if (!cfg->start) 216 | return true; 217 | 218 | if (cfg->shared && usage_inc(cfg->name) > 1) 219 | return true; 220 | 221 | return cmd_list_exec(cfg, cfg->start, NULL); 222 | } 223 | 224 | bool 225 | stop(cfg_t *cfg) { 226 | assert(cfg != NULL); 227 | 228 | if (!cfg->stop) 229 | return true; 230 | 231 | if (cfg->shared && usage_dec(cfg->name) > 0) 232 | return true; 233 | 234 | return cmd_list_exec(cfg, cfg->stop, NULL); 235 | } 236 | 237 | bool 238 | ban(cfg_t *cfg, const char *ip) { 239 | assert(cfg != NULL && ip != NULL); 240 | 241 | return cmd_list_exec(cfg, cfg->ban, ip); 242 | } 243 | 244 | bool 245 | unban(cfg_t *cfg, const char *ip) { 246 | assert(cfg != NULL && ip != NULL); 247 | 248 | return cmd_list_exec(cfg, cfg->unban, ip); 249 | } 250 | 251 | bool 252 | check(cfg_t *cfg, const char *ip) { 253 | assert(cfg != NULL && ip != NULL); 254 | 255 | if (!cfg->check) 256 | return false; 257 | 258 | return cmd_list_exec(cfg, cfg->check, ip); 259 | } 260 | 261 | bool 262 | ping(cfg_t *cfg) { 263 | assert(cfg != NULL); 264 | (void)(cfg); /* suppress warning about unused variable */ 265 | return true; 266 | } 267 | 268 | void 269 | destroy(cfg_t *cfg) { 270 | assert(cfg != NULL); 271 | 272 | /* free commands */ 273 | cmd_list_destroy(cfg->start); 274 | cmd_list_destroy(cfg->stop); 275 | cmd_list_destroy(cfg->ban); 276 | cmd_list_destroy(cfg->unban); 277 | cmd_list_destroy(cfg->check); 278 | free(cfg); 279 | } 280 | -------------------------------------------------------------------------------- /src/backends/ipset6.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include "../strlcpy.h" 20 | 21 | #include "backend.h" 22 | #define MODNAME "ipset" 23 | 24 | struct _config { 25 | char name[ID_MAX + 1]; 26 | void (*logcb)(enum loglevel lvl, const char *msg); 27 | struct ipset_session *sess; 28 | int flags; 29 | bool shared; 30 | }; 31 | 32 | #include "backend.c" 33 | 34 | inline static bool 35 | my_ipset_error(cfg_t *cfg, const char *func) { 36 | struct ipset_data *data = NULL; 37 | if (ipset_session_error(cfg->sess)) { 38 | log_msg(cfg, error, "%s: %s", func, ipset_session_error(cfg->sess)); 39 | } else /* IPSET_WARNING */ { 40 | log_msg(cfg, warn, "%s: %s", func, ipset_session_warning(cfg->sess)); 41 | } 42 | 43 | ipset_session_report_reset(cfg->sess); 44 | 45 | if ((data = ipset_session_data(cfg->sess)) != NULL) 46 | ipset_data_reset(data); 47 | 48 | return false; 49 | } 50 | 51 | static bool 52 | my_ipset_cmd(cfg_t *cfg, enum ipset_cmd cmd, const char *ip) { 53 | const struct ipset_type *type = NULL; 54 | 55 | if (ipset_parse_setname(cfg->sess, IPSET_SETNAME, cfg->name) < 0) 56 | return my_ipset_error(cfg, "ipset_parse_setname())"); 57 | 58 | if ((type = ipset_type_get(cfg->sess, cmd)) == NULL) 59 | return my_ipset_error(cfg, "ipset_type_get()"); 60 | 61 | if (ipset_parse_elem(cfg->sess, type->last_elem_optional, ip) < 0) 62 | return my_ipset_error(cfg, "ipset_parse_elem()"); 63 | 64 | if (ipset_cmd(cfg->sess, cmd, 0) < 0) { 65 | if (cmd == IPSET_CMD_TEST && ipset_session_error(cfg->sess) == NULL) 66 | return false; /* "ip is NOT in list" */ 67 | return my_ipset_error(cfg, "ipset_cmd()"); 68 | } 69 | 70 | return true; 71 | } 72 | 73 | cfg_t * 74 | create(const char *id) { 75 | cfg_t *cfg = NULL; 76 | 77 | assert(id != NULL); 78 | 79 | if ((cfg = calloc(1, sizeof(cfg_t))) == NULL) 80 | return NULL; 81 | strlcpy(cfg->name, id, sizeof(cfg->name)); 82 | cfg->logcb = &logcb_stub; 83 | cfg->flags |= MOD_IS_READY; 84 | cfg->flags |= MOD_TYPE_BACKEND; 85 | return cfg; 86 | } 87 | 88 | bool 89 | config(cfg_t *cfg, const char *key, const char *value) { 90 | assert(cfg != NULL); 91 | assert(key != NULL); 92 | assert(value != NULL); 93 | 94 | (void)(cfg); 95 | (void)(key); 96 | (void)(value); 97 | 98 | return false; 99 | } 100 | 101 | bool 102 | start(cfg_t *cfg) { 103 | assert(cfg != NULL); 104 | 105 | if (cfg->shared && usage_inc(cfg->name) > 1) 106 | return true; 107 | 108 | ipset_load_types(); 109 | 110 | if ((cfg->sess = ipset_session_init(NULL)) == NULL) { 111 | log_msg(cfg, error, "can't init ipset session"); 112 | return false; 113 | } 114 | 115 | if (ipset_session_output(cfg->sess, IPSET_LIST_NONE) < 0) 116 | return my_ipset_error(cfg, "ipset_session_output()"); 117 | 118 | if (ipset_envopt_parse(cfg->sess, IPSET_ENV_EXIST, NULL) < 0) 119 | return my_ipset_error(cfg, "ipset_envopt_parse()"); 120 | 121 | return true; 122 | } 123 | 124 | bool 125 | stop(cfg_t *cfg) { 126 | assert(cfg != NULL); 127 | 128 | if (cfg->shared && usage_dec(cfg->name) > 0) 129 | return true; 130 | 131 | if (cfg->sess) { 132 | ipset_session_fini(cfg->sess); 133 | cfg->sess = NULL; 134 | } 135 | 136 | return true; 137 | } 138 | 139 | bool 140 | ban(cfg_t *cfg, const char *ip) { 141 | assert(cfg != NULL); 142 | 143 | return my_ipset_cmd(cfg, IPSET_CMD_ADD, ip); 144 | } 145 | 146 | bool 147 | unban(cfg_t *cfg, const char *ip) { 148 | assert(cfg != NULL); 149 | 150 | return my_ipset_cmd(cfg, IPSET_CMD_DEL, ip); 151 | } 152 | 153 | bool 154 | check(cfg_t *cfg, const char *ip) { 155 | assert(cfg != NULL); 156 | 157 | return my_ipset_cmd(cfg, IPSET_CMD_TEST, ip); 158 | } 159 | 160 | bool 161 | ping(cfg_t *cfg) { 162 | assert(cfg != NULL); 163 | 164 | (void)(cfg); /* silence warning about unused variable */ 165 | 166 | return true; 167 | } 168 | 169 | void 170 | destroy(cfg_t *cfg) { 171 | assert(cfg != NULL); 172 | 173 | free(cfg); 174 | } 175 | -------------------------------------------------------------------------------- /src/backends/ipset7.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | #include "../strlcpy.h" 18 | 19 | #include "backend.h" 20 | #define MODNAME "ipset" 21 | 22 | #define IP_LEN_MAX 46 /* ipv6 in mind */ 23 | 24 | struct _config { 25 | char name[ID_MAX + 1]; 26 | void (*logcb)(enum loglevel lvl, const char *msg); 27 | struct ipset *ipset; 28 | enum ipset_cmd last_cmd; 29 | int flags; 30 | bool shared; 31 | }; 32 | 33 | #include "backend.c" 34 | 35 | /** 36 | * @brief Wrapper to get last ipset error and send to log 37 | * @note Called from this module */ 38 | inline static bool 39 | my_ipset_error(cfg_t *cfg, const char *func) { 40 | struct ipset_data *data = NULL; 41 | struct ipset_session *sess = ipset_session(cfg->ipset); 42 | 43 | if (ipset_session_report_type(sess) != IPSET_NO_ERROR) { 44 | log_msg(cfg, error, "%s: %s", func, ipset_session_report_msg(sess)); 45 | ipset_session_report_reset(sess); 46 | } 47 | 48 | if ((data = ipset_session_data(sess)) != NULL) 49 | ipset_data_reset(data); 50 | 51 | return false; 52 | } 53 | 54 | /** 55 | * @note Called from library internals 56 | */ 57 | int 58 | my_ipset_std_error_cb(struct ipset *ipset, void *cfg) { 59 | struct ipset_session *sess = ipset_session(ipset); 60 | 61 | switch (ipset_session_report_type(sess)) { 62 | case IPSET_NOTICE : 63 | case IPSET_WARNING : 64 | if (!ipset_envopt_test(sess, IPSET_ENV_QUIET)) 65 | log_msg(cfg, error, "warning: %s", ipset_session_report_msg(sess)); 66 | break; 67 | case IPSET_ERROR : 68 | default: 69 | if (((cfg_t *) cfg)->last_cmd != IPSET_CMD_TEST) { 70 | log_msg(cfg, error, "error: %s", ipset_session_report_msg(sess)); 71 | } 72 | break; 73 | } 74 | 75 | ipset_session_report_reset(sess); 76 | return -1; 77 | } 78 | 79 | cfg_t * 80 | create(const char *id) { 81 | cfg_t *cfg = NULL; 82 | 83 | assert(id != NULL); 84 | 85 | if ((cfg = calloc(1, sizeof(cfg_t))) == NULL) 86 | return NULL; 87 | strlcpy(cfg->name, id, sizeof(cfg->name)); 88 | cfg->logcb = &logcb_stub; 89 | cfg->flags |= MOD_IS_READY; 90 | cfg->flags |= MOD_TYPE_BACKEND; 91 | cfg->last_cmd = IPSET_CMD_NONE; 92 | return cfg; 93 | } 94 | 95 | bool 96 | config(cfg_t *cfg, const char *key, const char *value) { 97 | assert(cfg != NULL); 98 | assert(key != NULL); 99 | assert(value != NULL); 100 | 101 | (void)(cfg); 102 | (void)(key); 103 | (void)(value); 104 | 105 | return false; 106 | } 107 | 108 | bool 109 | start(cfg_t *cfg) { 110 | assert(cfg != NULL); 111 | 112 | if (cfg->shared && usage_inc(cfg->name) > 1) 113 | return true; 114 | 115 | ipset_load_types(); 116 | 117 | if ((cfg->ipset = ipset_init()) == NULL) { 118 | log_msg(cfg, error, "can't init ipset library"); 119 | return false; 120 | } 121 | 122 | if (ipset_session_output(ipset_session(cfg->ipset), IPSET_LIST_NONE) < 0) 123 | return my_ipset_error(cfg, "ipset_session_output()"); 124 | 125 | if (ipset_envopt_parse(cfg->ipset, IPSET_ENV_EXIST, NULL) < 0) 126 | return my_ipset_error(cfg, "ipset_envopt_parse()"); 127 | ipset_custom_printf(cfg->ipset, NULL, &my_ipset_std_error_cb, NULL, (void *) cfg); 128 | ipset_envopt_set(ipset_session(cfg->ipset), IPSET_ENV_QUIET); 129 | 130 | return true; 131 | } 132 | 133 | bool 134 | stop(cfg_t *cfg) { 135 | assert(cfg != NULL); 136 | 137 | if (cfg->shared && usage_dec(cfg->name) > 0) 138 | return true; 139 | 140 | if (cfg->ipset) { 141 | ipset_fini(cfg->ipset); 142 | cfg->ipset = NULL; 143 | } 144 | 145 | return true; 146 | } 147 | 148 | bool 149 | ban(cfg_t *cfg, const char *ip) { 150 | char ipw[IP_LEN_MAX] = ""; 151 | char *argv[] = { "ignored", "add", cfg->name, ipw, NULL }; 152 | assert(cfg != NULL); 153 | strlcpy(ipw, ip, sizeof(ipw)); 154 | cfg->last_cmd = IPSET_CMD_ADD; 155 | return ipset_parse_argv(cfg->ipset, 4, argv) == 0; 156 | } 157 | 158 | bool 159 | unban(cfg_t *cfg, const char *ip) { 160 | char ipw[IP_LEN_MAX] = ""; 161 | char *argv[] = { "ignored", "del", cfg->name, ipw, NULL }; 162 | assert(cfg != NULL); 163 | strlcpy(ipw, ip, sizeof(ipw)); 164 | cfg->last_cmd = IPSET_CMD_DEL; 165 | return ipset_parse_argv(cfg->ipset, 4, argv) == 0; 166 | } 167 | 168 | bool 169 | check(cfg_t *cfg, const char *ip) { 170 | char ipw[IP_LEN_MAX] = ""; 171 | char *argv[] = { "ignored", "test", cfg->name, ipw, NULL }; 172 | assert(cfg != NULL); 173 | strlcpy(ipw, ip, sizeof(ipw)); 174 | cfg->last_cmd = IPSET_CMD_TEST; 175 | return ipset_parse_argv(cfg->ipset, 4, argv) == 0; 176 | } 177 | 178 | bool 179 | ping(cfg_t *cfg) { 180 | assert(cfg != NULL); 181 | 182 | (void)(cfg); /* silence warning about unused variable */ 183 | 184 | return true; 185 | } 186 | 187 | void 188 | destroy(cfg_t *cfg) { 189 | assert(cfg != NULL); 190 | 191 | free(cfg); 192 | } 193 | -------------------------------------------------------------------------------- /src/backends/redis.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | #include "../strlcpy.h" 21 | 22 | #include "backend.h" 23 | #define MODNAME "redis" 24 | 25 | struct _config { 26 | void (*logcb)(enum loglevel lvl, const char *msg); 27 | redisContext *conn; 28 | time_t timeout; 29 | int flags; 30 | uint16_t port; 31 | uint8_t ping_num; /*< current number of ping() call */ 32 | uint8_t ping_max; /*< max ping() calls before actually pinginig redis server */ 33 | uint8_t database; 34 | bool shared; 35 | char password[32]; 36 | char host[32]; 37 | char name[ID_MAX + 1]; 38 | char hash[ID_MAX * 2]; 39 | }; 40 | 41 | #include "backend.c" 42 | 43 | static bool 44 | redis_connect(cfg_t *cfg) { 45 | assert(cfg != NULL); 46 | 47 | if (cfg->conn && !cfg->conn->err) 48 | return true; /* connected */ 49 | 50 | redisContext *conn = NULL; 51 | redisReply *reply = NULL; 52 | do { 53 | struct timeval timeout = { cfg->timeout, 0 }; 54 | conn = redisConnectWithTimeout(cfg->host, cfg->port, timeout); 55 | if (!conn) 56 | break; 57 | if (conn->err) { 58 | log_msg(cfg, error, "Connection error: %s", conn->errstr); 59 | break; 60 | } 61 | if (cfg->password[0]) { 62 | if ((reply = redisCommand(conn, "AUTH %s", cfg->password)) == NULL) 63 | break; 64 | if (reply->type == REDIS_REPLY_ERROR) { 65 | log_msg(cfg, error, "auth error: %s", reply->str); 66 | break; 67 | } 68 | freeReplyObject(reply); 69 | } 70 | if (cfg->database) { 71 | if ((reply = redisCommand(conn, "SELECT %d", cfg->database)) == NULL) 72 | break; 73 | if (reply->type == REDIS_REPLY_ERROR) { 74 | log_msg(cfg, error, "auth error: %s", reply->str); 75 | break; 76 | } 77 | freeReplyObject(reply); 78 | } 79 | if (cfg->conn) 80 | redisFree(cfg->conn); 81 | cfg->conn = conn; 82 | return true; 83 | } while (0); 84 | 85 | if (conn) 86 | redisFree(conn); 87 | if (reply) 88 | freeReplyObject(reply); 89 | 90 | return false; 91 | } 92 | 93 | static bool 94 | redis_disconnect(cfg_t *cfg) { 95 | assert(cfg != NULL); 96 | 97 | if (cfg->conn) { 98 | redisFree(cfg->conn); 99 | cfg->conn = NULL; 100 | } 101 | return true; 102 | } 103 | 104 | cfg_t * 105 | create(const char *id) { 106 | cfg_t *cfg = NULL; 107 | 108 | assert(id != NULL); 109 | 110 | if ((cfg = calloc(1, sizeof(cfg_t))) == NULL) 111 | return NULL; 112 | strlcpy(cfg->name, id, sizeof(cfg->name)); 113 | strlcpy(cfg->hash, "f2b-banned-", sizeof(cfg->hash)); 114 | strlcat(cfg->hash, id, sizeof(cfg->hash)); 115 | 116 | cfg->logcb = &logcb_stub; 117 | cfg->flags |= MOD_TYPE_BACKEND; 118 | return cfg; 119 | } 120 | 121 | bool 122 | config(cfg_t *cfg, const char *key, const char *value) { 123 | assert(cfg != NULL); 124 | assert(key != NULL); 125 | assert(value != NULL); 126 | 127 | if (strcmp(key, "timeout") == 0) { 128 | cfg->timeout = atoi(value); 129 | return true; 130 | } 131 | if (strcmp(key, "shared") == 0) { 132 | cfg->shared = (strcmp(value, "yes") == 0 ? true : false); 133 | return true; 134 | } 135 | if (strcmp(key, "host") == 0) { 136 | strlcpy(cfg->host, value, sizeof(cfg->host)); 137 | cfg->flags |= MOD_IS_READY; 138 | return true; 139 | } 140 | if (strcmp(key, "port") == 0) { 141 | cfg->port = atoi(value); 142 | return true; 143 | } 144 | if (strcmp(key, "ping") == 0) { 145 | cfg->ping_max = atoi(value); 146 | return true; 147 | } 148 | if (strcmp(key, "database") == 0) { 149 | cfg->database = atoi(value); 150 | return true; 151 | } 152 | if (strcmp(key, "password") == 0) { 153 | strlcpy(cfg->password, value, sizeof(cfg->password)); 154 | return true; 155 | } 156 | 157 | return false; 158 | } 159 | 160 | bool 161 | start(cfg_t *cfg) { 162 | assert(cfg != NULL); 163 | 164 | if (cfg->shared) 165 | usage_inc(cfg->name); 166 | 167 | redis_connect(cfg); /* may fail */ 168 | return true; 169 | } 170 | 171 | bool 172 | stop(cfg_t *cfg) { 173 | assert(cfg != NULL); 174 | 175 | if (cfg->shared && usage_dec(cfg->name) > 0) 176 | return true; /* skip disconnect, if not last user */ 177 | 178 | redis_disconnect(cfg); 179 | return true; 180 | } 181 | 182 | bool 183 | ban(cfg_t *cfg, const char *ip) { 184 | assert(cfg != NULL); 185 | 186 | if (!redis_connect(cfg)) 187 | return false; 188 | 189 | redisReply *reply; 190 | do { 191 | if ((reply = redisCommand(cfg->conn, "HINCRBY %s %s %d", cfg->hash, ip, 1)) == NULL) 192 | break; 193 | if (reply->type == REDIS_REPLY_ERROR) { 194 | log_msg(cfg, error, "HINCRBY: %s", reply->str); 195 | break; 196 | } 197 | freeReplyObject(reply); 198 | if ((reply = redisCommand(cfg->conn, "PUBLISH %s %s", cfg->hash, ip)) == NULL) 199 | break; 200 | if (reply->type == REDIS_REPLY_ERROR) { 201 | log_msg(cfg, error, "PUBLISH: %s", reply->str); 202 | break; 203 | } 204 | freeReplyObject(reply); 205 | return true; 206 | } while (0); 207 | 208 | if (reply) 209 | freeReplyObject(reply); 210 | 211 | return false; 212 | } 213 | 214 | bool 215 | unban(cfg_t *cfg, const char *ip) { 216 | assert(cfg != NULL); 217 | 218 | (void)(cfg); /* suppress warning for unused variable 'ip' */ 219 | (void)(ip); /* suppress warning for unused variable 'ip' */ 220 | 221 | return true; 222 | } 223 | 224 | bool 225 | check(cfg_t *cfg, const char *ip) { 226 | assert(cfg != NULL); 227 | 228 | (void)(cfg); /* suppress warning for unused variable 'ip' */ 229 | (void)(ip); /* suppress warning for unused variable 'ip' */ 230 | 231 | return false; 232 | } 233 | 234 | bool 235 | ping(cfg_t *cfg) { 236 | assert(cfg != NULL); 237 | 238 | if (!cfg->conn || cfg->conn->err) 239 | redis_connect(cfg); 240 | if (!cfg->conn) 241 | return false; /* reconnect failure */ 242 | 243 | if (cfg->conn->err) { 244 | log_msg(cfg, error, "connection error: %s", cfg->conn->errstr); 245 | return false; 246 | } 247 | 248 | cfg->ping_num++; 249 | if (cfg->ping_num < cfg->ping_max) 250 | return true; /* skip this try */ 251 | 252 | /* max empty calls reached, make real ping */ 253 | cfg->ping_num = 0; 254 | redisReply *reply = redisCommand(cfg->conn, "PING"); 255 | if (reply) { 256 | bool result = true; 257 | if (reply->type == REDIS_REPLY_ERROR) { 258 | log_msg(cfg, error, "%s", reply->str); 259 | result = false; 260 | } 261 | freeReplyObject(reply); 262 | return result; 263 | } 264 | 265 | return false; 266 | } 267 | 268 | void 269 | destroy(cfg_t *cfg) { 270 | assert(cfg != NULL); 271 | 272 | free(cfg); 273 | } 274 | -------------------------------------------------------------------------------- /src/buf.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "buf.h" 3 | 4 | bool 5 | f2b_buf_alloc(f2b_buf_t *buf, size_t size) { 6 | assert(buf != NULL); 7 | assert(size > 0); 8 | 9 | memset(buf, 0x0, sizeof(f2b_buf_t)); 10 | if ((buf->data = malloc(size)) == NULL) 11 | return false; /* can't allocate memory */ 12 | 13 | buf->size = size; 14 | return true; 15 | } 16 | 17 | void 18 | f2b_buf_free(f2b_buf_t *buf) { 19 | assert(buf != NULL); 20 | 21 | free(buf->data); 22 | memset(buf, 0x0, sizeof(f2b_buf_t)); 23 | } 24 | 25 | size_t 26 | f2b_buf_append(f2b_buf_t *buf, const char *str, size_t len) { 27 | assert(buf != NULL); 28 | assert(str != NULL); 29 | 30 | if (len == 0) 31 | len = strlen(str); 32 | if ((buf->used + len) > buf->size) { 33 | /* not enough space, append as much as possible */ 34 | len = buf->size - buf->used; 35 | } 36 | 37 | memcpy(&buf->data[buf->used], str, len); 38 | buf->used += len; 39 | buf->data[buf->used] = '\0'; 40 | return len; 41 | } 42 | 43 | /** 44 | * @brief Extracts line terminated by delimiter 45 | * @return Pointer to extracted string on success or NULL otherwise 46 | * @note Use only with 'read' buffer type 47 | */ 48 | char * 49 | f2b_buf_extract(f2b_buf_t *buf, const char *end) { 50 | char *s = NULL; 51 | size_t len = 0; 52 | 53 | assert(buf != NULL); 54 | assert(end != NULL); 55 | 56 | if (buf->data == NULL || buf->used == 0) 57 | return NULL; /* no data */ 58 | 59 | if ((s = strstr(buf->data, end)) == NULL) 60 | return NULL; /* not found */ 61 | 62 | /* copy the data before modifying buffer */ 63 | len = s - buf->data; 64 | if ((s = strndup(buf->data, len)) == NULL) 65 | return NULL; /* malloc error */ 66 | 67 | /* shift data inside buffer */ 68 | len += strlen(end); 69 | f2b_buf_splice(buf, len); 70 | 71 | return s; 72 | } 73 | 74 | size_t 75 | f2b_buf_splice(f2b_buf_t *buf, size_t len) { 76 | assert(buf != NULL); 77 | 78 | if (len == 0) 79 | return len; 80 | 81 | if (buf->used <= len) 82 | len = buf->used; 83 | 84 | buf->used -= len, 85 | memmove(buf->data, &buf->data[len], buf->used); 86 | buf->data[buf->used] = '\0'; 87 | return len; 88 | } 89 | -------------------------------------------------------------------------------- /src/buf.h: -------------------------------------------------------------------------------- 1 | #ifndef F2B_BUF_H_ 2 | #define F2B_BUF_H_ 3 | 4 | typedef struct f2b_buf_t { 5 | size_t used; /**< bytes used in buffer */ 6 | size_t size; /**< available size in data */ 7 | char *data; /**< allocated buffer */ 8 | } f2b_buf_t; 9 | 10 | bool f2b_buf_alloc(f2b_buf_t *buf, size_t max); 11 | void f2b_buf_free(f2b_buf_t *buf); 12 | size_t f2b_buf_append(f2b_buf_t *buf, const char *str, size_t size); 13 | char * f2b_buf_extract(f2b_buf_t *buf, const char *end); 14 | size_t f2b_buf_splice(f2b_buf_t *buf, size_t len); 15 | 16 | #endif /* F2B_BUF_H_ */ 17 | -------------------------------------------------------------------------------- /src/client.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #include "common.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #define DEFAULT_CSOCKET_PATH "/var/run/f2b.sock" 19 | 20 | #ifdef WITH_READLINE 21 | #include 22 | #include 23 | #else 24 | /* stubs */ 25 | #define add_history(x) (void)(x) 26 | #define using_history() 27 | #define rl_clear_visible_line() 28 | #define rl_on_new_line() 29 | #define rl_redisplay() 30 | #endif /* WITH_READLINE */ 31 | 32 | struct { 33 | enum { interactive = 0, oneshot } mode; 34 | char spath[PATH_MAX]; 35 | } opts = { 36 | interactive, 37 | DEFAULT_CSOCKET_PATH, 38 | }; 39 | 40 | static int csock = -1; 41 | 42 | void usage(int exitcode) { 43 | fputs("Usage: f2bc [-h] [-s ] [-c ]\n", stdout); 44 | fputs("\t-h Show this message\n", stdout); 45 | fputs("\t-c Send command to daemon\n", stdout); 46 | fputs("\t-s Path to socket (default: " DEFAULT_CSOCKET_PATH ")\n", stdout); 47 | exit(exitcode); 48 | } 49 | 50 | void 51 | handle_print(const char *buf, size_t len, bool last) { 52 | rl_clear_visible_line(); 53 | if (len == 0) len = strlen(buf); 54 | fwrite(buf, len, 1, stdout); 55 | if (last) return; 56 | rl_on_new_line(); 57 | rl_redisplay(); 58 | } 59 | 60 | int 61 | handle_recv() { 62 | char buf[WBUF_SIZE] = ""; /* our "read" is server "write" */ 63 | int ret; 64 | 65 | if (csock < 0) 66 | return 0; /* not connected */ 67 | 68 | ret = recv(csock, &buf, sizeof(buf), MSG_DONTWAIT); 69 | if (ret > 0) { 70 | handle_print(buf, ret, false); 71 | } else if (ret == 0) { 72 | handle_print("connection closed\n", 0, true); 73 | exit(EXIT_SUCCESS); /* received EOF */ 74 | } else if (ret < 0 && errno == EAGAIN) { 75 | return 0; 76 | } else /* ret < 0 */ { 77 | handle_print(strerror(errno), 0, true); 78 | exit(EXIT_FAILURE); 79 | } 80 | return 0; 81 | } 82 | 83 | int 84 | handle_cmd(const char *line) { 85 | const char *p = NULL; 86 | char buf[WBUF_SIZE] = ""; /* our "read" is server "write" */ 87 | int ret; int len; 88 | 89 | assert(line != NULL); 90 | 91 | snprintf(buf, sizeof(buf), "%s\n", line); 92 | p = buf; 93 | while (p && *p != '\0') { 94 | len = strlen(p); 95 | ret = send(csock, p, strlen(p), 0); 96 | if (ret < 0 && errno == EAGAIN) { 97 | continue; /* try again */ 98 | } else if (ret < 0) { 99 | perror("send()"); 100 | exit(EXIT_FAILURE); 101 | } else if (ret == len){ 102 | break; /* all data sent */ 103 | } else /* ret > 0 */ { 104 | p += ret; 105 | } 106 | } 107 | 108 | return 0; 109 | } 110 | 111 | void 112 | handle_signal(int signum) { 113 | switch (signum) { 114 | case SIGINT: 115 | case SIGTERM: 116 | exit(EXIT_SUCCESS); 117 | break; 118 | default: 119 | break; 120 | } 121 | } 122 | 123 | void 124 | setup_sigaction(int signum) { 125 | struct sigaction act; 126 | memset(&act, 0x0, sizeof(act)); 127 | act.sa_handler = &handle_signal; 128 | if (sigaction(signum, &act, NULL) != 0) { 129 | perror("sigaction()"); 130 | exit(EXIT_FAILURE); 131 | } 132 | } 133 | 134 | void 135 | setup_socket() { 136 | struct sockaddr_un saddr; 137 | 138 | if ((csock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { 139 | perror("socket()"); 140 | exit(EXIT_FAILURE); 141 | } 142 | 143 | memset(&saddr, 0x0, sizeof(saddr)); 144 | saddr.sun_family = AF_UNIX; 145 | strlcpy(saddr.sun_path, opts.spath, sizeof(saddr.sun_path) - 1); 146 | if (connect(csock, (struct sockaddr *) &saddr, sizeof(struct sockaddr_un)) < 0) { 147 | perror("connect()"); 148 | exit(EXIT_FAILURE); 149 | } 150 | } 151 | 152 | #ifdef WITH_READLINE 153 | rl_hook_func_t *rl_event_hook = &handle_recv; 154 | #else 155 | char * 156 | readline(const char *prompt) { 157 | char line[RBUF_SIZE+1]; 158 | char *p; 159 | 160 | while (1) { 161 | line[0] = '\0'; 162 | p = &line[0]; 163 | handle_recv(); 164 | fputs(prompt, stdout); 165 | if (!fgets(line, sizeof(line) - 1, stdin)) { 166 | if (!feof(stdin)) 167 | fputs("read error\n", stdout); 168 | return NULL; 169 | } 170 | while (isspace(*p)) p++; 171 | if (*p != '\n' && *p != '\0') 172 | return strdup(p); 173 | } 174 | return NULL; 175 | } 176 | #endif /* WITH_READLINE */ 177 | 178 | int main(int argc, char *argv[]) { 179 | char *line = NULL; 180 | char opt = '\0'; 181 | int ret; 182 | 183 | while ((opt = getopt(argc, argv, "c:hs:")) != -1) { 184 | switch (opt) { 185 | case 'c': 186 | opts.mode = oneshot; 187 | line = strndup(optarg, RBUF_SIZE); 188 | break; 189 | case 's': 190 | strlcpy(opts.spath, optarg, sizeof(opts.spath)); 191 | break; 192 | case 'h': 193 | usage(EXIT_SUCCESS); 194 | break; 195 | default: 196 | usage(EXIT_FAILURE); 197 | break; 198 | } 199 | } 200 | 201 | setup_sigaction(SIGTERM); 202 | setup_sigaction(SIGINT); 203 | 204 | setup_socket(); 205 | 206 | if (opts.mode == oneshot) { 207 | ret = handle_cmd(line); 208 | shutdown(csock, SHUT_RDWR); 209 | exit(ret); 210 | } 211 | 212 | using_history(); 213 | while ((line = readline("f2b >> ")) != NULL) { 214 | if (line[0] != '\n' && line[0] != '\0') { 215 | add_history(line); 216 | handle_cmd(line); 217 | } 218 | free(line); 219 | line = NULL; 220 | } 221 | putc('\n', stdout); 222 | 223 | return EXIT_SUCCESS; 224 | } 225 | -------------------------------------------------------------------------------- /src/commands.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #ifndef F2B_COMMANDS_H_ 8 | #define F2B_COMMANDS_H_ 9 | 10 | /** 11 | * @file 12 | * This header contains definition of control commands and routines 13 | * for parsing user input 14 | */ 15 | 16 | #define CMD_TOKENS_MAXCOUNT 6 /**< Maximum count of data pieces in control message data buf */ 17 | #define CMD_TOKENS_MAXSIZE 260 /**< parsed tokens */ 18 | 19 | enum f2b_command_type { 20 | CMD_UNKNOWN = 0, /**< unset */ 21 | CMD_AUTH, /**< authorization */ 22 | CMD_HELP, /**< show help for commands */ 23 | CMD_STATUS, /**< show general status of f2b daemon */ 24 | CMD_RELOAD, /**< reload all jails */ 25 | CMD_SHUTDOWN, /**< gracefull shutdown daemon */ 26 | /* logging */ 27 | CMD_LOG_ROTATE, /**< reopen logfile. (only for `logdest = file`) */ 28 | CMD_LOG_LEVEL, /**< change maximum level of logged messages */ 29 | CMD_LOG_EVENTS, /**< enable/disable sending events to connected client */ 30 | /* jail commands */ 31 | CMD_JAIL_STATUS, /**< show status of given jail */ 32 | CMD_JAIL_SET, /**< set parameter of given jail */ 33 | CMD_JAIL_IP_STATUS, /**< show status of given ip */ 34 | CMD_JAIL_IP_BAN, /**< force ban given ip */ 35 | CMD_JAIL_IP_RELEASE, /**< force unban given ip */ 36 | CMD_JAIL_SOURCE_STATS, /**< show stats of source */ 37 | CMD_JAIL_FILTER_STATS, /**< show stats of filter matches */ 38 | CMD_JAIL_FILTER_RELOAD, /**< reload filter patterns from file */ 39 | CMD_MAX_NUMBER, /**< placeholder */ 40 | }; 41 | 42 | /** control command type */ 43 | typedef struct f2b_cmd_t { 44 | enum f2b_command_type type; 45 | int argc; 46 | char *args[CMD_TOKENS_MAXCOUNT+1]; 47 | f2b_buf_t data; 48 | } f2b_cmd_t; 49 | 50 | /** 51 | * @brief Returns multiline string with commands list and it's brief descriptions 52 | * @returns constant multiline string 53 | */ 54 | const char * 55 | f2b_cmd_help(); 56 | 57 | /** 58 | * @brief Creates new struct from user input 59 | * @param line User input string 60 | * @returns pointer to newly allocated struct or NULL on malloc()/parse error 61 | */ 62 | f2b_cmd_t * 63 | f2b_cmd_create(const char *line); 64 | 65 | /** 66 | * @brief Frees memory 67 | */ 68 | void 69 | f2b_cmd_destroy(f2b_cmd_t *cmd); 70 | 71 | /** 72 | * @brief Try to parse user input 73 | * @param cmd pointer to preallocated f2b_cmd_t struct 74 | * @param src Line with user input 75 | * @returns true on success, false otherwise 76 | */ 77 | bool 78 | f2b_cmd_parse(f2b_cmd_t *cmd, const char *src); 79 | 80 | #endif /* F2B_COMMANDS_H_ */ 81 | -------------------------------------------------------------------------------- /src/common.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #ifndef F2B_COMMON_H_ 8 | #define F2B_COMMON_H_ 9 | 10 | #include 11 | #include 12 | #include 13 | #include /* PATH_MAX */ 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | /** 28 | * @file 29 | * This header contains common includes, usefull macro defs and default paths 30 | */ 31 | 32 | #include "strlcpy.h" 33 | 34 | /** 35 | * @def DEFAULT_PIDFILE_PATH 36 | * Self-descriptive 37 | */ 38 | #define DEFAULT_PIDFILE_PATH "/var/run/f2b.pid" 39 | 40 | /** 41 | * Default path of directory to store ip states for jails 42 | */ 43 | #define DEFAULT_STATEDIR_PATH "/var/db/f2b" 44 | 45 | /** 46 | * @def UNUSED 47 | * Supresses compiler warning about unused variable 48 | */ 49 | #define UNUSED(x) (void)(x) 50 | 51 | /* default size of buffers */ 52 | #define RBUF_SIZE 256 53 | #define WBUF_SIZE 32768 /* 32Kb */ 54 | 55 | #endif /* F2B_COMMON_H_ */ 56 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #ifndef F2B_CONFIG_H_ 8 | #define F2B_CONFIG_H_ 9 | 10 | /** 11 | * @file 12 | * This header describes f2b ini-style config structs and routines 13 | */ 14 | 15 | /** 16 | * @def CONFIG_LINE_MAX 17 | * Maximum length of config line 18 | */ 19 | #define CONFIG_LINE_MAX 256 20 | /** 21 | * @def CONFIG_KEY_MAX 22 | * Maximum length of parameter name 23 | */ 24 | #define CONFIG_KEY_MAX 32 25 | /** 26 | * @def CONFIG_VAL_MAX 27 | * Maximum length of parameter value 28 | */ 29 | #define CONFIG_VAL_MAX 192 30 | 31 | /** Section types in config */ 32 | typedef enum f2b_section_type { 33 | t_unknown = 0, /**< default value */ 34 | t_main, /**< [main] section */ 35 | t_csocket, /**< [csocket] section */ 36 | t_defaults, /**< [defaults] section */ 37 | t_source, /**< [source:*] section */ 38 | t_filter, /**< [filter:*] section */ 39 | t_backend, /**< [backend:*] section */ 40 | t_jail, /**< [jail:*] section */ 41 | } f2b_section_type; 42 | 43 | /** Key-value line in config */ 44 | typedef struct f2b_config_param_t { 45 | struct f2b_config_param_t *next; /**< pointer to next parameter of this section */ 46 | char name[CONFIG_KEY_MAX]; /**< parameter name */ 47 | char value[CONFIG_VAL_MAX]; /**< parameter value */ 48 | } f2b_config_param_t; 49 | 50 | /** Section of config */ 51 | typedef struct f2b_config_section_t { 52 | struct f2b_config_section_t *next; /**< pointer to next section of same type */ 53 | char name[CONFIG_KEY_MAX]; /**< section name (eg [type:$NAME]) */ 54 | f2b_section_type type; /**< section type */ 55 | f2b_config_param_t *param; /**< linked list of parameters */ 56 | f2b_config_param_t *last; /**< tail of parameter list */ 57 | } f2b_config_section_t; 58 | 59 | /** topmost f2b config struct */ 60 | typedef struct f2b_config_t { 61 | f2b_config_section_t *main; /**< section [main] */ 62 | f2b_config_section_t *csocket; /**< section [csocket] */ 63 | f2b_config_section_t *defaults; /**< section [defaults] */ 64 | f2b_config_section_t *sources; /**< sections [source:*] */ 65 | f2b_config_section_t *filters; /**< sections [filter:*] */ 66 | f2b_config_section_t *backends; /**< sections [backend:*] */ 67 | f2b_config_section_t *jails; /**< sections [jail:*] */ 68 | } f2b_config_t; 69 | 70 | /** 71 | * @brief Try parse line with `key = value` parameter 72 | * @param src Source line 73 | * @returns Pointer to created allocated parameter struct or NULL on error 74 | */ 75 | f2b_config_param_t * f2b_config_param_create(const char *src); 76 | 77 | /** 78 | * @brief Find parameter with given name in list 79 | * @param list Linked list of parameters 80 | * @param name Name of wanted parameter 81 | * @returns Pointer to found parameter or NULL if nothing found 82 | */ 83 | f2b_config_param_t * f2b_config_param_find (f2b_config_param_t *list, const char *name); 84 | 85 | /** 86 | * @brief Find section with given name in list 87 | * @param list Linked list of sections 88 | * @param name Name of wanted section 89 | * @returns Pointer to found section or NULL if nothing found 90 | */ 91 | f2b_config_section_t * f2b_config_section_find (f2b_config_section_t *list, const char *name); 92 | 93 | /** 94 | * @brief Load config from file 95 | * @param c Config struct pointer 96 | * @param path Path to config file 97 | * @param recursion Process `include = ` parameter 98 | * @returns true on success, false if error(s) occured 99 | */ 100 | bool f2b_config_load(f2b_config_t *c, const char *path, bool recursion); 101 | /** 102 | * @brief Destroy config and free all resources 103 | * @param c Config pointer 104 | */ 105 | void f2b_config_free(f2b_config_t *c); 106 | 107 | #endif /* F2B_CONFIG_H_ */ 108 | -------------------------------------------------------------------------------- /src/csocket-test.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "config.h" 3 | #include "buf.h" 4 | #include "log.h" 5 | #include "commands.h" 6 | #include "csocket.h" 7 | 8 | static int run = 1; 9 | 10 | void 11 | cmd_handler(const f2b_cmd_t *cmd, f2b_buf_t *res) { 12 | fprintf(stdout, "[handler] received cmd with type %d and %d args:\n", cmd->type, cmd->argc); 13 | for (int i = 0; i < cmd->argc; i++) { 14 | fprintf(stdout, "[handler] arg %d : %s\n", i + 1, cmd->args[i]); 15 | } 16 | if (cmd->type == CMD_HELP) { 17 | f2b_buf_append(res, f2b_cmd_help(), 0); 18 | } else { 19 | f2b_buf_append(res, "+ok\n", 0); 20 | } 21 | return; 22 | } 23 | 24 | int main(int argc, const char **argv) { 25 | f2b_config_t config; 26 | 27 | if (argc < 2) { 28 | puts("Usage: csocket-test "); 29 | exit(EXIT_FAILURE); 30 | } 31 | 32 | f2b_log_set_level("debug"); 33 | f2b_log_to_stderr(); 34 | 35 | memset(&config, 0x0, sizeof(config)); 36 | if (!f2b_config_load(&config, argv[1], false)) { 37 | f2b_log_msg(log_fatal, "can't load config"); 38 | return EXIT_FAILURE; 39 | } 40 | 41 | if (!f2b_csocket_create(config.csocket)) { 42 | perror("f2b_csocket_create()"); 43 | exit(EXIT_FAILURE); 44 | } 45 | 46 | while (run) { 47 | f2b_csocket_poll(cmd_handler); 48 | sleep(1); 49 | } 50 | f2b_csocket_destroy(); 51 | 52 | return 0; 53 | } 54 | -------------------------------------------------------------------------------- /src/csocket.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #ifndef F2B_CSOCKET_H_ 8 | #define F2B_CSOCKET_H_ 9 | 10 | #define CSOCKET_MAX_LISTEN 3 11 | #define CSOCKET_MAX_CLIENTS 10 12 | #define CSOCKET_DEFAULT_PORT "3370" 13 | 14 | /* connection flags */ 15 | #define CSOCKET_CONN_TYPE_UNIX 0x01 16 | #define CSOCKET_CONN_TYPE_INET 0x02 17 | #define CSOCKET_CONN_AUTH_OK 0x04 18 | #define CSOCKET_CONN_EVENTS 0x08 19 | 20 | /** 21 | * @file 22 | * This file contains control socket manage routines 23 | */ 24 | 25 | /** 26 | * @brief Create control socket 27 | * @param spec String with socket path/address specification 28 | * @returns Allocated socket struct 29 | */ 30 | bool f2b_csocket_create (f2b_config_section_t *config); 31 | 32 | /** 33 | * @brief Destroy socket struct and free resources 34 | * @param csock Socket struct 35 | */ 36 | void f2b_csocket_destroy(); 37 | 38 | /** 39 | * @brief Poll control socket for new messages 40 | * @param csock Control socket fd 41 | * @param cb Callback for handling message 42 | * @returns -1 on error, 0 on no messages, and > 0 on some messages processed 43 | */ 44 | void f2b_csocket_poll(void (*cb)(const f2b_cmd_t *cmd, f2b_buf_t *res)); 45 | 46 | /** 47 | * @brief Send text message to connected clients 48 | * @param msg text 49 | */ 50 | void f2b_csocket_event_broadcast(const char *msg); 51 | 52 | #endif /* F2B_CSOCKET_H_ */ 53 | -------------------------------------------------------------------------------- /src/event.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2021 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #include "common.h" 8 | 9 | static void (*handlers[4])(const char *) = { NULL, NULL, NULL, NULL }; 10 | 11 | void 12 | f2b_event_send(const char *evt) { 13 | for (short int i = 0; i < 3; i++) { 14 | if (!handlers[i]) break; /* end of list */ 15 | handlers[i](evt); 16 | } 17 | } 18 | 19 | bool 20 | f2b_event_handler_register(void (*h)(const char *)) { 21 | for (short int i = 0; i < 3; i++) { 22 | if (handlers[i] == NULL) { 23 | handlers[i] = h; 24 | return true; 25 | } 26 | } 27 | return false; 28 | } 29 | 30 | void 31 | f2b_event_handler_flush() { 32 | memset(handlers, 0x0, sizeof(handlers)); 33 | } 34 | -------------------------------------------------------------------------------- /src/event.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2020-2021 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #ifndef F2B_EVENT_H_ 8 | #define F2B_EVENT_H_ 9 | 10 | /** 11 | * @file 12 | * This file contains event-related routines 13 | */ 14 | 15 | /** 16 | * @def EVENT_MAX 17 | * Maximum text length of event line 18 | */ 19 | #define EVENT_MAX 256 20 | 21 | /** 22 | * @brief Send event to all registered handlers 23 | * @param evt Constant string in specified format 24 | */ 25 | void 26 | f2b_event_send(const char *evt); 27 | 28 | /** 29 | * @brief Adds event handler 30 | * @param h Pointer to function 31 | * @note For now you can register not more than three handlers. 32 | * It will be called in order of registration 33 | * @return true on success, false on error 34 | */ 35 | bool 36 | f2b_event_handler_register(void (*h)(const char *evt)); 37 | 38 | /** 39 | * @brief Clean event handlers list 40 | */ 41 | void 42 | f2b_event_handlers_flush(); 43 | 44 | #endif /* F2B_EVENT_H_ */ 45 | -------------------------------------------------------------------------------- /src/filter-test.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #include "common.h" 8 | #include "log.h" 9 | #include "matches.h" 10 | #include "ipaddr.h" 11 | #include "config.h" 12 | #include "filter.h" 13 | 14 | #include 15 | #include /* struct in_addr */ 16 | #include 17 | 18 | void usage() { 19 | fprintf(stderr, "Usage: filter-test []\n"); 20 | exit(EXIT_FAILURE); 21 | } 22 | 23 | int main(int argc, char *argv[]) { 24 | f2b_config_t config; 25 | f2b_config_section_t *section = NULL; 26 | f2b_filter_t *filter = NULL; 27 | char match[IPADDR_MAX] = ""; 28 | char line[LOGLINE_MAX] = ""; 29 | char stats[4096]; 30 | size_t read = 0, matched = 0; 31 | FILE *file = NULL; 32 | uint32_t ftag; 33 | short int score; 34 | 35 | if (argc < 3) 36 | usage(); 37 | 38 | memset(&config, 0x0, sizeof(config)); 39 | if (f2b_config_load(&config, argv[1], false) != true) { 40 | f2b_log_msg(log_fatal, "can't load config"); 41 | return EXIT_FAILURE; 42 | } 43 | 44 | if (config.filters == NULL) { 45 | f2b_log_msg(log_fatal, "no filters found in config"); 46 | return EXIT_FAILURE; 47 | } else { 48 | section = config.filters; 49 | } 50 | 51 | if ((filter = f2b_filter_create(section->name, argv[2])) == false) { 52 | f2b_log_msg(log_fatal, "can't create filter '%s'", section->name); 53 | return EXIT_FAILURE; 54 | } 55 | 56 | if (!f2b_filter_init(filter, section)) { 57 | f2b_log_msg(log_fatal, "can't configure filter '%s' with file '%s'", filter->name, argv[2]); 58 | return EXIT_FAILURE; 59 | } 60 | 61 | if (argc > 3) { 62 | if ((file = fopen(argv[3], "r")) == NULL) { 63 | f2b_log_msg(log_fatal, "can't open log file '%s': %s", argv[3], strerror(errno)); 64 | return EXIT_FAILURE; 65 | } 66 | } else { 67 | file = stdin; 68 | } 69 | 70 | while (fgets(line, sizeof(line), file) != NULL) { 71 | read++; 72 | fputs(line, stdout); 73 | if ((ftag = f2b_filter_match(filter, line, match, sizeof(match), &score)) > 0) { 74 | matched++; 75 | fprintf(stdout, "# match -- addr: %s, score: %d, tag: %08X\n", match, score, ftag); 76 | } 77 | } 78 | fclose(file); 79 | 80 | fputs("---\n", stdout); 81 | fprintf(stdout, "stats: %zu lines read, %zu matched\n", read, matched); 82 | f2b_filter_cmd_stats(stats, sizeof(stats), filter); 83 | fputs(stats, stdout); 84 | 85 | return EXIT_SUCCESS; 86 | } 87 | -------------------------------------------------------------------------------- /src/filter.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #ifndef F2B_FILTER_H_ 8 | #define F2B_FILTER_H_ 9 | 10 | /** 11 | * @file 12 | * This header describes filter module definition and related routines 13 | */ 14 | 15 | /** filter module definition */ 16 | typedef struct f2b_filter_t { 17 | void *h; /**< dlopen handler */ 18 | void *cfg; /**< opaque pointer of module config */ 19 | int flags; /**< module flags (update with state() call) */ 20 | /* handlers */ 21 | /** dlsym pointer to handler of @a create command */ 22 | void *(*create) (const char *id); 23 | /** dlsym pointer to handler of @a config command */ 24 | bool (*config) (void *cfg, const char *key, const char *value); 25 | /** dlsym pointer to handler of @a append command */ 26 | bool (*append) (void *cfg, const char *pattern); 27 | /** dlsym pointer to handler of @a logcb command */ 28 | void (*logcb) (void *cfg, void (*cb)(log_msgtype_t lvl, const char *msg)); 29 | /** dlsym pointer to handler of @a state command */ 30 | int (*state) (void *cfg); 31 | /** dlsym pointer to handler of @a flush command */ 32 | bool (*flush) (void *cfg); 33 | /** dlsym pointer to handler of @a stats command */ 34 | bool (*stats) (void *cfg, char *buf, size_t bufsize); 35 | /** dlsym pointer to handler of @a match command */ 36 | uint32_t (*match)(void *cfg, const char *line, char *buf, size_t bufsize, short int *score); 37 | /** dlsym pointer to handler of @a destroy command */ 38 | void (*destroy) (void *cfg); 39 | /* config variables */ 40 | char name[CONFIG_KEY_MAX]; /**< filter name from config (eg [filter:$NAME] section) */ 41 | char init[CONFIG_VAL_MAX]; /**< filter init string (eg `filter = NAME:$INIT_STRING` line from jail section) */ 42 | } f2b_filter_t; 43 | 44 | /** 45 | * @brief Allocate new filter struct and fill name/init fields 46 | * @param name Module name 47 | * @param init Module init string 48 | * @returns Pointer to allocated module struct or NULL on error 49 | */ 50 | f2b_filter_t * f2b_filter_create (const char *name, const char *init); 51 | 52 | /** 53 | * @brief Initialize and configure filter 54 | * @param config Pointer to config section with module description 55 | * @return true on success, false on error 56 | */ 57 | bool f2b_filter_init (f2b_filter_t *filter, f2b_config_section_t *config); 58 | 59 | /** 60 | * @brief Free module metadata 61 | * @param f Pointer to module struct 62 | */ 63 | void f2b_filter_destroy (f2b_filter_t *f); 64 | 65 | /** 66 | * @brief Append pattern to filter 67 | * @param f Pointer to filter struct 68 | * @param pattern Match pattern 69 | * @returns true on success, false on error 70 | */ 71 | 72 | bool f2b_filter_append(f2b_filter_t *f, const char *pattern); 73 | /** 74 | * @brief Match a line against given filter 75 | * @param f Pointer to filter struct 76 | * @param line Line of data 77 | * @param buf Match buffer 78 | * @param bufsize Size of match buffer 79 | * @param score Pointer to score 80 | * @returns >0 on match 0 otherwise, fills @a buf with extracted host string and @a score with match score 81 | */ 82 | uint32_t f2b_filter_match (f2b_filter_t *f, const char *line, char *buf, size_t bufsize, short int *score); 83 | 84 | /* handlers for csocket commands processing */ 85 | /** handler of 'jail $JAIL filter reload' cmd */ 86 | void f2b_filter_cmd_reload(char *buf, size_t bufsize, f2b_filter_t *f); 87 | 88 | /** handler of 'jail $JAIL filter stats' cmd */ 89 | void f2b_filter_cmd_stats (char *buf, size_t bufsize, f2b_filter_t *f); 90 | 91 | #endif /* F2B_FILTER_H_ */ 92 | -------------------------------------------------------------------------------- /src/filters/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 2 | unset(CMAKE_SHARED_MODULE_PREFIX) 3 | set(FILTERS "") 4 | 5 | add_library("f_preg" MODULE "preg.c" "../strlcpy.c" "../fnv32a.c") 6 | list(APPEND FILTERS "preg") 7 | 8 | if (WITH_PCRE) 9 | pkg_check_modules(PCRE "libpcre" REQUIRED) 10 | add_library("f_pcre" MODULE "pcre.c" "../strlcpy.c" "../fnv32a.c") 11 | target_link_libraries("f_pcre" "pcre") 12 | list(APPEND FILTERS "pcre") 13 | endif () 14 | 15 | foreach (FILTER IN LISTS FILTERS) 16 | set_target_properties("f_${FILTER}" PROPERTIES OUTPUT_NAME "filter_${FILTER}") 17 | install(TARGETS "f_${FILTER}" LIBRARY DESTINATION ${PLUGINS_PATH}) 18 | endforeach () 19 | 20 | message(STATUS "- Filters : ${FILTERS}") 21 | -------------------------------------------------------------------------------- /src/filters/filter.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | 8 | static void 9 | logcb_stub(enum loglevel lvl, const char *str) { 10 | assert(str != NULL); 11 | (void)(lvl); 12 | (void)(str); 13 | } 14 | 15 | __attribute__ ((format (printf, 3, 4))) 16 | static void 17 | log_msg(const cfg_t *cfg, enum loglevel lvl, const char *format, ...) { 18 | char buf[4096] = ""; 19 | va_list args; 20 | size_t len; 21 | 22 | len = snprintf(buf, sizeof(buf), "filter/%s: ", MODNAME); 23 | va_start(args, format); 24 | vsnprintf(buf + len, sizeof(buf) - len, format, args); 25 | va_end(args); 26 | 27 | cfg->logcb(lvl, buf); 28 | } 29 | 30 | void 31 | logcb(cfg_t *cfg, void (*cb)(enum loglevel lvl, const char *msg)) { 32 | assert(cfg != NULL); 33 | assert(cb != NULL); 34 | 35 | cfg->logcb = cb; 36 | } 37 | 38 | bool 39 | stats(cfg_t *cfg, char *buf, size_t bufsize) { 40 | char tmp[PATTERN_MAX + 64]; 41 | const char *fmt = 42 | "- pattern: %s\n" 43 | " info: tag=%08X score=%d matches=%d\n"; 44 | 45 | assert(cfg != NULL); 46 | 47 | if (buf == NULL || bufsize == 0) 48 | return false; 49 | 50 | for (rx_t *rx = cfg->regexps; rx != NULL; rx = rx->next) { 51 | snprintf(tmp, sizeof(tmp), fmt, rx->pattern, rx->ftag, rx->score, rx->matches); 52 | strlcat(buf, tmp, bufsize); 53 | } 54 | 55 | return true; 56 | } 57 | 58 | int 59 | state(cfg_t *cfg) { 60 | assert(cfg != NULL); 61 | return cfg->flags; 62 | } 63 | -------------------------------------------------------------------------------- /src/filters/filter.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #if defined(__linux__) 8 | #include 9 | #endif 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "../strlcpy.h" 19 | #include "../fnv.h" 20 | #include "../mod-defs.h" 21 | #include "../mod-api.h" 22 | 23 | /** 24 | * @file 25 | * This header describes module API of type 'filter' 26 | * 27 | * Sample workflow of module usage: 28 | * 29 | * @msc 30 | * f2b, filter; 31 | * f2b => filter [label="create(id)"]; 32 | * f2b << filter [label="module handler, cfg_t *cfg"]; 33 | * |||; 34 | * f2b => filter [label="config(cfg, param, value)"]; 35 | * f2b << filter [label="true"]; 36 | * f2b => filter [label="config(cfg, param, value)"]; 37 | * f2b << filter [label="true"]; 38 | * f2b => filter [label="config(cfg, param, value)"]; 39 | * f2b <<= filter [label="logcb(level, char *msg)"]; 40 | * f2b << filter [label="false"]; 41 | * --- [label="filter is ready for append()"]; 42 | * f2b => filter [label="append(cfg, pattern)"]; 43 | * f2b << filter [label="true"]; 44 | * f2b => filter [label="append(cfg, pattern)"]; 45 | * f2b <<= filter [label="logcb(level, char *msg)"]; 46 | * f2b << filter [label="false"]; 47 | * ||| [label="more calls of append()"]; 48 | * f2b => filter [label="state(cfg)"]; 49 | * f2b << filter [label="int"]; 50 | * --- [label="check for MOD_IS_READY flag"]; 51 | * f2b => filter [label="match(cfg, line, buf, sizeof(buf))"]; 52 | * f2b << filter [label="false"]; 53 | * ... [label="no match"]; 54 | * f2b => filter [label="match(cfg, line, buf, sizeof(buf))"]; 55 | * f2b << filter [label="true"]; 56 | * ... [label="match found, buf filled with ipaddr"]; 57 | * f2b >> filter [label="stats(cfg, &matches, &pattern, true)"]; 58 | * f2b << filter [label="true"]; 59 | * f2b >> filter [label="stats(cfg, &matches, &pattern, false)"]; 60 | * f2b << filter [label="true"]; 61 | * f2b >> filter [label="stats(cfg, &matches, &pattern, false)"]; 62 | * f2b << filter [label="false"]; 63 | * ... [label="no more stats"]; 64 | * f2b => filter [label="flush(cfg)"]; 65 | * f2b << filter [label="true"]; 66 | * --- [label="now you may config(), append() or destroy() filter"]; 67 | * f2b => filter [label="destroy(cfg)"]; 68 | * @endmsc 69 | */ 70 | 71 | #define ID_MAX 32 /**< Maximum length of name for usage in @a start() */ 72 | #define PATTERN_MAX 256 /**< Maximum length of regex */ 73 | #define HOST_TOKEN "" /**< Use this string in place where to search for ip address */ 74 | 75 | /** opaque pointer to regexp type */ 76 | typedef struct _regexp rx_t; 77 | 78 | /** type-specific module api routines */ 79 | 80 | /** 81 | * @brief Add match pattern 82 | * @param cfg Module handler 83 | * @param pattern Regex expression 84 | * @returns true on success, false on error 85 | */ 86 | extern bool append(cfg_t *cfg, const char *pattern); 87 | /** 88 | * @brief Fetch filter match stats 89 | * @param cfg Module handler 90 | * @param buf Pointer to storage for stats report 91 | * @param bufsize Available size for report on @a buf pointer 92 | * @returns true on success, false on error 93 | */ 94 | extern bool stats(cfg_t *cfg, char *buf, size_t bufsize); 95 | /** 96 | * @brief Match given line against configured regexps 97 | * @param cfg Module handler 98 | * @param line Source line of data 99 | * @param buf Buffer for storing match result 100 | * @param bufsize Size of buffer above 101 | * @returns false if no match or true otherwise with filling @a buf 102 | */ 103 | extern uint32_t match(cfg_t *cfg, const char *line, char *buf, size_t bufsize, short int *score); 104 | /** 105 | * @brief Destroy all added patterns and free it's resources 106 | * @param cfg Module handler 107 | */ 108 | extern void flush(cfg_t *cfg); 109 | -------------------------------------------------------------------------------- /src/filters/pcre.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #include "filter.h" 8 | 9 | #include 10 | 11 | #define MODNAME "pcre" 12 | #define HOST_REGEX "(?[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3})" 13 | 14 | struct _regexp { 15 | rx_t *next; 16 | uint32_t ftag; 17 | int matches; 18 | short int score; 19 | pcre *regex; 20 | pcre_extra *data; 21 | char pattern[PATTERN_MAX]; 22 | }; 23 | 24 | struct _config { 25 | rx_t *regexps; 26 | rx_t *rlast; /* pointer to last regex in list */ 27 | void (*logcb)(enum loglevel lvl, const char *msg); 28 | short int defscore; 29 | int flags; 30 | char id[ID_MAX]; 31 | bool icase; 32 | bool study; 33 | bool usejit; 34 | }; 35 | 36 | #include "filter.c" 37 | 38 | cfg_t * 39 | create(const char *id) { 40 | cfg_t *cfg = NULL; 41 | 42 | if ((cfg = calloc(1, sizeof(cfg_t))) == NULL) 43 | return NULL; 44 | strlcpy(cfg->id, id, sizeof(cfg->id)); 45 | 46 | cfg->logcb = &logcb_stub; 47 | cfg->flags |= MOD_TYPE_FILTER; 48 | cfg->defscore = MATCH_DEFSCORE; 49 | return cfg; 50 | } 51 | 52 | bool 53 | config(cfg_t *cfg, const char *key, const char *value) { 54 | assert(cfg != NULL); 55 | assert(key != NULL); 56 | assert(value != NULL); 57 | 58 | if (strcmp(key, "icase") == 0) { 59 | cfg->icase = (strcmp(value, "yes") == 0) ? true : false; 60 | return true; 61 | } 62 | if (strcmp(key, "study") == 0) { 63 | cfg->study = (strcmp(value, "yes") == 0) ? true : false; 64 | return true; 65 | } 66 | if (strcmp(key, "usejit") == 0) { 67 | cfg->usejit = (strcmp(value, "yes") == 0) ? true : false; 68 | #ifndef PCRE_CONFIG_JIT 69 | if (cfg->usejit) { 70 | cfg->usejit = false; 71 | log_msg(cfg, error, "seems like your pcre library doesn't support jit"); 72 | return false; 73 | } 74 | #endif 75 | return true; 76 | } 77 | if (strcmp(key, "defscore") == 0) { 78 | cfg->defscore = atoi(value); 79 | return true; 80 | } 81 | 82 | return false; 83 | } 84 | 85 | bool 86 | append(cfg_t *cfg, const char *pattern) { 87 | rx_t *regex = NULL; 88 | int flags = 0; 89 | size_t bufsize; 90 | char *buf = NULL; 91 | char *token = NULL; 92 | const char *errptr = NULL; 93 | int erroffset = 0; 94 | 95 | assert(pattern != NULL); 96 | 97 | if (cfg->icase) 98 | flags |= PCRE_CASELESS; 99 | 100 | if ((token = strstr(pattern, HOST_TOKEN)) == NULL) 101 | return false; 102 | 103 | bufsize = strlen(pattern) + strlen(HOST_REGEX) + 1; 104 | if ((buf = alloca(bufsize)) == NULL) 105 | return false; 106 | 107 | memset(buf, 0x0, bufsize); 108 | memcpy(buf, pattern, token - pattern); 109 | strlcat(buf, HOST_REGEX, bufsize); 110 | strlcat(buf, token + strlen(HOST_TOKEN), bufsize); 111 | 112 | if ((regex = calloc(1, sizeof(rx_t))) == NULL) 113 | return false; 114 | 115 | if ((regex->regex = pcre_compile(buf, flags, &errptr, &erroffset, NULL)) == NULL) { 116 | log_msg(cfg, error, "regex compilation failed at %d: %s", erroffset, errptr); 117 | free(regex); 118 | return false; 119 | } 120 | 121 | if (cfg->study) { 122 | flags = 0; 123 | #ifdef PCRE_CONFIG_JIT 124 | if (cfg->usejit) 125 | flags |= PCRE_STUDY_JIT_COMPILE; 126 | #endif 127 | if ((regex->data = pcre_study(regex->regex, 0, &errptr)) == NULL) { 128 | log_msg(cfg, error, "regex learn failed: %s", errptr); 129 | pcre_free(regex->regex); 130 | free(regex); 131 | return false; 132 | } 133 | } 134 | 135 | regex->score = cfg->defscore; 136 | regex->ftag = fnv_32a_str(pattern, FNV1_32A_INIT); 137 | strlcpy(regex->pattern, pattern, sizeof(regex->pattern)); 138 | /* update regex list */ 139 | if (cfg->rlast) { 140 | cfg->rlast->next = regex; 141 | } else { 142 | cfg->regexps = regex; 143 | } 144 | cfg->rlast = regex; 145 | cfg->flags |= MOD_IS_READY; 146 | return true; 147 | } 148 | 149 | uint32_t 150 | match(cfg_t *cfg, const char *line, char *buf, size_t buf_size, short int *score) { 151 | enum { OVECSIZE = 30 }; 152 | int ovector[OVECSIZE]; 153 | int flags = 0; 154 | int rc = 0, sc = 0; /* sc = stringcount */ 155 | 156 | assert(cfg != NULL); 157 | assert(line != NULL); 158 | assert(buf != NULL); 159 | 160 | for (rx_t *r = cfg->regexps; r != NULL; r = r->next) { 161 | rc = pcre_exec(r->regex, r->data, line, strlen(line), 0, flags, ovector, OVECSIZE); 162 | if (rc < 0 && rc == PCRE_ERROR_NOMATCH) 163 | continue; 164 | if (rc < 0) { 165 | log_msg(cfg, error, "pcre match failed with error: %d", rc); 166 | continue; 167 | } 168 | /* matched */ 169 | r->matches++; 170 | sc = (rc) ? rc : OVECSIZE / 3; 171 | rc = pcre_copy_named_substring(r->regex, line, ovector, sc, "host", buf, buf_size); 172 | if (rc < 0) { 173 | log_msg(cfg, error, "can't copy matched string: %d", rc); 174 | continue; 175 | } 176 | *score = r->score; 177 | return r->ftag; 178 | } 179 | 180 | return 0; 181 | } 182 | 183 | void 184 | flush(cfg_t *cfg) { 185 | rx_t *next = NULL; 186 | 187 | assert(cfg != NULL); 188 | 189 | for (rx_t *r = cfg->regexps; r != NULL; r = next) { 190 | next = r->next; 191 | #ifdef PCRE_CONFIG_JIT 192 | if (cfg->study) 193 | pcre_free_study(r->data); 194 | #else 195 | if (cfg->study) 196 | pcre_free(r->data); 197 | #endif 198 | pcre_free(r->regex); 199 | free(r); 200 | } 201 | cfg->regexps = NULL; 202 | cfg->rlast = NULL; 203 | cfg->defscore = MATCH_DEFSCORE; 204 | } 205 | 206 | void 207 | destroy(cfg_t *cfg) { 208 | assert(cfg != NULL); 209 | 210 | flush(cfg); 211 | free(cfg); 212 | } 213 | -------------------------------------------------------------------------------- /src/filters/preg.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #include 8 | 9 | #include "../strlcpy.h" 10 | #include "filter.h" 11 | 12 | #define MODNAME "preg" 13 | /* draft */ 14 | #define HOST_REGEX "([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3})" 15 | 16 | struct _regexp { 17 | rx_t *next; 18 | uint32_t ftag; 19 | int matches; 20 | short int score; 21 | regex_t regex; 22 | char pattern[PATTERN_MAX]; 23 | }; 24 | 25 | struct _config { 26 | rx_t *regexps; 27 | rx_t *rlast; /* pointer to last regex in list */ 28 | void (*logcb)(enum loglevel lvl, const char *msg); 29 | short int defscore; 30 | int flags; 31 | char id[ID_MAX]; 32 | bool icase; 33 | }; 34 | 35 | #include "filter.c" 36 | 37 | cfg_t * 38 | create(const char *id) { 39 | cfg_t *cfg = NULL; 40 | 41 | if ((cfg = calloc(1, sizeof(cfg_t))) == NULL) 42 | return NULL; 43 | strlcpy(cfg->id, id, sizeof(cfg->id)); 44 | 45 | cfg->logcb = &logcb_stub; 46 | cfg->flags |= MOD_TYPE_FILTER; 47 | cfg->defscore = MATCH_DEFSCORE; 48 | return cfg; 49 | } 50 | 51 | bool 52 | config(cfg_t *cfg, const char *key, const char *value) { 53 | assert(cfg != NULL); 54 | assert(key != NULL); 55 | assert(value != NULL); 56 | 57 | if (strcmp(key, "icase") == 0) { 58 | cfg->icase = (strcmp(value, "yes") == 0) ? true : false; 59 | return true; 60 | } 61 | if (strcmp(key, "defscore") == 0) { 62 | cfg->defscore = atoi(value); 63 | return true; 64 | } 65 | 66 | return false; 67 | } 68 | 69 | bool 70 | append(cfg_t *cfg, const char *pattern) { 71 | rx_t *regex = NULL; 72 | int flags = REG_EXTENDED; 73 | size_t bufsize; 74 | char *buf = NULL; 75 | char *token = NULL; 76 | int ret; 77 | 78 | assert(pattern != NULL); 79 | 80 | if (cfg->icase) 81 | flags |= REG_ICASE; 82 | 83 | if ((token = strstr(pattern, HOST_TOKEN)) == NULL) 84 | return false; 85 | 86 | bufsize = strlen(pattern) + strlen(HOST_REGEX) + 1; 87 | if ((buf = alloca(bufsize)) == NULL) 88 | return false; 89 | 90 | memset(buf, 0x0, bufsize); 91 | memcpy(buf, pattern, token - pattern); 92 | strlcat(buf, HOST_REGEX, bufsize); 93 | strlcat(buf, token + strlen(HOST_TOKEN), bufsize); 94 | 95 | if ((regex = calloc(1, sizeof(rx_t))) == NULL) 96 | return false; 97 | 98 | if ((ret = regcomp(®ex->regex, buf, flags)) != 0) { 99 | char buf[256] = ""; 100 | regerror(ret, ®ex->regex, buf, sizeof(buf)); 101 | log_msg(cfg, error, "regex compile error: %s", buf); 102 | free(regex); 103 | return false; 104 | } 105 | 106 | regex->score = cfg->defscore; 107 | regex->ftag = fnv_32a_str(pattern, FNV1_32A_INIT); 108 | strlcpy(regex->pattern, pattern, sizeof(regex->pattern)); 109 | /* update regex list */ 110 | if (cfg->rlast) { 111 | cfg->rlast->next = regex; 112 | } else { 113 | cfg->regexps = regex; 114 | } 115 | cfg->rlast = regex; 116 | cfg->flags |= MOD_IS_READY; 117 | return true; 118 | } 119 | 120 | uint32_t 121 | match(cfg_t *cfg, const char *line, char *buf, size_t buf_size, short int *score) { 122 | size_t match_len = 0; 123 | regmatch_t match[2]; 124 | 125 | assert(cfg != NULL); 126 | assert(line != NULL); 127 | assert(buf != NULL); 128 | 129 | for (rx_t *r = cfg->regexps; r != NULL; r = r->next) { 130 | if (regexec(&r->regex, line, 2, &match[0], 0) != 0) 131 | continue; 132 | /* matched */ 133 | r->matches++; 134 | match_len = match[1].rm_eo - match[1].rm_so; 135 | assert(buf_size > match_len); 136 | memcpy(buf, &line[match[1].rm_so], match_len); 137 | buf[match_len] = '\0'; 138 | buf[buf_size - 1] = '\0'; 139 | *score = r->score; 140 | return r->ftag; 141 | } 142 | 143 | return 0; 144 | } 145 | 146 | void 147 | flush(cfg_t *cfg) { 148 | rx_t *next = NULL; 149 | 150 | assert(cfg != NULL); 151 | 152 | for (rx_t *r = cfg->regexps; r != NULL; r = next) { 153 | next = r->next; 154 | regfree(&r->regex); 155 | free(r); 156 | } 157 | cfg->regexps = NULL; 158 | cfg->rlast = NULL; 159 | cfg->defscore = MATCH_DEFSCORE; 160 | } 161 | 162 | void 163 | destroy(cfg_t *cfg) { 164 | assert(cfg != NULL); 165 | 166 | flush(cfg); 167 | free(cfg); 168 | } 169 | -------------------------------------------------------------------------------- /src/fnv.h: -------------------------------------------------------------------------------- 1 | /* 2 | * fnv - Fowler/Noll/Vo- hash code 3 | * 4 | * @(#) $Revision: 5.4 $ 5 | * @(#) $Id: fnv.h,v 5.4 2009/07/30 22:49:13 chongo Exp $ 6 | * @(#) $Source: /usr/local/src/cmd/fnv/RCS/fnv.h,v $ 7 | * 8 | *** 9 | * 10 | * Fowler/Noll/Vo- hash 11 | * 12 | * The basis of this hash algorithm was taken from an idea sent 13 | * as reviewer comments to the IEEE POSIX P1003.2 committee by: 14 | * 15 | * Phong Vo (http://www.research.att.com/info/kpv/) 16 | * Glenn Fowler (http://www.research.att.com/~gsf/) 17 | * 18 | * In a subsequent ballot round: 19 | * 20 | * Landon Curt Noll (http://www.isthe.com/chongo/) 21 | * 22 | * improved on their algorithm. Some people tried this hash 23 | * and found that it worked rather well. In an EMail message 24 | * to Landon, they named it the ``Fowler/Noll/Vo'' or FNV hash. 25 | * 26 | * FNV hashes are designed to be fast while maintaining a low 27 | * collision rate. The FNV speed allows one to quickly hash lots 28 | * of data while maintaining a reasonable collision rate. See: 29 | * 30 | * http://www.isthe.com/chongo/tech/comp/fnv/index.html 31 | * 32 | * for more details as well as other forms of the FNV hash. 33 | * 34 | *** 35 | * 36 | * NOTE: The FNV-0 historic hash is not recommended. One should use 37 | * the FNV-1 hash instead. 38 | * 39 | * To use the 32 bit FNV-0 historic hash, pass FNV0_32_INIT as the 40 | * Fnv32_t hashval argument to fnv_32_buf() or fnv_32_str(). 41 | * 42 | * To use the recommended 32 bit FNV-1 hash, pass FNV1_32_INIT as the 43 | * Fnv32_t hashval argument to fnv_32_buf() or fnv_32_str(). 44 | * 45 | * To use the recommended 32 bit FNV-1a hash, pass FNV1_32A_INIT as the 46 | * Fnv32_t hashval argument to fnv_32a_buf() or fnv_32a_str(). 47 | * 48 | *** 49 | * 50 | * Please do not copyright this code. This code is in the public domain. 51 | * 52 | * LANDON CURT NOLL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, 53 | * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO 54 | * EVENT SHALL LANDON CURT NOLL BE LIABLE FOR ANY SPECIAL, INDIRECT OR 55 | * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF 56 | * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 57 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 58 | * PERFORMANCE OF THIS SOFTWARE. 59 | * 60 | * By: 61 | * chongo /\oo/\ 62 | * http://www.isthe.com/chongo/ 63 | * 64 | * Share and Enjoy! :-) 65 | */ 66 | 67 | #if !defined(__FNV_H__) 68 | #define __FNV_H__ 69 | 70 | #include 71 | 72 | #define FNV_VERSION "5.0.2" /* @(#) FNV Version */ 73 | 74 | /* 75 | * 32 bit FNV-0 hash type 76 | */ 77 | typedef uint32_t Fnv32_t; 78 | 79 | /* 80 | * 32 bit FNV-1 and FNV-1a non-zero initial basis 81 | * 82 | * The FNV-1 initial basis is the FNV-0 hash of the following 32 octets: 83 | * 84 | * chongo /\../\ 85 | * 86 | * NOTE: The \'s above are not back-slashing escape characters. 87 | * They are literal ASCII backslash 0x5c characters. 88 | * 89 | * NOTE: The FNV-1a initial basis is the same value as FNV-1 by definition. 90 | */ 91 | #define FNV1_32_INIT ((Fnv32_t)0x811c9dc5) 92 | #define FNV1_32A_INIT FNV1_32_INIT 93 | 94 | extern Fnv32_t fnv_32a_buf(const void *buf, size_t len, Fnv32_t hashval); 95 | extern Fnv32_t fnv_32a_str(const char *buf, Fnv32_t hashval); 96 | 97 | #endif /* __FNV_H__ */ 98 | -------------------------------------------------------------------------------- /src/fnv32a.c: -------------------------------------------------------------------------------- 1 | /* 2 | * hash_32 - 32 bit Fowler/Noll/Vo FNV-1a hash code 3 | * 4 | * @(#) $Revision: 5.1 $ 5 | * @(#) $Id: hash_32a.c,v 5.1 2009/06/30 09:13:32 chongo Exp $ 6 | * @(#) $Source: /usr/local/src/cmd/fnv/RCS/hash_32a.c,v $ 7 | * 8 | *** 9 | * 10 | * Fowler/Noll/Vo hash 11 | * 12 | * The basis of this hash algorithm was taken from an idea sent 13 | * as reviewer comments to the IEEE POSIX P1003.2 committee by: 14 | * 15 | * Phong Vo (http://www.research.att.com/info/kpv/) 16 | * Glenn Fowler (http://www.research.att.com/~gsf/) 17 | * 18 | * In a subsequent ballot round: 19 | * 20 | * Landon Curt Noll (http://www.isthe.com/chongo/) 21 | * 22 | * improved on their algorithm. Some people tried this hash 23 | * and found that it worked rather well. In an EMail message 24 | * to Landon, they named it the ``Fowler/Noll/Vo'' or FNV hash. 25 | * 26 | * FNV hashes are designed to be fast while maintaining a low 27 | * collision rate. The FNV speed allows one to quickly hash lots 28 | * of data while maintaining a reasonable collision rate. See: 29 | * 30 | * http://www.isthe.com/chongo/tech/comp/fnv/index.html 31 | * 32 | * for more details as well as other forms of the FNV hash. 33 | *** 34 | * 35 | * To use the recommended 32 bit FNV-1a hash, pass FNV1_32A_INIT as the 36 | * Fnv32_t hashval argument to fnv_32a_buf() or fnv_32a_str(). 37 | * 38 | *** 39 | * 40 | * Please do not copyright this code. This code is in the public domain. 41 | * 42 | * LANDON CURT NOLL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, 43 | * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO 44 | * EVENT SHALL LANDON CURT NOLL BE LIABLE FOR ANY SPECIAL, INDIRECT OR 45 | * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF 46 | * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 47 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 48 | * PERFORMANCE OF THIS SOFTWARE. 49 | * 50 | * By: 51 | * chongo /\oo/\ 52 | * http://www.isthe.com/chongo/ 53 | * 54 | * Share and Enjoy! :-) 55 | */ 56 | 57 | #include 58 | #include 59 | #include "fnv.h" 60 | 61 | /* 62 | * 32 bit magic FNV-1a prime 63 | */ 64 | #define FNV_32_PRIME ((Fnv32_t)0x01000193) 65 | 66 | /* 67 | * fnv_32a_buf - perform a 32 bit Fowler/Noll/Vo FNV-1a hash on a buffer 68 | * 69 | * input: 70 | * buf - start of buffer to hash 71 | * len - length of buffer in octets 72 | * hval - previous hash value or 0 if first call 73 | * 74 | * returns: 75 | * 32 bit hash as a static hash type 76 | * 77 | * NOTE: To use the recommended 32 bit FNV-1a hash, use FNV1_32A_INIT as the 78 | * hval arg on the first call to either fnv_32a_buf() or fnv_32a_str(). 79 | */ 80 | Fnv32_t 81 | fnv_32a_buf(const void *buf, size_t len, Fnv32_t hval) 82 | { 83 | unsigned char *bp = (unsigned char *)buf; /* start of buffer */ 84 | unsigned char *be = bp + len; /* beyond end of buffer */ 85 | 86 | /* 87 | * FNV-1a hash each octet in the buffer 88 | */ 89 | while (bp < be) { 90 | 91 | /* xor the bottom with the current octet */ 92 | hval ^= (Fnv32_t)*bp++; 93 | 94 | /* multiply by the 32 bit FNV magic prime mod 2^32 */ 95 | #if defined(NO_FNV_GCC_OPTIMIZATION) 96 | hval *= FNV_32_PRIME; 97 | #else 98 | hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24); 99 | #endif 100 | } 101 | 102 | /* return our new hash value */ 103 | return hval; 104 | } 105 | 106 | /* 107 | * fnv_32a_str - perform a 32 bit Fowler/Noll/Vo FNV-1a hash on a string 108 | * 109 | * input: 110 | * str - string to hash 111 | * hval - previous hash value or 0 if first call 112 | * 113 | * returns: 114 | * 32 bit hash as a static hash type 115 | * 116 | * NOTE: To use the recommended 32 bit FNV-1a hash, use FNV1_32A_INIT as the 117 | * hval arg on the first call to either fnv_32a_buf() or fnv_32a_str(). 118 | */ 119 | Fnv32_t 120 | fnv_32a_str(const char *str, Fnv32_t hval) 121 | { 122 | unsigned char *s = (unsigned char *)str; /* unsigned string */ 123 | 124 | /* 125 | * FNV-1a hash each octet in the buffer 126 | */ 127 | while (*s) { 128 | 129 | /* xor the bottom with the current octet */ 130 | hval ^= (Fnv32_t)*s++; 131 | 132 | /* multiply by the 32 bit FNV magic prime mod 2^32 */ 133 | #if defined(NO_FNV_GCC_OPTIMIZATION) 134 | hval *= FNV_32_PRIME; 135 | #else 136 | hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24); 137 | #endif 138 | } 139 | 140 | /* return our new hash value */ 141 | return hval; 142 | } 143 | -------------------------------------------------------------------------------- /src/ipaddr.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #include "common.h" 8 | #include "matches.h" 9 | #include "ipaddr.h" 10 | 11 | #include 12 | #include 13 | 14 | f2b_ipaddr_t * 15 | f2b_ipaddr_create(const char *addr) { 16 | f2b_ipaddr_t *a = NULL; 17 | 18 | assert(addr != NULL); 19 | 20 | if ((a = calloc(1, sizeof(f2b_ipaddr_t))) != NULL) { 21 | strlcpy(a->text, addr, sizeof(a->text)); 22 | if (strchr(addr, ':') == NULL) { 23 | a->type = AF_INET; 24 | if (inet_pton(a->type, addr, &a->binary) < 1) 25 | goto cleanup; 26 | } else { 27 | a->type = AF_INET6; 28 | if (inet_pton(a->type, addr, &a->binary) < 1) 29 | goto cleanup; 30 | } 31 | } 32 | return a; 33 | 34 | cleanup: 35 | free(a); 36 | return NULL; 37 | } 38 | 39 | void 40 | f2b_ipaddr_destroy(f2b_ipaddr_t *ipaddr) { 41 | assert(ipaddr != NULL); 42 | 43 | f2b_matches_flush(&ipaddr->matches); 44 | free(ipaddr); 45 | } 46 | 47 | void 48 | f2b_ipaddr_status(f2b_ipaddr_t *addr, char *res, size_t ressize) { 49 | struct tm t; 50 | char buf[2048], stime[32] = "", btime1[32] = "", btime2[32] = ""; 51 | assert(addr != NULL); 52 | assert(res != NULL); 53 | const char *fmt_dt = "%Y-%m-%d %H:%M:%S"; 54 | const char *fmt_status = 55 | "ipaddr: %s\n" 56 | "lastseen: %s\n" 57 | "bancount: %u\n"; 58 | const char *fmt_ban = 59 | "banned: yes\n" 60 | "bantime:\n" 61 | " from: %s\n" 62 | " to: %s\n"; 63 | const char *fmt_match = 64 | " - time: %s, score: %d, stag: %08X, ftag: %08X\n"; 65 | strftime(stime, sizeof(stime), fmt_dt, localtime(&addr->lastseen)); 66 | snprintf(res, ressize, fmt_status, addr->text, stime, addr->bancount); 67 | if (addr->banned) { 68 | strftime(btime1, sizeof(btime1), fmt_dt, localtime_r(&addr->banned_at, &t)); 69 | strftime(btime2, sizeof(btime2), fmt_dt, localtime_r(&addr->release_at, &t)); 70 | snprintf(buf, sizeof(buf), fmt_ban, btime1, btime2); 71 | strlcat(res, buf, ressize); 72 | } else { 73 | strlcat(res, "banned: no\n", ressize); 74 | if (addr->matches.count) { 75 | strlcat(res, "matches:\n", ressize); 76 | for (f2b_match_t *m = addr->matches.list; m != NULL; m = m->next) { 77 | strftime(stime, sizeof(stime), fmt_dt, localtime_r(&m->time, &t)); 78 | snprintf(buf, sizeof(buf), fmt_match, stime, m->score, m->stag, m->ftag); 79 | strlcat(res, buf, ressize); 80 | } 81 | } 82 | } 83 | } 84 | 85 | f2b_ipaddr_t * 86 | f2b_addrlist_append(f2b_ipaddr_t *list, f2b_ipaddr_t *ipaddr) { 87 | assert(ipaddr != NULL); 88 | 89 | ipaddr->next = list; 90 | return ipaddr; 91 | } 92 | 93 | f2b_ipaddr_t * 94 | f2b_addrlist_lookup(f2b_ipaddr_t *list, const char *addr) { 95 | assert(addr != NULL); 96 | 97 | if (list == NULL) 98 | return NULL; 99 | 100 | for (f2b_ipaddr_t *a = list; a != NULL; a = a->next) { 101 | if (strncmp(a->text, addr, sizeof(a->text)) == 0) 102 | return a; 103 | } 104 | 105 | return NULL; 106 | } 107 | 108 | f2b_ipaddr_t * 109 | f2b_addrlist_remove(f2b_ipaddr_t *list, const char *addr) { 110 | f2b_ipaddr_t *a = NULL, *prev = NULL; 111 | 112 | assert(addr != NULL); 113 | 114 | for (a = list; a != NULL; a = a->next) { 115 | if (strncmp(a->text, addr, sizeof(a->text)) == 0) { 116 | if (prev == NULL) { 117 | /* first elem in list */ 118 | list = a->next; 119 | } else { 120 | /* somewhere in list */ 121 | prev->next = a->next; 122 | } 123 | f2b_ipaddr_destroy(a); 124 | return list; 125 | } 126 | prev = a; 127 | } 128 | 129 | return list; 130 | } 131 | 132 | f2b_ipaddr_t * 133 | f2b_addrlist_destroy(f2b_ipaddr_t *list) { 134 | f2b_ipaddr_t *next = NULL; 135 | 136 | for (; list != NULL; list = next) { 137 | next = list->next; 138 | f2b_ipaddr_destroy(list); 139 | } 140 | 141 | return NULL; 142 | } 143 | -------------------------------------------------------------------------------- /src/ipaddr.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #ifndef F2B_IPADDR_H_ 8 | #define F2B_IPADDR_H_ 9 | 10 | /** 11 | * @file 12 | * This file contains definition of ipaddr struct and related routines 13 | */ 14 | 15 | /** 16 | * @def IPADDR_MAX 17 | * Maximum text length of address 18 | */ 19 | #define IPADDR_MAX 48 /* 8 x "ffff" + 7 x "::" + '\0' */ 20 | 21 | /** Describes ip-address and it's metadata */ 22 | typedef struct f2b_ipaddr_t { 23 | struct f2b_ipaddr_t *next; /**< pointer to next addr */ 24 | int type; /**< address type, AF_INET/AF_INET6 */ 25 | char text[IPADDR_MAX]; /**< textual address */ 26 | bool banned; /**< is address banned, flag */ 27 | size_t bancount; /**< how many times this address was banned */ 28 | time_t lastseen; /**< self-descriptive, unixtime */ 29 | time_t banned_at; /**< self-descriptive, unixtime */ 30 | time_t release_at; /**< self-descriptive, unixtime */ 31 | unsigned char binary[16]; /**< AF_INET/AF_INET6 binary address, see inet_pton() */ 32 | f2b_matches_t matches; /**< list of matches */ 33 | } f2b_ipaddr_t; 34 | 35 | /** 36 | * @brief Create address record and fill related metadata 37 | * @param addr Textual address 38 | * @returns Pointer to address or NULL no error 39 | */ 40 | f2b_ipaddr_t * f2b_ipaddr_create (const char *addr); 41 | 42 | /** 43 | * @brief Free address metadata 44 | * @param ipaddr Pointer to f2b_ipaddr_t struct 45 | * @note @a ipaddr pointer becomes invalid after calling this function 46 | */ 47 | void f2b_ipaddr_destroy(f2b_ipaddr_t *ipaddr); 48 | /** 49 | * @brief Get some stats about given address 50 | * @param ipaddr Pointer to f2b_ipaddr_t struct 51 | * @param res Buffer for storing stats 52 | * @param ressize Size of buffer above 53 | */ 54 | void f2b_ipaddr_status (f2b_ipaddr_t *ipaddr, char *res, size_t ressize); 55 | 56 | /** 57 | * @brief Append address to list 58 | * @param list Pointer to ipaddr list (can be NULL) 59 | * @param ipaddr Pointer to ipaddr struct for adding to list 60 | * @returns Pointer to new address list 61 | */ 62 | f2b_ipaddr_t * f2b_addrlist_append(f2b_ipaddr_t *list, f2b_ipaddr_t *ipaddr); 63 | /** 64 | * @brief Search for given ipaddr in list 65 | * @param list Pointer to ipaddr list (can be NULL) 66 | * @param addr IP address for search 67 | * @returns Pointer to found struct or NULL if not found 68 | */ 69 | f2b_ipaddr_t * f2b_addrlist_lookup(f2b_ipaddr_t *list, const char *addr); 70 | /** 71 | * @brief Remove given address from list 72 | * @param list Pointer to ipaddr list (can be NULL) 73 | * @param addr IP address for remove 74 | * @returns Pointer to new address list or NULL if new list is empty 75 | */ 76 | f2b_ipaddr_t * f2b_addrlist_remove(f2b_ipaddr_t *list, const char *addr); 77 | /** 78 | * @brief Free all addresses in list 79 | * @param list Pointer to ipaddr list (can be NULL) 80 | * @returns NULL 81 | * @note Return value not void to match other functions 82 | */ 83 | f2b_ipaddr_t * f2b_addrlist_destroy(f2b_ipaddr_t *list); 84 | 85 | #endif /* F2B_IPADDR_H_ */ 86 | -------------------------------------------------------------------------------- /src/jail.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #ifndef F2B_JAIL_H_ 8 | #define F2B_JAIL_H_ 9 | 10 | /** 11 | * @file 12 | * This header describes jail definition and related routines 13 | */ 14 | 15 | /* jail flags */ 16 | #define JAIL_CONFIGURED 1 /* loaded all modules */ 17 | #define JAIL_ENABLED 2 /* poll for new events? */ 18 | /* reserved : 4 */ 19 | #define JAIL_HAS_STATE 8 20 | #define JAIL_HAS_SOURCE 16 21 | #define JAIL_HAS_FILTER 32 22 | #define JAIL_HAS_BACKEND 64 23 | 24 | /** jail metadata struct */ 25 | typedef struct f2b_jail_t { 26 | struct f2b_jail_t *next; /**< pointer to next jail */ 27 | char name[CONFIG_KEY_MAX]; /**< name of the jail */ 28 | unsigned int flags; /**< jail flags, see above */ 29 | short int banscore; /**< option: minimum amount of score to ban host */ 30 | /* duration of misc time periods */ 31 | time_t findtime; /**< option: length of time period for estimating recent host activity (in seconds) */ 32 | time_t bantime; /**< option: host ban time on excess activity (seconds) */ 33 | time_t expiretime; /**< option: forget about host after this time with no activity (seconds, for banned hosts - after it's release, for not banned - after latest match) */ 34 | /** time period length modifiers for already banned hosts */ 35 | float findtime_extend; /**< (float, 0.0 - 1.0) */ 36 | float bantime_extend; /**< (float, 0.0 - 1.0) */ 37 | float expiretime_extend; /**< (float, 0.0 - 1.0) */ 38 | /* jail stats */ 39 | struct { 40 | unsigned int hosts; /**< number of tracked hosts */ 41 | unsigned int bans; /**< number of ban events */ 42 | unsigned int matches; /**< number of match events */ 43 | } stats; 44 | char source_name[CONFIG_KEY_MAX]; 45 | char source_init[CONFIG_VAL_MAX]; 46 | char filter_name[CONFIG_KEY_MAX]; 47 | char filter_init[CONFIG_VAL_MAX]; 48 | char backend_name[CONFIG_KEY_MAX]; 49 | char backend_init[CONFIG_VAL_MAX]; 50 | f2b_source_t *source; /**< pointer to source */ 51 | f2b_filter_t *filter; /**< pointer to filter */ 52 | f2b_backend_t *backend; /**< pointer to backend */ 53 | f2b_statefile_t *sfile; /**< pointer to state file description */ 54 | f2b_ipaddr_t *ipaddrs; /**< list of known ip addresses */ 55 | } f2b_jail_t; 56 | 57 | /** 58 | * @var jails 59 | * Global list of Defined jails 60 | */ 61 | extern f2b_jail_t *jails; 62 | 63 | /** 64 | * @brief Apply defaults to jail template (affects later f2b_jail_create()) 65 | * @param section 'defaults' section from config 66 | */ 67 | void f2b_jail_set_defaults(f2b_config_section_t *section); 68 | /** 69 | * @brief Create jail struct and init it's metadata 70 | * @param section Jail config section 71 | * @return Pointer to allocated jail or NULL on error 72 | */ 73 | f2b_jail_t *f2b_jail_create (f2b_config_section_t *section); 74 | /** 75 | * @brief Find jail in jail list by name 76 | * @param list Jails list 77 | * @param name Jail name 78 | * @returns Pointer to wanted jail or NULL if not found 79 | */ 80 | f2b_jail_t *f2b_jail_find (f2b_jail_t *list, const char *name); 81 | /** 82 | * @brief Set tunable parameter of jail 83 | * @param jail Jail pointer 84 | * @param param Parameter name 85 | * @param value Parameter value 86 | * @return true if parameter set, false if not found 87 | */ 88 | bool f2b_jail_set_param(f2b_jail_t *jail, const char *param, const char *value); 89 | /** 90 | * @brief Setup source, filter and backend in jail 91 | * @param jail Jail pointer 92 | * @param config Pointer to f2b config 93 | * @return true on success, false on error 94 | */ 95 | bool f2b_jail_init(f2b_jail_t *jail, f2b_config_t *config); 96 | /** 97 | * @brief Load state file and restore bans 98 | * @param jail Jail pointer 99 | * @returns true on success, false on error 100 | */ 101 | bool f2b_jail_start(f2b_jail_t *jail); 102 | /** 103 | * @brief Jail main maintenance routine 104 | * Polls source for data, match against filter (if set), manage matches, 105 | * ban ips, that exceeded their limit, unban ips after bantime expire 106 | * @param jail Jail for processing 107 | */ 108 | void f2b_jail_process (f2b_jail_t *jail); 109 | /** 110 | * @brief Correctly shutdown given jail 111 | * @param jail Jail pointer 112 | * @note Jail structure not deallocated 113 | */ 114 | bool f2b_jail_stop (f2b_jail_t *jail); 115 | 116 | /* handlers for csocket commands processing */ 117 | 118 | /** 119 | * @brief Get jail status 120 | * @param res Response buffer 121 | * @param ressize Size of buffer above 122 | * @param jail Jail pointer 123 | */ 124 | void f2b_jail_cmd_status (char *res, size_t ressize, f2b_jail_t *jail); 125 | /** 126 | * @brief Get jail status 127 | * @param res Response buffer 128 | * @param ressize Size of buffer above 129 | * @param jail Jail pointer 130 | * @param param Parameter name 131 | * @param value Parameter value 132 | */ 133 | void f2b_jail_cmd_set (char *res, size_t ressize, f2b_jail_t *jail, const char *param, const char *value); 134 | /** 135 | * @brief ipaddr manage routine in given jail 136 | * @param res Response buffer 137 | * @param ressize Size of buffer above 138 | * @param jail Jail pointer 139 | * @param op Operation for ipaddr >0 - ban, 0 - check, <0 - unban 140 | * @param ip Ip address 141 | */ 142 | void f2b_jail_cmd_ip_xxx (char *res, size_t ressize, f2b_jail_t *jail, int op, const char *ip); 143 | 144 | #endif /* F2B_JAIL_H_ */ 145 | -------------------------------------------------------------------------------- /src/log.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #include "common.h" 8 | #include "log.h" 9 | 10 | #include 11 | 12 | static log_msgtype_t minlevel = log_info; 13 | static enum { 14 | log_stderr = 0, 15 | log_stdout = 1, 16 | log_file = 2, 17 | log_syslog = 3 18 | } dest = log_stderr; 19 | static FILE *logfile = NULL; 20 | 21 | static const char *loglevels[] = { 22 | "debug", 23 | "info", 24 | "notice", 25 | "warn", 26 | "error", 27 | "fatal", 28 | }; 29 | 30 | static inline int 31 | get_facility(log_msgtype_t l) { 32 | switch (l) { 33 | case log_debug: return LOG_DEBUG; 34 | case log_info: return LOG_INFO; 35 | case log_note: return LOG_NOTICE; 36 | case log_warn: return LOG_WARNING; 37 | case log_error: return LOG_ERR; 38 | case log_fatal: return LOG_CRIT; 39 | break; 40 | } 41 | return LOG_INFO; 42 | } 43 | 44 | void f2b_log_msg(log_msgtype_t l, const char *fmt, ...) { 45 | va_list args; 46 | char msg[LOGLINE_MAX] = ""; 47 | char when[64] = ""; 48 | time_t now = time(NULL); 49 | 50 | if (l < minlevel) 51 | return; 52 | 53 | va_start(args, fmt); 54 | vsnprintf(msg, sizeof(msg), fmt, args); 55 | va_end(args); 56 | 57 | switch (dest) { 58 | case log_syslog: 59 | syslog(get_facility(l), "%s", msg); 60 | break; 61 | default: 62 | case log_stderr: 63 | logfile = stderr; 64 | /* fallthru */ 65 | case log_file: 66 | strftime(when, sizeof(when), "%F %H:%M:%S", localtime(&now)); 67 | fprintf(logfile, "%s [%s] %s\n", when, loglevels[l], msg); 68 | break; 69 | } 70 | 71 | return; 72 | } 73 | 74 | void f2b_log_mod_cb(log_msgtype_t l, const char *msg) { 75 | f2b_log_msg(l, "%s", msg); 76 | } 77 | 78 | void f2b_log_set_level(const char *level) { 79 | if (strcmp(level, "debug") == 0) { minlevel = log_debug; return; } 80 | if (strcmp(level, "info") == 0) { minlevel = log_info; return; } 81 | if (strcmp(level, "notice")== 0) { minlevel = log_note; return; } 82 | if (strcmp(level, "warn") == 0) { minlevel = log_warn; return; } 83 | if (strcmp(level, "error") == 0) { minlevel = log_error; return; } 84 | if (strcmp(level, "fatal") == 0) { minlevel = log_fatal; return; } 85 | } 86 | 87 | void f2b_log_to_stdout() { 88 | if (logfile && logfile != stdout) 89 | fclose(logfile); 90 | dest = log_stdout; 91 | logfile = stdout; 92 | } 93 | 94 | void f2b_log_to_stderr() { 95 | if (logfile && logfile != stderr) 96 | fclose(logfile); 97 | dest = log_stderr; 98 | logfile = stderr; 99 | } 100 | 101 | void f2b_log_to_file(const char *path) { 102 | FILE *new = NULL; 103 | if (path == NULL || *path == '\0') 104 | return; 105 | if ((new = fopen(path, "a")) != NULL) { 106 | setvbuf(new, NULL , _IONBF, 0); 107 | if (logfile && logfile != stderr) 108 | fclose(logfile); 109 | dest = log_file; 110 | logfile = new; 111 | } 112 | } 113 | 114 | void f2b_log_to_syslog() { 115 | if (logfile && logfile != stderr) 116 | fclose(logfile); 117 | dest = log_syslog; 118 | openlog("f2b", 0 | LOG_PID, LOG_DAEMON); 119 | } 120 | -------------------------------------------------------------------------------- /src/log.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #ifndef F2B_LOG_H_ 8 | #define F2B_LOG_H_ 9 | 10 | /** 11 | * @file 12 | * This file contains logging routines 13 | */ 14 | 15 | /** 16 | * @def LOGLINE_MAX 17 | * Maximum length of log message 18 | */ 19 | #define LOGLINE_MAX 1024 20 | 21 | /** levels of log messages */ 22 | typedef enum { 23 | log_debug = 0, /**< diagnostic messages */ 24 | log_info = 1, /**< usefull, but not important messages */ 25 | log_note = 2, /**< ban/unban events */ 26 | log_warn = 3, /**< something goes wrong */ 27 | log_error = 4, /**< error messages */ 28 | log_fatal = 5 /**< critical error, program terminates */ 29 | } log_msgtype_t; 30 | 31 | /** 32 | * @brief Write message to log 33 | * @param l Level of message 34 | * @param fmt Message format string 35 | */ 36 | void f2b_log_msg(log_msgtype_t l, const char *fmt, ...) 37 | __attribute__ ((format (printf, 2, 3))); 38 | /** 39 | * @brief Logging callback function for use in modules 40 | * @param l Level of message 41 | * @param msg Log message string 42 | */ 43 | void f2b_log_mod_cb(log_msgtype_t l, const char *msg); 44 | 45 | /** 46 | * @brief Limit logging messages by importance 47 | * @param level Min level of messages for logging 48 | */ 49 | void f2b_log_set_level(const char *level); 50 | /** 51 | * @brief Use logging to file 52 | * @param path Path for logfile 53 | */ 54 | void f2b_log_to_file (const char *path); 55 | /** @brief Use logging to stdout */ 56 | void f2b_log_to_stdout(); 57 | /** @brief Use logging to stderr */ 58 | void f2b_log_to_stderr(); 59 | /** @brief Use logging to syslog */ 60 | void f2b_log_to_syslog(); 61 | 62 | #endif /* F2B_LOG_H_ */ 63 | -------------------------------------------------------------------------------- /src/matches.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #include "common.h" 8 | #include "matches.h" 9 | 10 | f2b_match_t * 11 | f2b_match_create(time_t t) { 12 | f2b_match_t *m; 13 | 14 | if ((m = calloc(1, sizeof(f2b_match_t))) == NULL) 15 | return false; 16 | m->time = t; 17 | return m; 18 | } 19 | 20 | void 21 | f2b_matches_flush(f2b_matches_t *ms) { 22 | f2b_match_t *head, *tmp; 23 | assert(ms != NULL); 24 | 25 | head = ms->list; 26 | while (head != NULL) { 27 | tmp = head; 28 | head = head->next; 29 | free(tmp); 30 | } 31 | memset(ms, 0x0, sizeof(f2b_matches_t)); 32 | } 33 | 34 | void 35 | f2b_matches_prepend(f2b_matches_t *ms, f2b_match_t *m) { 36 | assert(ms != NULL); 37 | assert(m != NULL); 38 | 39 | m->next = ms->list; 40 | ms->list = m; 41 | ms->count++; 42 | ms->last = m->time > ms->last ? m->time : ms->last; 43 | } 44 | 45 | void 46 | f2b_matches_expire(f2b_matches_t *ms, time_t before) { 47 | f2b_match_t *prev, *node, *next; 48 | assert(ms != NULL); 49 | 50 | if (ms->list == NULL) 51 | return; 52 | 53 | node = ms->list; 54 | prev = NULL; 55 | while (node != NULL) { 56 | if (node->time > before) { 57 | prev = node; node = node->next; 58 | continue; 59 | } 60 | next = node->next; 61 | free(node); 62 | if (prev) { 63 | prev->next = next; 64 | } else { 65 | ms->list = next; 66 | } 67 | node = next; 68 | ms->count--; 69 | } 70 | ms->last = ms->list ? ms->list->time : 0; 71 | } 72 | 73 | int 74 | f2b_matches_score(f2b_matches_t *ms, time_t after) { 75 | int score = 0; 76 | 77 | assert(ms != NULL); 78 | for (f2b_match_t *match = ms->list; match != NULL; match = match->next) { 79 | if (after > match->time) 80 | break; /* speedhack: consider list is sorted from newest to oldest matches */ 81 | score += match->score; 82 | } 83 | 84 | return score; 85 | } 86 | -------------------------------------------------------------------------------- /src/matches.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #ifndef F2B_MATCHES_H_ 8 | #define F2B_MATCHES_H_ 9 | 10 | /** 11 | * @file 12 | * This file contains definition of ipaddr matches struct and related routines 13 | */ 14 | 15 | typedef struct f2b_match_t { 16 | struct f2b_match_t *next; 17 | time_t time; 18 | uint32_t stag; 19 | uint32_t ftag; 20 | int16_t score; 21 | } f2b_match_t; 22 | 23 | /** matches container */ 24 | typedef struct { 25 | size_t count; /**< Count of entries in linked list */ 26 | time_t last; /**< latest match time */ 27 | f2b_match_t *list; /**< linked list */ 28 | } f2b_matches_t; 29 | 30 | /** 31 | * @brief Allocate memory for new match struct & initialize it 32 | * @param t Match time 33 | * @returns pointer to allocated struct 34 | */ 35 | f2b_match_t * f2b_match_create(time_t t); 36 | 37 | /** 38 | * @brief Clean list of matches and free memory 39 | * @param ms Pointer to struct 40 | */ 41 | void f2b_matches_flush(f2b_matches_t *ms); 42 | 43 | /** 44 | * @brief Insert new match to head of matches list 45 | * @param ms Matches list struct 46 | * @param match Match entry 47 | */ 48 | void f2b_matches_prepend (f2b_matches_t *ms, f2b_match_t *m); 49 | 50 | /** 51 | * @brief Remove matches before given time 52 | * @param m Pointer to struct 53 | * @param before Start time 54 | */ 55 | void f2b_matches_expire (f2b_matches_t *m, time_t before); 56 | 57 | /** 58 | * @brief Get sum of scores after specified time 59 | * @param m Pointer to struct 60 | * @param after Only check matches after this time 61 | * @returns Sum of scores 62 | */ 63 | int f2b_matches_score(f2b_matches_t *ms, time_t after); 64 | 65 | #endif /* F2B_MATCHES_H_ */ 66 | -------------------------------------------------------------------------------- /src/md5.h: -------------------------------------------------------------------------------- 1 | /* See md5.c for explanation and copyright information. */ 2 | 3 | #ifndef MD5_H 4 | #define MD5_H 5 | 6 | /* Unlike previous versions of this code, uint32 need not be exactly 7 | 32 bits, merely 32 bits or more. Choosing a data type which is 32 8 | bits instead of 64 is not important; speed is considerably more 9 | important. ANSI guarantees that "unsigned long" will be big enough, 10 | and always using it seems to have few disadvantages. */ 11 | typedef unsigned long uint32; 12 | 13 | struct MD5Context { 14 | uint32 buf[4]; 15 | uint32 bits[2]; 16 | unsigned char in[64]; 17 | unsigned char digest[16]; /*< added for usability */ 18 | char hexdigest[33]; /*< added for usability */ 19 | }; 20 | 21 | void MD5Init (struct MD5Context *context); /* modified: zortes context before use */ 22 | void MD5Update (struct MD5Context *context, unsigned char const *buf, unsigned len); 23 | void MD5Final (struct MD5Context *context); /* modified: dropped outparam 'digest', fills 'digest' and 'hexdigest' fields in context */ 24 | void MD5Transform (uint32 buf[4], const unsigned char in[64]); 25 | 26 | /* 27 | * This is needed to make RSAREF happy on some MS-DOS compilers. 28 | */ 29 | typedef struct MD5Context MD5_CTX; 30 | 31 | #endif /* !MD5_H */ 32 | -------------------------------------------------------------------------------- /src/mod-api.h: -------------------------------------------------------------------------------- 1 | /** @file This file contains common exportable types and routines used by all modules */ 2 | 3 | enum loglevel { 4 | debug = 0, 5 | info = 1, 6 | notice = 2, 7 | warn = 3, 8 | error = 4, 9 | fatal = 5, 10 | }; 11 | 12 | /** 13 | * Opaque module handler, contains module internal structs 14 | */ 15 | typedef struct _config cfg_t; 16 | 17 | /** 18 | * @brief Create instance of module 19 | * @param init Module-specific init string 20 | * @returns Opaque module handler or NULL on failure 21 | */ 22 | extern cfg_t *create(const char *init); 23 | 24 | /** 25 | * @brief Configure module instance 26 | * @param cfg Module handler 27 | * @param key Parameter name 28 | * @param value Parameter value 29 | * @returns true on success, false on error 30 | */ 31 | extern bool config(cfg_t *cfg, const char *key, const char *value); 32 | 33 | /** 34 | * @brief Get internal module state as set of flags (see mod.h) 35 | * @param cfg Module handler 36 | * @returns <0 on error 37 | */ 38 | extern int state(cfg_t *cfg); 39 | 40 | /** 41 | * @brief Sets the log callback 42 | * @param cfg Module handler 43 | * @param cb Logging callback 44 | * @note Optional, if this function is not called, warnings/errors of module will be suppressed 45 | */ 46 | extern void logcb(cfg_t *cfg, void (*cb)(enum loglevel l, const char *msg)); 47 | 48 | /** 49 | * @brief Free module handle 50 | * @param cfg Module handler 51 | * @note Module handler becomes invalid after calling this function on it 52 | */ 53 | extern void destroy(cfg_t *cfg); 54 | -------------------------------------------------------------------------------- /src/mod-defs.h: -------------------------------------------------------------------------------- 1 | /** used as in return value of state() call */ 2 | #define MOD_IS_READY 1 3 | #define MOD_WRONG_API 2 4 | #define MOD_STARTED 4 /* source, backend */ 5 | #define MOD_NEED_FILTER 8 /* only source */ 6 | /* reserved */ 7 | #define MOD_TYPE_SOURCE 1024 8 | #define MOD_TYPE_FILTER 2048 9 | #define MOD_TYPE_BACKEND 4096 10 | 11 | #define MATCH_DEFSCORE 10 12 | -------------------------------------------------------------------------------- /src/source-test.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #include "common.h" 8 | #include "log.h" 9 | #include "config.h" 10 | #include "source.h" 11 | 12 | #include 13 | 14 | bool run = 1; 15 | 16 | void sigint_handler (int signal) { 17 | UNUSED(signal); 18 | run = 0; 19 | } 20 | 21 | void usage() { 22 | fprintf(stderr, "Usage: source-test \n"); 23 | exit(EXIT_FAILURE); 24 | } 25 | 26 | int main(int argc, char *argv[]) { 27 | struct sigaction act; 28 | f2b_config_t config; 29 | f2b_config_section_t *section = NULL; 30 | f2b_source_t *source = NULL; 31 | char buf[4096] = ""; 32 | uint32_t stag; 33 | bool reset; 34 | 35 | if (argc < 3) 36 | usage(); 37 | 38 | memset(&config, 0x0, sizeof(config)); 39 | if (f2b_config_load(&config, argv[1], false) != true) { 40 | f2b_log_msg(log_fatal, "can't load config"); 41 | return EXIT_FAILURE; 42 | } 43 | 44 | if (config.sources == NULL) { 45 | f2b_log_msg(log_fatal, "no sources found in config"); 46 | return EXIT_FAILURE; 47 | } else { 48 | section = config.sources; 49 | } 50 | 51 | if ((source = f2b_source_create(section->name, argv[2])) == NULL) { 52 | f2b_log_msg(log_fatal, "can't create source '%s'", section->name); 53 | return EXIT_FAILURE; 54 | } 55 | 56 | if (!f2b_source_init(source, section)) { 57 | f2b_log_msg(log_fatal, "can't init source '%s'", source->name); 58 | return EXIT_FAILURE; 59 | } 60 | 61 | if (f2b_source_start(source) == false) { 62 | f2b_log_msg(log_fatal, "source start error"); 63 | exit(EXIT_FAILURE); 64 | } 65 | 66 | memset(&act, 0x0, sizeof(act)); 67 | act.sa_handler = sigint_handler; 68 | if (sigaction(SIGINT, &act, NULL) != 0) { 69 | f2b_log_msg(log_fatal, "can't register handler for SIGINT"); 70 | return EXIT_FAILURE; 71 | } 72 | 73 | while (run) { 74 | reset = true; 75 | while ((stag = f2b_source_next(source, buf, sizeof(buf), reset)) > 0) { 76 | reset = false; 77 | printf("stag: %08X, data: %s\n", stag, buf); 78 | } 79 | sleep(1); 80 | } 81 | 82 | if (f2b_source_stats(source, buf, sizeof(buf))) { 83 | printf("stats:\n" "%s\n", buf); 84 | } 85 | 86 | f2b_source_stop(source); 87 | 88 | return EXIT_SUCCESS; 89 | } 90 | -------------------------------------------------------------------------------- /src/source.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #include "common.h" 8 | #include "config.h" 9 | #include "log.h" 10 | #include "mod-defs.h" 11 | #include "source.h" 12 | 13 | #include 14 | 15 | #define SOURCE_LIBRARY_PARAM "load" 16 | 17 | f2b_source_t * 18 | f2b_source_create(const char *name, const char *init) { 19 | f2b_source_t *source = NULL; 20 | 21 | if ((source = calloc(1, sizeof(f2b_source_t))) == NULL) 22 | return NULL; 23 | 24 | strlcpy(source->name, name, sizeof(source->name)); 25 | strlcpy(source->init, init, sizeof(source->init)); 26 | 27 | return source; 28 | } 29 | 30 | bool 31 | f2b_source_init(f2b_source_t *source, f2b_config_section_t *config) { 32 | f2b_config_param_t *param = NULL; 33 | int flags = RTLD_NOW | RTLD_LOCAL; 34 | const char *dlerr = NULL; 35 | 36 | assert(config != NULL); 37 | assert(config->type == t_source); 38 | 39 | param = f2b_config_param_find(config->param, SOURCE_LIBRARY_PARAM); 40 | if (!param) { 41 | f2b_log_msg(log_error, "can't find '%s' param in source config", SOURCE_LIBRARY_PARAM); 42 | return false; 43 | } 44 | 45 | if ((source->h = dlopen(param->value, flags)) == NULL) 46 | goto cleanup; 47 | if ((*(void **) (&source->create) = dlsym(source->h, "create")) == NULL) 48 | goto cleanup; 49 | if ((*(void **) (&source->config) = dlsym(source->h, "config")) == NULL) 50 | goto cleanup; 51 | if ((*(void **) (&source->state) = dlsym(source->h, "state")) == NULL) 52 | goto cleanup; 53 | if ((*(void **) (&source->logcb) = dlsym(source->h, "logcb")) == NULL) 54 | goto cleanup; 55 | if ((*(void **) (&source->start) = dlsym(source->h, "start")) == NULL) 56 | goto cleanup; 57 | if ((*(void **) (&source->next) = dlsym(source->h, "next")) == NULL) 58 | goto cleanup; 59 | if ((*(void **) (&source->stats) = dlsym(source->h, "stats")) == NULL) 60 | goto cleanup; 61 | if ((*(void **) (&source->stop) = dlsym(source->h, "stop")) == NULL) 62 | goto cleanup; 63 | if ((*(void **) (&source->destroy) = dlsym(source->h, "destroy")) == NULL) 64 | goto cleanup; 65 | 66 | if ((source->cfg = source->create(source->init)) == NULL) { 67 | f2b_log_msg(log_error, "source create config failed"); 68 | goto cleanup; 69 | } 70 | 71 | if ((source->state(source->cfg) & MOD_TYPE_SOURCE) == 0) { 72 | f2b_log_msg(log_error, "loaded module is not source type"); 73 | goto cleanup; 74 | } 75 | 76 | source->logcb(source->cfg, f2b_log_mod_cb); 77 | 78 | /* try init */ 79 | for (param = config->param; param != NULL; param = param->next) { 80 | if (strcmp(param->name, SOURCE_LIBRARY_PARAM) == 0) 81 | continue; 82 | if (source->config(source->cfg, param->name, param->value)) 83 | continue; 84 | f2b_log_msg(log_warn, "param pair not accepted by source '%s': %s=%s", 85 | config->name, param->name, param->value); 86 | } 87 | 88 | if ((source->flags = source->state(source->cfg)) < 0) { 89 | f2b_log_msg(log_error, "can't get module state"); 90 | goto cleanup; 91 | } 92 | 93 | if (source->flags & MOD_WRONG_API) { 94 | f2b_log_msg(log_error, "module reports wrong api version"); 95 | goto cleanup; 96 | } 97 | 98 | if (source->flags & MOD_IS_READY) 99 | return true; 100 | 101 | /* still not ready */ 102 | f2b_log_msg(log_error, "source '%s' not fully configured", config->name); 103 | 104 | cleanup: 105 | dlerr = dlerror(); 106 | if (dlerr) 107 | f2b_log_msg(log_error, "source load error: %s", dlerr); 108 | if (source->h) { 109 | if (source->cfg && source->destroy) { 110 | source->destroy(source->cfg); 111 | source->cfg = NULL; 112 | } 113 | dlclose(source->h); 114 | source->create = NULL; 115 | source->config = NULL; 116 | source->state = NULL; 117 | source->logcb = NULL; 118 | source->start = NULL; 119 | source->next = NULL; 120 | source->stats = NULL; 121 | source->stop = NULL; 122 | source->destroy = NULL; 123 | source->h = NULL; 124 | } 125 | return false; 126 | } 127 | 128 | void 129 | f2b_source_destroy(f2b_source_t *source) { 130 | if (!source) return; 131 | if (source->h) { 132 | if (source->cfg) 133 | source->destroy(source->cfg); 134 | dlclose(source->h); 135 | } 136 | free(source); 137 | } 138 | 139 | uint32_t 140 | f2b_source_next(f2b_source_t *source, char *buf, size_t bufsize, bool reset) { 141 | assert(source != NULL); 142 | return source->next(source->cfg, buf, bufsize, reset); 143 | } 144 | 145 | bool 146 | f2b_source_stats(f2b_source_t *source, char *buf, size_t bufsize) { 147 | assert(source != NULL); 148 | return source->stats(source->cfg, buf, bufsize); 149 | } 150 | 151 | #define SOURCE_CMD_ARG0(CMD, RETURNS) \ 152 | RETURNS \ 153 | f2b_source_ ## CMD(f2b_source_t *source) { \ 154 | assert(source != NULL); \ 155 | return source->CMD(source->cfg); \ 156 | } 157 | 158 | SOURCE_CMD_ARG0(start, bool) 159 | SOURCE_CMD_ARG0(stop, bool) 160 | 161 | void 162 | f2b_source_cmd_stats(char *buf, size_t bufsize, f2b_source_t *source) { 163 | assert(source != NULL); 164 | assert(buf != NULL); 165 | 166 | source->stats(source->cfg, buf, bufsize); 167 | } 168 | -------------------------------------------------------------------------------- /src/source.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #ifndef F2B_SOURCE_H_ 8 | #define F2B_SOURCE_H_ 9 | 10 | /** 11 | * @file 12 | * This header describes source module definition and related routines 13 | */ 14 | 15 | /** source module definition */ 16 | typedef struct f2b_source_t { 17 | void *h; /**< dlopen handler */ 18 | void *cfg; /**< opaque pointer of module config */ 19 | int flags; /**< module flags (update with state() call) */ 20 | /* handlers */ 21 | /** dlsym pointer to handler of @a create command */ 22 | void *(*create) (const char *init); 23 | /** dlsym pointer to handler of @a config command */ 24 | bool (*config) (void *cfg, const char *key, const char *value); 25 | /** dlsym pointer to handler of @a state command */ 26 | int (*state) (void *cfg); 27 | /** dlsym pointer to handler of @a logcb command */ 28 | void (*logcb) (void *cfg, void (*cb)(log_msgtype_t l, const char *msg)); 29 | /** dlsym pointer to handler of @a start command */ 30 | bool (*start) (void *cfg); 31 | /** dlsym pointer to handler of @a next command */ 32 | uint32_t (*next) (void *cfg, char *buf, size_t bufsize, bool reset); 33 | /** dlsym pointer to handler of @a stats command */ 34 | bool (*stats) (void *cfg, char *buf, size_t bufsize); 35 | /** dlsym pointer to handler of @a stop command */ 36 | bool (*stop) (void *cfg); 37 | /** dlsym pointer to handler of @a destroy command */ 38 | void (*destroy) (void *cfg); 39 | /* config variables */ 40 | char name[CONFIG_KEY_MAX]; /**< source name from config (eg [source:$NAME] section) */ 41 | char init[CONFIG_VAL_MAX]; /**< source init string (eg `source = NAME:$INIT_STRING` line from jail section) */ 42 | } f2b_source_t; 43 | 44 | /** 45 | * @brief Allocate new source struct and fill name/init fields 46 | * @param name Module name 47 | * @param init Module init string 48 | * @returns Pointer to allocated module struct or NULL on error 49 | */ 50 | f2b_source_t * f2b_source_create(const char *name, const char *init); 51 | 52 | /** 53 | * @brief Initialize and configure source 54 | * @param config Pointer to config section with module description 55 | * @return true on success, false on error 56 | */ 57 | bool f2b_source_init(f2b_source_t *source, f2b_config_section_t *config); 58 | 59 | /** 60 | * @brief Free module metadata 61 | * @param s Pointer to module struct 62 | */ 63 | void f2b_source_destroy (f2b_source_t *s); 64 | 65 | /** 66 | * @brief Start given source 67 | * @param s Pointer to source struct 68 | * @returns true on success, false on error 69 | */ 70 | bool f2b_source_start (f2b_source_t *s); 71 | /** 72 | * @brief Get next line of data from given source 73 | * @param s Pointer to source struct 74 | * @param buf Buffer for data 75 | * @param bufsize Size of buffer for data 76 | * @param reset Reset source internals 77 | * @returns >0 on new data available with filling @a buf and 0 on no data/error 78 | */ 79 | uint32_t f2b_source_next (f2b_source_t *s, char *buf, size_t bufsize, bool reset); 80 | /** 81 | * @brief Get internal stats from source 82 | * @param buf Buffer for data 83 | * @param bufsize Size of buffer for data 84 | * @returns true on success, false on error 85 | */ 86 | bool f2b_source_stats (f2b_source_t *s, char *buf, size_t bufsize); 87 | /** 88 | * @brief Stop given source 89 | * @param s Pointer to source struct 90 | * @returns true on success, false on error 91 | */ 92 | bool f2b_source_stop (f2b_source_t *s); 93 | 94 | /* handlers for csocket commands processing */ 95 | 96 | /** handler of 'jail $JAIL source stats' cmd */ 97 | void f2b_source_cmd_stats (char *buf, size_t bufsize, f2b_source_t *f); 98 | 99 | #endif /* F2B_SOURCE_H_ */ 100 | -------------------------------------------------------------------------------- /src/sources/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 2 | unset(CMAKE_SHARED_MODULE_PREFIX) 3 | set(SOURCES "") 4 | 5 | add_library("s_files" MODULE "files.c" "../strlcpy.c" "../fnv32a.c") 6 | list(APPEND SOURCES "files") 7 | 8 | add_library("s_portknock" MODULE "portknock.c" "../strlcpy.c" "../fnv32a.c") 9 | list(APPEND SOURCES "portknock") 10 | 11 | if (WITH_REDIS) 12 | pkg_check_modules(REDIS "hiredis" REQUIRED) 13 | add_library("s_redis" MODULE "redis.c" "../strlcpy.c") 14 | target_link_libraries("s_redis" "hiredis") 15 | list(APPEND SOURCES "redis") 16 | endif () 17 | 18 | foreach (SOURCE IN LISTS SOURCES) 19 | set_target_properties("s_${SOURCE}" PROPERTIES OUTPUT_NAME "source_${SOURCE}") 20 | install(TARGETS "s_${SOURCE}" LIBRARY DESTINATION ${PLUGINS_PATH}) 21 | endforeach () 22 | 23 | message(STATUS "- Sources : ${SOURCES}") 24 | -------------------------------------------------------------------------------- /src/sources/files.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #include "source.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #define MODNAME "files" 16 | 17 | typedef struct f2b_file_t { 18 | struct f2b_file_t *next; 19 | uint32_t lines; 20 | uint32_t stag; 21 | FILE *fd; 22 | char path[PATH_MAX]; 23 | struct stat st; 24 | } f2b_file_t; 25 | 26 | struct _config { 27 | void (*logcb)(enum loglevel lvl, const char *msg); 28 | f2b_file_t *files; 29 | f2b_file_t *current; 30 | int flags; 31 | char path[256]; 32 | }; 33 | 34 | #include "source.c" 35 | 36 | static bool 37 | file_open(f2b_file_t *file, const char *path) { 38 | FILE *fd; 39 | struct stat st; 40 | char buf[PATH_MAX] = ""; 41 | 42 | assert(file != NULL); 43 | assert(path != NULL || file->path[0] != '\0'); 44 | 45 | strlcpy(buf, path ? path : file->path, sizeof(buf)); 46 | 47 | memset(file, 0x0, sizeof(f2b_file_t)); 48 | 49 | if (stat(buf, &st) != 0) 50 | return false; 51 | 52 | if (!(S_ISREG(st.st_mode) || S_ISFIFO(st.st_mode))) 53 | return false; 54 | 55 | if ((fd = fopen(buf, "r")) == NULL) 56 | return false; 57 | 58 | if (S_ISREG(st.st_mode) && fseek(fd, 0, SEEK_END) < 0) { 59 | fclose(fd); 60 | return false; 61 | } 62 | 63 | memcpy(&file->st, &st, sizeof(st)); 64 | strlcpy(file->path, buf, sizeof(file->path)); 65 | file->fd = fd; 66 | file->stag = fnv_32a_str(file->path, FNV1_32A_INIT); 67 | 68 | return true; 69 | } 70 | 71 | static void 72 | file_close(f2b_file_t *file) { 73 | assert(file != NULL); 74 | 75 | if (file->fd == NULL) 76 | return; 77 | 78 | fclose(file->fd); 79 | file->fd = NULL; 80 | file->lines = 0; 81 | memset(&file->st, 0, sizeof(struct stat)); 82 | } 83 | 84 | static bool 85 | file_rotated(const cfg_t *cfg, f2b_file_t *file) { 86 | struct stat st; 87 | assert(file != NULL); 88 | 89 | if (file->fd == NULL) 90 | return true; 91 | 92 | if (stat(file->path, &st) != 0) { 93 | log_msg(cfg, error, "file stat error: %s", strerror(errno)); 94 | return true; 95 | } 96 | 97 | if (file->st.st_dev != st.st_dev || 98 | file->st.st_ino != st.st_ino || 99 | file->st.st_size > st.st_size) { 100 | log_msg(cfg, info, "file replaced: %s", file->path); 101 | return true; 102 | } else if (file->st.st_size < st.st_size) { 103 | memcpy(&file->st, &st, sizeof(struct stat)); 104 | } 105 | 106 | return false; 107 | } 108 | 109 | static bool 110 | file_getline(const cfg_t *cfg, f2b_file_t *file, char *buf, size_t bufsize) { 111 | char *p; 112 | assert(file != NULL); 113 | assert(buf != NULL); 114 | 115 | /* fread()+EOF set is implementation defined */ 116 | if (fgets(buf, bufsize, file->fd) != NULL) { 117 | if ((p = strchr(buf, '\n')) != NULL) 118 | *p = '\0'; /* strip newline(s) */ 119 | file->lines++; 120 | return true; 121 | } 122 | if (feof(file->fd)) 123 | clearerr(file->fd); 124 | if (ferror(file->fd)) { 125 | log_msg(cfg, error, "file error: %s -- %s", file->path, strerror(errno)); 126 | clearerr(file->fd); 127 | } 128 | 129 | return false; 130 | } 131 | 132 | cfg_t * 133 | create(const char *init) { 134 | cfg_t *cfg = NULL; 135 | if ((cfg = calloc(1, sizeof(cfg_t))) == NULL) 136 | return NULL; 137 | cfg->logcb = &logcb_stub; 138 | cfg->flags |= MOD_TYPE_SOURCE; 139 | cfg->flags |= MOD_NEED_FILTER; 140 | if (init != NULL && strlen(init) > 0) { 141 | strlcpy(cfg->path, init, sizeof(cfg->path)); 142 | cfg->flags |= MOD_IS_READY; 143 | } 144 | return cfg; 145 | } 146 | 147 | bool 148 | config(cfg_t *cfg, const char *key, const char *value) { 149 | assert(cfg != NULL); 150 | assert(key != NULL); 151 | assert(value != NULL); 152 | 153 | /* no options */ 154 | (void)(cfg); /* suppress warning for unused variable 'ip' */ 155 | (void)(key); /* suppress warning for unused variable 'ip' */ 156 | (void)(value); /* suppress warning for unused variable 'ip' */ 157 | 158 | return false; 159 | } 160 | 161 | bool 162 | start(cfg_t *cfg) { 163 | f2b_file_t *file = NULL; 164 | glob_t globbuf; 165 | 166 | assert(cfg != NULL); 167 | 168 | if (glob(cfg->path, GLOB_MARK | GLOB_NOESCAPE, NULL, &globbuf) != 0) 169 | return NULL; 170 | 171 | for (size_t i = 0; i < globbuf.gl_pathc; i++) { 172 | if ((file = calloc(1, sizeof(f2b_file_t))) == NULL) 173 | continue; 174 | if (file_open(file, globbuf.gl_pathv[i]) == false) { 175 | log_msg(cfg, error, "can't open file: %s -- %s", globbuf.gl_pathv[i], strerror(errno)); 176 | free(file); 177 | continue; 178 | } 179 | if (cfg->files == NULL) { 180 | cfg->files = file; 181 | } else { 182 | file->next = cfg->files; 183 | cfg->files = file; 184 | } 185 | } 186 | 187 | globfree(&globbuf); 188 | return true; 189 | } 190 | 191 | bool 192 | stop(cfg_t *cfg) { 193 | f2b_file_t *next = NULL; 194 | 195 | assert(cfg != NULL); 196 | 197 | for (; cfg->files != NULL; cfg->files = next) { 198 | next = cfg->files->next; 199 | file_close(cfg->files); 200 | free(cfg->files); 201 | } 202 | 203 | return true; 204 | } 205 | 206 | uint32_t 207 | next(cfg_t *cfg, char *buf, size_t bufsize, bool reset) { 208 | assert(cfg != NULL); 209 | assert(buf != NULL); 210 | assert(bufsize > 0); 211 | 212 | if (reset || cfg->current == NULL) 213 | cfg->current = cfg->files; 214 | 215 | for (f2b_file_t *file = cfg->current; file != NULL; file = file->next) { 216 | if (file_rotated(cfg, file)) 217 | file_close(file); 218 | if (file->fd == NULL) { 219 | if (!file_open(file, NULL)) 220 | log_msg(cfg, error, "can't open file: %s", file->path); 221 | continue; 222 | } 223 | if (file_getline(cfg, file, buf, bufsize)) 224 | return file->stag; 225 | } 226 | 227 | return 0; 228 | } 229 | 230 | bool 231 | stats(cfg_t *cfg, char *buf, size_t bufsize) { 232 | char tmp[PATH_MAX + 512]; 233 | time_t mtime; 234 | const char *fmt = 235 | "- path: %s\n" 236 | " info: fd=%d inode=%d size=%ld pos=%ld tag=%08X readl=%lu mtime=%lu\n"; 237 | int fd, ino; off_t sz; long pos; 238 | assert(cfg != NULL); 239 | assert(buf != NULL); 240 | assert(bufsize > 0); 241 | 242 | if (buf == NULL || bufsize == 0) 243 | return false; 244 | 245 | for (f2b_file_t *f = cfg->files; f != NULL; f = f->next) { 246 | if (f->fd) { 247 | fd = fileno(f->fd), ino = f->st.st_ino, sz = f->st.st_size, pos = ftell(f->fd); 248 | mtime = f->st.st_mtime; 249 | } else { 250 | fd = -1, ino = -1, sz = 0, pos = -1, mtime = 0; 251 | } 252 | snprintf(tmp, sizeof(tmp), fmt, f->path, fd, ino, sz, pos, f->stag, f->lines, mtime); 253 | strlcat(buf, tmp, bufsize); 254 | } 255 | 256 | return true; 257 | } 258 | 259 | void 260 | destroy(cfg_t *cfg) { 261 | assert(cfg != NULL); 262 | 263 | free(cfg); 264 | } 265 | -------------------------------------------------------------------------------- /src/sources/portknock.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "source.h" 15 | #define MODNAME "portknock" 16 | #define HOST_MAX 48 17 | #define PORT_MAX 6 18 | 19 | typedef struct f2b_port_t { 20 | struct f2b_port_t *next; 21 | unsigned int accepts; 22 | uint32_t stag; 23 | int sock; 24 | char host[HOST_MAX]; 25 | char port[PORT_MAX]; 26 | } f2b_port_t; 27 | 28 | struct _config { 29 | void (*logcb)(enum loglevel lvl, const char *msg); 30 | f2b_port_t *ports; 31 | f2b_port_t *current; 32 | int flags; 33 | char name[32]; 34 | }; 35 | 36 | #include "source.c" 37 | 38 | static bool 39 | try_parse_listen_opt(f2b_port_t *port, const char *value) { 40 | char buf[256]; 41 | char *p; 42 | 43 | strlcpy(buf, value, sizeof(buf)); 44 | if (*buf == '[') { 45 | /* IPv6, expected: [XXXX::XXXX:XXXX]:YYYY */ 46 | if ((p = strstr(buf, "]:")) == NULL) { 47 | *p = '\0', p += 2; 48 | strlcpy(port->port, p, sizeof(port->port)); 49 | p = buf + 1; 50 | strlcpy(port->host, p, sizeof(port->host)); 51 | return true; 52 | } 53 | return false; /* can't find port */ 54 | } 55 | if ((p = strchr(buf, ':')) != NULL) { 56 | /* IPv4, expected: XX.XX.XX.XX:YYYY */ 57 | *p = '\0', p += 1; 58 | strlcpy(port->port, p, sizeof(port->port)); 59 | p = buf; 60 | strlcpy(port->host, p, sizeof(port->host)); 61 | return true; 62 | } 63 | if (isdigit(*buf) && strlen(buf) <= 5) { 64 | /* IPv4, expected: YYYY */ 65 | strlcpy(port->host, "0.0.0.0", sizeof(port->host)); 66 | strlcpy(port->port, buf, sizeof(port->port)); 67 | return true; 68 | } 69 | 70 | return false; 71 | } 72 | 73 | cfg_t * 74 | create(const char *init) { 75 | cfg_t *cfg = NULL; 76 | assert(init != NULL); 77 | if ((cfg = calloc(1, sizeof(cfg_t))) == NULL) 78 | return NULL; 79 | strlcpy(cfg->name, init, sizeof(cfg->name)); 80 | cfg->logcb = &logcb_stub; 81 | cfg->flags |= MOD_TYPE_SOURCE; 82 | return cfg; 83 | } 84 | 85 | bool 86 | config(cfg_t *cfg, const char *key, const char *value) { 87 | assert(cfg != NULL); 88 | assert(key != NULL); 89 | assert(value != NULL); 90 | 91 | if (strcmp(key, "listen") == 0) { 92 | f2b_port_t *port = NULL; 93 | if ((port = calloc(1, sizeof(f2b_port_t))) == NULL) { 94 | log_msg(cfg, error, "out of memory"); 95 | return false; 96 | } 97 | if (try_parse_listen_opt(port, value) == false) { 98 | log_msg(cfg, error, "can't parse: %s", value); 99 | free(port); 100 | return false; 101 | } 102 | port->stag = fnv_32a_str(port->port, FNV1_32A_INIT); 103 | port->next = cfg->ports; 104 | cfg->ports = port; 105 | cfg->flags |= MOD_IS_READY; 106 | return true; 107 | } 108 | 109 | return false; 110 | } 111 | 112 | bool 113 | start(cfg_t *cfg) { 114 | struct addrinfo hints; 115 | struct addrinfo *result; 116 | int opt; 117 | 118 | assert(cfg != NULL); 119 | 120 | memset(&hints, 0, sizeof(struct addrinfo)); 121 | hints.ai_family = AF_UNSPEC; 122 | hints.ai_socktype = SOCK_STREAM; 123 | hints.ai_protocol = 0; 124 | 125 | for (f2b_port_t *port = cfg->ports; port != NULL; port = port->next) { 126 | port->sock = -1; 127 | int ret = getaddrinfo(port->host, port->port, &hints, &result); 128 | if (ret != 0) { 129 | log_msg(cfg, error, "getaddrinfo: %s", gai_strerror(ret)); 130 | continue; 131 | } 132 | for (struct addrinfo *rp = result; rp != NULL; rp = rp->ai_next) { 133 | port->sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); 134 | if (port->sock == -1) 135 | continue; 136 | if ((opt = fcntl(port->sock, F_GETFL, 0)) < 0) 137 | continue; 138 | fcntl(port->sock, F_SETFL, opt | O_NONBLOCK); 139 | opt = 1; 140 | setsockopt(port->sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); 141 | if (bind(port->sock, rp->ai_addr, rp->ai_addrlen) == 0) { 142 | if (listen(port->sock, 5) == 0) /* TODO: hardcoded */ 143 | break; /* success */ 144 | close(port->sock); 145 | port->sock = -1; 146 | } 147 | } 148 | freeaddrinfo(result); 149 | if (port->sock < 0) 150 | log_msg(cfg, error, "can't bind/listen on %s:%s", port->host, port->port); 151 | } 152 | 153 | return true; 154 | } 155 | 156 | bool 157 | stop(cfg_t *cfg) { 158 | assert(cfg != NULL); 159 | 160 | for (f2b_port_t *port = cfg->ports; port != NULL; port = port->next) 161 | close(port->sock); 162 | 163 | return true; 164 | } 165 | 166 | uint32_t 167 | next(cfg_t *cfg, char *buf, size_t bufsize, bool reset) { 168 | struct sockaddr_storage addr; 169 | socklen_t addrlen; 170 | 171 | assert(cfg != NULL); 172 | assert(buf != NULL); 173 | assert(bufsize > 0); 174 | 175 | if (reset || cfg->current == NULL) 176 | cfg->current = cfg->ports; 177 | 178 | for (f2b_port_t *port = cfg->current; port != NULL; port = port->next) { 179 | if (port->sock < 0) 180 | continue; 181 | addrlen = sizeof(addr); 182 | int sock = accept(port->sock, (struct sockaddr *) &addr, &addrlen); 183 | if (sock < 0 && errno == EAGAIN) 184 | continue; 185 | if (sock < 0) { 186 | log_msg(cfg, error, "accept() error: %s", strerror(errno)); 187 | continue; 188 | } 189 | port->accepts++; 190 | shutdown(sock, SHUT_RDWR); 191 | close(sock); 192 | if (addr.ss_family == AF_INET) { 193 | inet_ntop(AF_INET, &(((struct sockaddr_in *) &addr)->sin_addr), buf, bufsize); 194 | return port->stag; 195 | } 196 | if (addr.ss_family == AF_INET6) { 197 | inet_ntop(AF_INET6, &(((struct sockaddr_in6 *) &addr)->sin6_addr), buf, bufsize); 198 | return port->stag; 199 | } 200 | cfg->logcb(error, "can't convert sockaddr to string: unknown AF"); 201 | } 202 | 203 | return 0; 204 | } 205 | 206 | bool 207 | stats(cfg_t *cfg, char *buf, size_t bufsize) { 208 | char tmp[256]; 209 | const char *fmt = 210 | "- listen: %s:%s\n" 211 | " info: tag=%08X sock=%ld accepts=%u\n"; 212 | assert(cfg != NULL); 213 | 214 | if (buf == NULL || bufsize == 0) 215 | return false; 216 | 217 | for (f2b_port_t *p = cfg->ports; p != NULL; p = p->next) { 218 | snprintf(tmp, sizeof(tmp), fmt, p->host, p->port, p->stag, p->sock, p->accepts); 219 | strlcat(buf, tmp, bufsize); 220 | } 221 | 222 | return true; 223 | } 224 | 225 | void 226 | destroy(cfg_t *cfg) { 227 | f2b_port_t *next; 228 | assert(cfg != NULL); 229 | 230 | for (; cfg->ports != NULL; cfg->ports = next) { 231 | next = cfg->ports->next; 232 | free(cfg->ports); 233 | } 234 | 235 | free(cfg); 236 | } 237 | -------------------------------------------------------------------------------- /src/sources/redis.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #include 8 | #include 9 | 10 | #include "../strlcpy.h" 11 | 12 | #include "source.h" 13 | 14 | #define MODNAME "redis" 15 | #define ID_MAX 32 16 | 17 | struct _config { 18 | redisContext *conn; 19 | void (*logcb)(enum loglevel lvl, const char *msg); 20 | time_t timeout; 21 | int flags; 22 | uint16_t port; 23 | uint32_t received; 24 | uint8_t database; 25 | char password[32]; 26 | char host[32]; 27 | char name[ID_MAX + 1]; 28 | char hash[ID_MAX * 2]; 29 | }; 30 | 31 | #include "source.c" 32 | 33 | static bool 34 | redis_connect(cfg_t *cfg) { 35 | assert(cfg != NULL); 36 | 37 | if (cfg->conn && !cfg->conn->err) 38 | return true; /* connected */ 39 | 40 | redisContext *conn = NULL; 41 | redisReply *reply = NULL; 42 | do { 43 | struct timeval timeout = { .tv_sec = cfg->timeout, .tv_usec = 0 }; 44 | conn = redisConnectWithTimeout(cfg->host, cfg->port, timeout); 45 | if (!conn) 46 | break; 47 | if (conn->err) { 48 | log_msg(cfg, error, "connection error: %s", conn->errstr); 49 | break; 50 | } 51 | if (cfg->password[0]) { 52 | if ((reply = redisCommand(conn, "AUTH %s", cfg->password)) == NULL) 53 | break; 54 | if (reply->type == REDIS_REPLY_ERROR) { 55 | log_msg(cfg, error, "auth error: %s", reply->str); 56 | break; 57 | } 58 | freeReplyObject(reply); 59 | } 60 | if (cfg->database) { 61 | if ((reply = redisCommand(conn, "SELECT %d", cfg->database)) == NULL) 62 | break; 63 | if (reply->type == REDIS_REPLY_ERROR) { 64 | log_msg(cfg, error, "reply error: %s", reply->str); 65 | break; 66 | } 67 | freeReplyObject(reply); 68 | } 69 | if ((reply = redisCommand(conn, "SUBSCRIBE %s", cfg->hash)) == NULL) { 70 | log_msg(cfg, error, "can't subscribe: %s", conn->errstr); 71 | break; 72 | } 73 | if (reply->type == REDIS_REPLY_ERROR) { 74 | log_msg(cfg, error, "can't subscribe: %s", reply->str); 75 | break; 76 | } 77 | timeout.tv_sec = 0; 78 | timeout.tv_usec = 10000; /* 0.01s */ 79 | if (redisSetTimeout(conn, timeout) != REDIS_OK) { 80 | log_msg(cfg, error, "can't enable nonblocking mode"); 81 | break; 82 | } 83 | freeReplyObject(reply); 84 | if (cfg->conn) 85 | redisFree(cfg->conn); 86 | cfg->conn = conn; 87 | return true; 88 | } while (0); 89 | 90 | if (conn) 91 | redisFree(conn); 92 | if (reply) 93 | freeReplyObject(reply); 94 | 95 | return false; 96 | } 97 | 98 | static bool 99 | redis_disconnect(cfg_t *cfg) { 100 | assert(cfg != NULL); 101 | 102 | if (cfg->conn) { 103 | redisFree(cfg->conn); 104 | cfg->conn = NULL; 105 | } 106 | return true; 107 | } 108 | 109 | cfg_t * 110 | create(const char *init) { 111 | cfg_t *cfg = NULL; 112 | 113 | if ((cfg = calloc(1, sizeof(cfg_t))) == NULL) 114 | return NULL; 115 | 116 | strlcpy(cfg->hash, "f2b-banned-", sizeof(cfg->hash)); 117 | strlcat(cfg->hash, init, sizeof(cfg->hash)); 118 | cfg->logcb = &logcb_stub; 119 | cfg->flags |= MOD_TYPE_SOURCE; 120 | if (init && strlen(init) > 0) { 121 | strlcpy(cfg->name, init, sizeof(cfg->name)); 122 | cfg->flags |= MOD_IS_READY; 123 | } 124 | return cfg; 125 | } 126 | 127 | bool 128 | config(cfg_t *cfg, const char *key, const char *value) { 129 | assert(cfg != NULL); 130 | assert(key != NULL); 131 | assert(value != NULL); 132 | 133 | if (strcmp(key, "timeout") == 0) { 134 | cfg->timeout = atoi(value); 135 | return true; 136 | } 137 | if (strcmp(key, "host") == 0) { 138 | strlcpy(cfg->host, value, sizeof(cfg->host)); 139 | return true; 140 | } 141 | if (strcmp(key, "port") == 0) { 142 | cfg->port = atoi(value); 143 | return true; 144 | } 145 | if (strcmp(key, "database") == 0) { 146 | cfg->database = atoi(value); 147 | return true; 148 | } 149 | if (strcmp(key, "password") == 0) { 150 | strlcpy(cfg->password, value, sizeof(cfg->password)); 151 | return true; 152 | } 153 | 154 | return false; 155 | } 156 | 157 | bool 158 | start(cfg_t *cfg) { 159 | assert(cfg != NULL); 160 | 161 | redis_connect(cfg); /* may fail */ 162 | return true; 163 | } 164 | 165 | bool 166 | stop(cfg_t *cfg) { 167 | assert(cfg != NULL); 168 | 169 | redis_disconnect(cfg); 170 | return true; 171 | } 172 | 173 | uint32_t 174 | next(cfg_t *cfg, char *buf, size_t bufsize, bool reset) { 175 | uint32_t res = 0; 176 | assert(cfg != NULL); 177 | assert(buf != NULL); 178 | assert(bufsize > 0); 179 | 180 | (void)(reset); /* suppress warning */ 181 | 182 | if (!cfg->conn || cfg->conn->err) 183 | redis_connect(cfg); 184 | if (!cfg->conn) 185 | return 0; /* reconnect failure */ 186 | 187 | if (cfg->conn->err) { 188 | log_msg(cfg, error, "connection error: %s", cfg->conn->errstr); 189 | return 0; 190 | } 191 | 192 | redisReply *reply = NULL; 193 | if (redisGetReply(cfg->conn, (void **) &reply) == REDIS_OK) { 194 | cfg->received++; 195 | if (reply->type == REDIS_REPLY_ARRAY) { 196 | if (strcmp(reply->element[0]->str, "message") == 0 || 197 | strcmp(reply->element[1]->str, cfg->hash) == 0) { 198 | strlcpy(buf, reply->element[2]->str, bufsize); 199 | res = (uint32_t) -1; 200 | } else { 201 | log_msg(cfg, error, "wrong redis message type: %s", reply->element[0]->str); 202 | } 203 | } else { 204 | log_msg(cfg, error, "reply is not a array type"); 205 | } 206 | freeReplyObject(reply); 207 | } else if (cfg->conn->err == REDIS_ERR_IO && errno == EAGAIN) { 208 | cfg->conn->err = 0; /* reset error to prevent reconnecting */ 209 | } else { 210 | log_msg(cfg, error, "can't get reply from server %s: %s", cfg->host, cfg->conn->errstr); 211 | } 212 | 213 | return res; 214 | } 215 | 216 | bool 217 | stats(cfg_t *cfg, char *buf, size_t bufsize) { 218 | const char *fmt = 219 | "connected: %s\n" 220 | "last error: %d (%s)\n" 221 | "messages: %u\n"; 222 | 223 | assert(cfg != NULL); 224 | 225 | if (buf == NULL || bufsize == 0) 226 | return false; 227 | 228 | if (cfg->conn) { 229 | const char *err = cfg->conn->errstr[0] == '\0' ? cfg->conn->errstr : "---"; 230 | snprintf(buf, bufsize, fmt, "yes", cfg->conn->err, err, cfg->received); 231 | } else { 232 | snprintf(buf, bufsize, fmt, "no", "0", "---", cfg->received); 233 | } 234 | 235 | return true; 236 | } 237 | 238 | void 239 | destroy(cfg_t *cfg) { 240 | assert(cfg != NULL); 241 | 242 | free(cfg); 243 | } 244 | -------------------------------------------------------------------------------- /src/sources/source.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | 8 | /* function shared between modules */ 9 | 10 | static void 11 | logcb_stub(enum loglevel lvl, const char *str) { 12 | assert(str != NULL); 13 | (void)(lvl); 14 | (void)(str); 15 | } 16 | 17 | __attribute__ ((format (printf, 3, 4))) 18 | static void 19 | log_msg(const cfg_t *cfg, enum loglevel lvl, const char *format, ...) { 20 | char buf[4096] = ""; 21 | va_list args; 22 | size_t len; 23 | 24 | len = snprintf(buf, sizeof(buf), "source/%s: ", MODNAME); 25 | va_start(args, format); 26 | vsnprintf(buf + len, sizeof(buf) - len, format, args); 27 | va_end(args); 28 | 29 | cfg->logcb(lvl, buf); 30 | } 31 | 32 | void 33 | logcb(cfg_t *cfg, void (*cb)(enum loglevel lvl, const char *msg)) { 34 | assert(cfg != NULL); 35 | assert(cb != NULL); 36 | 37 | cfg->logcb = cb; 38 | } 39 | 40 | int 41 | state(cfg_t *cfg) { 42 | assert(cfg != NULL); 43 | 44 | return cfg->flags; 45 | } 46 | -------------------------------------------------------------------------------- /src/sources/source.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "../strlcpy.h" 18 | #include "../fnv.h" 19 | #include "../mod-defs.h" 20 | #include "../mod-api.h" 21 | 22 | /** 23 | * @file 24 | * This header describes module API of type 'source' 25 | * 26 | * Sample workflow of module usage: 27 | * 28 | * @msc 29 | * f2b, source; 30 | * f2b => source [label="create(init)"]; 31 | * f2b << source [label="module handler, cfg_t *cfg"]; 32 | * f2b => source [label="logcb(cfg, cb)"]; 33 | * |||; 34 | * f2b => source [label="config(cfg, param, value)"]; 35 | * f2b << source [label="true"]; 36 | * f2b => source [label="config(cfg, param, value)"]; 37 | * f2b << source [label="true"]; 38 | * f2b => source [label="config(cfg, param, value)"]; 39 | * f2b <<= source [label="logcb(level, char *msg)"]; 40 | * f2b << source [label="false"]; 41 | * |||; 42 | * f2b => source [label="state(cfg)"]; 43 | * f2b << source [label="int"]; 44 | * --- [label="check for MOD_IS_READY flag"]; 45 | * f2b => source [label="start()"]; 46 | * f2b << source [label="true"]; 47 | * --- [label="source is ready to use"]; 48 | * f2b => source [label="next(cfg, buf, sizeof(buf), true)"]; 49 | * f2b << source [label="false"]; 50 | * ... [label="no data this time, try later"]; 51 | * f2b => source [label="next(cfg, buf, sizeof(buf), true)"]; 52 | * f2b << source [label="true"]; 53 | * f2b => source [label="next(cfg, buf, sizeof(buf), false)"]; 54 | * f2b <<= source [label="logcb(level, char *msg)"]; 55 | * f2b << source [label="true"]; 56 | * f2b => source [label="next(cfg, buf, sizeof(buf), false)"]; 57 | * f2b << source [label="false"]; 58 | * ... [label="time passed"]; 59 | * f2b => source [label="stop(cfg)"]; 60 | * f2b << source [label="true"]; 61 | * --- [label="now you may config(), start() or destroy() source"]; 62 | * f2b => source [label="destroy(cfg)"]; 63 | * @endmsc 64 | */ 65 | 66 | /** 67 | * @brief Allocate resources and start processing 68 | * @param cfg Module handler 69 | * @returns true on success, false on error 70 | */ 71 | extern bool start(cfg_t *cfg); 72 | /** 73 | * @brief Poll source for new data 74 | * @param cfg Module handler 75 | * @param buf Pointer to buffer for storing result 76 | * @param bufsize Size of buffer above (in bytes) 77 | * @param reset Reset internals to start of list 78 | * @returns false if no new data available, or true otherwise with filling @a buf 79 | */ 80 | extern uint32_t next(cfg_t *cfg, char *buf, size_t bufsize, bool reset); 81 | /** 82 | * @brief Get statistics for source 83 | * @param cfg Module handler 84 | * @param buf Pointer to buffer for stats report 85 | * @param bufsize Size of buffer above (in bytes) 86 | * @returns true on success, false on error 87 | */ 88 | extern bool stats(cfg_t *cfg, char *buf, size_t bufsize); 89 | /** 90 | * @brief Deallocate resources, prepare for module destroy 91 | * @param cfg Module handler 92 | * @returns true on success 93 | */ 94 | extern bool stop(cfg_t *cfg); 95 | -------------------------------------------------------------------------------- /src/statefile.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | 8 | #include "common.h" 9 | #include "log.h" 10 | #include "matches.h" 11 | #include "ipaddr.h" 12 | #include "statefile.h" 13 | 14 | f2b_statefile_t * 15 | f2b_statefile_create(const char *statedir, const char *jailname) { 16 | f2b_statefile_t *sf = NULL; 17 | char path[PATH_MAX]; 18 | 19 | assert(statedir != NULL); 20 | assert(jailname != NULL); 21 | 22 | snprintf(path, sizeof(path), "%s/%s.state", statedir, jailname); 23 | 24 | if (access(path, R_OK | W_OK) < 0) { 25 | if (errno != ENOENT) { 26 | f2b_log_msg(log_error, "can't access statefile: %s", strerror(errno)); 27 | return NULL; 28 | } 29 | mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP; /* 0640 */ 30 | int fd = open(path, O_CREAT | O_WRONLY, mode); 31 | if (fd < 0) { 32 | f2b_log_msg(log_error, "can't create statefile: %s", strerror(errno)); 33 | return NULL; 34 | } 35 | close(fd); 36 | } 37 | 38 | if ((sf = calloc(1, sizeof(f2b_statefile_t))) == NULL) { 39 | f2b_log_msg(log_error, "can't allocate memory for statefile: %s", strerror(errno)); 40 | return NULL; 41 | } 42 | 43 | strlcpy(sf->path, path, sizeof(sf->path)); 44 | 45 | return sf; 46 | } 47 | 48 | void 49 | f2b_statefile_destroy(f2b_statefile_t *sf) { 50 | if (!sf) return; 51 | free(sf); 52 | } 53 | 54 | f2b_ipaddr_t * 55 | f2b_statefile_load(f2b_statefile_t *sf) { 56 | const int fields = 3; 57 | const char *format = "%48s %u %u"; /* 48 == IPADDR_MAX == sizeof(addr) */ 58 | f2b_ipaddr_t *addrlist = NULL, *ipaddr = NULL; 59 | char buf[256], addr[IPADDR_MAX + 1], *p; 60 | unsigned int banned_at, release_at; 61 | FILE *f = NULL; 62 | 63 | assert(sf != NULL); 64 | 65 | if ((f = fopen(sf->path, "r")) == NULL) { 66 | f2b_log_msg(log_error, "can't open statefile: %s", strerror(errno)); 67 | return NULL; 68 | } 69 | 70 | while(fgets(buf, sizeof(buf), f)) { 71 | p = &buf[0]; 72 | while(isspace(*p)) 73 | p++; /* skip leading spaces */ 74 | if (*p == '#') 75 | continue; /* is comment */ 76 | if (sscanf(p, format, addr, &banned_at, &release_at) != fields) { 77 | f2b_log_msg(log_warn, "can't parse, ignoring line: %s", buf); 78 | continue; 79 | } 80 | if ((ipaddr = f2b_ipaddr_create(addr)) == NULL) { 81 | f2b_log_msg(log_warn, "can't parse addr: %s", addr); 82 | continue; 83 | } 84 | ipaddr->banned = true; 85 | ipaddr->banned_at = banned_at; 86 | ipaddr->release_at = release_at; 87 | ipaddr->next = addrlist; 88 | addrlist = ipaddr; 89 | } 90 | 91 | fclose(f); 92 | return addrlist; 93 | } 94 | 95 | bool 96 | f2b_statefile_save(f2b_statefile_t *sf, f2b_ipaddr_t *addrlist) { 97 | const char *fmt = "%s\t%lu\t%lu\n"; 98 | char path[PATH_MAX]; 99 | FILE *f = NULL; 100 | 101 | assert(sf != NULL); 102 | 103 | if (addrlist == NULL) 104 | return true; 105 | 106 | strlcpy(path, sf->path, sizeof(path)); 107 | strlcat(path, ".new", sizeof(path)); 108 | 109 | if ((f = fopen(path, "w")) == NULL) { 110 | f2b_log_msg(log_error, "can't open statefile: %s", strerror(errno)); 111 | return false; 112 | } 113 | 114 | fprintf(f, "# address\tbanned_at\trelease_at\n"); 115 | for (f2b_ipaddr_t *ipaddr = addrlist; ipaddr != NULL; ipaddr = ipaddr->next) { 116 | if (!ipaddr->banned) 117 | continue; 118 | fprintf(f, fmt, ipaddr->text, ipaddr->banned_at, ipaddr->release_at); 119 | } 120 | fclose(f); 121 | 122 | if (rename(path, sf->path) < 0) { 123 | f2b_log_msg(log_error, "can't replace statefile %s: %s", sf->path, strerror(errno)); 124 | return false; 125 | } 126 | 127 | sf->need_save = false; 128 | return true; 129 | } 130 | -------------------------------------------------------------------------------- /src/statefile.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License version 2 as 5 | * published by the Free Software Foundation. 6 | */ 7 | #ifndef F2B_STATEFILE_H_ 8 | #define F2B_STATEFILE_H_ 9 | 10 | typedef struct f2b_statefile_t { 11 | char path[PATH_MAX]; 12 | bool need_save; 13 | } f2b_statefile_t; 14 | 15 | f2b_statefile_t * 16 | f2b_statefile_create(const char *statedir, const char *jailname); 17 | 18 | void 19 | f2b_statefile_destroy(f2b_statefile_t *sf); 20 | 21 | f2b_ipaddr_t * 22 | f2b_statefile_load(f2b_statefile_t *sf); 23 | 24 | bool 25 | f2b_statefile_save(f2b_statefile_t *sf, f2b_ipaddr_t *addrlist); 26 | 27 | #endif /* F2B_STATEFILE_H_ */ 28 | -------------------------------------------------------------------------------- /src/strlcpy.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: strlcpy.c,v 1.13 2015/08/31 02:53:57 guenther Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 1998, 2015 Todd C. Miller 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #include 20 | #include 21 | 22 | /* 23 | * Copy string src to buffer dst of size dsize. At most dsize-1 24 | * chars will be copied. Always NUL terminates (unless dsize == 0). 25 | * Returns strlen(src); if retval >= dsize, truncation occurred. 26 | */ 27 | size_t 28 | strlcpy(char *dst, const char *src, size_t dsize) { 29 | const char *osrc = src; 30 | size_t nleft = dsize; 31 | 32 | /* Copy as many bytes as will fit. */ 33 | if (nleft != 0) { 34 | while (--nleft != 0) { 35 | if ((*dst++ = *src++) == '\0') 36 | break; 37 | } 38 | } 39 | 40 | /* Not enough room in dst, add NUL and traverse rest of src. */ 41 | if (nleft == 0) { 42 | if (dsize != 0) 43 | *dst = '\0'; /* NUL-terminate dst */ 44 | while (*src++) 45 | ; 46 | } 47 | 48 | return(src - osrc - 1); /* count does not include NUL */ 49 | } 50 | 51 | /* 52 | * Appends src to string dst of size dsize (unlike strncat, dsize is the 53 | * full size of dst, not space left). At most dsize-1 characters 54 | * will be copied. Always NUL terminates (unless dsize <= strlen(dst)). 55 | * Returns strlen(src) + MIN(dsize, strlen(initial dst)). 56 | * If retval >= dsize, truncation occurred. 57 | */ 58 | size_t 59 | strlcat(char *dst, const char *src, size_t dsize) { 60 | const char *odst = dst; 61 | const char *osrc = src; 62 | size_t n = dsize; 63 | size_t dlen; 64 | 65 | /* Find the end of dst and adjust bytes left but don't go past end. */ 66 | while (n-- != 0 && *dst != '\0') 67 | dst++; 68 | dlen = dst - odst; 69 | n = dsize - dlen; 70 | 71 | if (n-- == 0) 72 | return(dlen + strlen(src)); 73 | while (*src != '\0') { 74 | if (n != 0) { 75 | *dst++ = *src; 76 | n--; 77 | } 78 | src++; 79 | } 80 | *dst = '\0'; 81 | 82 | return(dlen + (src - osrc)); /* count does not include NUL */ 83 | } 84 | -------------------------------------------------------------------------------- /src/strlcpy.h: -------------------------------------------------------------------------------- 1 | #ifndef HAS_STRLFUNCS_ 2 | #define HAS_STRLFUNCS_ 3 | 4 | /* $OpenBSD: strlcpy.c,v 1.13 2015/08/31 02:53:57 guenther Exp $ */ 5 | 6 | /* 7 | * Copyright (c) 1998, 2015 Todd C. Miller 8 | * 9 | * Permission to use, copy, modify, and distribute this software for any 10 | * purpose with or without fee is hereby granted, provided that the above 11 | * copyright notice and this permission notice appear in all copies. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 14 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 15 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 16 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 17 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 18 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 19 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 20 | */ 21 | 22 | size_t strlcat(char *dst, const char *src, size_t dsize); 23 | size_t strlcpy(char *dst, const char *src, size_t dsize); 24 | 25 | #endif /* HAS_STRLFUNCS_ */ 26 | -------------------------------------------------------------------------------- /t/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | enable_testing() 2 | 3 | set(SRC_DIR "../src") 4 | 5 | add_executable("t_md5" "t_md5.c" "${SRC_DIR}/md5.c") 6 | add_executable("t_buf" "t_buf.c" "${SRC_DIR}/strlcpy.c" "${SRC_DIR}/buf.c") 7 | add_executable("t_cmd" "t_cmd.c" "${SRC_DIR}/strlcpy.c" "${SRC_DIR}/buf.c" "${SRC_DIR}/commands.c") 8 | add_executable("t_matches" "t_matches.c" "${SRC_DIR}/strlcpy.c" "${SRC_DIR}/matches.c") 9 | add_executable("t_ipaddr" "t_ipaddr.c" "${SRC_DIR}/strlcpy.c" "${SRC_DIR}/matches.c" "${SRC_DIR}/ipaddr.c") 10 | add_executable("t_statefile" "t_statefile.c" "${SRC_DIR}/strlcpy.c" "${SRC_DIR}/matches.c" "${SRC_DIR}/ipaddr.c" "${SRC_DIR}/statefile.c" "${SRC_DIR}/log.c") 11 | add_executable("t_config_param" "t_config_param.c" "${SRC_DIR}/strlcpy.c" "${SRC_DIR}/config.c" "${SRC_DIR}/log.c") 12 | 13 | add_test("tests/f2b_buf_*" "t_buf") 14 | add_test("tests/f2b_cmd_*" "t_cmd") 15 | add_test("tests/f2b_md5_*" "t_md5") 16 | add_test("tests/f2b_matches_*" "t_matches") 17 | add_test("tests/f2b_ipaddr_*" "t_ipaddr") 18 | add_test("tests/f2b_statefile_*" "t_statefile") 19 | add_test("tests/f2b_config_param*" "t_config_param") 20 | 21 | add_executable("t_filter_preg" "t_filters.c" "${SRC_DIR}/filters/preg.c" "${SRC_DIR}/strlcpy.c" "${SRC_DIR}/fnv32a.c") 22 | add_test("tests/filter/preg" "t_filter_preg") 23 | 24 | if (WITH_PCRE) 25 | add_test("tests/filter/pcre" "t_filter_pcre") 26 | add_executable("t_filter_pcre" "t_filters.c" "${SRC_DIR}/filters/pcre.c" "${SRC_DIR}/strlcpy.c" "${SRC_DIR}/fnv32a.c") 27 | target_link_libraries("t_filter_pcre" "pcre") 28 | endif () 29 | -------------------------------------------------------------------------------- /t/t_buf.c: -------------------------------------------------------------------------------- 1 | #include "../src/common.h" 2 | #include "../src/buf.h" 3 | 4 | int main() { 5 | f2b_buf_t buf; 6 | char *line; 7 | bool ret; 8 | int len; 9 | 10 | memset(&buf, 0x0, sizeof(buf)); 11 | 12 | ret = f2b_buf_alloc(&buf, 512); 13 | assert(ret = true); 14 | assert(buf.used == 0); 15 | assert(buf.size == 512); 16 | 17 | ret = f2b_buf_append(&buf, "test ", 0); 18 | assert(ret == true); 19 | assert(buf.used == 5); 20 | 21 | line = f2b_buf_extract(&buf, "\n"); 22 | assert(line == NULL); 23 | 24 | ret = f2b_buf_append(&buf, "test2\n", 0); 25 | assert(ret == true); 26 | assert(buf.used == 11); 27 | 28 | ret = f2b_buf_append(&buf, "test3\n", 0); 29 | assert(ret == true); 30 | assert(buf.used == 17); 31 | 32 | line = f2b_buf_extract(&buf, "\n"); 33 | assert(line != NULL); 34 | assert(strcmp(line, "test test2") == 0); 35 | assert(buf.used == 6); 36 | free(line); 37 | 38 | line = f2b_buf_extract(&buf, "\n"); 39 | assert(line != NULL); 40 | assert(buf.used == 0); 41 | free(line); 42 | 43 | f2b_buf_append(&buf, "test4\n\n", 6); 44 | assert(buf.used == 6); 45 | assert(strcmp(buf.data, "test4\n") == 0); 46 | 47 | len = f2b_buf_splice(&buf, 0); 48 | assert(len == 0); 49 | assert(buf.used == 6); 50 | assert(strcmp(buf.data, "test4\n") == 0); 51 | 52 | len = f2b_buf_splice(&buf, 2); 53 | assert(len == 2); 54 | assert(buf.used == 4); 55 | assert(strcmp(buf.data, "st4\n") == 0); 56 | 57 | len = f2b_buf_splice(&buf, 6); 58 | assert(len == 4); 59 | assert(buf.used == 0); 60 | assert(buf.data[0] == '\0'); 61 | 62 | f2b_buf_free(&buf); 63 | assert(buf.used == 0); 64 | assert(buf.size == 0); 65 | assert(buf.data == NULL); 66 | 67 | return 0; 68 | } 69 | -------------------------------------------------------------------------------- /t/t_cmd.c: -------------------------------------------------------------------------------- 1 | #include "../src/common.h" 2 | #include "../src/buf.h" 3 | #include "../src/commands.h" 4 | 5 | int main() { 6 | f2b_cmd_t cmd; 7 | f2b_cmd_t *c; 8 | 9 | assert(f2b_cmd_parse(&cmd, "") == false); 10 | assert(f2b_cmd_parse(&cmd, " ") == false); 11 | 12 | assert(f2b_cmd_parse(&cmd, "status") == true); 13 | assert(cmd.type = CMD_STATUS); 14 | assert(cmd.argc == 1); 15 | assert(strcmp(cmd.args[0], "status") == 0); 16 | assert(cmd.data.used == 6); /* "status" */ 17 | f2b_buf_free(&cmd.data); 18 | 19 | assert(f2b_cmd_parse(&cmd, "stat") == false); /* no such command */ 20 | assert(cmd.type == CMD_UNKNOWN); 21 | 22 | assert(f2b_cmd_parse(&cmd, "jail test") == false); /* incomplete command */ 23 | assert(cmd.type == CMD_UNKNOWN); 24 | 25 | assert(f2b_cmd_parse(&cmd, "jail test status") == true); 26 | assert(cmd.type == CMD_JAIL_STATUS); 27 | assert(cmd.argc == 3); 28 | assert(strcmp(cmd.args[0], "jail") == 0); 29 | assert(strcmp(cmd.args[1], "test") == 0); 30 | assert(strcmp(cmd.args[2], "status") == 0); 31 | assert(cmd.data.used == 16); /* "jail\0test\0status" */ 32 | f2b_buf_free(&cmd.data); 33 | 34 | assert(f2b_cmd_parse(&cmd, "jail test set bantime 7200") == true); 35 | assert(cmd.type == CMD_JAIL_SET); 36 | assert(cmd.argc == 5); 37 | assert(strcmp(cmd.args[0], "jail") == 0); 38 | assert(strcmp(cmd.args[1], "test") == 0); 39 | assert(strcmp(cmd.args[2], "set") == 0); 40 | assert(strcmp(cmd.args[3], "bantime") == 0); 41 | assert(strcmp(cmd.args[4], "7200") == 0); 42 | f2b_buf_free(&cmd.data); 43 | 44 | c = f2b_cmd_create(""); 45 | assert(c == NULL); 46 | c = f2b_cmd_create(" "); 47 | assert(c == NULL); 48 | c = f2b_cmd_create("\n\r\n"); 49 | assert(c == NULL); 50 | c = f2b_cmd_create("test"); 51 | assert(c == NULL); /* no such command */ 52 | c = f2b_cmd_create("help"); 53 | assert(c != NULL); 54 | f2b_cmd_destroy(c); 55 | 56 | f2b_buf_free(&cmd.data); 57 | 58 | return EXIT_SUCCESS; 59 | } 60 | -------------------------------------------------------------------------------- /t/t_config_param.c: -------------------------------------------------------------------------------- 1 | #include "../src/common.h" 2 | #include "../src/config.h" 3 | 4 | int main() { 5 | f2b_config_param_t *param = NULL; 6 | 7 | assert(param == NULL); 8 | f2b_config_param_create(""); 9 | assert(param == NULL); 10 | f2b_config_param_create("#"); 11 | assert(param == NULL); 12 | f2b_config_param_create("="); 13 | assert(param == NULL); 14 | f2b_config_param_create("key="); 15 | assert(param == NULL); 16 | f2b_config_param_create("key ="); 17 | assert(param == NULL); 18 | f2b_config_param_create("key = "); 19 | assert(param == NULL); 20 | f2b_config_param_create( "=value"); 21 | assert(param == NULL); 22 | f2b_config_param_create( "= value"); 23 | assert(param == NULL); 24 | f2b_config_param_create(" = value"); 25 | assert(param == NULL); 26 | 27 | param = f2b_config_param_create("key=value"); 28 | assert(param != NULL); 29 | assert(strcmp(param->name, "key") == 0); 30 | assert(strcmp(param->value, "value") == 0); 31 | free(param); 32 | 33 | param = f2b_config_param_create("key = value"); 34 | assert(param != NULL); 35 | assert(strcmp(param->name, "key") == 0); 36 | assert(strcmp(param->value, "value") == 0); 37 | free(param); 38 | 39 | param = f2b_config_param_create("key=value #comment"); 40 | assert(param != NULL); 41 | assert(strcmp(param->name, "key") == 0); 42 | assert(strcmp(param->value, "value") == 0); 43 | free(param); 44 | 45 | param = f2b_config_param_create("key=value#compose"); 46 | assert(param != NULL); 47 | assert(strcmp(param->name, "key") == 0); 48 | assert(strcmp(param->value, "value#compose") == 0); 49 | free(param); 50 | 51 | return 0; 52 | } 53 | -------------------------------------------------------------------------------- /t/t_filters.c: -------------------------------------------------------------------------------- 1 | #include "../src/common.h" 2 | #include "../src/log.h" 3 | #include "../src/matches.h" 4 | #include "../src/ipaddr.h" 5 | #include "../src/filters/filter.h" 6 | 7 | int main() { 8 | cfg_t *filter = NULL; 9 | char matchbuf[IPADDR_MAX] = ""; 10 | bool result = false; 11 | short int score; 12 | int flags; 13 | uint32_t ftag; 14 | 15 | UNUSED(result); 16 | 17 | filter = create("test"); 18 | assert(filter != NULL); 19 | 20 | result = config(filter, "nonexistent", "yes"); 21 | assert(result == false); 22 | 23 | result = config(filter, "icase", "yes"); 24 | assert(result == true); 25 | 26 | result = config(filter, "icase", "no"); 27 | assert(result == true); 28 | 29 | flags = state(filter); 30 | assert((flags & MOD_IS_READY) == 0); 31 | 32 | result = append(filter, "host without marker"); 33 | assert(result == false); 34 | 35 | result = append(filter, "host with marker "); 36 | assert(result == true); 37 | 38 | flags = state(filter); 39 | assert(flags & MOD_IS_READY); 40 | 41 | ftag = match(filter, "host", matchbuf, sizeof(matchbuf), &score); 42 | assert(ftag == 0); 43 | 44 | ftag = match(filter, "host with marker ", matchbuf, sizeof(matchbuf), &score); 45 | assert(ftag == 0); 46 | 47 | ftag = match(filter, "host with marker 1.2.3.4", matchbuf, sizeof(matchbuf), &score); 48 | assert(ftag > 0); 49 | 50 | ftag = match(filter, "host with marker example.com", matchbuf, sizeof(matchbuf), &score); 51 | assert(ftag == 0); 52 | 53 | destroy(filter); 54 | 55 | return 0; 56 | } 57 | -------------------------------------------------------------------------------- /t/t_ipaddr.c: -------------------------------------------------------------------------------- 1 | #include "../src/common.h" 2 | #include "../src/matches.h" 3 | #include "../src/ipaddr.h" 4 | 5 | #include 6 | #include 7 | 8 | int main() { 9 | bool res = false; 10 | f2b_ipaddr_t *list = NULL; 11 | f2b_ipaddr_t *addr = NULL; 12 | 13 | UNUSED(res); 14 | UNUSED(addr); 15 | UNUSED(list); 16 | 17 | addr = f2b_addrlist_lookup(list, "127.0.0.1"); 18 | assert(addr == NULL); 19 | 20 | addr = f2b_ipaddr_create("400.400.400.400"); 21 | assert(addr == NULL); 22 | addr = f2b_ipaddr_create("127.0.0.1"); 23 | assert(addr != NULL); 24 | 25 | assert(addr->type == AF_INET); 26 | assert(addr->next == NULL); 27 | assert(strncmp(addr->text, "127.0.0.1", sizeof(addr->text)) == 0); 28 | assert(addr->matches.count == 0); 29 | 30 | list = f2b_addrlist_append(list, addr); 31 | assert(list != NULL); 32 | assert(f2b_addrlist_lookup(list, "127.0.0.1") != NULL); 33 | assert(list == addr); 34 | 35 | list = f2b_addrlist_remove(list, "127.4.4.4"); 36 | assert(list != NULL); 37 | list = f2b_addrlist_remove(list, "127.0.0.1"); 38 | assert(list == NULL); 39 | 40 | assert((addr = f2b_ipaddr_create("127.0.0.1")) != NULL); 41 | assert((list = f2b_addrlist_append(list, addr)) != NULL); 42 | assert((addr = f2b_ipaddr_create("127.0.0.2")) != NULL); 43 | assert((list = f2b_addrlist_append(list, addr)) != NULL); 44 | assert((addr = f2b_ipaddr_create("127.0.0.3")) != NULL); 45 | assert((list = f2b_addrlist_append(list, addr)) != NULL); 46 | assert((addr = f2b_ipaddr_create("127.0.0.4")) != NULL); 47 | assert((list = f2b_addrlist_append(list, addr)) != NULL); 48 | assert((addr = f2b_ipaddr_create("127.0.0.5")) != NULL); 49 | assert((list = f2b_addrlist_append(list, addr)) != NULL); 50 | 51 | assert((list = f2b_addrlist_remove(list, "127.0.0.2")) != NULL); 52 | assert((list = f2b_addrlist_remove(list, "127.0.0.4")) != NULL); 53 | assert((list = f2b_addrlist_remove(list, "127.0.0.3")) != NULL); 54 | assert((list = f2b_addrlist_remove(list, "127.0.0.5")) != NULL); 55 | assert((list = f2b_addrlist_remove(list, "127.0.0.1")) == NULL); 56 | 57 | return 0; 58 | } 59 | -------------------------------------------------------------------------------- /t/t_matches.c: -------------------------------------------------------------------------------- 1 | #include "../src/common.h" 2 | #include "../src/matches.h" 3 | 4 | int main() { 5 | f2b_match_t *match = NULL; 6 | f2b_matches_t matches; 7 | bool result = false; 8 | size_t size = 15; 9 | time_t now = time(NULL); 10 | int score = 0; 11 | 12 | UNUSED(result); 13 | 14 | memset(&matches, 0x0, sizeof(matches)); 15 | 16 | match = f2b_match_create(now); 17 | assert(match != NULL); 18 | assert(match->next == NULL); 19 | 20 | f2b_matches_prepend(&matches, match); 21 | assert(matches.count == 1); 22 | assert(matches.last == now); 23 | assert(matches.list == match); 24 | 25 | f2b_matches_expire(&matches, now + 1); 26 | assert(matches.count == 0); 27 | assert(matches.last == 0); 28 | assert(matches.list == NULL); 29 | 30 | for (size_t i = 1; i < size; i++) { 31 | match = f2b_match_create(now - 60 * (size - i)); 32 | match->score = i * 10; 33 | f2b_matches_prepend(&matches, match); 34 | assert(matches.count == i); 35 | assert(matches.last == now - (time_t) (60 * (size - i))); 36 | } 37 | 38 | f2b_matches_expire(&matches, now - 60 * 4); 39 | assert(matches.count == 3); 40 | 41 | score = f2b_matches_score(&matches, 0); 42 | assert(score == 140 + 130 + 120); 43 | score = f2b_matches_score(&matches, now - 60); 44 | assert(score == 140); 45 | 46 | f2b_matches_flush(&matches); 47 | assert(matches.count == 0); 48 | assert(matches.last == 0); 49 | assert(matches.list == NULL); 50 | 51 | return 0; 52 | } 53 | -------------------------------------------------------------------------------- /t/t_md5.c: -------------------------------------------------------------------------------- 1 | #include "../src/common.h" 2 | #include "../src/md5.h" 3 | 4 | int main() { 5 | MD5_CTX md5; 6 | char *sample = "098f6bcd4621d373cade4e832627b4f6"; // md5_hex("test") 7 | 8 | memset(&md5, 0x0, sizeof(md5)); 9 | 10 | MD5Init(&md5); 11 | MD5Update(&md5, (unsigned char *) "test", 4); 12 | MD5Final(&md5); 13 | 14 | assert(strcmp(md5.hexdigest, sample) == 0); 15 | return 0; 16 | } 17 | -------------------------------------------------------------------------------- /t/t_statefile.c: -------------------------------------------------------------------------------- 1 | #include "../src/common.h" 2 | #include "../src/matches.h" 3 | #include "../src/ipaddr.h" 4 | #include "../src/statefile.h" 5 | 6 | int main() { 7 | bool res = false; 8 | f2b_ipaddr_t *list = NULL; 9 | f2b_statefile_t *sf = NULL; 10 | char jailname[64] = ""; 11 | struct stat st; 12 | time_t banned_at = 0, release_at = 0; 13 | 14 | UNUSED(res); 15 | UNUSED(list); 16 | 17 | snprintf(jailname, sizeof(jailname), "%lu", time(NULL)); /* gen unique jailname */ 18 | 19 | sf = f2b_statefile_create("/tmp", jailname); 20 | assert(sf != NULL); 21 | 22 | list = f2b_statefile_load(sf); 23 | assert(list == NULL); 24 | 25 | banned_at = time(NULL) - 60; 26 | release_at = banned_at + 60 * 2; 27 | 28 | list = f2b_ipaddr_create("1.2.3.4"); 29 | list->banned = true; 30 | list->banned_at = banned_at; 31 | list->release_at = release_at; 32 | 33 | /* empty file should be created after f2b_statefile_create() call */ 34 | stat(sf->path, &st); 35 | assert(st.st_size == 0); 36 | 37 | res = f2b_statefile_save(sf, list); 38 | assert(res == true); 39 | 40 | stat(sf->path, &st); 41 | assert(st.st_size > 0); 42 | 43 | f2b_ipaddr_destroy(list); 44 | list = NULL; 45 | 46 | list = f2b_statefile_load(sf); 47 | assert(list != NULL); 48 | assert(list->banned == true); 49 | assert(list->banned_at == banned_at); 50 | assert(list->release_at == release_at); 51 | 52 | f2b_ipaddr_destroy(list); 53 | 54 | unlink(sf->path); 55 | 56 | f2b_statefile_destroy(sf); 57 | 58 | return 0; 59 | } 60 | --------------------------------------------------------------------------------