From 537dc8b487d4b1ed3521e3dd51b605cfd3415a49 Mon Sep 17 00:00:00 2001 From: CN_SZTL Date: Thu, 23 Jul 2020 17:33:32 +0800 Subject: [PATCH] luci-app-beardropper: add package --- CONTRIBUTED.md | 3 +- package/ctcgfw/luci-app-beardropper/Makefile | 22 + .../luasrc/controller/beardropper.lua | 19 + .../luasrc/model/cbi/beardropper/log.lua | 17 + .../luasrc/model/cbi/beardropper/setting.lua | 54 ++ .../luasrc/view/beardropper/status.htm | 22 + .../po/zh-cn/beardropper.po | 115 ++++ .../po/zh-tw/beardropper.po | 115 ++++ .../root/etc/config/beardropper | 18 + .../root/etc/init.d/beardropper | 46 ++ .../root/etc/uci-defaults/luci-beardropper | 12 + .../root/usr/sbin/beardropper | 517 ++++++++++++++++++ 12 files changed, 959 insertions(+), 1 deletion(-) create mode 100644 package/ctcgfw/luci-app-beardropper/Makefile create mode 100755 package/ctcgfw/luci-app-beardropper/luasrc/controller/beardropper.lua create mode 100644 package/ctcgfw/luci-app-beardropper/luasrc/model/cbi/beardropper/log.lua create mode 100755 package/ctcgfw/luci-app-beardropper/luasrc/model/cbi/beardropper/setting.lua create mode 100755 package/ctcgfw/luci-app-beardropper/luasrc/view/beardropper/status.htm create mode 100755 package/ctcgfw/luci-app-beardropper/po/zh-cn/beardropper.po create mode 100644 package/ctcgfw/luci-app-beardropper/po/zh-tw/beardropper.po create mode 100644 package/ctcgfw/luci-app-beardropper/root/etc/config/beardropper create mode 100755 package/ctcgfw/luci-app-beardropper/root/etc/init.d/beardropper create mode 100755 package/ctcgfw/luci-app-beardropper/root/etc/uci-defaults/luci-beardropper create mode 100755 package/ctcgfw/luci-app-beardropper/root/usr/sbin/beardropper diff --git a/CONTRIBUTED.md b/CONTRIBUTED.md index 0653abdfd8..51940fab3c 100644 --- a/CONTRIBUTED.md +++ b/CONTRIBUTED.md @@ -54,7 +54,8 @@ luci-theme-argon-mod source: [Leo-Jo-My/luci-theme-argon-mod](https://github.com node-request source: [jerrykuku/node-request](https://github.com/jerrykuku/node-request).
luci-app-jd-dailybonus source: [jerrykuku/luci-app-jd-dailybonus](https://github.com/jerrykuku/luci-app-jd-dailybonus).
luci-app-oled source: [NateLol/luci-app-oled](https://github.com/NateLol/luci-app-oled).
-luci-theme-edge source: [garypang13/luci-theme-edge](https://github.com//garypang13/luci-theme-edge). +luci-theme-edge source: [garypang13/luci-theme-edge](https://github.com//garypang13/luci-theme-edge).
+luci-app-beardropper source: [NateLol/natelol](https://github.com/NateLol/natelol). ## License ### Depend on their own License. diff --git a/package/ctcgfw/luci-app-beardropper/Makefile b/package/ctcgfw/luci-app-beardropper/Makefile new file mode 100644 index 0000000000..bbdbdcc0e3 --- /dev/null +++ b/package/ctcgfw/luci-app-beardropper/Makefile @@ -0,0 +1,22 @@ +# +# Copyright (C) 2020 Nate Ding +# +# This is free software, licensed under the GNU General Public License v3. +# See /LICENSE for more information. +# + +include $(TOPDIR)/rules.mk + +LUCI_Title:=LuCI Support for BearDropper +LUCI_PKGARCH=all + +PKG_VERSION:=1.1 +PKG_RELEASE:=20200522 + +PKG_MAINTANINER:=Nate Ding +PKG_LICENSE:=GLPv3 +PKG_LICENSE_FILES:=LICENSE + +include $(TOPDIR)/feeds/luci/luci.mk + +# call BuildPackage - OpenWrt buildroot signature diff --git a/package/ctcgfw/luci-app-beardropper/luasrc/controller/beardropper.lua b/package/ctcgfw/luci-app-beardropper/luasrc/controller/beardropper.lua new file mode 100755 index 0000000000..2c31a2ffc5 --- /dev/null +++ b/package/ctcgfw/luci-app-beardropper/luasrc/controller/beardropper.lua @@ -0,0 +1,19 @@ +module("luci.controller.beardropper", package.seeall) + +function index() + if not nixio.fs.access("/etc/config/beardropper") then + return + end + entry({"admin", "services", "beardropper"}, alias("admin", "services", "beardropper", "setting"),_("BearDropper"), 20).dependent = true + entry({"admin", "services", "beardropper", "status"}, call("act_status")) + entry({"admin", "services", "beardropper", "setting"}, cbi("beardropper/setting"), _("Setting"), 30).leaf= true + entry({"admin", "services", "beardropper", "log"}, form("beardropper/log"),_("Log"), 40).leaf= true + --entry: +end + +function act_status() + local e={} + e.running = luci.sys.call("pgrep -f /usr/sbin/beardropper >/dev/null")==0 + luci.http.prepare_content("application/json") + luci.http.write_json(e) +end \ No newline at end of file diff --git a/package/ctcgfw/luci-app-beardropper/luasrc/model/cbi/beardropper/log.lua b/package/ctcgfw/luci-app-beardropper/luasrc/model/cbi/beardropper/log.lua new file mode 100644 index 0000000000..c725bcdf04 --- /dev/null +++ b/package/ctcgfw/luci-app-beardropper/luasrc/model/cbi/beardropper/log.lua @@ -0,0 +1,17 @@ +f = SimpleForm("logview") +f.reset = false +f.submit = false +t = f:field(TextValue, "conf") +t.rmempty = true +t.rows = 20 +function t.cfgvalue() +local logs = luci.util.execi("logread | grep authpriv | grep beardropper") +local s = "" +for line in logs do +s = line .. "\n" .. s +end +return s +end +t.readonly="readonly" + +return f diff --git a/package/ctcgfw/luci-app-beardropper/luasrc/model/cbi/beardropper/setting.lua b/package/ctcgfw/luci-app-beardropper/luasrc/model/cbi/beardropper/setting.lua new file mode 100755 index 0000000000..2735200bfd --- /dev/null +++ b/package/ctcgfw/luci-app-beardropper/luasrc/model/cbi/beardropper/setting.lua @@ -0,0 +1,54 @@ + +m = Map("beardropper", translate("BearDropper"), +translate("luci-app-beardropper, the LuCI app built with the elegant firewall rule generation on-the-fly script bearDropper.

Should you have any questions, please refer to the repo: ")..[[luci-app-beardropper]] +) +m:chain("luci") + +m:section(SimpleSection).template="beardropper/status" + +s = m:section(TypedSection, "beardropper", translate("")) +s.anonymous = true +s.addremove = false + +-- TABS +s:tab("options", translate("Options")) +s:tab("blocked", translate("Blocked IP")) + +o = s:taboption("options", Flag, "enabled",translate("Enabled")) +o.default = 0 + +-- OPTIONS +o = s:taboption("options", ListValue, "defaultMode", translate("Running Mode")) +o.default = "follow" +o:value("follow", translate("Follow")) +o:value("entire", translate("Entire")) +o:value("today", translate("Today")) +o:value("wipe", translate("Wipe")) + + +o = s:taboption("options", Value, "attemptCount", translate("Attempt Tolerance"), translate("failure attempts from a given IP required to trigger a ban")) + +o = s:taboption("options", Value, "attemptPeriod", translate("Attempt Cycle"), translate("time period during which attemptCount must be exceeded in order to trigger a ban
Format: 1w2d3h4m5s represents 1week 2days 3hours 4minutes 5 seconds")) + +o = s:taboption("options", Value, "banLength", translate("Ban Period"), translate("how long a ban exist once the attempt threshold is exceeded")) + +o = s:taboption("options", ListValue, "logLevel", translate("Log Level")) +o.default = "1" +o:value("0", translate("Silent")) +o:value("1", translate("Default")) +o:value("2", translate("Verbose")) +o:value("3", translate("Debug")) + + +o = s:taboption("blocked", Value, "blocked", translate("Blocked IP List")) +o.template="cbi/tvalue" +o.rows=40 +o.wrap="off" +o.readonly="true" +function o.cfgvalue(e, e) + return luci.sys.exec("cat /tmp/beardropper.bddb | awk /'=1/'| awk -F '=' '{print $1}' | awk '{print substr($0,6)}' | awk 'gsub(/_/,\":\",$0)'") +end + + + +return m diff --git a/package/ctcgfw/luci-app-beardropper/luasrc/view/beardropper/status.htm b/package/ctcgfw/luci-app-beardropper/luasrc/view/beardropper/status.htm new file mode 100755 index 0000000000..808b3a0080 --- /dev/null +++ b/package/ctcgfw/luci-app-beardropper/luasrc/view/beardropper/status.htm @@ -0,0 +1,22 @@ + + +
+

+ <%:Collecting data...%> +

+
\ No newline at end of file diff --git a/package/ctcgfw/luci-app-beardropper/po/zh-cn/beardropper.po b/package/ctcgfw/luci-app-beardropper/po/zh-cn/beardropper.po new file mode 100755 index 0000000000..bcffc10793 --- /dev/null +++ b/package/ctcgfw/luci-app-beardropper/po/zh-cn/beardropper.po @@ -0,0 +1,115 @@ +bearDropper#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:36 +msgid "Attempt Cycle" +msgstr "尝试登录时间段" + + +msgid "Setting" +msgstr "设置" + +msgid "Log" +msgstr "日志" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:34 +msgid "Attempt Tolerance" +msgstr "最大尝试登录次数" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:38 +msgid "Ban Period" +msgstr "封禁IP时长" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/controller/bearDropper.lua:7 +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:7 +msgid "BearDropper" +msgstr "" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:20 +msgid "Blocked IP" +msgstr "屏蔽列表" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:52 +msgid "Blocked IP List" +msgstr "已屏蔽IP列表" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/view/bearDropper/status.htm:20 +msgid "Collecting data..." +msgstr "" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:45 +msgid "Debug" +msgstr "调试" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:43 +msgid "Default" +msgstr "默认" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:22 +msgid "Enabled" +msgstr "启用" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:29 +msgid "Entire" +msgstr "已有记录" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:28 +msgid "Follow" +msgstr "后台监控" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:40 +msgid "Log Level" +msgstr "日志等级" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/view/bearDropper/status.htm:10 +msgid "NOT RUNNING" +msgstr "" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:19 +msgid "Options" +msgstr "选项" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/view/bearDropper/status.htm:7 +msgid "RUNNING" +msgstr "" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:26 +msgid "Running Mode" +msgstr "运行模式" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:42 +msgid "Silent" +msgstr "安静" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:30 +msgid "Today" +msgstr "仅今日" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:44 +msgid "Verbose" +msgstr "详细" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:31 +msgid "Wipe" +msgstr "清除所有" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:34 +msgid "failure attempts from a given IP required to trigger a ban" +msgstr "尝试登录超过设定值次数的IP将被封禁" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:38 +msgid "how long a ban exist once the attempt threshold is exceeded" +msgstr "IP将被封禁设定的时间" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:8 +msgid "" +"luci-app-beardropper, the LuCI app built with the elegant firewall rule " +"generation on-the-fly script bearDropper.

Should you have any " +"questions, please refer to the repo:" +msgstr "" +"luci-app-beardropper, 是一款能够在开启公网访问之后对潜在的ssh attack进行防御" +"的脚本.

如果你在使用中有任何问题,请到项目中提问: " + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:36 +msgid "" +"time period during which attemptCount must be exceeded in order to trigger a " +"ban
Format: 1w2d3h4m5s represents 1week 2days 3hours 4minutes 5 seconds" +msgstr "" +"在设定的时间段内连续尝试失败
格式:1w2d3h4m5s代表1周2天3小时4分5秒" diff --git a/package/ctcgfw/luci-app-beardropper/po/zh-tw/beardropper.po b/package/ctcgfw/luci-app-beardropper/po/zh-tw/beardropper.po new file mode 100644 index 0000000000..00c1da72ea --- /dev/null +++ b/package/ctcgfw/luci-app-beardropper/po/zh-tw/beardropper.po @@ -0,0 +1,115 @@ +bearDropper#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:36 +msgid "Attempt Cycle" +msgstr "嘗試登錄時間段" + + +msgid "Setting" +msgstr "設置" + +msgid "Log" +msgstr "日誌" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:34 +msgid "Attempt Tolerance" +msgstr "最大嘗試登錄次數" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:38 +msgid "Ban Period" +msgstr "封禁IP時長" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/controller/bearDropper.lua:7 +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:7 +msgid "BearDropper" +msgstr "" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:20 +msgid "Blocked IP" +msgstr "屏蔽列表" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:52 +msgid "Blocked IP List" +msgstr "已屏蔽IP列表" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/view/bearDropper/status.htm:20 +msgid "Collecting data..." +msgstr "" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:45 +msgid "Debug" +msgstr "調試" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:43 +msgid "Default" +msgstr "默認" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:22 +msgid "Enabled" +msgstr "啟用" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:29 +msgid "Entire" +msgstr "已有記錄" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:28 +msgid "Follow" +msgstr "後臺監控" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:40 +msgid "Log Level" +msgstr "日誌等級" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/view/bearDropper/status.htm:10 +msgid "NOT RUNNING" +msgstr "" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:19 +msgid "Options" +msgstr "選項" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/view/bearDropper/status.htm:7 +msgid "RUNNING" +msgstr "" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:26 +msgid "Running Mode" +msgstr "運行模式" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:42 +msgid "Silent" +msgstr "安靜" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:30 +msgid "Today" +msgstr "僅今日" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:44 +msgid "Verbose" +msgstr "詳細" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:31 +msgid "Wipe" +msgstr "清除所有" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:34 +msgid "failure attempts from a given IP required to trigger a ban" +msgstr "嘗試登錄超過設定值次數的IP將被封禁" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:38 +msgid "how long a ban exist once the attempt threshold is exceeded" +msgstr "IP將被封禁設定的時間" + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:8 +msgid "" +"luci-app-beardropper, the LuCI app built with the elegant firewall rule " +"generation on-the-fly script bearDropper.

Should you have any " +"questions, please refer to the repo:" +msgstr "" +"luci-app-beardropper, 是壹款能夠在開啟公網訪問之後對潛在的ssh attack進行防禦" +"的腳本.

如果妳在使用中有任何問題,請到項目中提問: " + +#: ../../package/feeds/luci/luci-app-beardropper/luasrc/model/cbi/bearDropper/setting.lua:36 +msgid "" +"time period during which attemptCount must be exceeded in order to trigger a " +"ban
Format: 1w2d3h4m5s represents 1week 2days 3hours 4minutes 5 seconds" +msgstr "" +"在設定的時間段內連續嘗試失敗
格式:1w2d3h4m5s代表1周2天3小時4分5秒" diff --git a/package/ctcgfw/luci-app-beardropper/root/etc/config/beardropper b/package/ctcgfw/luci-app-beardropper/root/etc/config/beardropper new file mode 100644 index 0000000000..ec5d0d4e05 --- /dev/null +++ b/package/ctcgfw/luci-app-beardropper/root/etc/config/beardropper @@ -0,0 +1,18 @@ +config beardropper + option attemptCount '5' + option attemptPeriod '12h' + option banLength '1w' + option logFacility 'authpriv.notice' + option fileStateType 'bddb' + option fileStateTempPrefix '/tmp/beardropper' + option fileStatePersistPrefix '/etc/beardropper' + list firewallHookChain 'input_wan_rule:1' + list firewallHookChain 'forwarding_wan_rule:1' + option firewallTarget 'DROP' + list logRegex 's/[`$"\'\'']//g' + list logRegex '/has invalid shell, rejected$/d' + list logRegex '/^[A-Za-z ]+[0-9: ]+authpriv.warn dropbear\[.+([0-9]+\.){3}[0-9]+/p' + list logRegex '/^[A-Za-z ]+[0-9: ]+authpriv.info dropbear\[.+:\ Exit before auth:.*/p' + option defaultMode 'follow' + option enabled '1' + option logLevel '2' diff --git a/package/ctcgfw/luci-app-beardropper/root/etc/init.d/beardropper b/package/ctcgfw/luci-app-beardropper/root/etc/init.d/beardropper new file mode 100755 index 0000000000..a7c1c98dbc --- /dev/null +++ b/package/ctcgfw/luci-app-beardropper/root/etc/init.d/beardropper @@ -0,0 +1,46 @@ +#!/bin/sh /etc/rc.common +START=98 + +PROG=/usr/sbin/beardropper +SERVICE_DAEMONIZE=1 +SERVICE_WRITE_PID=1 + +getKids() { + egrep "^PPid: *$1$" /proc/[0-9]*/s*s 2>/dev/null | cut -f3 -d/ | xargs echo +} + +start() { + service_start ${PROG} -m follow + echo "beardropper started!" +} + +stop() { + #PID=`cat /var/run/bearDropper.pid` + #kill `getKids $PID` + kill -9 `pgrep -f /usr/sbin/beardropper` + sleep 1 + service_stop ${PROG} + echo "beardropper exit...." +} + +restart() { + enabled=$(uci get beardropper.@beardropper[0].enabled) + pgrep -f ${PROG} >/dev/null + if [ $? -eq 0 ];then #running + if [ $enabled -eq 1 ]; then + stop + sleep 1 + echo "beardropper is restarting..." + start + else + stop + fi + else + if [ $enabled -eq 1 ]; then + start + else + exit 0 + fi + fi + +} diff --git a/package/ctcgfw/luci-app-beardropper/root/etc/uci-defaults/luci-beardropper b/package/ctcgfw/luci-app-beardropper/root/etc/uci-defaults/luci-beardropper new file mode 100755 index 0000000000..b484337f27 --- /dev/null +++ b/package/ctcgfw/luci-app-beardropper/root/etc/uci-defaults/luci-beardropper @@ -0,0 +1,12 @@ +#!/bin/sh + +uci -q batch <<-EOF >/dev/null + delete ucitrack.@beardropper[-1] + add ucitrack beardropper + set ucitrack.@beardropper[-1].init=beardropper + commit ucitrack +EOF + +touch /tmp/beardropper.bddb +rm -rf /tmp/luci-* +exit 0 diff --git a/package/ctcgfw/luci-app-beardropper/root/usr/sbin/beardropper b/package/ctcgfw/luci-app-beardropper/root/usr/sbin/beardropper new file mode 100755 index 0000000000..d3015491fe --- /dev/null +++ b/package/ctcgfw/luci-app-beardropper/root/usr/sbin/beardropper @@ -0,0 +1,517 @@ +#!/bin/ash +# +# beardropper - dropbear log parsing ban agent for OpenWRT (Chaos Calmer rewrite of dropBrute.sh) +# http://github.com/robzr/bearDropper -- Rob Zwissler 11/2015 +# +# - lightweight, no dependencies, busybox ash + native OpenWRT commands +# - uses uci for configuration, overrideable via command line arguments +# - runs continuously in background (via init script) or periodically (via cron) +# - uses BIND time shorthand, ex: 1w5d3h1m8s is 1 week, 5 days, 3 hours, 1 minute, 8 seconds +# - Whitelist IP or CIDR entries (TBD) in uci config file +# - Records state file to tmpfs and intelligently syncs to persistent storage (can disable) +# - Persistent sync routines are optimized to avoid excessive writes (persistentStateWritePeriod) +# - Every run occurs in one of the following modes. If not specified, interval mode (24 hours) is +# the default when not specified (the init script specifies follow mode via command line) +# +# "follow" mode follows syslog to process entries as they happen; generally launched via init +# script. Responds the fastest, runs the most efficiently, but is always in memory. +# "interval" mode only processes entries going back the specified interval; requires +# more processing than today mode, but responds more accurately. Use with cron. +# "today" mode looks at log entries from the day it is being run, simple and lightweight, +# generally run from cron periodically (same simplistic behavior as dropBrute.sh) +# "entire" mode runs through entire contents of the syslog ring buffer +# "wipe" mode tears down the firewall rules and removes the state files + +# Load UCI config variable, or use default if not set +# Args: $1 = variable name (also uci option name), $2 = default_value +uciSection='beardropper.@[0]' +uciLoadVar () { + local getUci + getUci=`uci -q get ${uciSection}."$1"` || getUci="$2" + eval $1=\'$getUci\'; +} +uciLoad() { + local tFile=`mktemp` delim=" +" + [ "$1" = -d ] && { delim="$2"; shift 2; } + uci -q -d"$delim" get "$uciSection.$1" 2>/dev/null >$tFile + if [ $? = 0 ] ; then + sed -e s/^\'// -e s/\'$// <$tFile + else + while [ -n "$2" ]; do echo $2; shift; done + fi + rm -f $tFile +} + +# Common config variables - edit these in /etc/config/beardropper +# or they can be overridden at runtime with command line options +# +uciLoadVar defaultMode entire +uciLoadVar enabled 0 +uciLoadVar attemptCount 10 +uciLoadVar attemptPeriod 12h +uciLoadVar banLength 1w +uciLoadVar logLevel 1 +uciLoadVar logFacility authpriv.notice +uciLoadVar persistentStateWritePeriod -1 +uciLoadVar fileStateType bddb +uciLoadVar fileStateTempPrefix /tmp/beardropper +uciLoadVar fileStatePersistPrefix /etc/beardropper +firewallHookChains="`uciLoad -d \ firewallHookChain input_wan_rule:1 forwarding_wan_rule:1`" +uciLoadVar firewallTarget DROP + +# Not commonly changed, but changeable via uci or cmdline (primarily +# to enable multiple parallel runs with different parameters) +uciLoadVar firewallChain beardropper + +# Advanced variables, changeable via uci only (no cmdline), it is +# unlikely that these will need to be changed, but just in case... +# +uciLoadVar syslogTag "beardropper[$$]" +# how often to attempt to expire bans when in follow mode +uciLoadVar followModeCheckInterval 30m +uciLoadVar cmdLogread 'logread' # for tuning, ex: "logread -l250" +uciLoadVar cmdLogreadEba 'logread' # for "Exit before auth:" backscanning +uciLoadVar formatLogDate '%b %e %H:%M:%S %Y' # used to convert syslog dates +uciLoadVar formatTodayLogDateRegex '^%a %b %e ..:..:.. %Y' # filter for today mode + +# Begin functions +# +# Clear bddb entries from environment +bddbClear () { + local bddbVar + for bddbVar in `set | egrep '^bddb_[0-9_]*=' | cut -f1 -d= | xargs echo -n` ; do eval unset $bddbVar ; done + bddbStateChange=1 +} + +# Returns count of unique IP entries in environment +bddbCount () { set | egrep '^bddb_[0-9_]*=' | wc -l ; } + +# Loads existing bddb file into environment +# Arg: $1 = file, $2 = type (bddb/bddbz), $3 = +bddbLoad () { + local loadFile="$1.$2" fileType="$2" + if [ "$fileType" = bddb -a -f "$loadFile" ] ; then + . "$loadFile" + elif [ "$fileType" = bddbz -a -f "$loadFile" ] ; then + local tmpFile="`mktemp`" + zcat $loadFile > "$tmpFile" + . "$tmpFile" + rm -f "$tmpFile" + fi + bddbStateChange=0 +} + +# Saves environment bddb entries to file, Arg: $1 = file to save in +bddbSave () { + local saveFile="$1.$2" fileType="$2" + if [ "$fileType" = bddb ] ; then + set | egrep '^bddb_[0-9_]*=' | sed s/\'//g > "$saveFile" + elif [ "$fileType" = bddbz ] ; then + set | egrep '^bddb_[0-9_]*=' | sed s/\'//g | gzip -c > "$saveFile" + fi + bddbStateChange=0 +} + +# Set bddb record status=1, update ban time flag with newest +# Args: $1=IP Address $2=timeFlag +bddbEnableStatus () { + local record=`echo $1 | sed -e 's/\./_/g' -e 's/^/bddb_/'` + local newestTime=`bddbGetTimes $1 | sed 's/.* //' | xargs echo $2 | tr \ '\n' | sort -n | tail -1 ` + eval $record="1,$newestTime" + bddbStateChange=1 +} + +# Args: $1=IP Address +bddbGetStatus () { + bddbGetRecord $1 | cut -d, -f1 +} + +# Args: $1=IP Address +bddbGetTimes () { + bddbGetRecord $1 | cut -d, -f2- +} + +# Args: $1 = IP address, $2 [$3 ...] = timestamp (seconds since epoch) +bddbAddRecord () { + local ip="`echo "$1" | tr . _`" ; shift + local newEpochList="$@" status="`eval echo \\\$bddb_$ip | cut -f1 -d,`" + local oldEpochList="`eval echo \\\$bddb_$ip | cut -f2- -d, | tr , \ `" + local epochList=`echo $oldEpochList $newEpochList | xargs -n 1 echo | sort -un | xargs echo -n | tr \ ,` + [ -z "$status" ] && status=0 + eval "bddb_$ip"\=\"$status,$epochList\" + bddbStateChange=1 +} + +# Args: $1 = IP address +bddbRemoveRecord () { + local ip="`echo "$1" | tr . _`" + eval unset bddb_$ip + bddbStateChange=1 +} + +# Returns all IPs (not CIDR) present in records +bddbGetAllIPs () { + local ipRaw record + set | egrep '^bddb_[0-9_]*=' | tr \' \ | while read record ; do + ipRaw=`echo $record | cut -f1 -d= | sed 's/^bddb_//'` + if [ `echo $ipRaw | tr _ \ | wc -w` -eq 4 ] ; then + echo $ipRaw | tr _ . + fi + done +} + +# retrieve single IP record, Args: $1=IP +bddbGetRecord () { + local record + record=`echo $1 | sed -e 's/\./_/g' -e 's/^/bddb_/'` + eval echo \$$record +} + +isValidBindTime () { echo "$1" | egrep -q '^[0-9]+$|^([0-9]+[wdhms]?)+$' ; } + +# expands Bind time syntax into seconds (ex: 3w6d23h59m59s), Arg: $1=time string +expandBindTime () { + isValidBindTime "$1" || { logLine 0 "Error: Invalid time specified ($1)" >&2 ; exit 254 ; } + echo $((`echo "$1" | sed -e 's/w+*/*7d+/g' -e 's/d+*/*24h+/g' -e 's/h+*/*60m+/g' -e 's/m+*/*60+/g' \ + -e s/s//g -e s/+\$//`)) +} + +# Args: $1 = loglevel, $2 = info to log +logLine () { + [ $1 -gt $logLevel ] && return + shift + if [ "$logFacility" = "stdout" ] ; then echo "$@" + elif [ "$logFacility" = "stderr" ] ; then echo "$@" >&2 + else logger -t "$syslogTag" -p "$logFacility" "$@" + fi +} + +# extra validation, fails safe. Args: $1=log line +getLogTime () { + local logDateString=`echo "$1" | sed -n \ + 's/^[A-Z][a-z]* \([A-Z][a-z]* *[0-9][0-9]* *[0-9][0-9]*:[0-9][0-9]:[0-9][0-9] [0-9][0-9]*\) .*$/\1/p'` + date -d"$logDateString" -D"$formatLogDate" +%s || logLine 1 \ + "Error: logDateString($logDateString) malformed line ($1)" +} + +# extra validation, fails safe. Args: $1=log line +getLogIP () { + local logLine="$1" + local ebaPID=`echo "$logLine" | sed -n 's/^.*authpriv.info \(dropbear\[[0-9]*\]:\) Exit before auth:.*/\1/p'` + [ -n "$ebaPID" ] && logLine=`$cmdLogreadEba | fgrep "${ebaPID} Child connection from "` + echo "$logLine" | sed -n 's/^.*[^0-9]\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*$/\1/p' +} + +# Args: $1=IP +unBanIP () { + if iptables -C $firewallChain -s $ip -j "$firewallTarget" 2>/dev/null ; then + logLine 1 "Removing ban rule for IP $ip from iptables" + iptables -D $firewallChain -s $ip -j "$firewallTarget" + else + logLine 3 "unBanIP() Ban rule for $ip not present in iptables" + fi +} + +# Args: $1=IP +banIP () { + local ip="$1" x chain position + if ! iptables -nL $firewallChain >/dev/null 2>/dev/null ; then + logLine 1 "Creating iptables chain $firewallChain" + iptables -N $firewallChain + fi + for x in $firewallHookChains ; do + chain="${x%:*}" ; position="${x#*:}" + if [ $position -ge 0 ] && ! iptables -C $chain -j $firewallChain 2>/dev/null ; then + logLine 1 "Inserting hook into iptables chain $chain" + if [ $position = 0 ] ; then + iptables -A $chain -j $firewallChain + else + iptables -I $chain $position -j $firewallChain + fi ; fi + done + if ! iptables -C $firewallChain -s $ip -j "$firewallTarget" 2>/dev/null ; then + logLine 1 "Inserting ban rule for IP $ip into iptables chain $firewallChain" + iptables -A $firewallChain -s $ip -j "$firewallTarget" + else + logLine 3 "banIP() rule for $ip already present in iptables chain" + fi +} + +wipeFirewall () { + local x chain position + for x in $firewallHookChains ; do + chain="${x%:*}" ; position="${x#*:}" + if [ $position -ge 0 ] ; then + if iptables -C $chain -j $firewallChain 2>/dev/null ; then + logLine 1 "Removing hook from iptables chain $chain" + iptables -D $chain -j $firewallChain + fi ; fi + done + if iptables -nL $firewallChain >/dev/null 2>/dev/null ; then + logLine 1 "Flushing and removing iptables chain $firewallChain" + iptables -F $firewallChain 2>/dev/null + iptables -X $firewallChain 2>/dev/null + fi +} + +# review state file for expired records - we could add the bantime to +# the rule via --comment but I can't think of a reason why that would +# be necessary unless there is a bug in the expiration logic. The +# state db should be more resiliant than the firewall in practice. +# +bddbCheckStatusAll () { + local now=`date +%s` + bddbGetAllIPs | while read ip ; do + if [ `bddbGetStatus $ip` -eq 1 ] ; then + logLine 3 "bddbCheckStatusAll($ip) testing banLength:$banLength + bddbGetTimes:`bddbGetTimes $ip` vs. now:$now" + if [ $((banLength + `bddbGetTimes $ip`)) -lt $now ] ; then + logLine 1 "Ban expired for $ip, removing from iptables" + unBanIP $ip + bddbRemoveRecord $ip + else + logLine 3 "bddbCheckStatusAll($ip) not expired yet" + banIP $ip + fi + elif [ `bddbGetStatus $ip` -eq 0 ] ; then + local times=`bddbGetTimes $ip | tr , \ ` + local timeCount=`echo $times | wc -w` + local lastTime=`echo $times | cut -d\ -f$timeCount` + if [ $((lastTime + attemptPeriod)) -lt $now ] ; then + bddbRemoveRecord $ip + fi ; fi + saveState + done + loadState +} + +# Only used when status is already 0 and possibly going to 1, Args: $1=IP +bddbEvaluateRecord () { + local ip=$1 firstTime lastTime + local times=`bddbGetRecord $1 | cut -d, -f2- | tr , \ ` + local timeCount=`echo $times | wc -w` + local didBan=0 + + # 1: not enough attempts => do nothing and exit + # 2: attempts exceed threshold in time period => ban + # 3: attempts exceed threshold but time period is too long => trim oldest time, recalculate + while [ $timeCount -ge $attemptCount ] ; do + firstTime=`echo $times | cut -d\ -f1` + lastTime=`echo $times | cut -d\ -f$timeCount` + timeDiff=$((lastTime - firstTime)) + logLine 3 "bddbEvaluateRecord($ip) count=$timeCount timeDiff=$timeDiff/$attemptPeriod" + if [ $timeDiff -le $attemptPeriod ] ; then + bddbEnableStatus $ip $lastTime + logLine 2 "bddbEvaluateRecord($ip) exceeded ban threshold, adding to iptables" + banIP $ip + didBan=1 + fi + times=`echo $times | cut -d\ -f2-` + timeCount=`echo $times | wc -w` + done + [ $didBan = 0 ] && logLine 2 "bddbEvaluateRecord($ip) does not exceed threshhold, skipping" +} + +# Reads filtered log line and evaluates for action Args: $1=log line +processLogLine () { + local time=`getLogTime "$1"` + local ip=`getLogIP "$1"` + local status="`bddbGetStatus $ip`" + + if [ "$status" = -1 ] ; then + logLine 2 "processLogLine($ip,$time) IP is whitelisted" + elif [ "$status" = 1 ] ; then + if [ "`bddbGetTimes $ip`" -ge $time ] ; then + logLine 2 "processLogLine($ip,$time) already banned, ban timestamp already equal or newer" + else + logLine 2 "processLogLine($ip,$time) already banned, updating ban timestamp" + bddbEnableStatus $ip $time + fi + banIP $ip + elif [ -n "$ip" -a -n "$time" ] ; then + bddbAddRecord $ip $time + logLine 2 "processLogLine($ip,$time) Added record, comparing" + bddbEvaluateRecord $ip + else + logLine 1 "processLogLine($ip,$time) malformed line ($1)" + fi +} + +# Args, $1=-f to force a persistent write (unless lastPersistentStateWrite=-1) +saveState () { + local forcePersistent=0 + [ "$1" = "-f" ] && forcePersistent=1 + + if [ $bddbStateChange -gt 0 ] ; then + logLine 3 "saveState() saving to temp state file" + bddbSave "$fileStateTempPrefix" "$fileStateType" + logLine 3 "saveState() now=`date +%s` lPSW=$lastPersistentStateWrite pSWP=$persistentStateWritePeriod fP=$forcePersistent" + fi + if [ $persistentStateWritePeriod -gt 1 ] || [ $persistentStateWritePeriod -eq 0 -a $forcePersistent -eq 1 ] ; then + if [ $((`date +%s` - lastPersistentStateWrite)) -ge $persistentStateWritePeriod ] || [ $forcePersistent -eq 1 ] ; then + if [ ! -f "$fileStatePersist" ] || ! cmp -s "$fileStateTemp" "$fileStatePersist" ; then + logLine 2 "saveState() writing to persistent state file" + bddbSave "$fileStatePersistPrefix" "$fileStateType" + lastPersistentStateWrite="`date +%s`" + fi ; fi ; fi +} + +loadState () { + bddbClear + bddbLoad "$fileStatePersistPrefix" "$fileStateType" + bddbLoad "$fileStateTempPrefix" "$fileStateType" + logLine 2 "loadState() loaded `bddbCount` entries" +} + +printUsage () { + cat <<-_EOF_ + Usage: beardropper [-m mode] [-a #] [-b #] [-c ...] [-C ...] [-f ...] [-l #] [-j ...] [-p #] [-P #] [-s ...] + + Running Modes (-m) (def: $defaultMode) + follow constantly monitors log + entire processes entire log contents + today processes log entries from same day only + # interval mode, specify time string or seconds + wipe wipe state files, unhook and remove firewall chain + + Options + -a # attempt count before banning (def: $attemptCount) + -b # ban length once attempts hit threshold (def: $banLength) + -c ... firewall chain to record bans (def: $firewallChain) + -C ... firewall chains/positions to hook into (def: $firewallHookChains) + -f ... log facility (syslog facility or stdout/stderr) (def: $logFacility) + -j ... firewall target (def: $firewallTarget) + -l # log level - 0=off, 1=standard, 2=verbose (def: $logLevel) + -p # attempt period which attempt counts must happen in (def: $attemptPeriod) + -P # persistent state file write period (def: $persistentStateWritePeriod) + -s ... persistent state file prefix (def: $fileStatePersistPrefix) + -t ... temporary state file prefix (def: $fileStateTempPrefix) + + All time strings can be specified in seconds, or using BIND style + time strings, ex: 1w2d3h5m30s is 1 week, 2 days, 3 hours, etc... + + _EOF_ +} + +# Begin main logic +# +unset logMode +while getopts a:b:c:C:f:hj:l:m:p:P:s:t: arg ; do + case "$arg" in + a) attemptCount="$OPTARG" ;; + b) banLength="$OPTARG" ;; + c) firewallChain="$OPTARG" ;; + C) firewallHookChains="$OPTARG" ;; + f) logFacility="$OPTARG" ;; + j) firewallTarget="$OPTARG" ;; + l) logLevel="$OPTARG" ;; + m) logMode="$OPTARG" ;; + p) attemptPeriod="$OPTARG" ;; + P) persistentStateWritePeriod="$OPTARG" ;; + s) fileStatePersistPrefix="$OPTARG" ;; + s) fileStatePersistPrefix="$OPTARG" ;; + *) printUsage + exit 254 + esac + shift `expr $OPTIND - 1` +done +[ -z $logMode ] && logMode="$defaultMode" + +fileStateTemp="$fileStateTempPrefix.$fileStateType" +fileStatePersist="$fileStatePersistPrefix.$fileStateType" + +attemptPeriod=`expandBindTime $attemptPeriod` +banLength=`expandBindTime $banLength` +[ $persistentStateWritePeriod != -1 ] && persistentStateWritePeriod=`expandBindTime $persistentStateWritePeriod` +followModeCheckInterval=`expandBindTime $followModeCheckInterval` +exitStatus=0 + +# Here we convert the logRegex list into a sed -f file +fileRegex="/tmp/beardropper.$$.regex" +uciLoad logRegex 's/[`$"'\\\'']//g' '/has invalid shell, rejected$/d' \ + '/^[A-Za-z ]+[0-9: ]+authpriv.warn dropbear\[.+([0-9]+\.){3}[0-9]+/p' \ + '/^[A-Za-z ]+[0-9: ]+authpriv.info dropbear\[.+:\ Exit before auth:.*/p' > "$fileRegex" +lastPersistentStateWrite="`date +%s`" +loadState +bddbCheckStatusAll + +# main event loops + +if [ "$logMode" = follow ] ; then + logLine 1 "Running in follow mode" + readsSinceSave=0 lastCheckAll=0 worstCaseReads=1 tmpFile="/tmp/beardropper.$$.1" +# Verify if these do any good - try saving to a temp. Scope may make saveState useless. + trap "rm -f "$tmpFile" "$fileRegex" ; exit " SIGINT + [ $persistentStateWritePeriod -gt 1 ] && worstCaseReads=$((persistentStateWritePeriod / followModeCheckInterval)) + firstRun=1 + $cmdLogread -f | while read -t $followModeCheckInterval line || true ; do + if [ $firstRun -eq 1 ] ; then + trap "saveState -f" SIGHUP + trap "saveState -f; exit" SIGINT + firstRun=0 + fi + sed -nEf "$fileRegex" > "$tmpFile" <<-_EOF_ + $line + _EOF_ + line="`cat $tmpFile`" + [ -n "$line" ] && processLogLine "$line" + logLine 3 "ReadComp:$readsSinceSave/$worstCaseReads" + if [ $((++readsSinceSave)) -ge $worstCaseReads ] ; then + now="`date +%s`" + if [ $((now - lastCheckAll)) -ge $followModeCheckInterval ] ; then + bddbCheckStatusAll + lastCheckAll="$now" + saveState + readsSinceSave=0 + fi + fi + done +elif [ "$logMode" = entire ] ; then + logLine 1 "Running in entire mode" + $cmdLogread | sed -nEf "$fileRegex" | while read line ; do + processLogLine "$line" + saveState + done + loadState + bddbCheckStatusAll + saveState -f +elif [ "$logMode" = today ] ; then + logLine 1 "Running in today mode" + # merge the egrep into sed with -e /^$formatTodayLogDateRegex/!d + $cmdLogread | egrep "`date +\'$formatTodayLogDateRegex\'`" | sed -nEf "$fileRegex" | while read line ; do + processLogLine "$line" + saveState + done + loadState + bddbCheckStatusAll + saveState -f +elif isValidBindTime "$logMode" ; then + logInterval=`expandBindTime $logMode` + logLine 1 "Running in interval mode (reviewing $logInterval seconds of log entries)..." + timeStart=$((`date +%s` - logInterval)) + $cmdLogread | sed -nEf "$fileRegex" | while read line ; do + timeWhen=`getLogTime "$line"` + [ $timeWhen -ge $timeStart ] && processLogLine "$line" + saveState + done + loadState + bddbCheckStatusAll + saveState -f +elif [ "$logMode" = wipe ] ; then + logLine 2 "Wiping state files, unhooking and removing iptables chains" + wipeFirewall + if [ -f "$fileStateTemp" ] ; then + logLine 1 "Removing non-persistent statefile ($fileStateTemp)" + rm -f "$fileStateTemp" + fi + if [ -f "$fileStatePersist" ] ; then + logLine 1 "Removing persistent statefile ($fileStatePersist)" + rm -f "$fileStatePersist" + fi +else + logLine 0 "Error: invalid log mode ($logMode)" + exitStatus=254 +fi + +rm -f "$fileRegex" +exit $exitStatus