{"id":17975535,"url":"https://github.com/rfxn/brute-force-detection","last_synced_at":"2026-04-12T03:06:12.461Z","repository":{"id":13735374,"uuid":"16429705","full_name":"rfxn/brute-force-detection","owner":"rfxn","description":"Brute force detection with exponential-decay pressure scoring, 57 service rules, 8 firewall backends, GeoIP enrichment, and multi-channel alerting","archived":false,"fork":false,"pushed_at":"2026-03-31T13:39:23.000Z","size":2886,"stargazers_count":26,"open_issues_count":1,"forks_count":12,"subscribers_count":4,"default_branch":"master","last_synced_at":"2026-03-31T15:29:48.274Z","etag":null,"topics":["bash","brute-force-detection","fail2ban-alternative","firewall","intrusion-prevention","iptables","linux-security","log-analysis"],"latest_commit_sha":null,"homepage":null,"language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rfxn.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG","contributing":null,"funding":null,"license":"COPYING.GPL","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2014-02-01T06:40:48.000Z","updated_at":"2026-03-18T05:05:14.000Z","dependencies_parsed_at":"2022-09-23T14:57:34.756Z","dependency_job_id":null,"html_url":"https://github.com/rfxn/brute-force-detection","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/rfxn/brute-force-detection","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rfxn%2Fbrute-force-detection","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rfxn%2Fbrute-force-detection/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rfxn%2Fbrute-force-detection/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rfxn%2Fbrute-force-detection/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rfxn","download_url":"https://codeload.github.com/rfxn/brute-force-detection/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rfxn%2Fbrute-force-detection/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31481238,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-06T14:34:32.243Z","status":"ssl_error","status_checked_at":"2026-04-06T14:34:31.723Z","response_time":112,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bash","brute-force-detection","fail2ban-alternative","firewall","intrusion-prevention","iptables","linux-security","log-analysis"],"created_at":"2024-10-29T17:19:51.309Z","updated_at":"2026-04-06T17:01:15.899Z","avatar_url":"https://github.com/rfxn.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Brute Force Detection (BFD)\n\n[![CI](https://github.com/rfxn/brute-force-detection/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/rfxn/brute-force-detection/actions/workflows/ci.yml)\n[![Version](https://img.shields.io/badge/version-2.0.1-blue.svg)](CHANGELOG)\n[![License: GPL v2](https://img.shields.io/badge/license-GPL_v2-green.svg)](COPYING.GPL)\n[![Shell](https://img.shields.io/badge/language-bash-89e051.svg)](https://www.gnu.org/software/bash/)\n[![Platform](https://img.shields.io/badge/platform-linux-lightgrey.svg)](README.md#11-supported-systems)\n\nLog-based brute force detection and automatic IP banning for Linux servers.\nPressure-based scoring lets humans make mistakes while stopping bots cold —\n57 service rules, 8 firewall backends, IPv4/IPv6, GeoIP enrichment,\nmulti-channel alerting, and continuous watch mode with ~10s detection latency.\n\n*Copyright (C) 1999-2026 [R-fx Networks](https://www.rfxn.com) · Ryan MacDonald · [GPL v2](COPYING.GPL)*\n\n---\n\n## Contents\n\n- [1. Introduction](#1-introduction)\n  - [1.1 Supported Systems](#11-supported-systems)\n- [2. Installation](#2-installation)\n  - [2.1 Scheduling](#21-scheduling)\n  - [2.2 Upgrading](#22-upgrading)\n- [3. Configuration](#3-configuration)\n  - [3.1 Pressure Model (Detection)](#31-pressure-model-detection)\n  - [3.2 Email Alerts](#32-email-alerts)\n  - [3.3 Banning](#33-banning)\n  - [3.4 Repeat Offender Handling](#34-repeat-offender-handling)\n  - [3.5 IPv6](#35-ipv6)\n  - [3.6 Log Paths](#36-log-paths)\n  - [3.7 Advanced](#37-advanced)\n  - [3.8 Country Weighting](#38-country-weighting)\n  - [3.9 SMTP Relay](#39-smtp-relay)\n  - [3.10 Email Templates](#310-email-templates)\n  - [3.11 Slack Alerts](#311-slack-alerts)\n  - [3.12 Telegram Alerts](#312-telegram-alerts)\n  - [3.13 Discord Alerts](#313-discord-alerts)\n- [4. Firewall Integration](#4-firewall-integration)\n- [5. General Usage](#5-general-usage)\n  - [5.1 Dry Run](#51-dry-run)\n  - [5.2 Health Check](#52-health-check)\n  - [5.3 Threat Activity](#53-threat-activity)\n  - [5.4 Watch Mode](#54-watch-mode)\n  - [5.5 Flush Bans](#55-flush-bans)\n  - [5.6 Structured Output](#56-structured-output)\n  - [5.7 Scan Mode](#57-scan-mode)\n  - [5.8 Events and Investigation](#58-events-and-investigation)\n- [6. Rule Engine](#6-rule-engine)\n  - [6.1 Rule Catalog](#61-rule-catalog)\n  - [6.2 Rule Customization](#62-rule-customization)\n- [7. Ignore Lists](#7-ignore-lists)\n- [8. Periodic Reports](#8-periodic-reports)\n- [9. Ban Management](#9-ban-management)\n- [10. IPv6 Support](#10-ipv6-support)\n- [11. Troubleshooting](#11-troubleshooting)\n- [12. License](#12-license)\n- [13. Support](#13-support)\n\n---\n\n## Quick Start\n\n```bash\n# Install (as root)\n./install.sh\n\n# Configure — set your firewall ban command\nvi /usr/local/bfd/conf.bfd\n\n# Verify — watch mode should be running (auto-enabled by installer)\nsystemctl status bfd-watch       # systemd\nbfd -c                           # health check\n\n# Dry run — detect without banning\nbfd -d\n\n# Run with output\nbfd -s\n\n# View top attackers and ban status\nbfd -a\n\n# Manage bans\nbfd -l               # list active bans\nbfd -b 192.0.2.1 sshd # manually ban an IP\nbfd -u 192.0.2.1      # unban an IP\n```\n\n---\n\n## 1. Introduction\n\nBrute Force Detection (BFD) is a modular shell script for parsing application logs and detecting authentication failures. It ships with 57 service rules covering SSH, mail, FTP, web, database, control panel, DNS, VPN, VoIP, proxy, and messaging services. Each rule declares fail2ban-compatible `\u003cHOST\u003e` regex patterns; the engine handles log reading, IP extraction, IPv6 normalization, and validation.\n\nUnlike traditional count-based tools that ban at a fixed failure count — forcing operators to choose between catching attackers fast or tolerating legitimate mistakes — BFD uses **exponential-decay pressure scoring**. Each failure adds pressure weighted by service severity, and pressure decays over time via a configurable half-life. A user who mistypes a password a few times over several minutes generates pressure that naturally fades, staying well below the trip point. A bot hammering the same service generates pressure faster than it can decay and trips the threshold almost immediately. The result is fewer false positives on real users with faster response to actual attacks.\n\nBFD uses a log tracking system so logs are only parsed from the point at which they were last read. This greatly assists in performance as we are not constantly reading the same log data. The log tracking system is compatible with syslog/logrotate style log rotations — it detects when rotations have occurred and grabs log tails from both the new log file and the rotated log file.\n\n**Detection**\n- 57 service rules with fail2ban-compatible `\u003cHOST\u003e` regex patterns\n- Exponential-decay pressure scoring — human typos fade away, bot attacks trip instantly\n- Per-service severity weights (SSH=3, control panels=5, noisy services=1)\n- Per-rule and global cross-service pressure trip points\n- Optional country-based pressure multipliers\n- Incremental log parsing with rotation-aware tracking\n\n**Banning**\n- Temporary bans with automatic expiry and firewall rule cleanup\n- Repeat offender escalation (linear, doubling, or capped growth)\n- 8 firewall backends with auto-detection (APF, CSF, firewalld, UFW, nftables, iptables, route, custom)\n- Manual ban/unban CLI with full state tracking\n\n**IPv4/IPv6**\n- Dual-stack detection and banning with no rule modifications needed\n- Separate IPv6 ban commands for firewalls that require it (ip6tables)\n- Automatic local address exclusion for both address families\n\n**Operational**\n- Health check mode for non-destructive diagnostics (`bfd -c`)\n- Dry-run mode for testing rules without banning (`bfd -d`)\n- Verbose mode for detailed per-rule and per-ban output (`bfd --verbose`)\n- Threat activity reporting with summary, dual-interval service breakdown, and ban status (`bfd -a`)\n- Per-run statistics logging (rules checked, events parsed, bans executed)\n- Batched email alerts with enriched context (ban type, duration, history) and per-rule routing\n- Man page (`man bfd`) and bash tab completion installed automatically\n\n### 1.1 Supported Systems\n\nBFD runs on any Linux distribution with bash 4.1+ and standard GNU utilities (grep, awk, sed). It has been tested and is supported on:\n\n**RHEL-family:**\n- CentOS 6, 7\n- Rocky Linux 8, 9, 10\n\n**Debian-family:**\n- Ubuntu 14.04, 16.04, 18.04, 20.04, 22.04, 24.04\n- Debian 12\n\nLog paths are auto-detected at runtime:\n- RHEL: `/var/log/secure`, `/var/log/messages`, `/var/log/maillog`\n- Debian/Ubuntu: `/var/log/auth.log`, `/var/log/syslog`, `/var/log/mail.log`\n\nBFD requires root privileges. No additional dependencies beyond the base system are needed.\n\n---\n\n## 2. Installation\n\nThe included `install.sh` script handles all installation tasks:\n\n```bash\n./install.sh\n```\n\nThis will:\n- Install BFD to `/usr/local/bfd`\n- Place the `bfd` command at `/usr/local/sbin/bfd`\n- Auto-enable watch mode for ~10s detection latency (systemd or SysVinit)\n- Install a 2-minute cronjob as fallback (skipped while watch runs)\n- If upgrading, import settings from the previous installation and restart watch mode\n\nPrevious installations are backed up before overwriting.\n\n- **Install Path:** `/usr/local/bfd`\n- **Bin Path:** `/usr/local/sbin/bfd`\n\nCustom install paths via environment variables:\n\n```bash\nINSTALL_PATH=/opt/bfd BIN_PATH=/usr/sbin/bfd ./install.sh\n```\n\n### 2.1 Scheduling\n\n**Watch mode (recommended):**\n\nBFD's watch mode runs as a persistent daemon, polling for new log data every `WATCH_INTERVAL` seconds (default 10). Detection latency is ~10 seconds, comparable to fail2ban and other daemon-based tools.\n\nThe installer automatically enables and starts watch mode on fresh installs.\nOn upgrades, the existing scheduling choice is preserved — if watch mode was\nrunning it is restarted; if `bfd.timer` was enabled it is left active.\n\nTo manually manage watch mode:\n\n```bash\n# systemd (Rocky 8+, Debian 12, Ubuntu 20+)\nsystemctl enable --now bfd-watch.service\n\n# SysVinit (CentOS 6/7, Ubuntu 14.04)\nservice bfd-watch start\nchkconfig bfd-watch on    # enable at boot (RHEL)\n```\n\n**Cron (automatic fallback):**\n\nThe installer places a cronjob at `/etc/cron.d/bfd` that runs BFD every 2 minutes in quiet mode. This serves as a fallback — when watch mode is active, cron runs detect the lock and silently skip. If the watch daemon exits, cron automatically resumes detection within 2 minutes.\n\nThe cron entry does not need to be removed when using watch mode.\n\n### 2.2 Upgrading\n\nWhen upgrading from a previous BFD installation (including v1.5-2), `install.sh` automatically:\n\n- Backs up the existing installation to `/usr/local/bfd.bk.DDMMYYYY-EPOCH` (symlinked as `/usr/local/bfd.bk.last` for easy access)\n- Runs `importconf` to merge your configuration and preserve state\n\n**What importconf migrates automatically:**\n\n| Category | Details |\n|----------|---------|\n| User configuration | `conf.bfd` values merged onto new template |\n| Legacy variable names | `TRIG` → `PRESSURE_TRIP`, `TRIG_WINDOW` → `PRESSURE_HALF_LIFE`, `TRIG_GLOBAL` → `PRESSURE_TRIP_GLOBAL`, `BAN_DURATION` → `BAN_TTL`, `BAN_PERMANENT_*` → `BAN_ESCALATE_*` |\n| Per-rule overrides | `thresholds.conf` → `pressure.conf` conversion |\n| Firewall backend | Set to `\"custom\"` if pre-2.0.1 `BAN_COMMAND` detected |\n| Ban state | `bans.active`, `bans.history`, `pressure.dat` |\n| Log tracking state | tlog byte-offsets, journal cursors |\n| Alert templates | `alert/` partials (user-modified preserved), legacy `alert.bfd` |\n| Ignore lists | `ignore.hosts` |\n\n**Post-upgrade verification:**\n\n```bash\nbfd -c                    # health check and diagnostics\nbfd --status              # show current ban/event summary\n```\n\n**Rollback:**\n\nIf the upgrade causes issues, restore from backup:\n\n```bash\nbfd --flush-all           # clear any new bans\nrm -rf /usr/local/bfd\ncp -a /usr/local/bfd.bk.last /usr/local/bfd\n```\n\n---\n\n## 3. Configuration\n\nThe main configuration file is `/usr/local/bfd/conf.bfd`. Each option has a descriptive comment directly above it in the file. Review the file from top to bottom before your first run.\n\nUse `bfd -c` to validate your configuration without banning anything.\n\n### 3.1 Pressure Model (Detection)\n\nBFD uses exponential-decay pressure scoring: each failed login adds pressure weighted by service severity, and pressure decays over time via a half-life. A ban fires when accumulated pressure crosses a trip point. This naturally separates human mistakes from automated attacks — a few typos over several minutes decay away harmlessly, while rapid-fire failures from a bot accumulate faster than they can decay.\n\n```\npressure = SUM { weight * 2^(-(now - event_time) / half_life) }\n```\n\n**Example** (SSH rule: weight=3, trip=15, half-life=300s):\n- **Bot attack** — 7 failures in 1 second: pressure = 3×7 = 21.0 → exceeds 15 → **BAN**\n- **Human typos** — 5 failures over 4 minutes: earlier events decay, total ≈ 11.1 → below 15 → **NO BAN**\n\n\u003e **Quick reference:** `PRESSURE_TRIP / weight` = minimum rapid failures for a ban. Default: 20 / 3 (sshd) = 7 rapid failures. Spread-out failures need more attempts (older ones decay).\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `PRESSURE_TRIP` | `20` | Accumulated pressure needed to trigger a ban; per-rule overrides in rule files or `pressure.conf` |\n| `PRESSURE_HALF_LIFE` | `300` | Half-life in seconds (how fast pressure decays); shorter = more forgiving |\nPer-rule weights are configured in `pressure.conf` (centralized) or in individual rule files via `PRESSURE_WEIGHT`. Higher weight = faster pressure accumulation. Default tiers: 5 (control panels), 3 (SSH/VPN/database/critical), 2 (mail/FTP/web), 1 (noisy/generic).\n\n### 3.2 Email Alerts\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `EMAIL_ALERTS` | `0` | Send email alerts (0 = off, 1 = on) |\n| `EMAIL_ADDRESS` | `root` | Alert recipient(s), comma-separated |\n| `EMAIL_SUBJECT` | `Brute Force Warning for $HOSTNAME` | Subject line (auto-appends `(N bans)` when batched) |\n| `EMAIL_LOGLINES` | `5` | Number of log lines per host in alert body and events view |\n| `EMAIL_FORMAT` | `text` | Email body format: `text` (plain text), `html` (HTML only), `both` (multipart text+HTML) |\n| `EMAIL_DIGEST` | `cycle` | Digest mode: `cycle` (one email per run), `timed` (accumulate and send on interval) |\n| `EMAIL_DIGEST_INTERVAL` | `900` | Digest flush interval in seconds when `EMAIL_DIGEST=\"timed\"` (default 15 minutes) |\n| `EMAIL_REPUTATION_LINKS` | *(empty)* | IP reputation links in alerts, comma-separated: `abuseipdb`, `shodan`, `virustotal`, `ipinfo`, `greynoise` |\n\nAlerts are **batched**: multiple bans in one check cycle produce a single email per recipient instead of one email per ban. Each alert includes host, service, pressure score with threshold, ban type (temporary/permanent/escalated) with duration and expiry, country code, recidivism history, IP reputation links, a pressure visualization bar, and source log lines.\n\n**Digest modes**: In `cycle` mode (default), one email is sent at the end of each detection run. In `timed` mode, alerts accumulate across runs and are flushed when `EMAIL_DIGEST_INTERVAL` expires — reducing email volume on active servers. Watch mode checks the spool each iteration; cron mode checks each run. Accumulated alerts are force-flushed on shutdown and daily rotation.\n\n**Format options**: `text` sends plain text via `mail`. `html` sends HTML via `sendmail`. `both` sends a multipart MIME message with both text and HTML parts via `sendmail`, so the recipient's email client displays whichever it prefers. If `sendmail` is not available, `html` and `both` fall back to text-only via `mail`.\n\nEmail templates are customizable — see [section 3.10](#310-email-templates). Individual rules can suppress alerts by setting `SKIP_ALERT=\"1\"` in the rule file. Set `RULE_EMAIL=\"addr\"` in a rule file to route that rule's alerts to a different recipient.\n\n### 3.3 Banning\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `FIREWALL` | `auto` | Firewall backend; see [section 4](#4-firewall-integration) |\n| `BAN_TTL` | `600` | Ban duration in seconds (0 = permanent). Temporary bans auto-expire |\n| `BAN_COMMAND` | APF deny | Command when `FIREWALL=\"custom\"`. See [section 4](#4-firewall-integration) |\n| `UNBAN_COMMAND` | APF unban | Reverse command. Required for temporary ban auto-expiry |\n\nThe variables `$ATTACK_HOST`, `$MOD` (service name), and `$PORTS` (from rule file) are available in ban/unban commands.\n\n### 3.4 Repeat Offender Handling\n\nThese four settings form a pipeline: `BAN_ESCALATION` controls how ban duration grows, `BAN_ESCALATION_CAP` limits that growth, `BAN_ESCALATE_AFTER` flips to permanent once the count is reached, and `BAN_ESCALATE_WINDOW` is the lookback window for all of the above.\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `BAN_ESCALATION` | `none` | How duration grows: `none` (fixed), `linear` (10m, 20m, 30m...), `double` (10m, 20m, 40m, 80m...) |\n| `BAN_ESCALATION_CAP` | `86400` | Maximum escalated duration in seconds (0 = no cap) |\n| `BAN_ESCALATE_AFTER` | `5` | Temporary bans before flipping to permanent (0 = never) |\n| `BAN_ESCALATE_WINDOW` | `86400` | Lookback window in seconds for counting repeat offenses |\n\n**Examples** (with `BAN_TTL=\"600\"`):\n- **Fixed 10m bans, permanent after 5th:** `ESCALATION=none`, `ESCALATE_AFTER=5` → 10m, 10m, 10m, 10m, 10m → permanent\n- **Doubling bans, permanent after 5th:** `ESCALATION=double`, `ESCALATE_AFTER=5` → 10m, 20m, 40m, 80m, 160m → permanent\n- **Linear growth, capped, never permanent:** `ESCALATION=linear`, `CAP=3600`, `ESCALATE_AFTER=0` → 10m, 20m, 30m... 1h, 1h, 1h (always temporary)\n\n### 3.5 IPv6\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `BAN_COMMAND_V6` | *(empty)* | IPv6-specific ban command. When empty, `BAN_COMMAND` is used for both address families |\n| `UNBAN_COMMAND_V6` | *(empty)* | IPv6-specific unban command. When empty, `UNBAN_COMMAND` is used for both |\n\nLeave empty when using tools that handle both protocols natively (nft with `inet` family, APF, ip route). Set explicitly for tools that require separate IPv4/IPv6 commands (iptables/ip6tables). See [section 10](#10-ipv6-support).\n\n### 3.6 Log Paths\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `AUTH_LOG_PATH` | `/var/log/secure` | Auth log (auto-detected: `/var/log/auth.log` on Debian) |\n| `KERNEL_LOG_PATH` | `/var/log/messages` | Kernel/syslog (auto-detected: `/var/log/syslog` on Debian) |\n| `MAIL_LOG_PATH` | `/var/log/maillog` | Mail log (auto-detected: `/var/log/mail.log` on Debian) |\n| `BFD_LOG_PATH` | `/var/log/bfd/bfd.log` | BFD's own application log |\n\nLog paths are auto-detected based on the distribution. Override in `conf.bfd` if your system uses non-standard paths.\n\n### 3.7 Advanced\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `PRESSURE_TRIP_GLOBAL` | `0` | Cross-service aggregate pressure trip (0 = disabled) |\n| `SUBNET_TRIG` | `0` | Unique IPs from same subnet to trigger subnet ban (0 = disabled) |\n| `SUBNET_MASK` | `24` | IPv4 subnet mask for distributed detection |\n| `SUBNET_MASK_V6` | `48` | IPv6 subnet mask (must be multiple of 16) |\n| `WATCH_INTERVAL` | `10` | Watch mode polling interval in seconds |\n| `SCAN_MAX_LINES` | `50000` | Max lines per log during scan mode; 0 = unlimited |\n| `SCAN_TIMEOUT` | `120` | Journal timeout per rule during scan in seconds |\n| `OUTPUT_SYSLOG` | `1` | Log to syslog (0 = off, 1 = on) |\n| `LOG_IDLE_SUPPRESS` | `1` | Suppress idle (0-event) run-complete messages from syslog (0 = off, 1 = on) |\n| `LOG_FORMAT` | `classic` | Log format: `classic` (syslog-style) or `json` (JSONL) |\n| `LOG_LEVEL` | `1` | Min severity: 0=debug, 1=info, 2=warn, 3=error |\n| `APOOL_RETENTION_DAYS` | `365` | Max age in days for attack pool entries |\n| `APOOL_MAX_LINES` | `500000` | Max lines in attack pool file |\n\nAdditional variables (`LOG_SOURCE`, `LOCK_FILE_TIMEOUT`, `BAN_RETRY_COUNT`, `OUTPUT_SYSLOG_FILE`) have sensible defaults in `internals.conf` and can be overridden by adding them to `conf.bfd`.\n\n**JSON log format**: When `LOG_FORMAT=\"json\"`, each log line is a JSON object with fields: `ts` (ISO 8601), `host`, `app`, `pid` (integer), `level` (debug/info/warn/error/critical), `tag` (service name, extracted from `{tag}` prefix if present), `msg`. Example:\n\n```json\n{\"ts\":\"2026-02-27T14:30:00+0000\",\"host\":\"srv1\",\"app\":\"bfd\",\"pid\":1234,\"level\":\"info\",\"tag\":\"sshd\",\"msg\":\"192.0.2.1 exceeded login failures\"}\n```\n\n### 3.8 Country Weighting\n\nCountry weighting is active automatically when `pressure-country.conf` contains uncommented entries. No toggle is required — if the file has entries, they are applied; if all entries are commented out (the default), country weighting is off.\n\nThe country database maps IP addresses to 2-letter country codes. IPv4 ranges are stored in `ipcountry.dat` (integer-range format) and IPv6 ranges in `ipcountry6.dat` (hex-range format). Both are downloaded automatically at install time (background) and refreshed via `cron.daily` when data is older than 30 days. Manual updates can be run with `update-ipcountry.sh`, which downloads per-country CIDR zones from ipverse.net (ipdeny.com fallback) and converts them to BFD's lookup format. Both IPv4 and IPv6 addresses are resolved to country codes.\n\nThe multiplier file (`pressure-country.conf`) uses `CC=N` format where N is weight×10 (e.g., `CN=20` means 2.0× weight, `US=10` means 1.0× = no change). Unlisted countries default to 1.0×.\n\n### 3.9 SMTP Relay\n\nBy default, BFD sends alerts through the local MTA (`mail` or `sendmail`). For servers without a local MTA or for routing through an external mail service, BFD supports authenticated SMTP relay via `curl`.\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `SMTP_RELAY` | *(empty)* | SMTP relay server URL (empty = use local MTA) |\n| `SMTP_FROM` | *(empty)* | Sender address (required when `SMTP_RELAY` is set) |\n| `SMTP_USER` | *(empty)* | SMTP authentication username |\n| `SMTP_PASS` | *(empty)* | SMTP authentication password |\n\n**URL formats:**\n\n| Format | Example | Description |\n|--------|---------|-------------|\n| `smtps://host:465` | `smtps://smtp.gmail.com:465` | Implicit TLS (Gmail, etc.) |\n| `smtp://host:587` | `smtp://relay.example.com:587` | STARTTLS (most relay services) |\n| `smtp://host:25` | `smtp://relay.internal:25` | Plain (internal relays) |\n\n**Example — Gmail SMTP relay:**\n\n```bash\nSMTP_RELAY=\"smtps://smtp.gmail.com:465\"\nSMTP_FROM=\"alerts@example.com\"\nSMTP_USER=\"alerts@example.com\"\nSMTP_PASS=\"app-password-here\"\n```\n\n`curl` is present on all target distributions and handles TLS/authentication natively. Credentials are stored in `conf.bfd` (permissions 640, root-owned). Use `bfd -c` to validate relay configuration.\n\n### 3.10 Email Templates\n\nAlert emails are rendered from customizable template partials in the `alert/` directory under the BFD install path (`/usr/local/bfd/alert/`). Templates use `{{VAR}}` mustache-style placeholders replaced at render time via a safe awk-based engine — no shell code execution, so templates cannot introduce security risks.\n\n**Template files:**\n\n| File | Description |\n|------|-------------|\n| `text.header.tpl` | Text: banner, hostname, timestamp |\n| `text.entry.tpl` | Text: per-ban detail block |\n| `text.summary.tpl` | Text: digest summary statistics |\n| `text.footer.tpl` | Text: version, project link |\n| `html.header.tpl` | HTML: banner and hostname bar |\n| `html.entry.tpl` | HTML: per-ban card with pressure detail |\n| `html.summary.tpl` | HTML: aggregate statistics table |\n| `html.footer.tpl` | HTML: footer and closing tags |\n\nThe entry template is rendered once per ban. The summary template is included only when multiple bans are batched in one email. Header and footer wrap the entire message.\n\nTo customize, copy the desired partial(s) into `alert/custom.d/` and edit the copies. BFD checks `custom.d/` first for each partial and falls back to the shipped default. Templates in `custom.d/` persist across upgrades automatically.\n\n**Key template variables:**\n\n| Variable | Example | Description |\n|----------|---------|-------------|\n| `{{HOSTNAME}}` | `web01.example.com` | System hostname |\n| `{{HOST}}` | `192.0.2.1` | Banned IP address |\n| `{{SERVICE}}` | `sshd` | Service name |\n| `{{PRESSURE}}` | `21.4` | Pressure score |\n| `{{FAIL_COUNT_DISPLAY}}` | `7` | Failed login attempts detected this cycle |\n| `{{BAN_TYPE}}` | `Temporary` | Ban type (Temporary/Permanent/Escalated) |\n| `{{BAN_DURATION_DETAIL}}` | `10m` | Human-readable duration |\n| `{{COUNTRY_FLAG}}` | `CN` | 2-letter country code |\n| `{{COUNTRY_DISPLAY}}` | `China (CN)` | Country name with code (degrades to bare code) |\n| `{{SOURCE_LOGS_SECTION_TEXT}}` | *(log lines)* | Sanitized source log excerpt |\n| `{{REPUTATION_SECTION_TEXT}}` | `AbuseIPDB: https://...` | Text-format reputation links |\n\nSee the shipped template files for the complete variable reference.\n\nMessaging channels (Slack, Telegram, Discord) have their own template partials:\n\n| File | Description |\n|------|-------------|\n| `slack.message.tpl` | Slack Block Kit JSON wrapper |\n| `slack.entry.tpl` | Slack per-ban section |\n| `telegram.message.tpl` | Telegram MarkdownV2 wrapper |\n| `telegram.entry.tpl` | Telegram per-ban block |\n| `discord.message.tpl` | Discord embed JSON wrapper |\n| `discord.entry.tpl` | Discord per-ban embed field |\n\nPeriodic reports (see [section 8](#8-periodic-reports)) use their own template partials:\n\n| File | Description |\n|------|-------------|\n| `report.html.header.tpl` | HTML report header and styles |\n| `report.html.body.tpl` | HTML report body and tables |\n| `report.text.header.tpl` | Plain text report header |\n| `report.text.body.tpl` | Plain text report body |\n| `report.slack.message.tpl` | Slack report summary |\n| `report.telegram.message.tpl` | Telegram report summary |\n| `report.discord.message.tpl` | Discord report summary |\n\n### 3.11 Slack Alerts\n\nBFD can send alert notifications to Slack channels via incoming webhooks or the Bot API.\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `SLACK_ALERTS` | `0` | Enable Slack alerts (`1` = enabled) |\n| `SLACK_MODE` | `webhook` | Delivery mode: `webhook` or `bot` |\n| `SLACK_WEBHOOK_URL` | *(empty)* | Incoming webhook URL (webhook mode) |\n| `SLACK_TOKEN` | *(empty)* | Bot API token, `xoxb-...` (bot mode) |\n| `SLACK_CHANNEL` | *(empty)* | Channel ID or `#name` (bot mode) |\n\n**Webhook mode** is simpler — create a webhook at [Slack Incoming Webhooks](https://api.slack.com/messaging/webhooks) and paste the URL. **Bot mode** supports file uploads and uses the [Slack Web API](https://api.slack.com/methods/chat.postMessage).\n\nRequires `curl` in PATH. Test with `bfd --test-alert slack`.\n\n### 3.12 Telegram Alerts\n\nBFD can send alert notifications via the Telegram Bot API.\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `TELEGRAM_ALERTS` | `0` | Enable Telegram alerts (`1` = enabled) |\n| `TELEGRAM_BOT_TOKEN` | *(empty)* | Bot token from [@BotFather](https://t.me/BotFather) |\n| `TELEGRAM_CHAT_ID` | *(empty)* | Chat, group, or channel ID |\n\nUse `@userinfobot` or the `getUpdates` API endpoint to find your chat ID. Messages are sent using MarkdownV2 formatting.\n\nRequires `curl` in PATH. Test with `bfd --test-alert telegram`.\n\n### 3.13 Discord Alerts\n\nBFD can send alert notifications to Discord channels via webhooks.\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `DISCORD_ALERTS` | `0` | Enable Discord alerts (`1` = enabled) |\n| `DISCORD_WEBHOOK_URL` | *(empty)* | Webhook URL from Server Settings \u003e Integrations |\n\nAlerts are rendered as Discord embeds with per-ban fields and a summary footer.\n\nRequires `curl` in PATH. Test with `bfd --test-alert discord`.\n\n---\n\n## 4. Firewall Integration\n\nBFD supports automatic firewall detection via `FIREWALL=\"auto\"` (default). When set to auto, BFD probes for installed firewalls in priority order: APF \u003e CSF \u003e firewalld \u003e UFW \u003e nftables \u003e iptables \u003e ip route.\n\n| `FIREWALL` Value | Description |\n|------------------|-------------|\n| `auto` | Auto-detect (default) |\n| `apf` | Advanced Policy Firewall |\n| `csf` | ConfigServer Security \u0026 Firewall |\n| `firewalld` | firewalld rich rules |\n| `ufw` | Uncomplicated Firewall |\n| `nftables` | nftables sets (inet bfd table) |\n| `iptables` | iptables/ip6tables chains |\n| `route` | ip route blackhole |\n| `custom` | User-defined BAN_COMMAND/UNBAN_COMMAND |\n\nWhen `FIREWALL=\"auto\"` or a named backend is configured, BFD handles ban/unban natively — `BAN_COMMAND` and `UNBAN_COMMAND` are only used with `FIREWALL=\"custom\"`.\n\nThe following examples are for `FIREWALL=\"custom\"` mode. The variable `$ATTACK_HOST` is replaced with the offending IP address at ban time. For temporary bans, also set `UNBAN_COMMAND` to the reverse operation.\n\n**APF:**\n```bash\nBAN_COMMAND=\"/etc/apf/apf -d $ATTACK_HOST {bfd.$MOD}\"\nUNBAN_COMMAND=\"/etc/apf/apf -u $ATTACK_HOST\"\n```\n\n**iptables (CentOS 6/7, Ubuntu 14-20):**\n```bash\nBAN_COMMAND=\"/sbin/iptables -I INPUT -s $ATTACK_HOST -j DROP\"\nUNBAN_COMMAND=\"/sbin/iptables -D INPUT -s $ATTACK_HOST -j DROP\"\n```\n\n**firewalld (Rocky 8+, CentOS 7):**\n```bash\nBAN_COMMAND=\"/usr/bin/firewall-cmd --add-rich-rule='rule family=ipv4 source address=$ATTACK_HOST drop'\"\nUNBAN_COMMAND=\"/usr/bin/firewall-cmd --remove-rich-rule='rule family=ipv4 source address=$ATTACK_HOST drop'\"\n```\n\n**nftables (Rocky 9+, Debian 12, Ubuntu 22+):**\n```bash\nBAN_COMMAND=\"/usr/sbin/nft add rule inet filter input ip saddr $ATTACK_HOST drop\"\nUNBAN_COMMAND=\"/usr/sbin/nft delete rule inet filter input handle $(/usr/sbin/nft -a list chain inet filter input | grep $ATTACK_HOST | awk '{print $NF}')\"\n```\n\n**ip route null-route (all distros):**\n```bash\nBAN_COMMAND=\"/sbin/ip route add blackhole $ATTACK_HOST/32\"\nUNBAN_COMMAND=\"/sbin/ip route del blackhole $ATTACK_HOST/32\"\n```\n\n**Port-specific blocking (uses `$PORTS` from rule files):**\n```bash\nBAN_COMMAND=\"/sbin/iptables -I INPUT -s $ATTACK_HOST -p tcp -m multiport --dports $PORTS -j DROP\"\nUNBAN_COMMAND=\"/sbin/iptables -D INPUT -s $ATTACK_HOST -p tcp -m multiport --dports $PORTS -j DROP\"\n```\n\n**IPv6 firewall commands** — set `BAN_COMMAND_V6` if your firewall needs separate commands for IPv6 (leave empty for tools that handle both):\n```bash\nBAN_COMMAND_V6=\"/sbin/ip6tables -I INPUT -s $ATTACK_HOST -j DROP\"\nUNBAN_COMMAND_V6=\"/sbin/ip6tables -D INPUT -s $ATTACK_HOST -j DROP\"\n```\n\n---\n\n## 5. General Usage\n\nThe `/usr/local/sbin/bfd` command provides the following options:\n\n```\nusage: bfd [OPTION]\n\nRun Modes:\n  -s, --standard              run detection cycle with output\n  -q, --quiet                 run detection cycle silently\n  -d, --dryrun                run detection without banning\n  -w, --watch                 run continuous watch mode (foreground)\n\nBan Management:\n  -b, --ban IP [SERVICE]      manually ban an IP (permanent)\n  -u, --unban IP              unban an IP address\n  --flush-temp                remove all temporary bans\n  --flush-all                 remove all bans\n\nReporting:                                      Supports: --json --csv\n  -l, --list                  list active bans\n  -a, --activity [IP|STR]     threat activity and IP investigation\n  -e, --events [IP|CIDR] [N]  event history, IP or subnet detail (N=log lines)\n\nSystem:\n  -S, --status [SERVICE]      operational status overview\n  -C, --config [VAR]          show active configuration\n  -R, --rules [RULE]          list rules or show rule detail (--active)\n  -c, --check                 health check diagnostics\n\nTesting:\n  -T, --test RULE [FILE|-]    test rule patterns against log or stdin\n  --test-pattern PAT [FILE|-] test raw \u003cHOST\u003e pattern against log or stdin\n  --test-alert TYPE           send test alert (email,slack,telegram,discord)\n\nScan Mode:\n  --scan [RULE] [-d]          full-log scan (all rules or specific rule)\n  --max-lines=N               max lines per log during scan (default 50000)\n  --scan-timeout=N            journal timeout per rule during scan (default 120)\n\nOutput Modifiers:\n  --json                      JSON output (with -l, -e, -a)\n  --csv                       CSV output (with -l, -e, -a)\n  --sort=MODE                 sort events: count (default), time, ip\n  --limit=N                   max IPs to display (default 100, 0=all)\n  --24h                       events from last 24 hours (default)\n  --7d                        events from last 7 days\n  --30d                       events from last 30 days\n  --active                    show only active rules (with -R)\n  -V, --verbose               detailed output (with -s, -d, -c, -S, --scan)\n\nGeneral:\n  -v, --version               display version\n  -h, --help                  display this help (see bfd.1 for full docs)\n```\n\nThe **`-s|--standard`** and **`-q|--quiet`** options run the full detection and banning cycle. Standard mode prints output; quiet mode suppresses it (used by cron). Both parse logs, compute pressure against trip points, and execute bans.\n\n### 5.1 Dry Run\n\nThe **`-d|--dryrun`** option runs full detection but logs \"would ban\" instead of executing the ban command. Use this to test rules safely, validate your configuration, and see what BFD would do without affecting production.\n\n```bash\nbfd -d\n```\n\n### 5.2 Health Check\n\nThe **`-c|--check`** option performs a non-destructive diagnostic check of your entire BFD installation:\n\n- Validates configuration (required variables, sane values)\n- Checks log file paths exist and are readable\n- Verifies ban command binary exists and is executable\n- Warns if `UNBAN_COMMAND` is empty when `BAN_TTL \u003e 0`\n- Checks `BAN_COMMAND_V6` binary if configured\n- Scans all rules: reports active vs inactive, pressure weight/trip, ports, log paths\n- Displays pressure model summary (half-life, trip, global trip)\n- Verifies tlog (log tracking script) is executable\n- Checks state directories exist with correct permissions\n- Reports lock file status\n- Counts active bans\n\n```bash\nbfd -c\n```\n\nOutput uses `[PASS]`, `[WARN]`, `[FAIL]`, and `[SKIP]` (inactive rules) indicators with a final summary.\n\n### 5.3 Threat Activity\n\nThe **`-a|--activity`** option displays a threat activity report with aggregate summary, top threat IPs, and per-service breakdown:\n\n```bash\nbfd -a           # show threat activity report\nbfd --activity   # same as above\nbfd -a 192.0.2   # search for a specific string\n```\n\nThe report includes:\n- **Threat Activity Summary** — unique IPs and total count for 24h and 7d windows, plus active bans\n- **Top 25 threat IPs (24h)** — event count, IP, pressure, country, first/last seen, services, ban status (active bans show `BANNED(perm)` or `BANNED(Xm)`, previous bans show `prev:N`)\n- **Top 25 threat IPs (7d)** — same format, 7-day window\n- **Per-service threat breakdown (24h / 7d)** — dual-interval count, unique IPs, and top country per service\n\n\u003e **`-a` vs `-e`:** Both commands read from the same attack pool data store. `-a` provides a summary-oriented threat activity overview with dual-interval (24h/7d) views and per-service breakdown. `-e` provides event-level detail with configurable time windows (`--24h`, `--7d`, `--30d`) and sort modes (`--sort=count|time|ip`). Use `-a` to review aggregate threat patterns and `-e` to drill into specific IPs or subnets.\n\n### 5.4 Watch Mode\n\nThe **`-w|--watch`** option runs BFD as a continuous daemon, polling for new log data every `WATCH_INTERVAL` seconds (default 10). This is the **recommended operating mode** — detection latency is ~10 seconds, comparable to fail2ban and other daemon-based tools.\n\n```bash\nbfd --watch\n```\n\nWatch mode holds a lock for its entire lifetime, so cron-based runs (`bfd -q`) will silently skip when watch mode is active. If the watch daemon exits unexpectedly (OOM, crash), cron automatically detects the dead PID and resumes detection within one cycle (~2 minutes). No cron modification is needed.\n\n**Signal handling:**\n\n| Signal | Action |\n|--------|--------|\n| `SIGTERM` / `SIGINT` | Clean shutdown (removes lock file) |\n| `SIGHUP` | Reload `conf.bfd`, `internals.conf`, and `pressure.conf` without restart (allows changing `WATCH_INTERVAL`, `PRESSURE_TRIP`, ban commands, etc.) |\n\n**Service management:**\n\n```bash\n# systemd (Rocky 8+, Debian 12, Ubuntu 20+)\nsystemctl enable --now bfd-watch.service\nsystemctl reload bfd-watch     # send SIGHUP\nsystemctl status bfd-watch\n```\n\nThis conflicts with `bfd.timer` — systemd prevents enabling both simultaneously.\n\n```bash\n# SysVinit (CentOS 6/7, Ubuntu 14.04)\nservice bfd-watch start\nservice bfd-watch reload        # send SIGHUP\nservice bfd-watch status\nchkconfig bfd-watch on          # enable at boot (RHEL)\n```\n\n**Configuration:**\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `WATCH_INTERVAL` | `10` | Polling interval in seconds for watch mode |\n\n### 5.5 Flush Bans\n\nRemove multiple bans at once:\n\n```bash\nbfd --flush-temp    # remove all temporary bans (keep permanent)\nbfd --flush-all     # remove all bans (temporary + permanent)\n```\n\nFlushed bans are recorded in the ban history.\n\n### 5.6 Structured Output\n\nUse `--json` or `--csv` with `-l`, `-e`, or `-a` for machine-readable output:\n\n```bash\nbfd -l --json          # active bans as JSON\nbfd -l --csv           # active bans as CSV\nbfd -e --json          # active events as JSON\nbfd -e 192.0.2.1 --csv # per-IP events as CSV\nbfd -a --json          # threat activity as JSON\nbfd -a 192.0.2.1 --csv # IP report as CSV\n```\n\n**JSON** outputs arrays of objects (or nested objects for IP detail and CIDR):\n```json\n[{\"ip\": \"...\", \"pressure\": 18.4, \"pressure_trip\": 20, \"events\": 5, \"services\": [\"sshd\"], ...}]\n```\n\n**CSV** outputs with a header row:\n```\nip,pressure,pressure_trip,events,services,first_seen,last_seen,status\n```\n\nTimestamps in structured output use ISO 8601 format (`YYYY-MM-DDTHH:MM:SS`).\nPressure values are unquoted numbers in JSON.\n\n### 5.7 Scan Mode\n\nScan mode processes the **full current log file** through the detection pipeline, bypassing the incremental tlog reader. This is useful for:\n\n- **First install:** Catch existing attackers immediately instead of waiting for new log events.\n- **Rule changes:** Retroactively apply new rules to existing log data.\n- **Recovery:** Process missed events after a detection gap (daemon down, cron disabled).\n- **Forensic review:** Combine with `-d` (dry-run) to see what would be detected without banning.\n\n```bash\nbfd --scan              # scan all active rules against full logs\nbfd --scan sshd         # scan a specific rule only\nbfd --scan -d           # dry-run: detect without banning or advancing cursors\nbfd --scan --max-lines=100000   # override line limit\nbfd --scan --scan-timeout=60    # override journal timeout\n```\n\nAfter a non-dry-run scan, tlog cursors are advanced to the current log position so the next normal run starts fresh. In dry-run mode, cursors are not modified.\n\n**Safety limits:** `SCAN_MAX_LINES` (default 50000) bounds the number of lines processed per log file. `SCAN_TIMEOUT` (default 120s) limits journal reads. Set `--max-lines=0` for unlimited (use with caution on large logs). Both can be configured in `conf.bfd` or overridden on the command line.\n\n**Lock behavior:** Scan acquires the same global lock as normal runs. If watch mode is running, stop it first (`systemctl stop bfd-watch`), run the scan, then restart.\n\n### 5.8 Events and Investigation\n\nThe `--events` command provides event history and IP investigation, reading from the attack pool which records all detected auth failures (both ban-triggering and sub-trip observations) with configurable retention (default: 365 days):\n\n```bash\nbfd --events                  # event list — top 100 IPs, last 24h\nbfd --events --7d --sort=time # last 7 days, newest first\nbfd --events --limit=0 --30d  # all IPs from last 30 days\nbfd --events 192.0.2.1        # IP investigation — history + pressure + logs\nbfd --events 192.0.2.0/24     # CIDR report — subnet-scoped view\n```\n\n**Event list** (no argument) shows IPs with failure counts, services, country, first/last seen, and ban status. Default: top 100 IPs sorted by count descending, 24-hour window. Use `--limit=N` to change the output cap (`0` for unlimited), `--sort=time` or `--sort=ip` to change ordering, and `--7d` or `--30d` to expand the time window.\n\n**IP investigation** shows a comprehensive report: historical failure counts from the attack pool (total and per-service breakdown), live pressure detail if the IP has active pressure, and a log sample from the triggering service.\n\n**CIDR mode** filters to a subnet (IPv4, mask 8-32) and includes a summary line with match count, total events, and banned count.\n\nAll three modes support `--json` and `--csv`:\n\n```bash\nbfd --events --json           # event list as JSON array\nbfd --events 192.0.2.1 --json # IP detail as single JSON object\nbfd --events 10.0.0.0/8 --csv # CIDR as CSV\n```\n\n---\n\n## 6. Rule Engine\n\nRules are located under `/usr/local/bfd/rules/`. Each rule is a shell fragment that declares the service name, required binary, log path, and a regex pattern for matching authentication failures.\n\nEach rule auto-enables based on the existence of a specific application binary (`PREREQ`). For example, if `/usr/sbin/sshd` exists, the sshd rule is active. No manual activation is needed — install the application and BFD will detect it.\n\nUse `bfd -c` to see which rules are active on your system.\n\n### 6.1 Rule Catalog\n\nBFD ships with 57 rules:\n\n| Category | Rules |\n|----------|-------|\n| **SSH** | sshd, dropbear |\n| **Mail** | dovecot, courier, postfix, postscreen, sendmail, exim_authfail, exim_nxuser, vpopmail, cyrus-imap, sogo |\n| **FTP** | vsftpd, vsftpd2, proftpd, pure-ftpd |\n| **Web** | apache-auth, nginx-http-auth, mod_sec, wordpress, roundcube, http_401, lighttpd, phpmyadmin, gitea, nextcloud, vaultwarden, drupal, jellyfin |\n| **Panel** | cpanel, plesk, webmin, directadmin, interworx, cockpit, gitlab, grafana, proxmox, guacamole |\n| **Auth** | pam_generic, xrdp |\n| **Database** | mysqld-auth, postgresql, mongodb |\n| **DNS** | named, powerdns |\n| **VPN** | openvpnas, openvpn |\n| **VoIP** | asterisk_badauth, asterisk_iax, asterisk_nopeer, freeswitch |\n| **Proxy** | haproxy, squid |\n| **Messaging** | ejabberd |\n| **Legacy** | rh_imapd, rh_ipop3d |\n\n### 6.2 Rule Customization\n\nEach rule file supports the following variables:\n\n| Variable | Description |\n|----------|-------------|\n| `PREREQ` | Path to required binary or file. Rule is active only if this file exists |\n| `LOG_FILE` | Log file path to monitor (uses config variables like `$AUTH_LOG_PATH`) |\n| `MATCHED_HOSTS` | Extracted IP list — tlog + extract_hosts pipeline using `\u003cHOST\u003e` patterns |\n| `PRESSURE_WEIGHT` | Per-service pressure weight (overrides `pressure.conf`; higher = faster accumulation) |\n| `PRESSURE_TRIP` | Per-service trip point (overrides `pressure.conf` and global `PRESSURE_TRIP`) |\n| `PORTS` | Service ports for port-specific blocking (e.g., `\"22\"` for sshd) |\n| `SKIP_ALERT` | Set to `\"1\"` to suppress email alerts for this service |\n| `RULE_EMAIL` | Override `EMAIL_ADDRESS` for this rule's alerts (per-rule routing) |\n| `LOG_TAG` | Tracking tag for tlog state file naming and journal filter dispatch (e.g., `\"sshd\"`, `\"dovecot\"`) |\n| `IGNOREREGEX` | Lines matching this ERE pattern are excluded before IP extraction (fail2ban-compatible) |\n\nTo customize a rule's pressure weight and trip point:\n```bash\n# In /usr/local/bfd/rules/sshd — make SSH failures count triple\nPRESSURE_WEIGHT=\"3\"\nPRESSURE_TRIP=\"15\"\n```\n\nCentralized per-rule overrides (without editing rule files) go in `/usr/local/bfd/pressure.conf`.\n\n---\n\n## 7. Ignore Lists\n\nBFD provides two mechanisms for excluding addresses from bans:\n\n- **`/usr/local/bfd/ignore.hosts`** — IPs to never ban, one per line. Supports IPv4 and IPv6 addresses.\n- **`/usr/local/bfd/exclude.files`** — additional files containing IPs to ignore. One file path per line; each referenced file contains IPs to exclude.\n\nBFD automatically detects local IPv4 and IPv6 addresses (including `::1`) and excludes them from bans. No manual configuration is needed for local address exclusion.\n\n---\n\n## 8. Periodic Reports\n\nBFD can generate scheduled threat reports delivered via all configured alerting channels (email, Slack, Telegram, Discord):\n\n```bash\nbfd --report              # daily report (default)\nbfd --report weekly       # last 7 days\nbfd --report monthly      # last 30 days\n```\n\nReports include:\n- **Threat summary:** unique IPs, total events, bans, active bans\n- **Top threat IPs** with country codes, live pressure, and ban status\n- **Per-service breakdown** with event counts and top country\n- **Trend comparison** vs the prior equivalent time window\n\n**Scheduled delivery** is configured in `conf.bfd`:\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `REPORT_ENABLED` | `0` | Enable periodic reports (0 = off, 1 = on) |\n| `REPORT_INTERVALS` | `\"daily\"` | Comma-separated intervals: `daily`, `weekly`, `monthly` |\n| `REPORT_CHANNELS` | *(empty)* | Delivery channels (empty = all enabled alert channels) |\n| `REPORT_EMAIL_ADDRESS` | *(empty)* | Report email recipient (empty = `EMAIL_ADDRESS`) |\n| `REPORT_EMAIL_SUBJECT` | *(template)* | Email subject (`{{INTERVAL}}` and `{{HOSTNAME}}` expand at send time) |\n| `REPORT_TOP_N` | `25` | Maximum IPs in report tables |\n\nWhen `REPORT_ENABLED=1`, `cron.daily` triggers daily reports every day, weekly reports on Mondays, and monthly reports on the 1st. Reports can also be generated on-demand from the CLI at any time.\n\n---\n\n## 9. Ban Management\n\nBans can be temporary (auto-expire after `BAN_TTL` seconds) or permanent (`BAN_TTL=0`). Temporary bans require `UNBAN_COMMAND` to be set for the firewall rule to be removed automatically on expiry.\n\nRepeat offenders are escalated to permanent bans after `BAN_ESCALATE_AFTER` temporary bans within `BAN_ESCALATE_WINDOW` seconds.\n\n**CLI commands:**\n```bash\nbfd -l                   # list all active bans (service, ports, expiry)\nbfd -u 192.0.2.1          # unban an IP address\nbfd -b 192.0.2.1          # manually ban an IP permanently\nbfd -b 192.0.2.1 sshd     # manually ban with a service label\n```\n\n**State files** in `/usr/local/bfd/tmp/`:\n\n| File | Description |\n|------|-------------|\n| `bans.active` | Currently active bans (timestamp, expiry, IP, service, ports) |\n| `bans.history` | Append-only log of all ban/unban events |\n| `pressure.dat` | Pressure scoring workspace (timestamp, IP, service, weight); pruned every ~10 half-lives |\n\n**State files** in `/usr/local/bfd/stats/`:\n\n| File | Description |\n|------|-------------|\n| `attack.pool` | Unified event store — all detected auth failures (ban, escalate, observed) and outcomes |\n\nBoth `bfd -a` (threat activity) and `bfd -e` (events) read from the attack pool. Sub-trip observations (ACTION=observed) are recorded alongside ban decisions so event history persists across pressure decay cycles. Each IP shows whether it is currently banned, its ban type (permanent or time remaining), and historical ban count.\n\n---\n\n## 10. IPv6 Support\n\nBFD detects and bans both IPv4 and IPv6 addresses automatically. Rules do not need modification — the extraction engine handles both address families. IPv6 addresses are normalized before counting and comparison.\n\nFor firewalls that handle both protocols natively (nft with `inet` family, APF, ip route), leave `BAN_COMMAND_V6` empty — `BAN_COMMAND` is used for all addresses.\n\nFor firewalls that require separate commands (iptables/ip6tables), set `BAN_COMMAND_V6` and `UNBAN_COMMAND_V6`:\n\n```bash\nBAN_COMMAND_V6=\"/sbin/ip6tables -I INPUT -s $ATTACK_HOST -j DROP\"\nUNBAN_COMMAND_V6=\"/sbin/ip6tables -D INPUT -s $ATTACK_HOST -j DROP\"\n```\n\nLocal IPv6 addresses (including `::1` and all link-local addresses) are auto-detected and excluded from bans.\n\n---\n\n## 11. Troubleshooting\n\nRun `bfd -c` first — it validates config, log paths, firewall binaries, rule status, state directories, and active bans in a single non-destructive check.\n\n| Symptom | Cause \u0026 Fix |\n|---------|-------------|\n| \"locked subsystem, already running?\" | A previous BFD run is still active or was killed. The lock auto-clears after 300 seconds |\n| \"BAN_COMMAND binary not found\" | The configured firewall tool is not installed. Update `BAN_COMMAND` in `conf.bfd` |\n| Rules not triggering | Check that the log path exists and the application is writing to the expected file. Use `bfd -d` to test detection without banning. Use `bfd -c` to see which rules are active |\n| Wrong log paths on Debian/Ubuntu | Log paths are auto-detected but can be overridden in `conf.bfd`: `AUTH_LOG_PATH`, `MAIL_LOG_PATH`, `KERNEL_LOG_PATH` |\n| \"UNBAN_COMMAND is empty\" | Set `UNBAN_COMMAND` in `conf.bfd` for temporary bans to auto-remove firewall rules on expiry |\n| IPv6 addresses not detected | IPv6 support is automatic. If using ip6tables, set `BAN_COMMAND_V6` in `conf.bfd` |\n\n---\n\n## 12. License\n\nBFD is developed and supported on a volunteer basis by Ryan MacDonald [ryan@rfxn.com].\n\nBFD (Brute Force Detection) is distributed under the GNU General Public License (GPL) without restrictions on usage or redistribution. The BFD copyright statement, and GNU GPL, \"COPYING.GPL\" are included in the top-level directory of the distribution. Credit must be given for derivative works as required under GNU GPL.\n\n---\n\n## 13. Support\n\nThe BFD source repository is at: https://github.com/rfxn/brute-force-detection\n\nBugs, feature requests, and general questions can be filed as GitHub issues or sent to proj@rfxn.com. When reporting issues, include the output of `bfd -c` to help diagnose configuration problems.\n\nThe official project page is at: https://www.rfxn.com/projects/brute-force-detection/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frfxn%2Fbrute-force-detection","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frfxn%2Fbrute-force-detection","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frfxn%2Fbrute-force-detection/lists"}