Blob


1 #!/bin/sh
2 # ISC License
3 #
4 # Copyright 2025 xs <xs@inda.re>
5 #
6 # Permission to use, copy, modify, and/or 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 WITH
11 # REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
12 # FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
13 # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
14 # LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
15 # OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
16 # PERFORMANCE OF THIS SOFTWARE.
18 : "${HTTPBAN_WHITELIST:=127.0.0.1}"
19 : "${HTTPBAN_LIMIT_500:=10}"
20 : "${HTTPBAN_LIMIT_400:=10}"
21 : "${HTTPBAN_LIMIT_300:=10}"
22 : "${HTTPBAN_TABLE:=httpban}"
24 alias log="logger -st http-ban"
25 alias show="doas /sbin/pfctl -t \$HTTPBAN_TABLE -T show"
26 alias ban="doas /sbin/pfctl -t \$HTTPBAN_TABLE -T add -f-"
27 alias grace="doas /sbin/pfctl -t \$HTTPBAN_TABLE -T delete -f-"
29 # Filter functions returning 'count IP' based on HTTP return code
30 IN_300_HOSTS() {
31 awk '$(NF-1) >= 300 && $(NF-1) < 400 { print $2 }' | sort | uniq -c
32 }
34 IN_400_HOSTS() {
35 awk '$(NF-1) >= 400 && $(NF-1) < 500 { print $2 }' | sort | uniq -c
36 }
38 IN_500_HOSTS() {
39 awk '$(NF-1) >= 500 && $(NF-1) < 600 { print $2 }' | sort | uniq -c
40 }
42 # Our local logs
43 access() {
44 doas /bin/cat /var/www/logs/access.log
45 doas /usr/bin/zcat /var/www/logs/access.log.*gz 2>/dev/null
46 }
48 limit() {
49 awk -vtrig="${1:-10}" ' $1 >= trig { print $2 }'
50 }
52 # shellcheck disable=SC2086,SC2046
53 block() {
54 set -- $HTTPBAN_WHITELIST
55 set -- $(for ip; do printf -- '-e %s ' "$ip"; done)
56 {
57 access | IN_500_HOSTS | limit "$HTTPBAN_LIMIT_500"
58 access | IN_400_HOSTS | limit "$HTTPBAN_LIMIT_400"
59 access | IN_300_HOSTS | limit "$HTTPBAN_LIMIT_300"
60 } | sort | uniq | {
61 if test -n "$*"; then grep -v "$@"; else cat; fi
62 }
63 }
65 umask 127
67 block | sort >/tmp/http-ban.new
69 # Diff processing
70 show | sort | awk '{ print $1 }' >/tmp/http-ban.current
71 NEW=$(comm -23 /tmp/http-ban.new /tmp/http-ban.current)
72 GRACE=$(comm -13 /tmp/http-ban.new /tmp/http-ban.current)
74 rm /tmp/http-ban.*
76 test -n "$NEW" -o -n "$GRACE" || exit 0
78 # There's new IP's
79 if test -n "$NEW"; then
80 log Banning new IPs:
81 log <<..
82 $NEW
83 ..
84 ban <<..
85 $NEW
86 ..
87 fi
89 # There's Old IP's not attacking anymore
90 if test -n "$GRACE"; then
91 log Gracing old IPs:
92 log <<..
93 $GRACE
94 ..
96 grace <<..
97 $GRACE
98 ..
99 fi